Bootstrap

单向链表Python

单向链表

链表结构

链表是一种物理存储单元上非连续、非顺序的存储结构

数据元素的逻辑顺序通过链表中的指针链接次序实现

链表由一系列结点组成,结点可以在运行时动态生成

每个结点包括两个部分:存储数据元素的数据域、存储下一个节点地址的指针域

在这里插入图片描述


用python实现单向链表

用class分别定义结点(Node)链表(linklist) 两个类,添加想要的属性即可!

节点类

代码实现如下:

class Node:
    # 结点类
    def __init__(self,data,_next = None):
        self.data = data
        self._next = _next

链表类
代码实现如下:

class Singlelinklist:
    def __init__(self):
        self.head = None
        self._length = 0

单项链表所具有的操作

方法功能
is_empty()链表是否为空
length()返回链表的长度
node_list()返回链表中所有节点的值组成的列表
add(data)链表的头部添加一个节点,值为data
append()链表尾部添加节点,值为data
insert(pos,data)指定的位置添加节点,值为data
remove(data)删除第一个值为data的节点
modify(pos,data)修改指定位置元素的值为data
search(data)查找节点是否存在

往链表头部添加一个结点add()

实现步骤: 链表为空的时候照样成立

  1. 创建一个新的节点
  2. 将新的节点的next属性指向原来的头部节点,即:self.head
  3. 将头部节点重新指向创建的新节点
  4. 链表的长度属性加1
    在这里插入图片描述

链表为空的情况如下:
打你想链表为空添加头部节点

代码实现如下:

注意本文def的所有的方法(函数)都需要放到类(class)中才可实现,其是一个类方法

    def add(self,data): # 请将我放入类中执行
        node = Node(data)
        node._next = self.head
        self.head = node       # 存储的是一个类对象  而不是对象   类对象中包括对象和下一个类对象的地址
        self.lenth += 1

返回链表中所有节点的值组成的列表

方法1实现步骤
循环节点法

  1. 创建一个新的列表
  2. 将每个节点的data属性添加到列表中
  3. 返回列表

具体第二步入何进行实现?如何才能遍历每个节点,请看下图:
在这里插入图片描述

代码实现如下:

def nodes_list(self):
        res = []
        cur = self.head   # 第一个结点
        while cur:        #如果有节点进入循环
            res.append(cur.data)     #将该节点的数据添加到列表中
            cur = cur.next             # 并赋值下一节点   如果下一节点为 None  跳出循环返回res
        return res

方法2实现步骤
循环索引法

  1. 创建一个列表res
  2. 循环遍历指定的次数
  3. 将指定的data值添加到列表中
  4. 返回res列表

具体实现请看下图:
在这里插入图片描述

具体的如何精确控制循环的次数?

a= 4     # a是多少就循环多少次
while a:
    print(f'这是第{5-a}次循环')
    a -= 1

这是第1次循环
这是第2次循环
这是第3次循环
这是第4次循环

代码实现如下:

    def nodes_list(self):
        res = []
        a = self.length()    #循环self.length()次
        cur = self.head
        while a:
            res.append(cur.data)
            cur = cur.next
            a -= 1
        return res

往链表的末尾添加一个结点

实现方法1步骤

  1. 循环找到尾节点
  2. 将尾节点的next属性指向新的节点
  3. 链表的长度加1
  • 通过next属性是否为None来指向尾部节点详见博主原创文章 ==双向链表python实现==(附件1:双向链表寻找尾结点(同单向))内容
    尾部添加节点
    空链表尾部添加节点

代码实现如下:

    def append(self, data):
        node = Node(data)
        cur = self.head
        if cur:  # 如果head属性有节点,就证明不是空链表
            while cur.next:  # 当cur为尾节点的时候 跳出循环
                cur = cur.next
            cur.next = node
        else:
            self.head = node
        self._length += 1

