链表原理及其python实现

一、原理详解

1、基本介绍

链表和数组同属于线性表,与数组不同的是,链表不必顺序存储,因此能够灵活利用计算机中的空间,并且链表在插入的时候可以达到O(1)的复杂度。
有利必有弊,在查找一个节点或者访问特定编号的节点时,链表需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。

2、链表的结构

1、单向链表

在这里插入图片描述
如图所示,单向链表通过一个外部的头链接来访问第1项,单向链表的节点被分成两个部分,第一部分保存或显示关于节点的信息,第二部分储存下一节点地址。
单向链表只能向一个方向遍历。

2、双向链表

(以下图片均来自于百度图片)
在这里插入图片描述
双向链表中每个节点有两个指针,一个指向前一节点,另一个指向后一节点。最后一项没有指向下一项的指针,第一项没有指向前一项的指针。在双向链表中还有一个外部的tail指针,它允许直接访问结构中的最后一个节点。

简单总结就是
单链表中只存储了下一个元素的地址,双向链表在此基础之上增加了上一个元素的地址。

3、循环链表

在这里插入图片描述
在单链表的基础上,增加了尾部→头部的功能。

4、双向循环链表

在这里插入图片描述
在双向链表的基础上,增加了头部和尾部的相互链接

二、python实现

1、单链表的Python实现

1、代码部分

链表与数组不同,链表中的基本单位是节点,单向链表的节点包含:数据项、指向下一节点的链接。双向链表的节点包含:数据项、指向下一节点的链接、指向上一节点的链接。

所以链表要先定义一个节点类

class Node(object):
	'''定义单链表节点类'''
	def __init__(self,data,next = None):
		'''data为数据项,next为下一节点的链接,初始化节点默认链接为None'''
		self.elem= elem
		self.next = None

接下来定义操作对象类,每个函数都有很详细的注释,应该很好理解

# 下面创建单链表,并实现其应有的功能

class SingleLinkList(object):
    """单链表"""

    def __init__(self, node=None):  # 使用一个默认参数,在传入头结点时则接收,在没有传入时,就默认头结点为空
        # 这段语句必须要有  用于给出头节点的属性
        self.__head = node  # 加双下划线表示私有属性(因为不供外部使用)

    def is_empty(self):
        '''链表是否为空'''
        return self.__head == None  # 判断头节点的指向即可

    def length(self):
        '''计算链表长度——需要从头到尾遍历一遍'''
        # cur游标,用来移动遍历节点
        cur = self.__head
        # count记录数量
        count = 0
        while cur != None:  # 如果count初始值为1,这里就改为cur.next,但同时需要额外处理空节点的特殊情况
            count += 1
            cur = cur.next
        return count

    def travel(self):
        '''遍历整个列表'''
        cur = self.__head
        while cur != None:
            print(cur.elem,end='')  # python3中用end=''来取消换行
            cur = cur.next
        print("\n")

    def add(self, item):
        '''头插法:链表头部添加元素'''
        node = Node(item)  # item是要插入的元素
        '''
        这里的先后顺序极为重要!
        必须先让待插入节点指向原有链表的头部,再让头节点指向待插入节点.
        要是头节点先指向待插入节点,那么原有链表串后面的内容就都丢失了!
        '''
        node.next = self.__head
        self.__head = node

    def append(self, item):
        '''尾插法:链表尾部添加元素'''
        node = Node(item)  # item就是要添加的元素
        # 当链表为空时没有next,所以要先判空
        if self.is_empty():
            self.__head = node
        else:
            cur = self.__head
            while cur.next != None:  # 如果未判空,空链表会在这出错
                cur = cur.next
            cur.next = node

    def insert(self, pos, item):
        '''指定位置添加元素'''
        if pos <= 0:  # pos是需要插入的位置坐标
            # 如果pos位置在0或者以前,那么都当做头插法来做
            self.add(item)
        elif pos > self.length() - 1:
            # 如果pos位置比原链表长,那么都当做尾插法来做
            self.append(item)
        else:
            per = self.__head
            count = 0
            while count < pos - 1:
                count += 1
                per = per.next
            # 当循环退出后,pre指向pos-1位置
            node = Node(item)
            node.next = per.next  # 顺序同样重要,还是得先让待插入节点指向原链表后面的节点
            per.next = node  # pre只是一个游标,所以都是用pre.next去操作,不然原链表根本没改动

    def remove(self, item):
        '''删除节点'''
        cur = self.__head
        pre = None
        while cur != None:
            if cur.elem == item:
                # 先判断该节点是否是头结点
                if cur == self.__head:
                    self.__head = cur.next  # 如果是头节点,直接将头指针调到下一位
                else:
                    pre.next = cur.next  # 将cur现在所在的节点直接跳过,达到删除的目的
                break
            else:
                pre = cur  # 这条的作用是始终保持pre在cur前一个节点上
                cur = cur.next

    def search(self, item):
        '''查找节点是否存在'''
        cur = self.__head
        while not cur:
            if cur.elem == item:
                return True
            else:
                cur = cur.next
        return False


