leetcode【数据结构简介】《链表》卡片——双链表

Author: Authur Whywait, 一个努力学习知识的孩子。好吧,其实是老男孩 \ (0^ ◇^0)/

想看博主的其他所有leetcode卡片学习笔记链接?传送门点这儿

简介

单链接列表中的结点具有 Value 字段,以及用于顺序链接结点的“Next”引用字段。
在本篇文章中,我们将介绍另一种类型的链表:双链表

定义

双链表以类似的方式工作,但还有一个引用字段,称为“prev”字段。有了这个额外的字段,我们就能够知道当前结点的前一个结点。

结点结构

struct DoublyListNode {
    int val;
    struct DoublyListNode *next;
    struct DoublyListNode *prev;
};

与单链接列表类似,我们将使用头结点来表示整个列表。

操作

与单链表类似,我们将介绍在双链表中如何访问数据插入新结点删除现有结点

我们可以与单链表相同的方式访问数据:

  • 我们不能在常量级的时间内访问随机位置
  • 我们必须从头部遍历才能得到我们想要的第一个结点。
  • 在最坏的情况下,时间复杂度将是 O(N),其中 N 是链表的长度。

相较于单链表,对于添加和删除,会稍微复杂一些,因为我们还需要处理“prev”字段。接下来,我们将介绍这两个操作。

双链表的添加操作

如果我们想在现有的结点prev后面插入一个新的结点cur,我们可以见此过程分为两个步骤:

  1. 链接 curprevnext,其中 nextprev 原始的下一个节点;
    在这里插入图片描述
  2. cur 重新链接 prevnext

在这里插入图片描述

思考: 如果我们想要在开头或者结尾插入一个新结点该怎么办?

双链表的删除操作

如果我们想从双链表中删除一个现有的结点 cur,我们可以简单地将它的前一个结点 prev 与下一个结点 next 链接起来。

与单链表不同,使用“prev”字段可以很容易地在常量时间内获得前一个结点。

因为我们不再需要遍历链表来获取前一个结点,所以时间和空间复杂度都是O(1)

思考: 如果我们想删除第一个结点或者最后一个结点怎么办?

相关编程-设计链表(双链表)

设计链表的实现。您可以选择使用单链表双链表(此处我们选择双链表)。单链表中的节点应该具有两个属性:valnextval 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

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

MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3
linkedList.get(1); //返回2
linkedList.deleteAtIndex(1); //现在链表是1-> 3
linkedList.get(1); //返回3

【提示】

  • 所有val值都在 [1, 1000] 之内。
  • 操作次数将在 [1, 1000] 之内。
  • 请不要使用内置的 LinkedList 库。

思路:其实就是双链表的一些基础操作,也没啥思路可说

我在设计双链表的时候,特意使用头节点的val记录链表头结点以外的结点个数。如此这般,不论是调试或者后期如果要使用,都会方便很多。
下面是代码:

文章有很多注释的地方,删除注释//可以很方便地观察出程序的哪一个函数有bug(没有bug的话自然就可以忽略那些注释了)

代码后面有一些设计双链表的执行结果以及 TIPs ~ 一定不要漏掉哦~

typedef struct NODE{
    int val;
    struct NODE * prev;
    struct NODE * next;
} MyLinkedList;

/** Initialize your data structure here. */

MyLinkedList* myLinkedListCreate() {
    MyLinkedList* obj = (MyLinkedList *)malloc(sizeof(MyLinkedList));
    if(!obj)  return NULL;
    obj->val = 0;
    obj->prev = NULL;
    obj->next = NULL;
    return obj;
}

/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
int myLinkedListGet(MyLinkedList* obj, int index) {
    
    // MyLinkedList *o=obj;
    // while(o){
    //     printf("%d  ", o->val);
    //     o = o->next;
    // }
    // printf("\n");

    int i=index; MyLinkedList* p=obj->next;
    while(i && p->next){
        i--;
        p = p->next;
    }
    if(i) return -1;
    return p->val;
}

/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
    obj->val++;

    MyLinkedList* head = (MyLinkedList *) malloc (sizeof(MyLinkedList));
    head->val = val;
    head->next = obj->next;
    obj->next = head;
    head->prev = obj;
    if(head->next) head->next->prev = head;

    // MyLinkedList *o=obj;
    // while(o){
    //     printf("%d  ", o->val);
    //     o = o->next;
    // }
    // printf("\n");
}

/** Append a node of value val to the last element of the linked list. */
void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
    obj->val++;

    MyLinkedList* p = obj;
    while(p->next) p = p->next;

    MyLinkedList* tail = (MyLinkedList *) malloc (sizeof(MyLinkedList));
    tail->val = val;
    tail->next = NULL;
    tail->prev = p;
    p->next = tail;

    // MyLinkedList *o=obj;
    // while(o){
    //     printf("%d  ", o->val);
    //     o = o->next;
    // }
    // printf("\n");

}

/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
    
    if(!index) myLinkedListAddAtHead(obj, val);
    else{
        obj->val++;
        MyLinkedList* p = obj;
        int i=index;
        while(p && i){
            p = p->next;
            i--;
        }
        if(i) return;

        MyLinkedList* newnode = (MyLinkedList *) malloc (sizeof(MyLinkedList));
        newnode->val = val;
        newnode->next = p->next;
        p->next = newnode;
        newnode->prev = p;
        if(newnode->next) newnode->next->prev = newnode;
    }
    

    // MyLinkedList *o=obj;
    // while(o){
    //     printf("%d  ", o->val);
    //     o = o->next;
    // }
    // printf("\n");

}

/** Delete the index-th node in the linked list, if the index is valid. */
void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
    obj->val--;
    MyLinkedList* p = obj; int i=index;
    while(p && i){
        i--;
        p = p->next;
    }
    if(!p || !p->next) return;
    MyLinkedList* q = p->next;
    p->next = q->next;
    if(p->next) p->next->prev = p;
    free(q);
    
    // MyLinkedList *o=obj;
    // while(o){
    //     printf("%d  ", o->val);
    //     o = o->next;
    // }
    // printf("\n");
    
}

void myLinkedListFree(MyLinkedList* obj) {
    MyLinkedList *p;
    while(obj){
        p = obj;
        obj = obj->next;
        free(p);
    }
    free(obj);

}

/**
 * Your MyLinkedList struct will be instantiated and called as such:
 * MyLinkedList* obj = myLinkedListCreate();
 * int param_1 = myLinkedListGet(obj, index);
 
 * myLinkedListAddAtHead(obj, val);
 
 * myLinkedListAddAtTail(obj, val);
 
 * myLinkedListAddAtIndex(obj, index, val);
 
 * myLinkedListDeleteAtIndex(obj, index);
 
 * myLinkedListFree(obj);
*/

在这里插入图片描述

关于双链表的一些基础操作,最难以理清的大概就是每个nodenextprev指针 “你指我我指你,指来指去指自己” 的现象。下面将给出以下总结,希望对你可以有帮助。

  1. 每个nodenextprev都要赋值。
  2. 如果是头结点,无前继,则prev=NULL;next结点指向其后继;
  3. 如果是链表的最后一个结点,无后继,则next=NULL;prev指向其前继
  4. 其他情况下,结点需要prev指向前继,next指向后继。

PS:如果你的脑袋瓜里没有一张动态图,我建议你可以拿出纸和笔,一边写看着上文的相应代码块,一边在纸上作图,相信你一定能非常轻松的get这些关于双链表的基操。

都看到这里了,确定不点个赞再走? (╯‵□′)╯︵┻━┻

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AuthurLEE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值