数据结构篇其三---链表分类和双向链表

前言

数据结构篇其二实现了一个简单的单链表,链表的概念,单链表具体实现已经说明,如下:
单链表

事实上,前面的单链表本质上是无头单向不循环链表。此篇说明的双向链表可以说完全反过来了了。无论是之前的单链表还是双向链表,本质都是链表家族的两位成员。
主题一:链表分类

详细说说链表的特征,以及这些特征组合的链表种类。
主题二:双向链表的实现

像上次实现单链表一样,这次也试着独立实现双向链表吧。
学习收获:十分钟手搓一个链表

为什么学习双向链表?

因为虽然字面上双向链表好像还难一点,结构虽然复杂,但是实现起来特别简单。应用场景有显著的优势。

链表的分类

  • 单向与双向

链表的单向与双向:这说明节点与节点之间的联系。单向链表节点的指针一路往后。双向链表节点指针指前指后。

可见,从定义上,双向链表天生应该有两个指针,所以在单链表的基础上,我们可以推出双向链表的定义。

c 复制代码
//双向链表的定义
typedef int LTDataType;
typedef struct ListNode {
	LTDataType data;//数据域
	struct ListNode* prev;//前驱指针
	struct ListNode* next;//后继指针
}ListNode;

链表双向和单向决定了它节点指针的数量

  • 带头与不带头

为了方便对链表进行操作,我们会在链表的第一个节点前附带一个头节点(哨兵位),注意头节点不是第一个节点,第一个节点存储的是有效数据。

头节点的数据域不存储有效的数据,指针域next指向第一个节点,若是双向的话,则前驱指针指向尾结点。

需注意这个时候头指针就指向头节点,而不是第一个节点了。

  • 循环非循环

若链表的尾结点指向头节点而不是NULL,则链表闭合形成了一个环,可以循环了,就称为循环链表。反之,则为不循环链表。

  1. 以上就是链表的三大特征,每种特征又分两种情况。组合起来一共8种,所以链表种类一共8种。
  2. 下面介绍双向链表,来熟悉一下双向,带头,循环的链表吧。

双向链表的实现

下面实现这些函数

c 复制代码
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* plist);
// 双向链表打印
void ListPrint(ListNode* plist);
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* plist);
// 双向链表头插
void ListPushFront(ListNode * plist, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* plist);

前面已经给出了双向链表的定义。但下图只体现了双向,循环和带头还要我们具体实现。

c 复制代码
typedef int LTDataType;
typedef struct ListNode {
	LTDataType data;//数据域
	struct ListNode* prev;//前驱指针
	struct ListNode* next;//后继指针
}ListNode;

开局一个头指针,能这样做吗?

c 复制代码
int main() {
	ListNode* plist = NULL;
	return 0;
}

这个是带头的链表,起码有头节点吧,所以先创建一个头节点。

其次,这个是循环链表,头节点的前驱指针和后继指针应该都指向自己吧。

节点创建

