数据结构——带头双向循环链表

呀哈喽,我是结衣。

前言

说到链表前面我们讲了单链表,但是链表可不止一种,要分类的话。链表可以分为带头或不带头,单向或双向,循环或者不循环,也就是说链表一共应该是有8种结构的,我们上次讲的链表就是不带头单向不循环链表。是链表中结构最简单的一种。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们在来简单的解释一下两种链表把
1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2.带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

链表的实现

前边讲了那么多,最重要的还是要自己能把链表给实现了。下面就是实现的教学了。

创建不同文件

创建3个文件,分别要实现的功能为函数的声明,函数的定义,以及测试。目的是方便后续的增删查改。
在这里插入图片描述

结构体的创建

prev表示上一个节点,next表示下一个节点。
在这里插入图片描述

函数的声明

和单链表的函数声明差不多,都是头删,头插,尾插,尾删,打印等等…
我们先函数的声明先写完,后面再对他们挨个实现。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{LTDataType data;struct ListNode* prev;struct ListNode* next;
}ListNode;// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

写完函数声明接下来就是函数的实现了。

函数的实现

函数的实现写在list.c文件里面
我们先来讲头节点的创建。头节点是链表的头部且不会存储有效的数据。

头节点的创建

// 创建返回链表的头结点.
ListNode* ListCreate()
{ListNode* head = (ListNode*)malloc(sizeof(ListNode));head->data = -1;//可以随便存一个数据,在后续的操作中不会用到头节点的数据。head->prev = head;head->next = head;//prev和next都要指向本身,以实现循环。return head;
}

写完头节点的创建,我们还要写一个打印函数,为了让后续的操作更加直观。

打印函数的实现

// 双向链表打印
void ListPrint(ListNode* pHead)
{ListNode* cur = pHead->next;printf("phead");while (cur != pHead){printf("%d<=>", cur->data);cur = cur->next;}printf("phead\n");
}

cur!=pHead,目的是为了不打印头节点。
下面我们要写一个插入函数,才能直观的观察。所以我们接下来写尾插函数。

尾插函数的实现

在这里插入图片描述
画图解释

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = CreatNode(x);ListNode* cur = pHead;ListNode* tail = pHead->prev;tail->next = newnode;newnode->prev = tail;cur->prev = newnode;newnode->next = cur;
}

写到尾插函数,我们又要写一个新节点的创建函数

ListNode* CreatNode(LTDataType x)
{ListNode* newnode = (ListNode* )malloc(sizeof(ListNode));newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}

欧克,写完了开始打印测试。
在这里插入图片描述
看上去是成功了,那就说明没什么问题了,有问题后续再检查。

头插函数的实现

尾插写完当然就是头插了。
在这里插入图片描述
画图解释

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = CreatNode(x);ListNode* cur = pHead;ListNode* next = pHead->next;cur->next = newnode;newnode->prev = cur;newnode->next = next;next->prev = newnode;
}

接下来测试看看。
在这里插入图片描述
依旧没有问题。

尾删函数的实现

删除后要记得free哦

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{assert(pHead->next != pHead);//当只有头节点时报错ListNode* cur = pHead;ListNode* tail = pHead->prev;cur->prev = tail->prev;tail->prev->next = cur;free(tail);tail = NULL;
}

测试
我们先删一些,再全部删完,再多删一个分别测试。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从这三张图片我们也可以看出来,代码是没有问题的。(在这里我悄悄改了一下打印函数的代码,看出来了吗?)

头删函数的实现

要记得free哦

// 双向链表头删
void ListPopFront(ListNode* pHead)
{assert(pHead->next != pHead);ListNode* first = pHead->next;pHead->next = first->next;first->next->prev = pHead;free(first);first = NULL;
}

下面再测试一下,有没有问题。
在这里插入图片描述

在这里插入图片描述
同样也是没有问题的。

销毁函数的实现

为什么就到销毁了呢?当然是想把剩下的指定位置的增删查改当成作业咯。

// 双向链表销毁
void ListDestory(ListNode* pHead)
{ListNode* cur = pHead->next;while (cur != pHead){ListNode* next = cur->next;free(cur);cur = NULL;cur = next;}//最后free pHeadfree(pHead);pHead = NULL;
}

