【数据结构】链表的分类和双向链表

本篇是基于上篇单链表所作,推荐与上篇配合阅读,效果更加

http://t.csdnimg.cn/UhXEj

1.链表的分类

链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:
我们一般叫这个头为哨兵位
我们上回讲的单链表就是不带头单项不循环链表。
今天我们要讲带头双向循环的链表。
不过在次之前容我先为大家画一画8种链表结构:

1.带头单向循环链表:

2.带头单向不循环链表

3.带头双向循环链表

4.带头双向不循环链表

5.不带头单向循环链表

6.不带头单向不循环链表

7.不带头双向循环链表

8.不带头双向不循环链表

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构: 单链表 双向带头循环链表
1. 无头单向非循环链表:结构简单,⼀般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,⼀般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。

2.双向带头循环链表

我们还是经典三个文件:

我们先定义头文件所需要的函数

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>//定义双向链表中节点的结构
typedef int LTDataType;
typedef struct ListNode {LTDataType data;struct ListNode* prev;struct ListNode* next;
}LTNode;//注意,双向链表是带有哨兵位的,插入数据之前链表中必须要先初始化一个哨兵位
//void LTInit(LTNode** pphead);
LTNode* LTInit();
//void LTDesTroy(LTNode** pphead);
void LTDesTroy(LTNode* phead);   //推荐一级(保持接口一致性)void LTPrint(LTNode* phead);//不需要改变哨兵位,则不需要传二级指针
//如果需要修改哨兵位的话,则传二级指针
void LTPushBack(LTNode* phead, LTDataType x);
void LTPushFront(LTNode* phead, LTDataType x);//头删、尾删
void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);//查找
LTNode* LTFind(LTNode* phead, LTDataType x);//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);
//删除pos位置的数据
void LTErase(LTNode* pos);

首先我们还是得先定义节点,由于是双向链表,所以节点内存在两个节点的地址,一个是前驱节点的(指向其前一个节点),一个是尾结点的(指向其后一个节点)

这一段代码,是为了确保数据类型

我们节点定义成这样:

接下来又是完成各个功能:增,删,查,改。但是,由于我们长线的是带头的链表,所以我们需要对头初始化

3.初始化

我们先定义初始化函数,然后写函数:

void LTInit(LTNode** pphead);
void ltinit(ltnode** pphead) {*pphead = (ltnode*)malloc(sizeof(ltnode));if (*pphead == null) {perror("malloc fail!");exit(1);}(*pphead)->data = -1;(*pphead)->next = (*pphead)->prev = *pphead;
}

和上回写单链表差不多,检测开辟是否成功,成功就接着给数据赋值,由于此时只有一个节点,即哨兵节点,且是循环链表,所以存放的前驱和尾节点就是哨兵节点自己

所以我们可以得出,如果哨兵节点的next指针或者prev指针指向自己,说明当前链表为空。

4.创建新的节点

我们写法和上次差不多

LTNode* LTBuyNode(LTDataType x) {LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL) {perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = newnode->prev = newnode;return newnode;
}

只是这回,多了一个前驱节点,我们定义时间默认前驱和后去节点指向的是本身。

但是既然我们都这么写了一个创建新节点的函数,那么我们可不可以用这个函数直接去进行哨兵节点的创建?

答案是肯定的,我们首先先改变以下我们定义的函数,

LTNode* LTInit();

然后调用创建新节点的函数,得到哨兵节点

LTNode* LTInit() {LTNode* phead = LTBuyNode(-1);return phead;
}

5.头插和尾插

注意:头插,是把新的节点插在第一个节点前,不是哨兵节点前

头插和尾插,头删和尾删的思路整体和单链表一致,我就不详细说明了,直接上代码

定义函数:

void LTPushBack(LTNode* phead, LTDataType x);
void LTPushFront(LTNode* phead, LTDataType x);

函数代码示例:

//尾插
void LTPushBack(LTNode* phead, LTDataType x) {assert(phead);LTNode* newnode = LTBuyNode(x);//phead phead->prev(ptail)  newnodenewnode->next = phead;newnode->prev = phead->prev;phead->prev->next = newnode;phead->prev = newnode;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x) {assert(phead);LTNode* newnode = LTBuyNode(x);//phead newnode phead->nextnewnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}

不懂的你们可以再看看图:

6.头删和尾删

定义函数:

void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);

函数代码示例:

//尾删
void LTPopBack(LTNode* phead) {assert(phead);//链表为空:只有一个哨兵位节点assert(phead->next != phead);LTNode* del = phead->prev;LTNode* prev = del->prev;prev->next = phead;phead->prev = prev;free(del);del = NULL;
}
//头删
void LTPopFront(LTNode* phead) {assert(phead);assert(phead->next != phead);LTNode* del = phead->next;LTNode* next = del->next;//phead del nextnext->prev = phead;phead->next = next;free(del);del = NULL;
}

7.查找

整体思路还是遍历,和单链表十分相似

定义函数:

LTNode* LTFind(LTNode* phead, LTDataType x);

函数代码示例:

LTNode* LTFind(LTNode* phead, LTDataType x) {assert(phead);LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x) {return pcur;}pcur = pcur->next;}return NULL;
}

8.在pos位置之后插入数据

这个和单恋表的也很相似,多了一个prev指针而已,写的时候要注意顺序,函数定义我就不写了