实现方法2(通过索引找到指定的节点)

  • 通过索引找到指定的节点的方法详见博主原创文章 ==双向链表python实现==(附件2:通过索引寻找指定的结点)内容
    查找尾节点
    def append_node(self,data):
        node = Node(data)
        if self.is_empty():
            self.head = node   # 空链表的执行条件
        cur = self.head        # 非空链表的执行条件
        a = self._length - 1
        while a:
            cur = cur.next
            a -= 1
        cur.next = node
        self._length += 1

在指定的位置插入结点

示意图如下:
指定的位置插入节点

思考这么一个问题:

就拿上面盖的这个图来说,给索引为2的地方插入一个值,重要的是找索引为1的节点还是索引为2的节点?

显然索引为1的节点才是重要的!!!

  • 首先Step1是将新的节点的next属性指向原来索引为2的节点,也就是索引为1的节点的next属性(就是索引为2的节点)
  • Step2是将索引为1的next属性指向新的节点,如果得到索引为2的节点无法直接完成操作(需要定义一个前驱节点)

实现具体步骤
代码实现如下:

  1. 创建新的节点node
  2. 找到索引为pos-1的节点(前面好像说了如何通过索引找到指定的节点呦)
  3. 将pos-1结点 next属性(原来pos位置上的结点)赋值给新的结点的next属性
  4. 将新的结点赋值给 pos-1结点的next属性
  5. 链表长度加1
    def insert(self,pos,data):   # pos代表插入节点的位置,也就是 第pos+1 个结点 pos是索引
        '''
        非正常情况下
        如果小于0  默认添加到头部
        如果超出范围  默认添加到尾部
        '''
        if pos <= 0:
            self.head = node
        elif pos > self.lenth:   # 在此处pos > self.lenth-1也行 只不过  当添加末尾元素的时候  会调用 append()方法
            self.append(data)
        else:
            node = Node(data)
            cur = self.head
            pos -= 1
            while pos:     # 这里应用的很巧妙  因为没有像传统的循环那样创建一个计数器  直接用现有的pos
                cur = cur.next
                pos -= 1
            node.next = cur.next
            cur.next = node
            self.lenth += 1

删除第一个数据为data的元素

删除第一个值为data的节点
具体步骤

  1. 创建前驱节点prev
  2. 遍历链表,找到值为data的结点
  3. 将被删除结点的next赋值给被删除结点的前一节点(也就是说程序执行的过程中,为前后两个结点共同执行)
  4. 如果data找不到返回-1 如果成功删除直接跳循环 返回0
  5. 链表长度-1
'''仅仅只是Singlelinklist类方法定义的部分  其他的部分参考上面的'''
def remove(self,data):
        # 删除链表中第一个值为data的元素
        cur = self.head
        prev = None    # 创建一个前驱节点
        while cur:
            if cur.data == data:   # 进行删除操作
                # 内层的if  都是删除操作
                if not prev:    # 删除的节点是第一个节点
                    self.head = cur.next
                    '''
                    原本的self.head 为 cur  但是如果是第一个的要被删除的话  那么 self.head 必须指向第二个  即第一个的next属性
                    '''
                else:
                    prev.next = cur.next
                    '''
                    如果删除的结点不是第一个结点  那么就将被删除结点的next属性赋值给  被删除结点的前一个结点prev
                    '''
                self.lenth -= 1
                return 0    #   加上这行代码  只删除一个  不加的话   删除全部
            prev = cur
            cur = cur.next
        return  -1

在这里再提供另一种代码的实现方法
但是下面的代码的可变形性不太好,自行使用哈

    def remove_(self,data):
        cur = self.head
        if cur.data == data:
            self.head = self.head.next
            self._length -= 1
            return 0
        prev = None
        while cur:
            if cur.data == data:
                prev.next = cur.next
                self._length -=1
                return 0
            else:
                prev = cur
                cur = cur.next
        return -1

修改链表中的某一结点的data

  • 正常情况:
    只用self.head一次 然后 进行pos次循环next 就可以找到相对应的结点
    将节点的data属性进行修改即可
  • 非正常情况下 输入的 值超出范围的时候

