【算法思想·链表】构造链表

浅谈链表

链表作为很常用的数据结构,很多同学在日常使用过程中或多或少都会遇到链表相关的问题,其中最常见的就是链表遍历指针使用的问题。

通过亲自手写链表,可以帮助我们深入理解链表。数据结构的工程化使用,无外乎就是增删查改,当我们亲手实现一次链表的功能,链表这个数据结构就算真正入门了。

本文参考[代码随想录]

 707. 设计链表

你可以选择使用单链表或者双链表,设计并实现自己的链表。

单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。

如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。

实现 MyLinkedList 类:

  • MyLinkedList() 初始化 MyLinkedList 对象。
  • int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
  • void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
  • void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
  • void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
  • void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

删除链表节点:

添加链表节点:

Tips

这里给大家介绍一个我对节点指针使用的理解:

例1 current_node.next = node  # current_node的next指针指向了node

例2 current_node = current_node.next # 将current_node的下一个节点赋给current_node,实则是节点向后遍历。

node.next 出现在等号左边,表明node的next指针将指向等号右边那个节点,是“指向”的概念!

node.next 出现在等号右边,指代的是node的next指针指向的那个节点,是“节点”的概念!

​​​​​​​

构造单向链表

单向链表即对于每一个节点,只有一个指针指向并找到下一个节点,不能找到上一个节点。

对于单向链表,我们通常会构造一个虚拟头结点dummy_head,其好处在于处理链表头结点时比较方便

注意:链表节点函数定义ListNode由我们自己手动构造!!!

# 单独定义链表节点
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class MyLinkedList:
    def __init__(self):
        # 虚拟头结点和链表长度必不可少
        self.dummy_head = ListNode()
        self.size = 0

    def get(self, index: int) -> int:
        if index < 0 or index >= self.size:
            return -1

        cur = self.dummy_head.next
        for _ in range(index):
            cur = cur.next
        return cur.val

    def addAtHead(self, val: int) -> None:
        self.dummy_head.next = ListNode(val, self.dummy_head.next)
        self.size += 1

    def addAtTail(self, val: int) -> None:
        cur = self.dummy_head
        while cur.next:
            cur = cur.next
        cur.next = ListNode(val)
        self.size += 1

    def addAtIndex(self, index: int, val: int) -> None:
        if index < 0 or index > self.size:
            return

        cur = self.dummy_head
        for _ in range(index):
            cur = cur.next
        new_node = ListNode(val, cur.next)
        cur.next = new_node
        self.size += 1

    def deleteAtIndex(self, index: int) -> None:
        if index < 0 or index >= self.size:
            return

        cur = self.dummy_head
        for _ in range(index):
            cur = cur.next
        cur.next = cur.next.next
        self.size -= 1

构造双向链表

双向链表,就是其上的每一个节点都自带两个指针prevnext,分别指向上一个节点和下一个节点,于是每一个节点都能向前或向后遍历

此处我们构造双向链表,使用了类似虚拟头尾节点的方式,head和tail都是实际的链表头和尾,这是不同于上面dummy_head的地方。

最值得注意的地方在于,当链表为空时,head = tail = None, 当链表只有一个节点时,head就是tail。

# 双向链表节点定义,向前和向后的指针都要有
class ListNode:
    def __init__(self, val=0, prev=None, next=None):
        self.val = val
        self.next = next
        self.prev = prev


class MyLinkedList:
    def __init__(self):
        # head 和 tail都表示实际的链表头尾,故在后续操作中需时刻注意
        self.head = None
        self.tail = None
        self.size = 0

    def get(self, index: int) -> int:
        if index < 0 or index >= self.size:
            return -1

        if index < self.size // 2:
            cur = self.head
            for _ in range(index):
                cur = cur.next
        else:
            cur = self.tail
            for _ in range(self.size - index - 1):
                cur = cur.prev
        return cur.val

    def addAtHead(self, val: int) -> None:
        # 直接新构造new_node,使其next指针指向当前头结点head
        new_node = ListNode(val, None, self.head)
        # 如果当前头结点不为None,则将其prev指向new_node
        if self.head:
            self.head.prev = new_node
        # 若当前头结点为None,则意味着链表为空,新增头结点后整个链表只有一个节点,故需将头和尾节点都设为new_node
        else:
            self.tail = new_node
        # 无论如何,新的头结点都将是new_node
        self.head = new_node
        self.size += 1

    def addAtTail(self, val: int) -> None:
        # 直接新构造new_node,使其prev指针指向当前头结点tail
        new_node = ListNode(val, self.tail, None)
        # 如果当前尾结点不为None,则将其next指向new_node
        if self.tail:
            self.tail.next = new_node
        # 若当前尾结点为None,则意味着链表为空,新增尾结点后整个链表只有一个节点,故需将头和尾节点都设为new_node
        else:
            self.head = new_node
        # 无论如何,新的尾结点都将是new_node
        self.tail = new_node
        self.size += 1

    def addAtIndex(self, index: int, val: int) -> None:
        if index < 0 or index > self.size:
            return

        if index == 0:
            self.addAtHead(val)
        elif index == self.size:
            self.addAtTail(val)
        else:
            # 不论从前向后,或是从后向前遍历,都将cur指向待添加位置的前一位
            if index < self.size // 2:
                cur = self.head
                for _ in range(index - 1):
                    cur = cur.next
            else:
                cur = self.tail
                for _ in range(self.size - index):
                    cur = cur.prev
            new_node = ListNode(val, cur, cur.next)
            cur.next.prev = new_node
            cur.next = new_node
            self.size += 1

    def deleteAtIndex(self, index: int) -> None:
        # 若是空链表则直接返回
        if index < 0 or index >= self.size:
            return

        if index == 0:
            self.head = self.head.next
            # 若此时head不为None,则将其prev指向None
            if self.head:
                self.head.prev = None
            # 若此时head为None,则说明此时链表为空,需要将尾结点tail同步置为None
            else:
                self.tail = None
        elif index == self.size - 1:
            self.tail = self.tail.prev
            # 若此时tail不为None,则将其next指向None
            if self.tail:
                self.tail.next = None
            # 若此时tail为None,则说明此时链表为空,需要将尾结点head同步置为None
            else:
                self.head = None
        else:
            # 不论从前向后,或是从后向前遍历,都将cur指向待删除节点的前一位
            if index < self.size // 2:
                cur = self.head
                for _ in range(index):
                    cur = cur.next
            else:
                cur = self.tail
                for _ in range(self.size - index - 1):
                    cur = cur.prev
            cur.prev.next = cur.next
            cur.next.prev = cur.prev
        self.size -= 1

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值