函数代码示例:

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x) {assert(pos);LTNode* newnode = LTBuyNode(x);//pos newnode pos->nextnewnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}

9.删除pos位置的数据

这个和单链表还是一样的,遍历整个表,然后相爱指针指向的地址,然后释放内存

函数代码示例:

void LTErase(LTNode* pos) {assert(pos);//pos->prev pos  pos->nextpos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);pos = NULL;
}

10.打印

这个其实是用来看每个节点中间的数据的,我们可以通过前驱节点或者尾节点实现正序或逆序打印,这一步也是遍历然后看哨兵节点是否是下一位,是就中断,我这里之举一种例子,另一种只要将next改成prev

函数代码示例:

void LTPrint(LTNode* phead) {//phead不能为空assert(phead);LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}

11.销毁

这个链表的销毁和点链表不大一样,因为存在哨兵节点,所以我们要分开释放内存

函数代码示例:

void LTDesTroy(LTNode* phead) {//哨兵位不能为空assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}//链表中只有一个哨兵位free(phead);phead = NULL;
}

最后还是一如既往的测试环节就交给大家了。推荐阅读完http://t.csdnimg.cn/UhXEj

然后再阅读这个

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

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

相关文章

树,二叉树及其相关知识

1.树概念及结构 1.1树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&#…

Tarjan 算法(超详细!!)

推荐在 cnblogs 上阅读 Tarjan 算法 前言 说来惭愧&#xff0c;这个模板仅是绿的算法至今我才学会。 我还记得去年 CSP2023 坐大巴路上拿着书背 Tarjan 的模板。虽然那年没有考连通分量类似的题目。 现在做题遇到了 Tarjan&#xff0c;那么&#xff0c;重学&#xff0c;开…

长城资产信息技术岗24届校招面试面经

本文介绍2024届秋招中&#xff0c;中国长城资产管理股份有限公司的信息技术岗岗位一面的面试基本情况、提问问题等。 10月投递了中国长城资产管理股份有限公司的信息技术岗岗位&#xff0c;所在部门为长城新盛信托有限责任公司。目前完成了一面&#xff0c;在这里记录一下一面经…

vue中图片不显示问题 - vue中静态资源加载

文章目录 vue中图片不显示问题静态资源URL 转换规则webpack 静态资源处理 图片不显示问题问题描述解决办法1&#xff1a;使用require引入require is not defined 解决办法2&#xff1a;使用import引入解决办法3&#xff1a;将图片放进公共文件夹static或public vue中图片不显示…

数据的存储结构

1.类别 顺序存储、链式存储、散列存储、索引存储 2.顺序存储与链式存储的区别 顺序存储链式存储优点 可以实现随机存取每个元素占用最少的空间 充分利用所有存储单元&#xff0c;不会出现碎片现象。缺点 只能使用整块的存储单元&#xff0c;会产出较多的碎片。 需要额外的存…

steam搬砖项目到底能不能做?新手小白入场前必看!

相信大家对于steam平台都不陌生。它是全球最大的中心化游戏平台。许多游戏将被添加到这个平台上。玩家通过这个平台购买游戏并体验游戏。大家经常看我的文章&#xff0c;应该对steam搬砖有或多或少的了解。 steam搬砖项目其实就是通过steam购买CSGO国外服务器游戏装备、皮肤、…

拷贝构造复习笔记

拷贝构造 使用一个已经创建完毕的对象来初始化一个新对象 默认情况下&#xff0c;c编译器至少给一个类添加3个函数 1&#xff0e;默认构造函数(无参&#xff0c;函数体为空) 2&#xff0e;默认析构函数(无参&#xff0c;函数体为空) 3&#xff0e;默认拷贝构造函数&#x…

what is `ContentCachingRequestWrapper` does?

ContentCachingRequestWrapper 是 Spring Framework 中提供的一种包装类&#xff0c;它扩展了 HttpServletRequestWrapper 类&#xff0c;用于缓存请求体的内容。 通常在处理 HTTP 请求时&#xff0c;原生的 HttpServletRequest 对象中的输入流 (getInputStream()) 只能被读取一…

C++:C/C++内存管理

C&#xff1a;C/C内存管理 C语言C语言内存分配回顾malloc & calloc & realloc & free Cnew & deletenew[ ] & delete[ ]定位newnew & delete原理 malloc / free 与 new / delete对比 C语言 C语言内存分配回顾 我们先回顾一下C语言的内存分配&#xf…

JS中splice方法的用法总结

1. 概述 JavaScript中的splice()方法是用于增加、删除或替换数组中的元素。这个方法可以实现数组的细粒度操作,非常灵活和强大。 2. 语法 splice()方法的语法如下所示: start:必需,表示开始删除或插入的索引位置。如果为负数,则从数组的末尾开始计算。deleteCount:可选…

音乐证书通过率发布,市场对持有者需求旺盛

音乐证书的考试难度备受关注&#xff0c;通过率终于揭晓。据官方公布的数据&#xff0c;该证书的通过率相对较低&#xff0c;需要考生在音乐技能和表现方面有出色的表现。然而&#xff0c;持有音乐证书的人才在市场上需求旺盛&#xff0c;各种音乐机构和企业对其表现出强烈兴趣…

历经15年,比特币以强势姿态进军华尔街!270亿美元投资狂潮引发市场震荡!

本月&#xff0c;比特币庆祝了它的15岁生日&#xff0c;并以强势的姿态进军华尔街。最近美国交易所开始交易的比特币交易所交易基金&#xff08;ETF&#xff09;&#xff0c;已经获得了投资者的广泛接受。这一进展标志着比特币作为一种年轻资产迈向成熟的重要里程碑。 根据Glas…