【Python-数据结构】——链式结构

《Python数据结构》@EnzoReventon

Python 数据结构 —— 链式结构LinkNode——单链表

目录:

1.单链表
1.1 首先初始化一个根节点,用于表示第一个位置
1.2 定义一个在尾部增加节点的方法
1.3 在链表的头部添加一个新的节点
1.4 迭代器
1.5 查找链表中指定的内容
1.6 查找链表中重复内容的数量
1.7 删除某个节点内容
1.8 删除链表中所有指定的相同内容

2. 函数测试

======================================================================================

在本文中我们主要介绍两种链式结构,单链表双链表。
顾名思义单链表是单向的,双链表是双向的,双链表可以逆向遍历
为什么要引入链表这个概念呢?
对于上文《线性结构》介绍的Array数组而言,使用list在头部或者中间插入数据会非常麻烦,需要把插入位置的数据一个一个的往后面平移。
特别是需要频繁的在头和尾的位置进行数据的插入或者删除,list就显得不太合适了。

链表是一个相对而言比较绕和复杂的东西,这样一来也显现出了他的一些缺点,比如说他不能像数组一样通过指定index快速的访问到某个位置的数据,而是需要从头到尾一个一个的进行遍历。
学习过程中重在了解链表之间的是如何建立联系的,把种种关系理清楚了,代码就好写了。

1 单链表

首先我们来介绍单链表,链表存储数据是不连续的,他是把许多数据“串”起来,不同于数组,数组中的数据就是单纯的一个数据,而链表就不一样了,数据只是其中的一个部分,往上一级叫做节点,一个节点中包含数据+指针,这里的指针类似于C语言中的指针,达到的效果类似,但是用法不一样,读者仔细研究之后就会发现其特点;言归正传,这里的指针指向于某一个节点,对于单链表而言,由于单链表是单项的,所以这个指针往往是next指向下一个节点。

在这里插入图片描述
初始化节点代码如下:

class Node():		#定义一个节点类,并初始化该类 该类中有value以及next
    def __init__(self,value,next=None):		#定义一个初始化的方法
        self.value = value					#方法中定义value用于存储数据,定义Next用于指向下一个节点
        self.next = next

那么链表是如何关联起来的呢?
如下图所示,节点1的Next指针指向节点2,节点2的指针指向节点3,以此类推。
在这里插入图片描述
当然,节点4的指针还可以指向节点1,形成一个循环列表。
举个例子,实现节点的关联。

node1 =  Node(value1)
node2 =  Node(value2)
node1.next = node2 

这样就可以实现链表节点的关联了,是不是看上去很简单?

当然对于一个链表而言,节点的关联只是其中一个很小的部分,对于链表而言我们更多的操作的是对其进行添加节点,删除节点,遍历节点等等。
在接下来,我就详细的介绍一下在尾部插入节点,在头部插入节点,遍历节点,寻找节点,寻找重复数据的个数,移除节点,移除所有节点是如何实现的。

1.1 首先初始化一个根节点,用于表示第一个位置

class LinkedList(object):  			#定义一个链表的类
    def __init__(self,root=None):	#定义链表的初始化方法
        self.root = Node()			#实例化一个根节点,数值为None
        self.size = 0				#记录这个链表的大小,含有多少个节点
        self.next = None 			#定义一个Next指针,新增数据的时候与新增数据进行关联,初始化的时候还没有所以为None

1.2 定义一个在尾部增加节点的方法

对于在尾部增加一个新的节点我们需要考虑,在这个链表中是否有节点,即考虑我是不是在根节点root后面增加新节点。
如果说,该链表还是空的只有一个root节点,那么我们就将新节点挂到root后面就好了。如下图所示。
在这里插入图片描述
如果说,该链表已经有节点了,那么就是在该链表的最后一个节点后面添加数据。
在这里插入图片描述

程序实现如下:

class LinkedList(object):
    def __init__(self,root=None):#初始化方法
        self.root = Node()
        self.size = 0
        self.next = None #增加新数据时,将新数据的地址与谁关联

    def add(self,value):
        node = Node(value)
        #判断是否已经有数据
        if not self.next:
            self.root.next = node #将新节点挂到root后
        else:
            self.next.next = node
        self.next = node