代码实现如下(这部分又涉及到了 通过索引找到指定的节点=附件2)

    def modify(self,pos,data):
        '''
        修改链表中指定位置的值
        '''
        if 0 <= pos < self.lenth:
            cur = self.head
            while pos:
                cur = cur.next
                pos -= 1
            cur.data = data

        else:
            print('索引不在范围内')

查找链表中是否值为data的结点

    def search(self,data):
        # 查找链表中是否有值为data的结点
        '''
        循环遍历每一个结点  判断其值是否为data
        如果是 返回索引值 否则 返回None
        '''
        cur = self.head
        count = 0
        while cur:
            if cur.data == data:
                return f'找到该数据了,其索引为{count}'
            else:
                cur = cur.next
                count += 1
        return '未找到data的结点'

另一种实现方法(方法本身不太好,但是是想引出一个知识点):

    def search(self,data):
        '''
        查找结点是否存在
        '''
        cur = self.head
        while cur != None and cur.data != data :
            cur = cur.next
        if cur == None:
            print('不存在')
        elif cur.data == data:
            print('存在')

本行代码
while cur != None and cur.data != data :😗
涉及到一个知识点,看代码
如果为空链表cur.data != data会报错,但是前面有一个cur != None值为False就已经可以确定条件表达式的值为False,后面的代码解释器直接跳过不会执行,也就不会报错。


链表和顺序表的时间复杂度

操作链表顺序表
访问元素O(n)O(1)
头部插入/删除O(1)O(n)
尾部插入/删除O(n)O(1)
中甲插入/删除O(n)O(n)

插入操作多的话,用链表
查找操作多的话,用顺序表


附件:用python实现链表功能的全部代码

# @Author  :泰敢人辣
# Date     :2023-07-21 12:40

class Node:
    # 结点类
    def __init__(self,data,_next=None):
        self.data = data
        self.next = _next


