DAY3(二)设计链表

本文介绍了如何在链表中实现插入节点(包括头部、尾部和指定位置)、删除节点以及获取节点值等操作。关键在于理解和使用链表的指针,以及在操作中处理边界条件。使用虚拟头节点简化了插入和删除操作。
摘要由CSDN通过智能技术生成

2.设计链表

题目要求:在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

题目理解

这道题目设计链表的五个接口:

  • 获取链表第index个节点的数值
  • 在链表的最前面插入一个节点
  • 在链表的最后面插入一个节点
  • 在链表第index个节点前面插入一个节点
  • 删除链表的第index个节点

也就是实现五个函数。分别是获取第n个节点,头部插入节点,尾部插入节点,第n个节点前插入节点,删除第n个节点。

注意:题目的假设中,这道题目的n是从0开始的,首先需要获取第0个节点,也就是链表的头节点。
在这里插入图片描述

链表头节点”下标“的问题

链表和数组是两种不同的数据结构,它们在内存中的存储方式和访问方式也不同。链表中的每个节点都包含一个数据域和一个指针域,指针域指向下一个节点的地址。数组中的每个元素都有一个固定的下标,下标从0开始。

链表的头节点是链表中的第一个节点,它不一定是0,而是根据分配的内存地址决定的。链表没有下标的概念,要访问链表中的某个节点,需要从头节点开始遍历,直到找到目标节点为止。

因此,链表头节点和数组第一个元素不一样都是0,链表也没有从0开始排序的下标。

链表头节点的问题

q: 头节点head到底是一个链表对象还是一个指针?

a: 在 C++ 中,头节点 head 通常是一个指针,而不是一个链表对象。

头节点是链表中的第一个节点,它包含了链表的起始位置。在许多链表的实现中,头节点通常是一个指针,它指向链表的第一个节点。

这样的设计允许我们通过头节点的指针来访问和操作整个链表。通过头节点指针,我们可以遍历链表,访问每个节点,并进行插入、删除等操作。

因此,头节点 head 在大多数情况下是一个指针,它指向链表的第一个节点。通过这个指针,我们可以访问整个链表的结构和数据。

头节点介绍:

链表的头节点 head 通常是一个指针。这个指针指向链表中的第一个节点。

通常情况下,链表的每个节点由一个自定义的结构体或类来表示,这个结构体或类包含节点的值和指向下一个节点的指针。

因此,head 是一个指向链表第一个节点的指针,通过它可以遍历和访问整个链表。通过 head,我们可以访问头节点的值以及通过头节点的 next 指针访问链表中的其他节点。

需要注意的是,C++ 中链表的头节点并不是固定要求为指针,可以使用其他方式来表示,例如使用模板类来实现链表。但在通常情况下,链表的头节点是一个指针。

注意点:链表操作中指针的重要性

链表的插入、提取、删除等操作基本都是通过指针完成的,而代码过程中不直接涉及链表对象本身。

链表的操作主要涉及节点之间指针的改变,通过改变指针的指向来实现插入、删除、提取等操作。具体而言:

  • 插入操作:我们创建一个新节点,并将其插入到适当的位置,即修改相应节点的指针来连接新节点。
  • 提取操作:我们可以通过指针访问到特定的节点,并提取所需的值或进行其他操作。
  • 删除操作:我们通过修改指针的指向来绕过需要删除的节点,从而将其从链表中移除,并释放相应的内存。

在这些操作中,代码主要关注的是指针的操作,而不是直接操作链表对象本身。通过操作指针,我们可以有效地改变链表的结构和节点之间的连接关系。

因此,链表的操作是通过指针完成的,而链表对象本身在代码中的作用相对较小,主要是提供一个起始位置(头节点)来引导整个链表的访问。

问题:链表数据结构每次使用的时候都需要自己定义吗?

在C++和许多其他语言中,ListNode这样的数据结构并不是内置的,所以在使用它们时,通常需要自己定义。例如,本题目中,就定义了一个ListNode类来表示链表的节点。这个类包含了节点的值(val)和一个指向下一个节点的指针(next)。

在某些特定的情况下,如果你的代码库或者项目中经常需要使用链表,你可以将ListNode类定义在一个公共的头文件中,这样在需要使用链表的时候,只需要包含这个头文件就可以了,不需要每次都重新定义。