c 复制代码
ListNode* ListCreate(LTDataType x) {
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	//判断动态空间是否开辟失败。
	if (phead == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	phead->data = x;//头节点数据随便赋值
	phead->next = phead;//前驱,后继指针指向自己
	phead->prev = phead;
	return phead;
}

双向链表初始化

c 复制代码
ListNode* ListInit() {
	return ListCreate(-1);//头节点的数据域随便给值。
}

还没有元素啊,那就先插入节点吧

双向链表的尾插


  1. 保证每个节点的两个指针有明确的指向。

  2. 尾插操作的节点有三个,头节点,尾结点,新节点。

  3. 按照上面图片的步骤写代码。

  4. 后面请自行画图分析,多创建临时变量,良好的命名习惯。写完一个函数去测试一下。

c 复制代码
void ListPushBack(ListNode* phead, LTDataType x) {
	assert(phead);
	ListNode* newnode = ListCreate(x);//创建新节点
    ListNode* tail = phead->prev;//记录尾结点

	//执行尾插操作
	phead->prev = newnode;
	newnode->next = phead;
	tail->next = newnode;
	newnode->prev = tail;
}

打印函数

c 复制代码
void ListPrint(ListNode* phead) {
	assert(phead);
	ListNode* pcur = phead->next;
	printf("head->");
	while (pcur!= phead) {
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	pcur = NULL;
	printf("return\n");
}

双链表的头插

c 复制代码
void ListPushFront(ListNode* phead,LTDataType x) {
	assert(phead);
	ListNode* newnode = ListCreate(x);
	ListNode* first = phead->next;

	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;


}

双向链表的尾删

c 复制代码
//尾删函数
void ListPopBack(ListNode* phead) {
	assert(phead);
	ListNode* tail = phead->prev;
	ListNode* tailprev = tail->prev;


	phead->prev = tailprev;
	tailprev->next = phead;
	if(phead->prev!=phead)//判断是否为空表,哨兵位不能释放了。
	      free(tail);
	tail = NULL;
	
}

双向链表的头删

c 复制代码
/头删函数
void ListPopFront(ListNode* phead) {
	assert(phead);
	ListNode* first = phead->next;
	ListNode* second = first->next;

	phead->next = first->next;
	second->prev = phead;
	if (phead != first)//哨兵位不能释放
	{
		free(first);
	}
	first = NULL;
	second = NULL;
}

双向链表的销毁

c 复制代码
void ListDestory(ListNode** pphead) {
	assert(pphead);
	assert(*pphead);
	ListNode* pcur = (*pphead)->next;
	ListNode* next = pcur->next;
	while (pcur != *pphead)
	{
		free(pcur);
		pcur = next;
		next = next->next;
	}
	free(pcur);
	pcur = NULL;
	next = NULL;
	*pphead = NULL;

}

双向链表补充

c 复制代码
// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

双向链表查找指定数据

c 复制代码
ListNode* ListFind(ListNode* phead, LTDataType x) {
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead) {
		if (pcur->data == x) {
			return pcur;//找到了返回当前节点的地址
		}
		pcur=pcur->next;
	}
	return NULL;//双链表跑完一遍都没找到,返回空。
}

在pos节点之前插入新节点

c 复制代码
//在pos之前插入
void ListInsert(ListNode* pos, LTDataType x) {
	assert(pos);
	ListNode* newnode = ListCreate(x);
	ListNode* prev = pos->prev;

	newnode->next = pos;
	pos->prev = newnode;
	prev->next = newnode;
	newnode->prev = prev;

	pos = NULL;
	prev = NULL;
	newnode = NULL;
}

删除位置为pos的节点

c 复制代码
void ListErase(ListNode* pos) {
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;

	pos = NULL;
	prev = NULL;
	next = NULL;
}

十分钟实现一个链表

  1. 实现什么类型的链表?
  2. 需要写什么函数?

双向链表;

实现函数,增删查改,还有来链表的初始化,销毁。

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>


typedef int LTDataType;
typedef struct ListNode {
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LT;

LT* LTInit(void) {
	LT* newnode = (LT*)malloc(sizeof(LT));
	if (newnode == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

LT LTDestory(LT** pphead) {
	assert(pphead);
	assert(*pphead);
	LT* pcur = (*pphead)->next;
	LT* next = pcur->next;
	while (pcur != *pphead) {
		free(pcur);
		pcur = next;
		next = next->next;
	}
	free(pcur);
	*pphead = NULL;

}

//在pos之前插入新节点
void LTInsert(LT* pos, LTDataType x) {
	assert(pos);
	LT* prev = pos->prev;
	LT * newnode = (LT*)malloc(sizeof(LT));
	if (newnode == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;

}

void LTErase(LT* pos) {
	assert(pos);
	LT* prev = pos->prev;
	LT* next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;
}


void LTPushBack(LT* phead,LTDataType x) {
	LTInsert(phead, x);
}

void LTPushFront(LT* phead, LTDataType x) {
	LTInsert(phead->next, x);
}

void LTPopBack(LT* phead) {
	LTErase(phead->prev);
}

void LTPopFront(LT* phead) {
	LTErase(phead->next);
}


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


LT* LTMidfy( LT* pos,LTDataType x) {
	pos->data = x;
}

双向链表完结。链表完结!

相关推荐
ChoSeitaku9 分钟前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
workflower1 小时前
数据结构练习题和答案
数据结构·算法·链表·线性回归
一个不喜欢and不会代码的码农1 小时前
力扣105:从先序和中序序列构造二叉树
数据结构·算法·leetcode
No0d1es3 小时前
2024年9月青少年软件编程(C语言/C++)等级考试试卷(九级)
c语言·数据结构·c++·算法·青少年编程·电子学会
bingw01143 小时前
华为机试HJ42 学英语
数据结构·算法·华为
Yanna_1234565 小时前
数据结构小项目
数据结构
木辛木辛子6 小时前
L2-2 十二进制字符串转换成十进制整数
c语言·开发语言·数据结构·c++·算法
誓约酱6 小时前
(动画版)排序算法 -希尔排序
数据结构·c++·算法·排序算法
誓约酱6 小时前
(动画版)排序算法 -选择排序
数据结构·算法·排序算法
可别是个可爱鬼7 小时前
代码随想录 -- 动态规划 -- 完全平方数
数据结构·python·算法·leetcode·动态规划