【数据结构与算法】学习笔记2----链表

一、什么是链表?

链表(linked list)是一种在物理上非连续非顺序的数据结构,由若干节点(node)所组成。
链表的内存分配方式:随机存储
在这里插入图片描述

二、链表的分类

1.单向链表

单向链表的每一个节点包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的指针next。链表的第1个节点被称为头节点,最后1个节点被称为尾节点,尾节点的next指针指向空。
在这里插入图片描述

2.单向循环链表

单向循环链表的尾节点的next指针指向头节点
在这里插入图片描述

3.双向链表

双向链表比单向链表稍微复杂一些,它的每一个节点除了拥有data和next指针,还拥有指向前置节点的prev指针。
在这里插入图片描述

4.双向循环链表

双向循环链表的尾节点的next指针指向头节点,头节点的prev指针指向尾节点。
在这里插入图片描述

三、链表的基本操作(以“单向链表”为例)

1.查找节点

在查找元素时,链表不像数组那样可以通过下标快速进行定位,只能从头节点开始向后一个一个节点逐一查找。

2.更新节点

先从头节点开始向后一个一个节点逐一查找,查找到后把旧数据替换成新数据即可。

3.插入节点

(1)尾部插入
(2)头部插入
(3)中间插入
a. 某个节点之前插入
在这里插入图片描述
b.某个节点之后插入
在这里插入图片描述

4.删除节点

删除链表中的某个value,可能会遇到下面几种情况:
1. 链表可能为空:通过head判断该链表是否为空,如果链表为空则结束
2. 链表不为空但可能没有value:如果从头找到尾都没有找到value,说明该链表中没有value
3. 链表的头节点可能为value:头节点没有前驱节点
4. 链表不为空&存在value:通过value的前驱节点进行删除
在这里插入图片描述
具体代码实现如下:

# -*- coding: utf-8 -*-

class SinglyLinkedList:

    def __init__(self) ->None:
        self.head = None


    # 声明一个内部类,代表一个节点,每个节点有两个属性
    class Node:
        def __init__(self, data) -> None:
            # 第一个属性用来存数据
            self.data = data
            # 第二个属性用来指向下一个节点
            self.next = None

    def insert_to_tail(self,value):
        # 先判断头节点是否为空,如果头节点为空则调用insert_to_head方法
        if self.head is None:
            self.insert_to_head(value)
            return
        # 如果头节点不为空,则找到头节点
        q = self.head
        # 通过头节点的next节点一直循环找到尾节点
        # 当q的next节点不为空时则继续查找,当q的next节点为空时说明找到了尾节点
        while q.next is not None:
            q = q.next
        # 声明一个新节点
        new_node = self.Node(value)
        # 把尾节点的next指针指向新插入的节点即可
        q.next = new_node


    def insert_to_head(self,value):
        # 声明一个新节点
        new_node = self.Node(value)
        # 如果头节点为空,则直接将新节点变为链表的头节点
        if self.head is None:
            self.head = new_node
        # 如果头节点不为空:
        else:
            # 将新节点的next指针指向原先的头节点
            new_node.next = self.head
            # 把新节点变为链表的头节点
            self.head = new_node


    def find_by_value(self,value):
        if self.head is None:
            return Exception("链表为空!")

        q = self.head
        while q is not None and q.data != value:
            q = q.next
        if q is None:
            return Exception("链表内没有该value!")
        return q


    def insert_after(self,node,value):
        if node is None:
            return Exception("该node不存在!")
        # 声明一个新节点
        new_node = self.Node(value)
        # 先将新插入节点的next指针指向node的next指针指向的节点 (注意这两步不要写反,写反后会进入死循环)
        new_node.next = node.next
        # 再将指定node的next指针指向新插入的节点
        node.next = new_node



    def insert_before(self,node,value):
        if self.head is None:
            return Exception("该链表为空!")
        # 否则,找到头节点,从头节点开始遍历查找,利用q找node的前驱节点:
        q = self.head
        while q is not None and q.next != node:
            q = q.next
        # 该链表中没有找到node节点
        if q is None:
            return Exception("该node不存在!")
        # 找到node的前驱节点之后,声明一个新节点
        new_node = self.Node(value)
        # 新节点的next指针指向node
        new_node.next = node
        # node的前一个节点指向新节点
        q.next = new_node

    def print_all(self):
        if self.head is None:
            return Exception("该链表为空!")
        q = self.head
        while q is not None:
            print(q.data)
            q = q.next

    def delete_by_value(self,value):
        # 1. 通过head判断该链表是否为空,如果链表为空则结束
        if self.head is None:
            return Exception("链表为空!")
        # 2. 如果链表不为空,则通过头节点依次往后找下一个节点
        q = self.head
        # 因为要想删除链表中的一个节点,必须找到该节点的前驱节点,所以这里定义一个p
        p = None
        while q is not None and q != value:
            # 在q进行迭代前,先让p=q,这样p就是q的前驱节点了
            p = q
            q = q.next
        # 如果从头找到尾都没有找到value,说明该链表中没有value
        if q is None:
            return Exception("该链表中没有value!")
        # 如果p为None,说明头节点即为该value
        if p is None:
            # 那么直接将链表中的头节点变为之前头节点的下一个节点即可
            self.head = self.head.next
        # 否则就是将q的前驱节点p直接指向q的下一个节点
        else:
            p.next = q.next
        return True