在一些其他的编程语言中,例如Java或者Python,链表或者类似的数据结构(如ArrayList或者LinkedList)可能是语言的标准库的一部分,这样你就不需要自己定义这些数据结构了。但是,了解并能自己实现这些数据结构,仍然是非常有用的,因为这可以帮助你更好地理解它们的工作原理和性能特性。

1.获取第Index个节点的值

1.注意题目中的N是从0开始的,第0个节点也就是链表的头节点。

2.遍历链表的时候,先定义一个指针来遍历,而不是直接操作头指针。因为操作完链表之后需要返回头节点,上来就操作头节点的话,头节点的值变化,链表头节点无法返回。

class MyLinkedList {
public:
    //定义链表节点结构体
    struct ListNode{
        int val;
        LinkNode* next;
        LinkNode(int val):val(val),next(nullptr){
            //自己写一个空的构造函数 
        }
        
    };
    //初始化MyLinkedList 对象
    //本题目中初始化的含义就是创建构造函数
    MyLinkedList() {  //构造函数
        dummyHead = new LinkNode(0); //定义虚拟头节点
        size = 0;  //定义记录链表节点数量的变量

    }
    
    //获取下标为index的节点数值,index是从0开始的
    int get(int index) {
        ListNode* cur = dummyHead->next;
        int count=0;
        while(cur!=nullptr){
            if(count==index){
                return cur->val;
            }
            else{
                count++;
                cur=cur->next;
            }
        }
        return -1;
        
    }
    
    void addAtHead(int val) {

    }
    
    
};

2.在链表最前面插入一个节点

void addAtHead(int val) {
    ListNode* list = new ListNode(val);
    List = dummyHead; //与List->next = _dummyHead->next相同?
    dummyHead->next = list;
    size++;
 }

3.在链表最后面添加一个节点

void addAtTail(int val) {
    ListNode* list = new ListNode(val);
    ListNode* cur = dummyHead; //需要遍历,定义遍历指针
   while(cur->next!=nullptr){
       cur=cur->next;
   }
   cur->next = list; //指针直接相等即可
   size++;
    
    
    }

cur->next = list将当前节点 curnext 指针指向新创建的节点 list

addAtTail 方法中,我们希望将一个新的节点添加到链表的尾部。为了找到链表的尾部节点,我们需要遍历链表,找到最后一个节点。

首先,我们使用一个遍历指针 cur 来表示当前节点,初始时指向虚拟头节点 dummyHead。然后,我们通过循环遍历链表,直到 cur 指针指向的节点的 next 指针为 nullptr,即当前节点为链表的最后一个节点。

在循环结束后,我们创建一个新的节点 list,并将当前节点 curnext 指针指向新节点 list,将新节点连接到链表的尾部。这样就完成了在链表尾部添加节点的操作。

最后,更新链表的节点数量 size

3.将节点插入index节点之前

// 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果index 等于链表的长度,那么新插入的节点为链表的尾结点
// 如果index大于链表的长度,则返回空
// 如果index小于0,则在头部插入节点
void addAtIndex(int index, int val) {
    if(index>size){
        return;  //返回空就是直接return; 即可
    }
    if(index=size){
         ListNode* list = new ListNode(val);
         ListNode* cur = dummyHead; //需要遍历,定义遍历指针
         while(cur->next!=nullptr){
               cur=cur->next;
        }
        cur->next = list; //指针直接相等即可
        size++;
    }
    if(index<=0){
        ListNode* list = new ListNode(val);
        List = dummyHead; //与List->next = _dummyHead->next相同?
        dummyHead->next = list;
        size++;
    }

    }

4.删除第Index个节点

void deleteAtIndex(int index) {
        if (index >= _size || index < 0) {
            return;
        }
        int count=0;
    
        ListNode* cur = _dummyHead;
        while(count!=index) {
            cur = cur ->next;
        }
        ListNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        _size--;
    }

5.打印链表

void printLinkedList(){
     ListNode* cur = dummyHead;
    while(cur->next!=nullptr){
        cout<<cur->next->val<<" ";
        cur=cur->next;
    }
    cout<<endl;
 }

6.完整版:

1.第一个版本:不用虚拟头节点
class ListNode {  //class ListNode这种写法是可以的,ListNode是一种数据结构,它表示一个链表中的节点。可以用它来命名类,但是要注意和其他类名区分开,避免混淆。
public:
    int val;
    ListNode* next;