首先创建一个LinkList链表这个类。

  • 在这个类下首先定义一个初始化方法,在这个方法中首先创建一个root根节点。
  • self.root = Node()实例化这个根节点。
  • self.next = None初始化这个根节点,其地址以及内容均为None。
  • self.size = 0记录该链表有多少个节点。

其次,定义一个add增加节点函数。

  • node = Node(value)表示实例化新增数据为一个新的节点,并自动指定好内存。
  • if not self.next:判断该链表是否只有一个root还是说该链表里面已经有了节点。
  • self.root.next = node 将新的节点连接到root根节点后面,当前的self还没有存储地址,为空,可以理解为一个指针,指向root。
  • else:之后self.next.next = node 将新增的节点添加到最后,其中self存储的是添加新节点之前的最后一个节点的地址,self.next.next就是指添加节点前最后一个节点的next,如果读者不明白的话,可以看此段函数的判断部分:if not self.next:这一句话是在判断,该链表root后面是否有节点,那么可以将self.next理解为永远指向最后一个节点,那么最后一个节点的next必然指向新添加的新节点node。Ps:可以将其理解为一个永远指向最后一个节点的指针。
  • self.next = node就是更新最后一个节点,指向最新添加的一个node。

这一部分的理解我和我同学花了一个半小时去理解,实验,从self的内存变化,self.next的内存变化,堆栈的角度分别去佐证,最后得出结论,self存储的是地址,类似于指针的东西,并且永远指向最后一个节点。也让我们更加深刻的理解了面向对象的意义。在此感谢我的好兄弟@ZoomToday,欢迎大家访问。

一旦理解了第一个,后面的其他函数就迎刃而解了!

1.3 在链表的头部添加一个新的节点

同样在做添加之前我们需要判断该链表是否为空,如果为空,那么我们直接在root后面进行添加。
否则我们需要将第一个于root关联的节点先与root断开,然后将新的节点于root关联并且与刚刚断开的节点关联。
如下图所示:
在这里插入图片描述

程序实现如下:

    def add_first(self, value):
        node = Node(value)
        if not self.next:
            self.root.next = node
            self.next = node
        else:
            temp = self.root.next  # 获取原来root后面的那个节点
            self.root.next = node  # 将新的节点挂到root上
            node.next = temp       # 新的节点的下一个节点是原来的root后的节点
        self.size += 1

在此程序中,我们格外需要注意的是else后面的,前面的部分和add函数是一样的,在此不多做赘述。

  • temp = self.root.next 在这里需要引入一个temp作为一个中间变量,用来保存root后面的那一个节点。
  • self.root.next = node 将root.next与新的node相关链。
  • node.next = temp 将新添加的node的next与刚刚保存的temp,也就是原来的第一个节点相关联。
  • self.size += 1 link的size+1
    从这一段代码也可以从侧面很好的证明,self.next就是始终指向该链表的最后一个节点的,因为这段代码else前部分是对链表的尾部进行操作的,需要更新尾部的节点,也就是更新self.next = node。而else之后,我们的操作不是在链表的尾部而是在头部,因此我们没有用到self.next,也没有更新他。

1.4 迭代器

用于后面遍历这个链表,整体的思想就是从root后的第一个节点开始一直往后遍历,直到到达self.next为止。
代码如下:

    def __iter__(self):             #遍历
        current = self.root.next
        if current:
            while current is not self.next:
                yield current
                current = current.next
            yield current

不断地输出遍历的节点内容,以便于后面使用find,remove等方法。

1.5 查找链表中指定的内容

这个比较简单,只需要使用迭代器,将你需要查找的内容与迭代器中输出内容作比较,如果相等那么就输出True。

    def find(self, value):          #查找指定元素
        for v in self.__iter__():
            if v.value == value:
                return True

