707. 设计链表 链表的知识复习

707. 设计链表

cpp 复制代码
class MyLinkedList {


public:
      struct LinkedNode {
        int val;
        LinkedNode *next;
        LinkedNode(int val):val(val),next(nullptr){}
    };
    MyLinkedList(){
        dummyhead = new LinkedNode(0);
        size=0; 
    }
    
    int get(int index) {
        if(index < 0 || index >= size){
            return -1;
        }
        LinkedNode * cur = dummyhead->next;//注意这里cur指针的指向是哪儿,画图判断逻辑是否是正确的。
        while(index--){
           //初始化要放在外面 ListNode * cur = dummyhead;
            cur = cur->next;
        }
       // return cur->next->val;
        return cur->val;
    }
    
    void addAtHead(int val) {
        LinkedNode * newNode = new LinkedNode(val);
        newNode->next = dummyhead->next;
        dummyhead->next = newNode;
        size++;
        
    }
    
    void addAtTail(int val) {
        LinkedNode *newNode = new LinkedNode(val);
        LinkedNode *cur = dummyhead;
        while(cur->next != nullptr){
            cur= cur->next;
        }
        cur->next = newNode;
        size++;
    }
    
    void addAtIndex(int index, int val) {
        if(index > size || size < 0) return ;
        // if(index == size ){
        //     addAtTail(val);
        //     return;
        // }
        LinkedNode * newNode = new LinkedNode(val);
        LinkedNode *cur=dummyhead;
        while(index--){
            cur=cur->next;
        }
        newNode->next = cur->next;
        cur->next= newNode;
        size++;
    }
    
    void deleteAtIndex(int index) {
        if(index<0||index>= size){
            return;
        }
        LinkedNode*cur=dummyhead;
        while(index--){
            cur= cur->next;
        }
        LinkedNode *temp= cur->next;
        cur->next = cur->next->next;
        delete temp;
        size--;
        temp =nullptr;
    }
private:
int size;
LinkedNode * dummyhead;
};

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */

这三个构造函数在 ListNode 结构体中定义的作用和用法有所不同,它们分别处理了不同的初始化场景。我们来逐一分析这三个构造函数的区别:

1. 默认构造函数

ListNode() : val(0), next(nullptr) {}
  • 作用 :这是一个 默认构造函数 ,用于在不提供任何参数的情况下创建 ListNode 对象。

  • 初始化

    • val(0):将节点的 val 初始化为 0
    • next(nullptr):将节点的 next 指针初始化为 nullptr,意味着该节点不指向任何其他节点。
  • 用途:如果你只是创建一个空的节点(没有指定值或下一个节点),使用这个构造函数非常方便。比如:

    ListNode node; // 创建一个 val 为 0, next 为 nullptr 的节点
    

2. 带一个参数的构造函数

ListNode(int x) : val(x), next(nullptr) {}
  • 作用 :这是一个 带一个参数的构造函数 ,用于在创建节点时指定 val 的值,但不指定 next,即将 next 指向 nullptr

  • 初始化

    • val(x):将节点的 val 初始化为传入的参数 x
    • next(nullptr):将节点的 next 指针初始化为 nullptr,意味着该节点没有连接到其他节点。
  • 用途 :当你创建一个节点,并且只关心它的值而不关心它的 next 指针时使用这个构造函数。例如:

    ListNode node(5); // 创建一个 val 为 5, next 为 nullptr 的节点
    

3. 带两个参数的构造函数

ListNode(int x, ListNode* next) : val(x), next(next) {}
  • 作用 :这是一个 带两个参数的构造函数 ,用于在创建节点时同时指定 valnext

  • 初始化

    • val(x):将节点的 val 初始化为传入的参数 x
    • next(next):将节点的 next 指针初始化为传入的参数 next,即指向另一个 ListNode 对象。这个参数通常是指向另一个链表节点的指针,表示该节点的 "下一个" 节点。
  • 用途:当你需要创建一个包含特定值且与其他节点连接的节点时使用。例如:

    ListNode* node3 = new ListNode(3);
    ListNode* node2 = new ListNode(2, node3); // 创建一个 val 为 2 的节点,它的 next 指向 node3
    

    在这个例子中,node2val2next 是指向 node3 的指针,node3val3nextnullptr