2、代码测试

if __name__ == "__main__":

        # node = Node(100)  # 先创建一个节点传进去

    ll = SingleLinkList()
    print(ll.is_empty())
    print(ll.length())

    ll.append(3)
    ll.add(999)
    ll.insert(-3, 110)
    ll.insert(99, 111)
    print(ll.is_empty())
    print(ll.length())
    ll.travel()
    ll.remove(111)
    ll.travel()

执行结果如下
在这里插入图片描述

2、双向链表的python实现

与单向链表相比多了一个指向前节点的指针,话不多说,直接看代码吧

class Node(object):
    def __init__(self, value):
        self.value = value
        # 前驱区
        self.prev = None
        # 后继区
        self.next = None


class LinkedListTwoway(object):
    def __init__(self):
        self.__head = None

    def is_empty(self):
        return self.__head is None

    def __len__(self):
        count = 0
        cur = self.__head
        while cur:
            count += 1
            cur = cur.next
        return count

    def traverse(self):
        cur = self.__head
        while cur:
            print(cur.value)
            cur = cur.next

    def add(self, value):
        node = Node(value)
        if self.is_empty():
            self.__head = node
        else:
            # 待插入节点的后继区指向原头节点
            node.next = self.__head
            # 原头节点的前驱区指向待插入节点
            self.__head.prev = node
            self.__head = node

    def append(self, value):
        node = Node(value)
        cur = self.__head
        if self.is_empty():
            self.__head = Node
            return
        while cur.next:
            cur = cur.next
        cur.next = node
        node.prev = cur

    def insert(self, pos, value):
        if pos <= 0:
            self.add(value)
        elif pos > len(self) - 1:
            self.append(value)
        else:
            # 单向链表中为了在特定位置插入,要先在链表中找到待插入位置和其前一个位置
            # 双向链表中就不需要两个游标了(当然单向链表中一个游标也是可以只找前一个位置)
            node = Node(value)
            count = 0
            cur = self.__head
            while count < pos - 1:
                count += 1
                cur = cur.next
            # 此时的游标指向pos的前一个位置
            # 这里的相互指向需尤为注意,有多种实现,需细细分析
            node.next = cur.next
            cur.next.prev = node
            node.prev = cur
            cur.next = node

    def search(self, value):
        cur = self.__head
        while cur:
            if cur.value == value:
                return True
            else:
                cur = cur.next
        return False

    def remove(self, value):
        if self.is_empty():
            return
        cur = self.__head
        while cur:
            if cur.value == value:
                if cur == self.__head:
                    self.__head = cur.next
                    # 处理链表只有一个节点的特殊情况
                    if cur.next:
                        cur.next.prev = None
                else:
                    cur.prev.next = cur.next
                    # 处理待删除节点是最后一个情况
                    if cur.next:
                        cur.next.prev = cur.prev
                return
            else:
                cur = cur.next

3、单向循环链表的python实现