class Singlelinklist:
    def __init__(self):
        self.head = None  # 链表的头节点
        self.lenth = 0    # 链表的长度

    def is_empty(self):
        return self.lenth == 0

    def _lenth(self):
        return self.lenth

    def nodes_list(self):
        res = []
        a = self.head   # 第一个结点
        while a:        #如果右节点进入循环
            res.append(a.data)     #将该节点的数据添加到列表中
            a = a.next             # 并赋值下一节点   如果下一节点为 None  跳出循环返回res
        return res

    def add(self,data):
        #往链表头部添加一个结点,值为data
        '''
        创建一个新的结点data
        使其node指向当前链表的头节点
        并将head指向当前链表的node结点
        '''
        node = Node(data)         # 创建类对象
        # print(f'1的类对象的id为{id(node)}')
        node.next = self.head
        self.head = node       # 存储的是一个类对象  而不是对象   类对象中包括对象和下一个类对象的地址
        self.lenth += 1

    def append(self,data):
        node = Node(data)   # 创建新的类对象
        '''
        找到链表的尾部结点  特征为 其next类属性为None
        将其.next属性改为类对象node
        '''
        a = self.head      # 第一个结点
        if a:        # 如果链表有头节点  就证明他不是一个空链表
            while a.next:     # a.next代表的是下一个节点  如果有东西  进入循环  反之 下一个结点就是新创建的结点
                a = a.next   # 有东西 接着往下走  知道找到没有东西的那一个  赋值node
            a.next = node
        else:
            self.head = node
        self.lenth += 1

    def insert(self,pos,data):   # pos代表插入节点的位置,也就是 第pos+1 个结点 pos是索引
        '''
        非正常情况下
        如果小于0  默认添加到头部
        如果超出范围  默认添加到尾部
        '''
        node = Node(data)
        if pos <= 0:
            self.head = node
        elif pos > self.lenth:   # 在此处pos > self.lenth-1也行 只不过  当添加末尾元素的时候  会调用 append()方法
            self.append(data)
            print('append()方法被调用执行了')
        else:
            '''
            正常情况下
            第一步:创建新的node对象     ===========
            第二步:找到索引为pos-1(前)结点    
            第三步:将pos-1结点 next属性(原来pos位置上的结点)赋值给新的结点的next属性
            第四步:将新的结点赋值给 pos-1结点的next属性
            第五步:长度加1
            '''
            a = self.head
            pos -= 1
            while pos :   # 这里应用的很巧妙  因为没有像传统的循环那样创建一个计数器  直接用现有的pos
                a = a.next
                pos -= 1
            node.next = a.next
            a.next = node
            self.lenth += 1

    def remove(self,data):
        # 删除链表中第一个值为data的元素
        '''
        1. 遍历链表,找到值为data的结点
        2. 将被删除结点的next赋值给被删除结点的前一节点(也就是说程序执行的过程中,为前后两个结点共同执行)
        3. 如果data找不到返回-1  如果成功删除直接跳循环  返回0
        4. 链表长度-1
        :param data:
        :return:
        '''
        cur = self.head
        prev = None
        while cur:
            if cur.data == data:
                # 内层的if  都是删除操作
                if not prev:
                    self.head = cur.next
                    '''
                    原本的self.head 为 cur  但是如果是第一个的要被删除的话  那么
                    self.head 必须指向第二个  即第一个的next属性
                    '''
                else:
                    prev.next = cur.next
                    '''
                    如果删除的结点不是第一个结点  那么就将被删除结点的next属性赋值给  
                    被删除结点的前一个结点prev
                    '''
                self.lenth -= 1
                return 0    #   加上这行代码  只删除一个  不加的话   删除全部
            prev = cur
            cur = cur.next
        return  -1

    def modify(self,pos,data):
        '''
        修改链表中指定位置的值
        思路
        正常情况下
        只用self.head一次 然后  进行pos次循环next  就可以找到相对应的结点
        将节点的data属性进行修改即可
        非正常情况下   输入的 值超出范围的时候
        :param pos:
        :param data:
        :return:
        '''
        if 0 <= pos < self.lenth:
            cur = self.head
            while pos:
                cur = cur.next
                pos -= 1
            cur.data = data

        else:
            print('索引不在范围内')

    def search(self,data):
        # 查找链表中是否有值为data的结点
        '''
        循环遍历每一个结点  判断其值是否为data
        如果是 返回索引值 否则 返回None
        :param data:
        :return:
        '''
        cur = self.head
        count = 0
        while cur:
            if cur.data == data:
                return f'找到该数据了,其索引为{count}'
            else:
                cur = cur.next
                count += 1
        return '未找到data的结点'


if __name__ == '__main__':
    l1 = Singlelinklist()
    l1.is_empty()

    print(l1.nodes_list())
    l1.append(2)
    print(l1.nodes_list())
    l1.append(3)
    print(l1.nodes_list())
    l1.append(4)
    print(l1.nodes_list())
    l1.append(5)
    print(l1.nodes_list())
    l1.insert(1,100)
    print(l1.nodes_list())
    l1.insert(4,100)
    print(l1.nodes_list())
    l1.insert(7,100)
    print(l1.nodes_list())

    print('==================链表元素data的删除操作=======================')
    l1.remove(100)
    print('删除100')
    print(l1.nodes_list())
    l1.remove(2)
    print('删除2')
    print(l1.nodes_list())
    print('==================链表元素data的修改操作=======================')
    l1.modify(0,10000)
    print('修改0为10000')
    print(l1.nodes_list())
    l1.modify(3, 20000)
    print('修改3为20000')
    print(l1.nodes_list())
    print('==================链表元素data的查找操作=======================')
    print(l1.search(3))
    print('==================判断链表是否为空=======================')
    print(l1.is_empty())

附件2:单向链表寻找尾结点

cur = self.head
while cur.next:      # 加上next尾节点不会进入循环 也就会得到尾节点
    cur = cur.next

cur = self.head
while cur:           #不加next尾节点进入循环,最终的结果只会是None
    cur = cur.next
;