总结

  • 默认构造函数 :不带任何参数,创建一个 val0nextnullptr 的节点。
  • 带一个参数的构造函数 :只传入一个 val 参数,next 默认为 nullptr。常用于创建值已知但不关心 next 的节点。
  • 带两个参数的构造函数 :同时传入 valnext,用于创建值已知并且与其他节点连接的节点。

这三种构造函数提供了不同的初始化方式,使得你可以灵活地创建和管理链表节点,特别是在处理链表时。

是的,虚拟头节点(dummy head)会作为链表中的第一个节点,但它的作用并不是作为链表数据的 "实际头节点"。它的存在是为了简化链表的操作,尤其是在插入和删除节点时避免处理空链表或单节点链表时的特殊情况。

虚拟头节点的作用

在链表中,特别是单向链表,在执行插入和删除操作时,往往需要特殊处理头节点,尤其是在头节点是空时。例如:

  • 删除头节点:你需要把头节点的指针指向第二个节点。
  • 插入新节点到头部:你需要更新头节点的指针。

这些操作如果没有虚拟头节点,可能会导致一些复杂的边界条件需要单独处理,增加代码的复杂性。

通过虚拟头节点,简化了这些边界条件的处理:
  • 无论链表是否为空,操作都变得统一。虚拟头节点总是存在的,插入和删除操作都可以直接从虚拟头节点开始,避免了处理空链表或只有一个节点的特殊情况。

虚拟头节点的工作原理

  1. 初始化时

    • MyLinkedList() 构造函数中通过 new LinkedNode(0) 创建一个值为 0 的虚拟头节点,并将 _dummyHead 指向它。
    • 这个节点的 next 指针初始为 nullptr,表示链表为空。
  2. 链表的操作

    • 例如,当你插入一个节点时,不需要检查链表是否为空,只需从虚拟头节点开始。
    • 例如,在插入新节点时(addAtHead),你只需将虚拟头节点的 next 指针指向新节点。
    • 这样,无论链表是空的,还是有节点,代码的逻辑都很简单。

虚拟头节点如何作为第一个节点?

即使虚拟头节点是链表中的第一个节点,它本身并不存储有效数据。它的 val 通常不被使用或不重要,它只是为了简化链表操作而存在。

  • 链表中的第一个有效节点 是紧随虚拟头节点之后的节点。虚拟头节点只是在链表结构中占据一个位置,用来保持链表的一致性和简化操作。

举个例子

假设你通过 addAtHead 函数添加一个新节点 5 到链表中:

list.addAtHead(5);  // 向链表头部添加一个节点,值为 5

在调用 addAtHead(5) 后,链表结构会变成:

虚拟头节点 (val=0) → 新节点 (val=5) → nullptr
  • 这里的虚拟头节点的值 0 是无关紧要的,它的作用只是作为链表的 "头" 节点。
  • 第一个有效的节点是 val=5 的节点,它是链表中的实际数据节点。

如何理解链表中的头节点

  • 链表的头节点(head node)通常指向链表中的第一个有效数据节点。
  • 虚拟头节点(dummy head)是为了方便链表操作而引入的,通常它的值是无关紧要的。

总结

  • 虚拟头节点MyLinkedList 类中是一个特殊的辅助节点,它是链表结构的一部分。
  • 在初始化时,虚拟头节点被创建并赋值给 _dummyHead,但它并不包含有效数据。
  • 链表的第一个有效数据节点 是虚拟头节点的 next 指向的节点。
  • 通过引入虚拟头节点,链表的插入和删除操作变得更简洁、统一。
相关推荐
bingw01141 小时前
2.组成最大数
数据结构
cdut_suye1 小时前
动态规划在斐波那契数列中的应用与优化
数据结构·c++·人工智能·python·算法·动态规划·热榜
ChillJavaGuy3 小时前
线性dp求解最小代价问题
java·数据结构·算法
梅茜Mercy3 小时前
数据结构:双向链表详解
数据结构·链表
七灵微3 小时前
【数据结构】循环队列原理与代码
开发语言·数据结构·python
是桃萌萌鸭~3 小时前
C++ List(双向链表)
c++·链表·list
丁总学Java3 小时前
点击数字层级从 admin.vue 跳转到 inviter-list.vue 组件
数据结构·list
TANGLONG2224 小时前
【初阶数据结构与算法】八大排序算法之选择排序(直接选择排序、堆排)
c语言·数据结构·c++·考研·算法·面试·排序算法
Y编程小白6 小时前
Leetcode经典题12--分发糖果
数据结构·算法·leetcode