    ListNode(int value) : val(value), next(nullptr) {}
};

class MyLinkedList {
    //自己定义一个头节点head
private:
    ListNode* head;

public:
    MyLinkedList() : head(nullptr) {}

    void addAtHead(int val) {
        ListNode* newNode = new ListNode(val);//建立新的listNode节点
        newNode->next = head;
        head = newNode; //更新头节点
    }

    void addAtTail(int val) { //不用虚拟头节点的话,需要考虑两种情况
        ListNode* newNode = new ListNode(val);
        if (head == nullptr) {
            head = newNode;
        } else {
            ListNode* curr = head;
            while (curr->next != nullptr) {
                curr = curr->next; //注意此处表达的意思就是cur++,但是不能写成cur++
            }
            curr->next = newNode; //此处cur指向的并不是空的内存,因此为了防止覆盖,需要放在next里面
        }
    }

    int get(int index) { //获取链表第index个元素的值
        ListNode* curr = head;
        int count = 0;//定义一个整数来进行存储
        while (curr != nullptr) {  //注意此处的while循环条件,是curr!=nullptr而不是curr->next!=nullptr
            if (count == index) {
                return curr->val;
            }
            curr = curr->next;
            count++;
        }
        return -1; // If index is out of range
    }

    void addAtIndex(int index, int val) {
        if (index == 0) {
            addAtHead(val);
        } else {
            ListNode* newNode = new ListNode(val);
            ListNode* curr = head;
            int count = 0;
            while (curr != nullptr && count < index - 1) {
                curr = curr->next;
                count++;
            }
            if (curr != nullptr) {
                newNode->next = curr->next;
                curr->next = newNode;
            }
        }
    }

    void deleteAtIndex(int index) {
        if (index == 0) {
            if (head != nullptr) {
                ListNode* temp = head;
                head = head->next;
                delete temp;
            }
        } else {
            ListNode* curr = head;
            int count = 0;
            while (curr != nullptr && count < index - 1) {
                curr = curr->next;
                count++;
            }
            if (curr != nullptr && curr->next != nullptr) {
                ListNode* temp = curr->next;
                curr->next = curr->next->next;
                delete temp;
            }
        }
    }
};

上述代码实现了一个简单的单链表 MyLinkedList,其中包含了常见的操作,如在头部添加节点、在尾部添加节点、获取指定位置的节点值、在指定位置插入节点和删除指定位置的节点。

你可以根据需要使用 MyLinkedList 类来初始化和操作链表对象。例如,使用以下代码来创建一个链表对象并进行操作:

MyLinkedList linkedList;

linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1, 2);
int val = linkedList.get(1); // 获取索引 1 处的节点值
linkedList.deleteAtIndex(1);
val = linkedList.get(1); // 获取索引 1 处的节点值

这里定义了两个类,一个是ListNode,另一个是MyLinkedList

ListNode是一个简单的数据结构,表示链表中的一个节点。它包含一个整型值(val)和一个指向下一个节点的指针(next)。

注意:

cur++的操作在链表里面是错误的,应该写成cur=cur->next

while (curr != nullptr)while (curr->next != nullptr) 的区别是什么?

while (curr->next != nullptr)会遍历链表直到curr指向最后一个节点。这是因为,当curr指向最后一个节点时,curr->next会是nullptr,此时循环就会结束。所以,循环结束后,curr会指向链表的最后一个节点。

这种循环条件在你需要获取到最后一个节点,以便在其后添加新节点(如在尾部添加节点)或者修改其next指针(如删除最后一个节点)时是非常有用的。

另一方面,**while (curr != nullptr)**会遍历链表直到curr指针已经越过最后一个节点,也就是curr变成nullptr。**循环结束后,curr会指向链表最后一个节点的下一个。**这种循环条件在你需要遍历整个链表,包括访问最后一个节点时是非常有用的。

while (curr != nullptr)适用于需要挨个链表元素(包括最后一个元素)的判断,例如本题中获取链表中下标为xx的元素的值,就需要用while (curr != nullptr)而不是while (curr->next != nullptr)

2.第二个版本:使用虚拟头节点

class MyLinkedList {
public:
    //定义链表节点结构体
    struct ListNode{
        int val;
        ListNode* next;
        ListNode(int val):val(val),next(nullptr){
            //自己写一个空的构造函数 
        }
        
    };
    //初始化MyLinkedList 对象
    //本题目中初始化的含义就是创建构造函数
    MyLinkedList() {  //构造函数
        dummyHead = new ListNode(0); //定义虚拟头节点
        size = 0;  //定义记录链表节点数量的变量

    }
    
