编程导航算法通关村(第2期)第一关 —— 小白也能学会的链表(青铜挑战)

1.什么是链表(单向链表)

链表(link list)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。单向链表中的每一个节点都包含两部分,一部分是存放数据的变量,一部分是指向下一个节点的指针next。

class Node(object):
    """单链表的结点"""
    def __init__(self, item):
        # _item存放数据元素
        self.item = item
        # _next是下一个节点的标识
        self.next = None

链表的第一个节点被称为头节点,最后1个节点被称为尾节点,尾节点的next指针指向空。对于链表的其中一个节点A,我们只能根据节点A的next指针来找到该节点的下一个节点B,再根据节点B的next指针找到下一个节点C……
在这里插入图片描述

1.1虚拟节点(还未使用过)

在做题以及工程中会看到虚拟节点的概念,其next指针指向head,是为了方便处理首部节点,否则我们需要在代码里单独处理首部节点的问题。

2.链表的随机存储

链表则采用了见缝插针的方式,链表的每一个节点分布在内存的不同位置,依靠next指针关联起来。这样可以灵活有效地利用零散的碎片空间。
让我们看一看下面两张图,对比一下数组和链表在内存中分配方式的不同。
在这里插入图片描述
图中的箭头代表链表节点的next指针。

3.创建链表

Python类中,__init__方法用于初始化对象,getData和setData方法用于获取和设置data属性,get_next和set_next方法用于获取和设置next属性。请注意,Python类中的属性是使用self.data和self.next的语法进行访问和设置的。

class ListNode:
    def __init__(self, data):
        self.data = data
        self.next = None
    
    def get_data(self):
        return self.data
    
    def set_data(self, data):
        self.data = data

    def get_next(self):
        return self.next
    
    def set_next(self, next):
        self.next = next

但是在LeetCode中算法题是经常使用这样的方式来创建链表(这一方法为什么违背面向对象的设计要求?):

class ListNode:
  def __init__(self, val=0, next=None):
      self.val = val
      self.next = next


listnode = ListNode(1)

这里的val就是当前节点的值,next指向下一个节点。因为两个变量都是public的,创建对象后能直接使用listnode.val和listnode.next来操作,虽然违背了面向对象的设计要求,但是上面的代码更为精简,因此在算法题目中应用广泛。

4.链表的增删改查

4.1查找节点

对于单向链表,链表中的数据只能按顺序进行访问,一定是从头开始逐个向后访问。
在这里插入图片描述

4.2 链表插入

单链表的插入情况需要考虑三种情况:首部、中部和尾部。

4.2.1 尾部插入

尾部插入是最简单的一种情况,把最后一个节点的next指针指向新插入的节点即
可。
在这里插入图片描述

4.2.2 头部插入

头部插入可以分为两步:第一步,把新节点的next指针指向原先的头节点。第二步,,把新节点变为链表的头节点。
在这里插入图片描述

4.2.3 中间插入

中间插入也可以分为两步:第1步,新节点的next指针,指向插入位置的节点。
第2步,插入位置前置节点的next指针,指向新节点。
在这里插入图片描述
这里插入教材中的叙述,感觉会更偏向实用。

链表的遍历代码如下:

def length(self):
		"""链表长度"""
		# cur初始时指向头节点
		cur = self._head
		count = 0
		# 尾节点指向None,当未到达尾部时
		while cur != None
			count += 1
			# 将cur后移一个节点
			cur = cur.next
		return count

在中间位置插入

在中间位置插入,我们必须遍历找到要插入的位置,然后将当前位置接入到前驱节点和后继节点之间,但是到了该位置之后我们却不能获得前驱节点了,也就无法将节点接入进来了。这就好比一边过河一边拆桥,结果自己也回不去了。

为此,我们要在目标节点的前一位置停下来,也就是使用cur.next的值而不是cur的值来判断,这是链表最常用的策略。

例如下图中,如果要在7的前面插入,当cur.next=node(7)了就应该停下来,此时cur.val=15。然后需要将newNode前后接两根线,此时只能先让new.next=node(15).next(图中虚线),然后node(15).next=new,而且顺序还不能错。

想一下为什么不能调到顺序?

由于每个节点都只有一个next,因此执行了node(15).next=new之后,节点15和7之间的连线就自动断开了,如下图所示:
在这里插入图片描述

4.3 链表删除

