单链表

单链表的每个结点,包含值和链接到下一个结点的引用字段。

//definition for singly-linked list.
struct SinglyListNode{
    int val;
    SinglyListNode *next;
    SinglyListNode(int x):val(x),next(Null){} //用头结点来表示整个列表
};

与数组不同,我们无法在常量时间内访问单链表中的随机元素。想知道第i个元素,需要从头结点逐个遍历,按照索引来访问元素平均花费O(N)时间,N为链表长度。

如访问第三个结点,我们需要使用头结点(23)中的next字段到达第二个结点(6),再次通过next访问第三个结点(15)。

A. 添加

用给定值初始化新的结点,将新的结点next链接到下一结点,再将当前结点的next链接到新结点。注意前后顺序,先让新来的结点有所指向(先给新来的画饼bushi

在链表开头添加新结点时,需要注意更新头结点(head)。

在链表末尾添加新结点时,初始化新结点cur,将cur的next-> NULL,遍历到链表尾部,直到结点的next为NULL,将该结点链接到cur。

B.删除

删除现有结点cur,先找到cur的上一结点prev和下一结点next,再将链接prev到next。

删除头结点时,可以直接将下一个结点分配为head。

删除最后一个结点时,找到next为NULL的结点cur,再将cur->NULL,删除最后的结点。

 

TMI:

评论区的其他方法:不需要找前序结点。

1.将当前结点的next结点赋值给当前结点,val=val->next;

2.此时有两个重复的结点,index->next=index->next->next;相当于删除了原来的next结点。

即将next结点的值保留给cur结点,再将next结点删除。

讨论:有人回复这条说这一方法没有区分结点的引用和结点对象本身。val=val->next;只是给了val一个新的引用ID,而并不能改变链表上指向val本身的链式结构,pre_node.next和node是两个不同的变量,因此改变node之后pre_node.next的值不变,链表结构也没有发生变化。

但是感觉这样可行?如果val=val->next;只是为了赋值的话,虽然没有删除原结点,但是通过index->next=index->next->next;删去了后继结点,在值的体现上还是实现了同样的效果。不过原评论说第1步相当于把后继结点提前确实表达的不太对,所以val只表示值的话,val->next是存在的吗?

更新讨论:即答不可存在。写完单链表实现之后,deleteAtIndex部分如下:

可以看到使用cur->next=cur->next->next;命令行删去指定位置的节点,即使是在末尾也可以实现,这与上面index->next=index->next->next;似乎一致,这样的确可以实现删除节点,因此上面方法的问题出在第1步:val=val->next;我们在定义node结构体的时候指明,val是数据域,next是指针域,指针可以表示前后结点之间的结构关系,但无法通过val直接改变这一结构,因此我们必须通过遍历来实现删除节点

void deleteAtIndex(int index){
    if(index<0||index>=size) return;
    node *cur=head;
    while(index--) cur=cur->next;
    cur->next=cur->next->next;
    size--;
}

 

单链表的实现:

单链表中的节点应该具备两个属性:valnext 。val 是当前节点的值,next 是指向下一个节点的指针/引用。假设链表中的所有节点下标从 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 的节点。

示例:

输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]

 

单链表的私有部分定义了头结点head和链表长度size。要特别注意head和size的更新。

在公共部分首先定义了结构体node。注意区分val和next,初始化新节点后val的值已经改为设定值,而next为NULL,所以在插入操作时需要先考虑将next与后继结点链接起来。在调用时也不能直接写val和next,需要借助->符号(val不要忘!)。

struct node{
     int val;//数据域
     node *next;//指针域
     node(int x):val(x),next(NULL){}; //初始化一个新节点
};//不要忘记结尾的;

 

在初始化部分,注明一下可能模糊的概念: 头指针,头结点,开始结点。

前两者的区别主要在于:

头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(但也可以用来存放链表的长度),使用头结点后,在第一个节点前插入和删除的操作与其他节点统一。

无论链表是否为空,头指针都存在。头指针是链表的必要部分,而头结点不是。

后两者的区别主要在于:

头结点是物理上的第一个结点,开始结点是逻辑上的第一个结点。头指针指向的是物理上的第一个结点。

因此head->next后的开始结点才是索引为0的结点。索引位置如下排列:

null(dummyHead,虚拟头结点) -> 0 -> 1 -> 2 -> 3 -> 4 -> null

MyLinkedList() {
    //初始化头节点,数据为0(head节点的数据域通常无意义,也可以用来存储链表长度);
    //相当于定义了一个空节点作为头,但不属于节点,所以head后面结点才是索引为0的开始节点;
    head=new node(0);
    size=0;
}

 

整体代码:

单链表的结构体包含值val和指针next。操作有初始化,查找,插入和删除。

具体实现过程中,查找和删除都需要通过新建辅助结点cur来进行遍历。

而针对插入操作,在头部插入不需要辅助节点,而在中间和尾部插入操作一致,都需要使用cur来遍历,即使在结尾也是使用newnode->next=cur->next;语句。

针对size需要考虑索引index是否越界,以及插入删除时记得更新size的值。

class MyLinkedList {
public:
    struct node{
        int val;//数据域
        node *next;//指针域
        node(int x):val(x),next(NULL){}; //初始化一个新节点
    };
    MyLinkedList() {
        //初始化头节点,数据为0(head节点的数据域通常无意义,也可以用来存储链表长度);
        //相当于定义了一个空节点作为头,但不属于节点,所以head后面结点才是索引为0的开始节点;
        head=new node(0);
        size=0;
    }
    
    int get(int index) {
        if(index<0||index>=size) return -1;
        node *cur=head->next;//辅助指针指向第一个结点
        while(index--) cur=cur->next;
        return cur->val;//指向数据输出
    }
    
    void addAtHead(int val) {
        node *newnode=new node(val);
        newnode->next=head->next;
        head->next=newnode;
        size++;
    }
    
    void addAtTail(int val) {
        node *newnode=new node(val);//创建新节点
        node *cur=head;//创建辅助节点,由于在尾部添加,因而需要辅助指针从头部遍历
        while(cur->next) cur=cur->next;
        newnode->next=cur->next;
        cur->next=newnode;
        size++;
    }
    
    void addAtIndex(int index, int val) {
        if(index<=0) addAtHead(val);//特别地,这里index<=0体现在链表上都是头结点处
        else if(index==size) addAtTail(val);
        else if(index>size) return ;
        else {
            node *newnode=new node(val);
            node *cur=head;
            while(index--) cur=cur->next;
            newnode->next=cur->next;
            cur->next=newnode;
            size++;      
        }
    }
    
    void deleteAtIndex(int index) {
        if(index<0||index>=size) return;
        node *cur=head;
        while(index--) cur=cur->next;
        cur->next=cur->next->next;
        size--;
    }
private:
    int size;
    node *head;
};

/**
 * 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);
 */

 

posted @ 2023-05-05 22:03  好想学会写代码  阅读(31)  评论(0)    收藏  举报