不适用__iter__迭代器的寻找方法如下,返回的是地址,整体思想都是差不多的:

    def find2(self, value):
        current = self.root.next
        if current:
            while current is not self.next:
                if current.value == value:
                    return current
                current = current.next

1.6 查找链表中重复内容的数量

与查找是一个道理,引入一个计数器++即可,遇到相同的不退出即可。

    def find_count(self,value):  #查找数据有多少个
        count = 0
        for n in self.__iter__():
            if n.value == value:
                count += 1
        return count

1.7 删除某个节点内容

删除某个节点的内容首先是要遍历找到对应的节点,然后判断这个节点是否为最后一个节点,如果要删除的是最后一个节点,那么我们需要将他前一个节点的next设置为None;如果不是最后一个节点,我们需要将待删除节点的前一个节点的next与待删除的后一个节点相关联起来。如下图所示:

  • 是最后一个节点
    在这里插入图片描述
  • 不是最后一个节点
    在这里插入图片描述

代码实现:

    def remove(self, value):
        prev = self.root
        for n in self.__iter__():
            if n.value == value:
                if n == self.next:
                    prev.next = None
                    self.next = prev
                prev.next = n.next
                del n
                self.size -= 1
                return True
            prev = n

此代码中有几个注意事项:

  • prev = self.root 记录一下当前节点的上一个节点
  • for n in self.__iter__(): 往后遍历
  • if n.value == value:判断遍历的内容是否与你需要删除的内容一样
  • if n == self.next: 如果找到了一样的,那么判断是否是最后一个节点
  • prev.next = None / self.next = prev 如果是最后一个节点,那么将这个节点的next赋值为None,同样self.next需要指向最后一个节点。
  • prev.next = n.next 如果说不是最后一个节点,那么,上一个节点的next等于这个节点的next,也就是将上一个节点与下一个节点相关链,del n 删除这个节点,self.size -= 1 总数-1。
  • prev = n 每次遍历结束更新一下上一个节点。以便于遍历

1.8 删除链表中所有指定的相同内容

思想和删除指定内容是一样的,只不过多一个循环,进行完整的遍历。

    def remove_all(self,value):
        prev = self.root
        for n in self.__iter__():
            if n.value == value:
                if n == self.next:
                    prev.next = None
                    self.next = prev
                prev.next = n.next
                del n
                self.size -= 1
                continue
            prev = n

区别在于continue 删除了该节点之后,继续循环下去,直到遍历结束,而上面的remove删除掉指定内容之后就return了。

2 函数测试

.add(value) .add_first(value)
if __name__ == '__main__':
    link = LinkedList()
    link.add("111111")
    link.add_first("222222")
    link.add("111111")
    link.add("111111")
    link.add("111111")
    link.add("111111")


    for v in link: #遍历链表
        print(v)

结果:我添加了5个“111111”,头部添加了一个“22222”。如下图所示:
在这里插入图片描述

.find(value)
if __name__ == '__main__':
    link = LinkedList()
    link.add("111111")
    link.add_first("222222")
    link.add("111111")
    link.add("111111")
    link.add("111111")
    link.add("111111")
    print(link.find("222222"))

结果:在这么多链表中找到了“22222”
在这里插入图片描述

.find_count(value)
if __name__ == '__main__':
    link = LinkedList()
    link.add('11111')
    link.add('22222')
    link.add('11111')
    print(link.find_count('11111'))
    print(link.find_count('22222'))

结果:
在这里插入图片描述

.remove(value)
if __name__ == '__main__':
    link = LinkedList()
    link.add('11111')
    link.add('22222')
    link.add('11111')


    link.remove('11111')

    print(link.find_count('11111'))
    for i in link:
        print(i.value)

结果:删除一个11111,还剩1个11111,打印出来。
在这里插入图片描述

.remove_all(value)
if __name__ == '__main__':
    link = LinkedList()
    link.add('11111')
    link.add('22222')
    link.add('11111')


    link.remove_all('11111')

    print(link.find_count('11111'))
    for i in link:
        print(i.value)

结果:删除所有11111,还剩0个,打印出来一个22222.
在这里插入图片描述

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EnzoReventon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值