同样分为删除尾部元素,删除头部元素和删除中间元素。

4.3.1 尾部删除

尾部删除,是最简单的情况,把倒数第2个节点的next指针指向空即可。
在这里插入图片描述

4.3.2 头部删除

头部删除,也很简单,把链表的头节点设为原先头节点的next指针即可。
在这里插入图片描述

4.3.3 中间删除

把要删除节点的前置节点的next指针,指向要删除元素的下一个节点即可。
在这里插入图片描述
这里需要注意的是,许多高级语言拥有自动化的垃圾回收机制,所以我们不用刻意去释放被删除的节点,只要没有外部引用指向它们,被删除的节点会被自动回收。

完整代码

class Node:
    "单链表的节点"
    def __init__(self, data) -> None:
        # data存放数据元素
        self.data = data
        # next是下一个节点的标识
        self.next = None


class SingleLinkList:
    def __init__(self):
        self._head = None

    def is_empty(self):
        """判断链表是否为空"""
        return self._head == None
    
    def length(self):
        """链表长度"""
        # cur初始时指向头节点
        cur = self._head
        count = 0
        # 尾节点指向None, 当未到达尾部时
        while cur != None:
            count += 1
            # 将cur后移一个节点
            cur = cur.next
        return count

    def travel(self):
        """遍历链表"""
        cur = self._head
        while cur != None:
            print(cur.data)
            cur = cur.next

    def get(self, index):
        """获得指定索引的节点"""
        # 先判断链表是否为空,若是空链表,则返回错误信息
        if self.is_empty():
            raise Exception('链表为空!')
        # 将变量 p 赋值为链表的头节点,即链表中的第一个节点。通过这个赋值操作,变量 p
        #  现在指向了链表的头节点,可以用来遍历链表或者访问链表中的其他节点。
        else:
            p = self._head
            for i in range(index):
                p = p.next
        return p
    
    def add(self, data):
        """头部插入元素"""
        # 先创建一个保存data值的节点
        node = Node(data)
        # 将新节点的next指向头节点,即_head指向的位置
        node.next = self._head
        # 将链表的头节点设为新的节点
        self._head = node

    def append(self, data):
        """尾部插入元素"""
        node = Node(data)
        # 先判断链表是否为空,若是空链表,则将_head指向新节点
        if self.is_empty():
            self._head = node
        # 若不为空,则找到尾部,将尾节点的next指向新节点
        else:
            cur = self._head
            while cur.next != None:
                cur = cur.next
            cur.next = node
    
    def insert(self, pos, data):
        """在指定位置插入元素"""
        # 若指定位置pos为第一个元素之前,则执行头部插入
        if pos == 0:
            self.add(data)
        # 若指定位置超过链表尾部,则执行尾部插入
        elif pos > (self.length() - 1):
            self.append(data)
        # 找到指定位置
        else:
            node = Node(data)
            count = 0
            # pre用来指向指定位置pos的前一个位置pos-1,初始从头节点开始移动到指定位置
            pre = self._head
            while count < (pos - 1):
                count += 1
                pre = pre.next
            # 先将新节点node的next指向插入位置的节点
            node.next = pre.next
            # 将插入位置的前一个节点的next指向新节点
            pre.next = node

    
    def remove(self, data):
        """删除节点"""
        cur = self._head
        pre = None
        while cur != None:
            # 找到了指定元素
            if cur.data == data:
                # 如果第一个就是要删除的节点
                if not pre:
                    # 将头指针指向头节点的后一个节点
                    self._head = cur.next
                else:
                    # 将删除位置前一个节点的next指向删除位置的后一个节点
                    pre.next = cur.next
                break
            else:
                pre = cur
                cur = cur.next

    def search(self, data):
        """链表查找节点是否存在,并返回True或者False"""
        cur = self._head
        while cur != None:
            if cur.data == data:
                return True
            else:
                cur = cur.next
        return False


if __name__ =="__main__":
    ll = SingleLinkList()
    ll.append(1)
    ll.append(2)
    ll.append(3)
    ll.append(4)
    ll.append(5)
    ll.remove(1)
    ll.insert(2, 4)
    ll.travel()
    print("length: ", ll.length())

本文中出现的理论为本人总结通关村教材书写,由于是新手,难免有疏漏,出现的图多数为《漫画算法 小灰的算法之旅》中图片。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值