def test_link():
    link = SinglyLinkedList()
    data = [1, 2, 5, 3, 1]
    for i in data:
        link.insert_to_tail(i)
    link.insert_to_tail(6)
    link.insert_to_head(0)
    # 打印内容为 0 1 2 5 3 1 6
    # link.print_all()

    # assert link.find_by_value(2) is not None
    # node = link.find_by_value(3)
    # link.insert_after(node, 10)
    # assert link.find_by_value(3).next.data == 10
    # # 打印内容为 0 1 2 5 3 10 1 6
    # link.print_all()
    # node = link.find_by_value(0)
    # print(node)
    # link.insert_after(node, 10)
    # # assert link.find_by_value(0).next.data == 10
    # # # 打印内容为 0 10 1 2 5 3 1 6
    # link.print_all()
    # #链表内没有该value,所以不进行插入
    # node = link.find_by_value(100)
    # link.insert_before(node, 30)
    # # # 打印内容为  0 1 2 5 3 1 6
    # link.print_all()
    # node = link.find_by_value(0)
    # print(node)
    # link.insert_before(node, 30)
    # link.print_all()
    # link.delete_by_value(2)
    # assert not link.delete_by_value(999)
    # assert link.delete_by_value(99)
    # # 打印内容为 1 5 3 1
    # link.print_all()
    
if __name__ == '__main__':
    test_link()

最近看《漫画算法: 小灰的算法之旅 (python篇)》发现里面代码写的更清晰明了,分别是「在指定位置插入元素」和「删除指定位置的元素」,插入和删除的分析其实是和上面一致的。看代码~

# -*- coding: utf-8 -*-
class SinglyLinkedList:
    def __init__(self) -> None:
        self.size = 0
        self.head = None
        self.last = None

    class Node:
        def __init__(self, data) -> None:
            self.data = data
            self.next = None

    def get_value(self,index):
        if index < 0 or index >= self.size:
            return Exception("查询位置超出链表的范围了!")
        p = self.head
        for i in range(index):
            p = p.next
        return p

    def insert(self,index,value):
        if index < 0 or index > self.size:
            return Exception("插入位置超出链表的范围了!")
        # 声明一个新节点
        new_node = self.Node(value)
        # 如果是空链表,则直接将该新节点为链表的头和尾
        if self.size == 0:
            self.head = new_node
            self.last = new_node
        # 如果插入在链表的头部
        elif index == 0:
            # 将新节点的next指针指向原先的头节
            new_node.next = self.head
            # 把新节点变为链表的头节点
            self.head = new_node
        # 如果插入在链表的尾部
        elif self.size == index:
            # 将原先的尾节点的next指向新节点
            self.last.next = new_node
            # 把新节点变为链表的尾节点
            self.last = new_node
        # 插入中间,则需要找到index的前驱节点
        else:
            prev_node = self.get_value(index-1)
            # 将新节点的next指向前驱节点的next
            new_node.next = prev_node.next
            # 将前驱节点的next指向新节点
            prev_node.next = new_node
        self.size += 1

    def delete(self,index):
        if index < 0 or index >= self.size:
            return Exception("删除位置超出链表的范围了!")
        # 删除头节点
        if index == 0:
            delete_node = self.head
            # 将原有头节点的下一个节点作为新的头节点
            self.head = self.head.next
        # 删除尾节点
        elif self.size == index:
            # 找到尾节点的前驱节点
            prev_node = self.get_value(index-1)
            # 删除节点为前驱节点的下一个节点
            delete_node = prev_node.next
            # 这个前驱节点作为新的尾节点,next指向None
            prev_node.next = None
        # 删除中间位置
        else:
            prev_node = self.get_value(index-1)
            # 找到删除节点的下一个节点
            next_node = prev_node.next.next
            delete_node = prev_node.next
            # 将前驱节点的next指向删除节点的下一个节点
            prev_node.next = next_node
        self.size -= 1
        return delete_node

    def print_all(self):
        if self.head is None:
            return Exception("该链表为空!")
        q = self.head
        while q is not None:
            print(q.data)
            q = q.next

def test_link():
    link = SinglyLinkedList()
    link.insert(0, 1)
    link.insert(1, 2)
    link.insert(2, 3)
    link.insert(1, 5)
    link.delete(0)
    link.delete(1)
    link.delete(1)
    link.print_all()
    
if __name__ == '__main__':
    test_link()

四、总结

1.链表和数组的比较
时间复杂度查找更新插入删除
数组O(1)O(1)O(n)O(n)
链表O(n)O(1)O(1)O(1)

数组:优势在于能够快速定位元素,对于读操作多、写操作少的场景,适合用数组。
链表:优势在于能够灵活地进行插入和删除操作,对于频繁插入、删除元素,适合用链表。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值