目录
上次我们学习了怎么实现单链表,这一次我们将以单链表为基础实现双链表。
附上单链表链接:http://t.csdn.cn/G7z4m
🤓我们先看下面这个结构体
相较与单链表,双链表多定义了一个prev(prevent)指针用来记录该结点的前一个结点,这样我们就既可以找前又可以找后了,双链表有很多种结构,有带哨兵不循环,带哨兵循环或者不带哨兵不循环以及不带哨兵循环。
本文选择的是带哨兵循环(双向带头循环)
我们先看一下双向带头循环链表的大致结构:
【1】我们可以很清楚的看到双向带头循环链表的一大优势:由哨兵位直接找到尾部结点。
【2】由于哨兵位的存在,我们不必考虑链入第一个结点这样的特殊情况。
【3】代码实用性强,实际运用最多。
![]()
🤓头文件:BoubleList.h用来包含一些必须的头文件,定义结构体,声明相关函数。
🤓源文件:
【1】BoubleList.c用来实现具体的功能。
【2】text.c用来测试接口函数。
后续的插入,初始化我们都需要生成一个新的结点,为了让代码更加简洁美观,我们把这个部分单独封装成函数BuyListNode(),函数返回值为新结点地址。
代码:
🤓初始化的要点:
【1】要申请一个哨兵位的结点。
【2】返回申请的哨兵位的地址。(也可以通过传入二级指针的方式进行初始化,形参和实参的关系在单链表一文已经讲过,这里不再赘述)
【3】这个时候只有一个结点,我们让这个结点自己成环。
代码:
🤓与单链表打印的不同:
【1】链表是循环的,没法以空指针位结束条件。
【2】链表的第一个结点(哨兵位)存储的是无效数据,我们要忽略哨兵结点。
综上所述我们可以以第一个有效结点为起始位置,以走到哨兵位为结束条件。
代码:
图解:
🤓尾插的基本思路:
【1】生成新结点。
【2】尾结点的next指向新结点。
【3】新结点的prev指向原尾结点,next指向头结点。
【4】头结点的prev指向新结点。
代码:
![]()
🤓尾删的基本思路:
【1】记录尾部的前一个结点。
【2】头指针的prev指向新的尾结点。
【3】新的尾结点的next指向头结点。
【4】注意不要删除掉头结点。
代码:
🤓头插的基本思路:
【1】记录原有效头的结点。
【2】原有效头的prev指向新有效头。
【3】新有效头的prev指向哨兵位,next指向原有效头。
【4】哨兵位的next指向新有效头。
代码:
![]()
🤓头删的基本思路:
【1】记录要删除的原有效头结点。
【2】新有效头结点的prev指向哨兵位。
【3】哨兵位的next指向新有效头结点。
【4】注意不要删除哨兵位。
代码:
🤓查找的基本思路:
【1】和打印类似,从有效头开始遍历,到哨兵结束。
【2】返回对应结点的地址。
代码:
🤓指定插入的基本思路:
【1】先用查找找到节点位置pos。
【2】记录后一个结点。
【3】新结点的next指向pos的后一个结点,新结点的prev指向pos
【4】后一个结点的prev指向新结点。
【5】pos结点的next指向新结点。
代码:
![]()
🤓指定删除的基本思路:
【1】记录要删除的结点的后一个结点和前一个结点。
【2】后一个结点的prev指向待删除结点的前一个结点。
【3】前一个结点的next指向待删除结点的后一个结点。
【4】注意不要删除掉哨兵位。
代码:
前面我们已经实现了指定插入和指定删除,而头尾删,头尾插不过是指定删除和指定插入的特殊情况而已,所有我们可以在这些函数中复用指定插入和删除,使代码更加简洁。
代码:
![]()
![]()
![]()
BoubleList.
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <assert.h> #include <stdlib.h> //重定义,方便更改存储数据的类型 typedef int LTDataType; typedef struct ListNode { LTDataType data; struct ListNode* next; struct ListNode* prev; }LT; //申请新结点 LT* BuyListNode(); //链表初始化 LT* ListInit(); //打印链表 LT* ListPrint(LT* phead); //尾部插入 void ListPushBack(LT* phead, LTDataType x); //尾部删除 void LsitPopBack(LT* phead); //头部插入 void ListPushFront(LT* phead, LTDataType x); //头部删除 void ListPopFront(LT* phead); //查找 LT* ListPopFind(LT* phead, LTDataType x); //指定插入 void ListInsert(LT* phead, LT* pos, LTDataType x); //指定删除 void ListErase(LT* phead, LT* pos);
BoubleList.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "BoubleList.h" //申请新结点 LT* BuyListNode() { LT* newNode = (LT*)malloc(sizeof(LT)); //新结点的prev,next最好置空 newNode->next = NULL; newNode->prev = NULL; //返回新结点 return newNode; } //初始化 LT* ListInit( ) { LT* phead = NULL; //生成哨兵结点 phead = BuyListNode(); //只有一个结点,这个结点自己成环 phead->next = phead; phead->prev = phead; //返回头结点 return phead; } //打印 LT* ListPrint(LT* phead) { //如果未初始化,报错 assert(phead != NULL); //从有效头结点开始走,一直到哨兵位结束 LT* cur = phead->next; while (cur != phead) { //打印 printf("%d ", cur->data); //迭代 cur = cur->next; } printf("NULL"); printf("\n"); } //尾插 void ListPushBack(LT* phead,LTDataType x) { //如果未初始化,报错 assert(phead != NULL); //复用指定插入 ListInsert(phead, phead->prev, x); } //尾部删除 void LsitPopBack(LT* phead) { //如果未初始化,报错 assert(phead != NULL); //不能删除哨兵结点 assert(phead != phead->prev); //复用指定删除 ListErase(phead, phead->prev); } //头部插入 void ListPushFront(LT* phead,LTDataType x) { //如果未初始化,报错 assert(phead != NULL); //复用指定插入 ListInsert(phead, phead, x); } //头部删除 void ListPopFront(LT* phead) { //如果未初始化,报错 assert(phead != NULL); //不能删除哨兵位 assert(phead->next != phead); //复用指定删除 ListErase(phead, phead->next); } //查找,返回结点位置 LT* ListPopFind(LT* phead, LTDataType x) { //如果未初始化,报错 assert(phead != NULL); //和打印一样从第一个有效结点开始遍历 LT* cur = phead->next; while (cur != phead) { //查找到返回结点位置 if (cur->data == x) return cur; //迭代 cur = cur->next; } //查找失败,返回null return NULL; } //指定插入(后) void ListInsert(LT* phead, LT* pos,LTDataType x) { //如果未初始化,报错 assert(phead != NULL); //生成新结点 LT* newnode = BuyListNode(); //记录后一个结点 LT* beforenode = pos->next; //新结点的next指向pos的后一个结点 newnode->next = beforenode; //新结点的prev指向pos newnode->prev = pos; //后一个结点的prev指向新结点 beforenode->prev = newnode; //pos结点的next指向新结点 pos->next = newnode; //存储数据 newnode->data = x; } //指定删除 void ListErase(LT* phead, LT* pos) { //如果未初始化,报错 assert(phead != NULL); //不能删除哨兵 assert(pos != phead); //记录要删除的结点的后一个结点 LT* beforepos = pos->next; //记录待删除结点的前一个结点 LT* aheadpos = pos->prev; //后一个结点的prev指向待删除结点的前一个结点 beforepos->prev = aheadpos; //前一个结点的next指向待删除结点的后一个结点 aheadpos->next = beforepos; //释放结点 free(pos); }
text.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "BoubleList.h" //测试1 void text1() { //初始化 LT* SL=ListInit(); //尾部插入 ListPushBack(SL, 5); ListPushBack(SL, 8); ListPushBack(SL, 10); //打印 ListPrint(SL); //头部插入 ListPushFront(SL, 8); ListPushFront(SL, 889); //尾部删除 LsitPopBack(SL); //打印 ListPrint(SL); //头部删除 ListPopFront(SL); ListPopFront(SL); ListPopFront(SL); //指定插入 ListInsert(SL, ListPopFind(SL,8), 56); ListPushBack(SL, 100); ListPrint(SL); ListErase(SL, ListPopFind(SL, 100)); ListPrint(SL); } void text2() { //初始化 LT* SL = ListInit(); ListPushFront(SL, 8); ListPushFront(SL, 889); ListPushBack(SL, 5); ListPushBack(SL, 8); ListPushFront(SL, 800); LsitPopBack(SL); ListPrint(SL); } int main() { text1(); //text2(); return 0; }
【1】与单链表相比,双向带头循环链表的结构更复杂,但是实用性更强。
【2】单链表结构一般不会单独用来存储数据,更多的是作为其它结构的子结构。
【3】正因单链表的不完美,所以能够更好考验水平。下一次我们会进行链表OJ题目的讲解,其中大部分的题目都是通过巧妙的方式弥补单链表的缺点。

我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He
我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.