就这样结束咯。
下面我们把代码都贴出来,(也包括查找函数和指定位置的增删查改函数)

代码

list.h

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{LTDataType data;struct ListNode* prev;struct ListNode* next;
}ListNode;// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

list.c

#define _CRT_SECURE_NO_WARNINGS
#include "list.h"
ListNode* CreatNode(LTDataType x)
{ListNode* newnode = (ListNode* )malloc(sizeof(ListNode));newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate()
{ListNode* head = (ListNode*)malloc(sizeof(ListNode));head->data = -1;//可以随便存一个数据,在后续的操作中不会用到头节点的数据。head->prev = head;head->next = head;//prev和next都要指向本身,以实现循环。return head;
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{ListNode* cur = pHead->next;printf("phead<=>");while (cur != pHead){printf("%d<=>", cur->data);cur = cur->next;}printf("phead\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = CreatNode(x);ListNode* cur = pHead;ListNode* tail = pHead->prev;tail->next = newnode;newnode->prev = tail;cur->prev = newnode;newnode->next = cur;
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = CreatNode(x);ListNode* cur = pHead;ListNode* next = pHead->next;cur->next = newnode;newnode->prev = cur;newnode->next = next;next->prev = newnode;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{assert(pHead->next != pHead);//当只有头节点时报错ListNode* cur = pHead;ListNode* tail = pHead->prev;cur->prev = tail->prev;tail->prev->next = cur;free(tail);tail = NULL;
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{assert(pHead->next != pHead);ListNode* first = pHead->next;pHead->next = first->next;first->next->prev = pHead;free(first);first = NULL;
}
// 双向链表销毁
void ListDestory(ListNode* pHead)
{ListNode* cur = pHead->next;while (cur != pHead){ListNode* next = cur->next;free(cur);cur = NULL;cur = next;}//最后free pHeadfree(pHead);pHead = NULL;
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{ListNode* cur = pHead->next;if (x == -1){return NULL;}while (cur != pHead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{ListNode* newnode = CreateNode(x);ListNode* prev = pos->prev;prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{ListNode* prev = pos->prev;ListNode* next = pos->next;prev->next = next;next->prev = prev;free(pos);pos = NULL;
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include "list.h"
void test1()
{ListNode* head = ListCreate();//头节点的创建ListPushBack(head, 1);ListPushBack(head, 2);ListPushBack(head, 3);ListPushBack(head, 4);ListPrint(head);ListPushFront(head, 5);ListPushFront(head, 6);ListPushFront(head, 7);ListPrint(head);
}void test2()
{ListNode* head = ListCreate();//头节点的创建ListPushBack(head, 1);ListPushBack(head, 2);ListPushBack(head, 3);ListPushBack(head, 4);ListPrint(head);ListPopFront(head);ListPopFront(head);ListPopFront(head);ListPopFront(head);ListPopFront(head);ListPrint(head);
}
int main()
{test2();return 0;
}


在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/175516.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vim批量多行缩进调整

网上其他教程&#xff1a; ctrl v 或者 v进行visual模式按方向键<&#xff0c;>调整光标位置选中缩进的行Shift > &#xff08;或者 Shift < &#xff09;进行左右缩进。 我只想说&#xff0c;乱七八糟&#xff0c;根本不管用 本文教程&#xff1a; 增加缩进…

HTTP/2.0协议详解

前言 HTTP/2.0&#xff1a;互联网通信的革新标准 随着互联网技术的飞速发展&#xff0c;HTTP协议作为互联网应用最广泛的通信协议&#xff0c;也在不断演进和优化。HTTP/2.0是HTTP协议的最新版本&#xff0c;它旨在提供更高效、更安全、更快速的互联网连接。 一、HTTP/2.0的…

深入理解JVM虚拟机第二十五篇:详解JVM方法的绑定机制静态绑定和动态绑定,早期绑定晚期绑定,并编写代码从字节码角度证明这件事情

大神链接&#xff1a;作者有幸结识技术大神孙哥为好友&#xff0c;获益匪浅。现在把孙哥视频分享给大家。 孙哥链接&#xff1a;孙哥个人主页 作者简介&#xff1a;一个颜值99分&#xff0c;只比孙哥差一点的程序员 本专栏简介&#xff1a;话不多说&#xff0c;让我们一起干翻J…

Windows系统隐藏窗口启动控制台程序

背景 上线项目有时候需要一些控制台应用作为辅助服务来协助UI应用满足实际需求&#xff0c;这时候如果一运行UI就冒出一系列的黑框&#xff0c;这将会导致客户被下的不起&#xff0c;生怕中了什么不知名病毒 方案 可以使用vbs来启动&#xff0c;这个是window系统自带的&#…

【2014年数据结构真题】

41. (13分&#xff09;二叉树的带权路径长度(WPL)是二叉树中所有叶结点的带权路径长度之和。 给定一棵二叉树T,采用二叉链表存储&#xff0c;结点结构如下&#xff1a; 其中叶结点的weight域保存该结点的非负权值。 设root为指向T的根结点的指针&#xff0c; 请设计求T 的WPL…

计算机 - - - 浏览器网页打开本地exe程序,网页打开微信,网页打开迅雷

效果 在电脑中安装了微信和迅雷&#xff0c;可以通过在地址栏中输入weixin:打开微信&#xff0c;输入magnet:打开迅雷。 同理&#xff1a;在网页中使用a标签&#xff0c;点击后跳转链接打开weixin:&#xff0c;也会同样打开微信。 运用同样的原理&#xff0c;在网页中点击超…

reactive和effect,依赖收集触发依赖

通过上一篇文章已经初始化项目&#xff0c;集成了ts和jest。本篇实现Vue3中响应式模块里的reactive方法。 前置知识要求 如果你熟练掌握Map, Set, Proxy, Reflect&#xff0c;可直接跳过这部分。 Map Map是一种用于存储键值对的集合&#xff0c;并且能够记住键的原始插入顺…

前端开发入门笔记(八)CSS3属性详解:动画详解+Flex布局图文详解+Web字体

参考链接&#xff1a;https://web.qianguyihao.com/02-CSS%E5%9F%BA%E7%A1%80/12-CSS3%E5%B1%9E%E6%80%A7%E8%AF%A6%E8%A7%A3%EF%BC%9A%E5%8A%A8%E7%94%BB%E8%AF%A6%E8%A7%A3.html#_3%E3%80%81%E6%97%8B%E8%BD%AC%EF%BC%9Arotate 过渡 transition的中文含义是过渡。过渡是CSS…

双写绕过 [极客大挑战 2019]BabySQL 1

打开题目 随便输入账号密码 根据报错信息可知这是单引号的字符型注入 那我们试试万能密码 1 or 11 页面报错 1 or 11 页面报错 而且根据报错内容显示是没有我们注入上去的or的 那我们就试试 1 order by 3 # 页面报错&#xff0c;根据报错显示页面过滤掉了or和by 那我们…

Dart利用私有构造函数_()创建单例模式

文章目录 类的构造函数_()函数dart中构造函数定义 类的构造函数 类的构造函数有两种&#xff1a; 1&#xff09;默认构造函数&#xff1a; 当实例化对象的时候&#xff0c;会自动调用的函数&#xff0c;构造函数的名称和类的名称相同&#xff0c;在一个类中默认构造函数只能由…

wpf devexpress设置行和编辑器

如下教程示范如何计算行布局&#xff0c;特定的表格单元编辑器&#xff0c;和格式化显示值。这个教程基于前一个文章 选择行显示 GridControl为所有字段生成行和绑定数据源&#xff0c;如果AutoGenerateColumns 属性选择AddNew。添加行到GridControl精确显示为特别的几行设置。…

掉瓶子小游戏

欢迎来到程序小院 掉瓶子 玩法&#xff1a;旋转的瓶子&#xff0c;根据瓶子方向&#xff0c;点击鼠标左键瓶子掉落&#xff0c;从桌面中间掉下即得1分&#xff0c;卡在桌边瓶子碎了游戏结束&#xff0c;快去掉瓶子吧^^。开始游戏https://www.ormcc.com/play/gameStart/203 htm…