    //获取下标为index的节点数值,index是从0开始的
    int get(int index) {
        ListNode* cur = dummyHead->next;
        int count=0;
        if (index > (size - 1) || index < 0) {
            return -1;
        }
        while(count<index&&cur!=nullptr){
            if(count==index&&cur!=nullptr){
                return cur->val;
            }
            else{
                count++;
                cur=cur->next;
            }
        }
        return -1;
    }
        
    
    
void addAtHead(int val) {
    ListNode* list = new ListNode(val);
    list->next = dummyHead->next; //与list= dummyHead不同,这一点一定要注意,这是要插入元素!
    dummyHead->next = list;
    size++;
 }


  void addAtTail(int val) {
    ListNode* list = new ListNode(val);
    ListNode* cur = dummyHead; //需要遍历,定义遍历指针
   while(cur->next!=nullptr){
       cur=cur->next;
   }
   cur->next = list; //指针直接相等即可
   size++;
    } 
    
  void addAtIndex(int index, int val) {
    if(index>size){
        return;  //返回空就是直接return; 即可
    }
    if(index==size){
         ListNode* list = new ListNode(val);
         ListNode* cur = dummyHead; //需要遍历,定义遍历指针
         while(cur->next!=nullptr){
               cur=cur->next;
        }
        cur->next = list; //指针直接相等即可
        size++;
    }
    if(index<=0){
        ListNode* list = new ListNode(val);
        list->next = dummyHead->next; //与List=dummyHead不同,这里也是插入元素!
        dummyHead->next = list;
        size++;
    }

    }
    void deleteAtIndex(int index) {
        if (index >= size || index < 0) {
            return;
        }
        int count=0;
    
        ListNode* cur = dummyHead;
        while(count!=index) {
            cur = cur ->next;
            count++;
        }
        ListNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        size--;
    }

    
 private:
    ListNode* dummyHead;
    int size;
    
};class MyLinkedList {
public:
    //定义链表节点结构体
    struct ListNode{
        int val;
        LinkNode* next;
        LinkNode(int val):val(val),next(nullptr){
            //自己写一个空的构造函数 
        }
        
    };
    //初始化MyLinkedList 对象
    //本题目中初始化的含义就是创建构造函数
    MyLinkedList() {  //构造函数
        dummyHead = new LinkNode(0); //定义虚拟头节点
        size = 0;  //定义记录链表节点数量的变量

    }
    
    //获取下标为index的节点数值,index是从0开始的
    int get(int index) {  //这里的get测试有点问题,还需要再看
        ListNode* cur = dummyHead->next;
        int count=0;
        while(cur!=nullptr){
            if(count==index){
                return cur->val;
            }
            else{
                count++;
                cur=cur->next;
            }
        }
        return -1;
        
    }
    
void addAtHead(int val) {
    ListNode* list = new ListNode(val);
    List = dummyHead; //与List->next = _dummyHead->next相同?
    dummyHead->next = list;
    size++;
 }
  void addAtTail(int val) {
    ListNode* list = new ListNode(val);
    ListNode* cur = dummyHead; //需要遍历,定义遍历指针
   while(cur->next!=nullptr){
       cur=cur->next;
   }
   cur->next = list; //指针直接相等即可
   size++;
    } 
    
  void addAtIndex(int index, int val) {
    if(index>size){
        return;  //返回空就是直接return; 即可
    }
    if(index=size){
         ListNode* list = new ListNode(val);
         ListNode* cur = dummyHead; //需要遍历,定义遍历指针
         while(cur->next!=nullptr){
               cur=cur->next;
        }
        cur->next = list; //指针直接相等即可
        size++;
    }
    if(index<=0){
        ListNode* list = new ListNode(val);
        List = dummyHead; //与List->next = _dummyHead->next相同?
        dummyHead->next = list;
        size++;
    }

    }
    void deleteAtIndex(int index) {
        if (index >= _size || index < 0) {
            return;
        }
        int count=0;
    
        ListNode* cur = _dummyHead;
        while(count!=index) {
            cur = cur ->next;
        }
        ListNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        _size--;
    }

    
 private:
    ListNode* dummyHead;
    int size;
    
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值