• C语言双向链表


    双向链表分为三个部分:

            1、上一个节点地址

            2、中间的数据

            3、下一个节点的地址                      ​

    双向链表在最开始的时候,不用初始化为NULL,直接创建一个哨兵位即可

     哨兵位

            哨兵位,一旦创建好了就不能动了,无论什么操作,他的位置都得在第一个,你头插也不能插在哨兵位的前面。

            哨兵位在传参的时候可以直接传 head ,因为不能改动哨兵位嘛,意味着不能修改 head 里面存放的这个地址,如果传的是 head 的地址,那么就可以修改 head 里面存放的地址了,

            直接 head->( ) ,就能访问使用了,这样也是可以操作的,不会无效操作具体请看下面

    双向链表与单向链表传参区别 

    双向链表在增删查改的时候传参,用的是一级指针接收。

    单向链表在增删查改的时候传参,用的是二级指针接收。

            原因是,我们起初给单向链表赋为NULL,你如果向增加一个节点,那么你要修改的是plist 里面存放的地址,我们想要的结果是改动这个plist 变量里面的地址,如果指示单纯传plist 是不能修改 plist 里面存放的地址的,

    看官请看

           plist 里面目前存放着一个地址,接下来我试图通过这个函数去修改plist里面存放的地址

    走到走到这里 p 已经成功修改

    但是出了函数并不能改掉 plist 里面存放的地址

    而换成 &plist 并用两颗星 ** 接受才能改动 plist 存放的地址

            难道一个星不能用?当然可以他虽然不能改动 plist 存放的地址,但是可以改动这个地址里面的数据,一颗星能做到的仅仅是改动节点里面的 data 和 下一个节点的地址,

     目前我们动态开辟的的一个节点里面的 next 存的是随机值,当我们将这个函数走完看看是否能改动这个 next 里面的地址。

    出了函数咱们可以看见成功的改动了节点里面的内容。

            这也就是为什么双向链表不用传二级指针,因为双向链表的默认配置就是有一个哨兵位,因为不能改动这个哨兵位所以,只传一级指针就够用了。

            而单向链表我们需要改plist 所以就需要传二级指针

    关于单链表解引用一次就能访问节点内部

    注意看slntail ,他已经解引用一次意味着可以修改plist ,而下面的slntail->next = list,是因为   ->本身就内涵一次解引用,所以才能访问节点里面指向的next

    申请节点

            双链表在申请节点的时候,next ,和 prev,指向的地址就是本节点地址,他是自循环的,nextprev不放空指针的原因是,在你插入就下一个节点的时候是不能解引用的,其实不管他貌似也行。

    1. LTN* Buynewnode(LTNtype n)
    2. {
    3. LTN* node = (LTN*)malloc(sizeof(LTN));
    4. if (node == NULL)
    5. {
    6. perror("malloc");
    7. exit(EOF);
    8. }
    9. node->data = n;
    10. node->next = node;
    11. node->prev = node;
    12. return node;
    13. }

    打印节点

    前言:SB 等于哨兵位,LTN为我创建的结构体类型的重命名

            1、进来先判断哨兵位有没有,和 是不是只有一个哨兵位那还打印个 “ 鲲 ”

            2、然后拷贝一份第一个节点的地址(不拷贝也行反正也改不了哨兵,只是我不想,我想拷贝一份,我觉得这样舒服一点),

            3、while判断语句,因为我们是从第一个有效节点开始,所以条件就是不等哨兵位就打印该节点的内容,

            4、打印完该节点,下一步就是让他走到下一个节点,上来判断是不是哨兵位,不是就打印

    1. void Printltn(LTN* SB)
    2. {
    3. if (SB == NULL||SB->next == SB)
    4. {
    5. printf("无效链表\n");
    6. exit(EOF);
    7. }
    8. LTN* head = SB->next;
    9. while (head != SB)
    10. {
    11. printf("%d->", head->data);
    12. head = head->next;
    13. }
    14. }

     尾插

            先开辟一个节点,接下来就是插入的环节,SB是哨兵位

            我的连接法是:先用尾结点的next连接新节点(SB->prev->next),再让新节点的prev(及上一个节点的地址)指向尾结点,再让哨兵位的 prev 指向新节点,最后新节点的next指向哨兵位。尾插完成

    顺序不唯一,只要能成功链接都可以。

    1. void LTNPushback(LTN* SB,LTNtype x)
    2. {
    3. LTN* Nnode = Buynewnode(x);
    4. SB->prev->next = Nnode;
    5. Nnode->prev = SB->prev;
    6. SB->prev = Nnode;
    7. Nnode->next = SB;
    8. }

    头插

    我的头插逻辑是:

            先让新节点的 prev 指向 哨兵next 指向 哨兵 的下一个节点,这样新节点就暂时和两个节点连接起来了。

            然后先让下一个 哨兵 的下一个节点的 prev 指向新节点,再让 哨兵  next 指向新节点

            顺序不唯一

    1. void LTNPushfront(LTN* SB, LTNtype x)
    2. {
    3. if (SB == NULL)
    4. {
    5. printf("无效< < < list node > > >\n");
    6. }
    7. LTN* Nnode = Buynewnode(x);
    8. Nnode->prev = SB;
    9. Nnode->next = SB->next;
    10. SB->next->prev = Nnode;
    11. SB->next = Nnode;
    12. }

    查找

    查找函数主要是用来辅助 指定位置的 增加 删除

            1、先判断链表是否有效,如果只有一个哨兵位也默认无效

            2、拷贝第一个节点地址

            3、进来判断是否等于 哨兵,不等于判断该节点的数据是不是我们要查找的,是就返回该节点地址,不是则让 Phead 走到下一位

            4、如果说循环完了都没有则打印 “不存在” ,我等会儿改一下代码,找到了会提前返回的。

    1. LTN* LTNFind(LTN*SB,LTNtype x)
    2. {
    3. if (SB == NULL || SB->next == SB)
    4. {
    5. printf("无效< < < list node > > >\n");
    6. exit(EOF);
    7. }
    8. LTN* phead = SB->next;
    9. while (phead != SB)
    10. {
    11. if (phead->data == x)
    12. {
    13. return phead;
    14. }
    15. else
    16. phead = phead->next;
    17. }
    18. printf("不存在返回 “ NULL ” \n");
    19. return NULL;
    20. }

    指定位置后插入

    插入的逻辑和头插的一样

    1. void LTInsert(LTN* pos, LTNtype x)//指定位置后插入
    2. {
    3. if (pos == NULL)
    4. {
    5. printf("不存在\a\n");
    6. exit(EOF);
    7. }
    8. LTN* newnode = Buynewnode(x);
    9. newnode->prev = pos;
    10. newnode->next = pos->next;
    11. pos->next->prev = newnode;
    12. pos->next = newnode;
    13. }

     删除指定位置节点

    上一个节点的next指向下一个节点

    下一个节点的prev指向上一个节点

    然后释放pos,将它置为NULL

    1. void LTErase(LTN* pos)//删除指定位置
    2. {
    3. if (pos == NULL)
    4. {
    5. printf("不存在\a\n");
    6. exit(EOF);
    7. }
    8. pos->prev->next = pos->next;
    9. pos->next->prev = pos->prev;
    10. free(pos);
    11. pos = NULL;
    12. }

    销毁链表

     

    1. void LTNDestroy(LTN*SB)
    2. {
    3. assert(SB);
    4. LTN* next = SB->next;
    5. while (next!=SB)
    6. {
    7. LTN* pcur = next->next;
    8. free(next);
    9. next = pcur;
    10. }
    11. }

  • 相关阅读:
    HP电脑主机清除BIOS密码
    网络安全行业黑话大全
    小波变换技术在图像压缩和重建中的应用研究-含Matlab代码
    C++征途 --- List链表容器
    Java语言
    项目经理领导力提升与塑造:从自己干到团队干
    【mysql官方文档】死锁
    RabbitMQ - 02 - 基本消息模型
    CSS block、inline和inline-block
    linux内核工作延迟机制
  • 原文地址:https://blog.csdn.net/liu_qiang_dage/article/details/142169472