注意两个点(与单链表相比):
1、需要遍历找到尾结点给其补完整循环
2、while遍历不包含尾结点

class Node(object):
    def __init__(self, value):
        # 元素域
        self.value = value
        # 链接域
        self.next = None


class CircularLinkedListOneway(object):
    # 单向循环链表与单向链表唯一的区别就是尾部指针指向head节点
    def __init__(self, node=None):
        # 构造非空链时,让其地址域指向自己
        if node:
            node.next = node  # 这里一开始就要构造循环
        self.__head = node

    def is_empty(self):
        # 判空与单链表一致
        return self.__head == None

    def __len__(self):
        # 遍历求长度
        count = 1
        cur = self.__head
        if self.is_empty():
            return 0
        while cur.next != self.__head:  # 与单链表相比,将指向none的判断条件改为指向头节点
            count += 1
            cur = cur.next
        return count

    def traversal(self):
        if self.is_empty():
            return
        cur = self.__head
        while cur.next != self.__head:
            print(cur.value,end='')
            cur = cur.next
        # 退出循环时,cur正是尾节点,尾结点的值未打印,所以要手动打印一下,这就是边界条件
        print(cur.value)

    def add(self, value):
        """头插法"""
        node = Node(value)
        if self.is_empty():
            self.__head = node  # 将新节点指向头节点
            self.__head.next = self.__head  # 别忘了加循环
            return
        cur = self.__head
        while cur.next != self.__head:
            cur = cur.next
        # 新节点的next指针指向原头节点
        node.next = self.__head
        # 将新节点指向头节点
        self.__head = node
        # 尾节点next指针指向新头节点
        cur.next = self.__head  # 这个是比单链表多出来的部分,而且为了找到尾结点,需要遍历一遍

    def append(self, value):
        node = Node(value)
        cur = self.__head
        if self.is_empty():
            self.__head = node
            self.__head.next = self.__head
            return
        while cur.next != self.__head:
            cur = cur.next
        node.next = cur.next
        cur.next = node

    def insert(self, pos, value):
        if pos <= 0:
            self.add(value)
        elif pos > len(self) - 1:
            self.append(value)
        else:
            node = Node(value)
            cur = self.__head
            count = 0
            while count < pos - 1:
                count += 1
                cur = cur.next
            node.next = cur.next
            cur.next = node

    def search(self, value):
        if self.is_empty():
            return False
        cur = self.__head
        while cur.next != self.__head:
            if cur.value == value:
                return True
            else:
                cur = cur.next
        # 别忘了while循环外的尾节点,尾结点还要再判断一遍
        if cur.value == value:
            return True
        return False

    def remove(self, value):
        cur = self.__head
        prior = None  # prior就是pre 始终在cur前面
        if self.is_empty():
            return
        while cur.next != self.__head:
            # 待删除节点如果找到
            if cur.value == value:
                # 待删除节点恰好是头节点,还要遍历一遍找到尾结点给它安排循环
                if cur == self.__head:
                    rear = self.__head
                    while rear.next != self.__head:  # 找尾部
                        rear = rear.next
                    self.__head = cur.next
                    rear.next = self.__head
                # 待删除节点在中间
                else:
                    prior.next = cur.next
                    return  # 这里不是跳出循环的break,而是退出函数的return哦,因为已经处理完毕了
                # 如果还没找到
            else:  # 跳出循环体,表示要删除的是尾结点
                prior = cur
                cur = cur.next
        # 待删除节点在尾部
        if cur.value == value:
            # 如果链表中只有一个元素,则此时prior为None,Next属性就会报错
            # 此时直接使其头部元素为None即可
            if cur == self.__head:
                self.__head = None
                return
            prior.next = cur.next  # 因为这个cur.next就等于头节点,这一步其实就是去掉尾结点直接连在头节点上了

参考:
https://www.cnblogs.com/yifeixu/p/8966613.html
https://blog.csdn.net/Tonywu2018/article/details/88853533
https://www.cnblogs.com/yudanqu/p/9172459.html

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值