双链表的代码实现
#define _CRT_SECURE_NO_WARNINGS
//本人使用的是Visual Studio 2019,所以定义了上面的第一个宏
#include<stdio.h> #include<string.h> #include<stdlib.h> typedef int ElemType; typedef struct DNode { //定义双链表的节点类型 ElemType data; //定义数据域 struct DNode* prior, * next; //定义两个指针,prior指针指向前一个结点,next指针指向下一个结点 }DNode,*DLinkList; //双链表的初始化 bool InitDLinkList(DLinkList& DL) //用DL表示双链表 { DL = (DNode*)malloc(sizeof(DNode)); if (DL == NULL) return false; DL->prior = NULL; //将头结点的头指针和尾指针都置零 DL->next = NULL; return true; } //判断双链表是否为空 bool EmptyDlinkList(DLinkList DL) { if (DL->next == NULL) //DL->next为NULL,表示双链表为空 return true; else return false; } //头插法创建双链表 DLinkList Creat_Head_DLinkList(DLinkList& DL) { ElemType x; DNode* s; DL = (DLinkList)malloc(sizeof(DNode)); //申请一个头结点DLinkList强调这是一个双链表 DL->prior = NULL; DL->next = NULL; scanf("%d", &x); //键入x的值 while (x != 9999) //以x=9999表示读取结束 { s = (DNode*)malloc(sizeof(DNode)); //为新结点申请一个空间 s->data = x; //将新结点的数据域设置为x //下面开始条件判断 if (DL->next == NULL) //如果DL->next为空,即此时为空的双链表 { DL->next = s; //将头结点DL的next指向s s->prior = DL; //将s的prior指向DL s->next = NULL; //将s的next指针置空 } else if (DL->next != NULL) //如果DL->next不为空,即此时双链表中已有元素 { s->next = DL->next; //则按照头插法插入元素 DL->next->prior = s; DL->next = s; s->prior = DL; } scanf("%d", &x); //继续读入x } return DL; } //在p结点之后插入节点——— //———这个是理解性的代码,如果真的要插入一个值,不能这样写, //——因为这个函数虽然传入了p结点,但是并不知道p结点究竟在哪里. //不过这个函数可以封装,运用在其他函数中,比如封装在下一个函数Insert_Next_Dnode_plus中 bool Insert_Next_Dnode(DNode* p,ElemType e) { DNode* s = (DNode*)malloc(sizeof(DNode)); if (p == NULL || s == NULL) //非法参数,为空就不必插入了呗 return false; s->data = e; s->next = p->next; //① if (p->next != NULL) //如果p结点有后继结点 p->next->prior = s; //② s->prior = p; //③ p->next = s; //④ 其中③和④可以交换顺序 } //在DL的p结点之后插入新节点 bool Insert_Next_Dnode_plus(DLinkList& DL, int i, ElemType e) { if (i < 1) return false; //头结点的位置不能插入 DNode* p; int j = 0; //向后扫描 p = DL; while (p != NULL && j < i - 1) //一直找到被插入位置的前一个结点 { p = p->next; j++; } if (p == NULL) return false; Insert_Next_Dnode(p, e); } //删除p节点的后继结点 ElemType Delete_Next_Node(DNode*p,ElemType &e) { if (p == NULL) return 0; DNode* q = p->next; //定义一个q结点作为p结点的后继结点 e = q->data; if (q == NULL) //q=NULL,表示p没有后继结点 return false; p->next = q->next; if (q->next != NULL) //q结点不是最后一个节点 q->next->prior = p; free(q); return e; } //删除指定结点p的后继结点,并带回被删除的值 bool Delete_Next_Node_plus(DLinkList&DL,int i,ElemType&e) { if (i < 1) return false; DNode* p; p = DL; int j = 0; while (p != NULL && j < i - 1) //向后扫描找到被删除位置的前一个结点 { p = p->next; j++; } if (p == NULL) return false; if (p->next == NULL) return false; Delete_Next_Node(p,e); printf("被删除的元素是%3d\n", e); //打印出被删除的元素 } //销毁双链表——这部分代码是错误的(2022年4月28日,18点25分) bool DestoryDList(DLinkList& DL) { DNode* q, * p; //定义两个结点 q = DL->next; //q表示第一个结点 p = q->next; //p表示第二个结点 while (DL->next->next->next != NULL)//表示第3个结点存在 { //DNode* q, * p; //定义两个结点 //q = DL->next; //q表示第一个结点 //p = q->next; //p表示第二个结点 DL->next = q->next; //这一步即下一步将头结点与第二个结点相连 p->prior = DL; free(q); //再释放掉第一个结点 //释放掉第一个q结点之后,再次循环,相当于不断释放新形成第一个结点,直到第三个结点不存在了 //也就是说双链表中只存在头结点和第一个结点,还有第二个节点 } q = DL->next; //跳出while循环之后,需要再次定义p,q,因为在while循环里它们被修改了 p = q->next; if (p->next == NULL)//p之后没结点了 { q->next = NULL;//就把第一个结点q的next指针置空 free(p); //再释放p结点 } if (q->next == NULL) { DL->next = NULL;//此时q的next指针为空,我们就可以就把头结点的next指针置空 free(q); //在释放掉q结点 } free(DL); //释放头结点 DL = NULL; //头指针置空 return true; }
//正序打印双链表——利用next指针从前向后检索 void Print_DLinkList_Order(DLinkList DL) { DNode* j; j = DL->next; printf("正序输出元素:"); while (j != NULL) { printf("%3d", j->data); j = j->next; } printf("\n"); } //逆序打印双链表——利用prior从后向前检索 void Print_DLinkList_Reverse(DLinkList DL) { DNode* j; j = DL->next; while (j->next!= NULL) //首先要保证j指针指向最后一个结点 { j = j->next; } printf("逆序输出元素:"); while (j != DL) //再让j指针一次向前扫描 { printf("%3d", j->data); j = j->prior; } printf("\n"); } //主函数 int main() { int e; bool ret; DLinkList DL; //调试发现DL占4个字节,DNode占12个字节,因为DL被转成了指针类型,DNode是结构体类型 InitDLinkList(DL); //这一步在这里没啥用,因为在创建双链表时还是需要对DL的两个指针置空 ret=EmptyDlinkList(DL); if (ret) printf("空\n"); else { printf("非空\n"); } Creat_Head_DLinkList(DL); //头插法创建新的双链表 Insert_Next_Dnode_plus(DL,2, 87); //向双链表第二个位置插入元素87 Print_DLinkList_Order(DL); //正序打印双链表 Print_DLinkList_Reverse(DL); //逆序打印双链表 Delete_Next_Node_plus(DL, 2, e); //删除双链表第二个元素,并返回其值 Print_DLinkList_Order(DL); //再次正序打印删除第二个元素之后的双链表 ret=DestoryDList(DL); //销毁双链表 if (ret) printf("销毁成功\n"); else { printf("销毁失败\n"); } return true; }
由于上面销毁双链表的代码有误,所以我做出了修改。如下
//销毁双链表————以下是正确的代码 //这个代码经过修改,才没得问题了,原来在while里面,我没有在free(q)之后再写上 //——q = DL->next;p = q->next; 如果不写上这两句,就会导致while循环出错,因为再次循环时q不符合我们一开始定义的 //——q作第一个结点,p作第二个结点 //——我慢慢修改注释风格,用全角的“——”表示接续上一行 bool DestoryDList(DLinkList& DL) { DNode* q, * p; //定义两个结点 q = DL->next; //q表示第一个结点 p = q->next; //p表示第二个结点 while (DL->next->next->next != NULL)//表示第3个结点存在 { DL->next = q->next; //这一步及下一步将头结点与第二个结点相连 p->prior = DL; free(q); //再释放掉第一个结点 q = DL->next; //q表示第一个结点 p = q->next; //释放掉第一个q结点之后,再次循环,相当于不断释放新形成第一个结点,直到第三个结点不存在了 //也就是说双链表中只存在头结点和第一个结点,还有第二个节点 } q = DL->next; //跳出while循环之后,需要再次定义p,q,因为在while循环里它们被修改了 p = q->next; if (p->next == NULL)//p之后没结点了 { q->next = NULL;//就把第一个结点q的next指针置空 free(p); //再释放p结点 } if (q->next == NULL) { DL->next = NULL;//此时q的next指针为空,我们就可以就把头结点的next指针置空 free(q); //在释放掉q结点 } free(DL); //释放头结点 DL = NULL; //头指针置空 return true; } //销毁双链表————以下是Modify的代码 //这个modify版本,减少了一部分代码,原来的代码我想的是通过循环保留头结点和两个后继结点, //但是其实经过循环之后只用保留头结点和第一个结点就行了 //因为原来的代码跳出循环之后需要对头结点和第一个结点以及第二个结点分别处理 //modify之后,只需要对头结点和第一个结点(此时第一个结点已无后继结点)分别处理就行了 //相当于减少了一部分代码量 bool DestoryDList_modify(DLinkList& DL) { DNode* q, * p; //定义两个结点 q = DL->next; //q表示第一个结点 p = q->next; //p表示第二个结点 while (DL->next->next!= NULL)//表示第2个结点存在 { DL->next = q->next; //这一步及下一步将头结点与第二个结点相连 p->prior = DL; free(q); //再释放掉第一个结点 q = DL->next; //q表示第一个结点 p = q->next; //释放掉第一个q结点之后,再次循环,相当于不断释放新形成第一个结点,直到第二个结点不存在了 //也就是说双链表中只存在头结点和第一个结点时跳出循环 } if (q->next == NULL) { DL->next = NULL;//此时q的next指针为空,我们就可以就把头结点的next指针置空 free(q); //在释放掉q结点 } free(DL); //释放头结点 DL = NULL; //头指针置空 return true; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)