数据结构---详解双向链表

前面我们讲过单向链表,但是链表的结构非常多。

一、链表的分类



  1. 虽然上面展示了很多链表的结构,但是我们实际上最常用的还是两种结构:单链表和双向链表。
  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。
  • 带头双向循环链表:结构复杂,一般用在单独储存数据。

二、双向链表的实现

  1. 首先创建三个文件

头文件List.h:声明函数和定义链表

源文件List.c:用来具体实现函数

测试文件test.c:用来测试工作

1、双向链表的初始化

  • 方法一:直接一个函数生成哨兵位,然后返回哨兵位
c 复制代码
LTNode* LTInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	phead->data = -1;  //这里的值随便定,data无效
	phead->next = phead->prev = phead;

	return phead;
}
  • 方法二:传指针,进行加工成哨兵位
c 复制代码
void LTInit(LTNode** pphead)
{
	assert(pphead);
	*pphead = (LTNode*)malloc(sizeof(LTNode));
	(*pphead)->data = -1;
	(*pphead)->next = (*pphead)->prev = *pphead;
}

2、双向链表的销毁

c 复制代码
void LTDesTroy(LTNode* phead)
{
    //创建一个指向头节点的下一个结点的指针
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
	    //创建一个next指针记录位置,避免后续释放的时候找不到下一个结点
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

3、双向链表的打印

  • 单向链表的打印靠循环循环的结束标志是pcur == NULL,那双向链表又该怎么打印呀?我们指定双向链表是循环的,如果跟单向链表一样的结束条件会出现死循环的情况,我们会发现前面初始化哨兵位的时候,哨兵位无有效数据,同时循环一轮还是回到哨兵位的位置,那我们就可以将pcur != phead作为循环的结束条件
c 复制代码
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;

	while (pcur != phead)
	{
		printf("%d-> ", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

4、双向链表的尾插

  • 创建一个新节点,想让新节点的prev指针和next指针分别指向phead->prev和phead,在让尾节点的next指针指向newnode和phead->prev指向newnode。
c 复制代码
void LTPushBack(LTNode* phead, LTDatatype x)
{
	assert(phead);
	//创建一个新节点
	LTNode* newnode = LTBuyNode(x);

	newnode->prev = phead->prev; //看图1
	newnode->next = phead;

	phead->prev->next = newnode; //图2
	phead->prev = newnode;
}
  • 图一
  • 图二

5、双向链表的头插

c 复制代码
void LTPushFront(LTNode* phead, LTDatatype x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

	newnode->next = phead->next; //图一
	newnode->prev = phead;

	phead->next->prev = newnode;
	phead->next = newnode;
}
  • 图一
  • 图二

6、双向链表判空

  • 链表的头节点的next指针指向自己的时候说明链表里面没有数据
c 复制代码
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

7、双向链表尾删

c 复制代码
void LTPopBack(LTNode* phead)
{
	assert(!LTEmpty(phead));

	LTNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;

	free(del);
	del = NULL;
}
  • 看下图更好的理解

8、双向链表头删

c 复制代码
void LTPopFront(LTNode* phead)
{
	assert(!LTEmpty(phead));

	LTNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}

9、在pos之后插入数据

c 复制代码
void LTInsert(LTNode* pos, LTDatatype x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);

	newnode->next = pos->next; //看红色的箭头
	newnode->prev = pos;

	pos->next->prev = newnode; //看粉色的箭头
	pos->next = newnode;
}

10、双向链表查找数据

c 复制代码
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

11、删除pos位置数据

c 复制代码
void LTErase(LTNode* pos) 
{
	assert(pos);

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

	free(pos);
	pos = NULL;
}

三、双向链表代码总览

1、List.h

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

typedef int LTDatatype;
//定义双线链表
typedef struct ListNode
{
	LTDatatype data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

//双向链表的初始化
LTNode* LTInit();
//void LTInit(LTNode** pphead);
//链表销毁
//传一级指针,保证接口一致性,但需要手动将实参置为NULL
void LTDesTroy(LTNode* phead);
//创建新节点
LTNode* LTBuyNode(LTDatatype x);
//打印
void LTPrint(LTNode* phead);

//尾插
void LTPushBack(LTNode* phead, LTDatatype x);
//头插
void LTPushFront(LTNode* phead, LTDatatype x);
//判断空
bool LTEmpty(LTNode* phead);
//尾删
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);

2、List.c

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include "List.h"

//双向链表初始化两种方法
LTNode* LTInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	phead->data = -1;
	phead->next = phead->prev = phead;

	return phead;
}

//void LTInit(LTNode** pphead)
//{
//	assert(pphead);
//	*pphead = (LTNode*)malloc(sizeof(LTNode));
//	(*pphead)->data = -1;
//	(*pphead)->next = (*pphead)->prev = *pphead;
//}

//链表的销毁
void LTDesTroy(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

//打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;

	while (pcur != phead)
	{
		printf("%d-> ", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}
//创建一个节点
LTNode* LTBuyNode(LTDatatype x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
		return NULL;
	node->data = x;
	node->prev = node->prev = node;

	return node;
}

//尾插
void LTPushBack(LTNode* phead, LTDatatype x)
{
	assert(phead);
	//创建一个新节点
	LTNode* newnode = LTBuyNode(x);

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

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

//头插
void LTPushFront(LTNode* phead, LTDatatype x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

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

	phead->next->prev = newnode;
	phead->next = newnode;
}
//判断空
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
//尾删
void LTPopBack(LTNode* phead)
{
	assert(!LTEmpty(phead));

	LTNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;

	free(del);
	del = NULL;
}
//头删
void LTPopFront(LTNode* phead)
{
	assert(!LTEmpty(phead));

	LTNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}

//在pos之后插入数据
void LTInsert(LTNode* pos, LTDatatype x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);

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

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

//查找数据
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
//删除pos位置数据
void LTErase(LTNode* pos) 
{
	assert(pos);

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

	free(pos);
	pos = NULL;
}

3、test.c

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include "List.h"

void test()
{
	//第一种初始化方式
	LTNode* plist = LTInit();
	//第二种初始化方式
	//LTNode* plist = NULL;
	//LTInit(&plist);

	//尾插
	LTPushBack(plist, 1);
	LTPrint(plist);
	LTPushBack(plist, 2);
	LTPrint(plist);
	LTPushBack(plist, 3);
	LTPrint(plist);
	LTPushBack(plist, 4);
	LTPrint(plist);

	//链表的销毁
	/*LTDesTroy(plist);
	plist = NULL;*/
	//删除pos位置数据
	/*LTNode* find = LTFind(plist, 2);
	if (find == NULL)
	{
		printf("没有找到!");
	}
	else
	{
		printf("找到了!");
	}

	LTErase(find);
	LTPrint(plist);*/

	//在pos位置之后插入数据
	/*LTNode* find = LTFind(plist, 2);
	if (find == NULL)
	{
		printf("没有找到!");
	}
	else
	{
		printf("找到了!");
	}
	LTInsert(find, 99);
	LTPrint(plist);*/

	//查找
	/*LTNode* find = LTFind(plist, 5);
	if (find == NULL)
	{
		printf("没有找到!");
	}
	else
	{
		printf("找到了!");
	}*/

	//尾删
	/*LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);*/

	//头删
	/*LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);*/

	//头插
	/*LTPushFront(plist, 1);
	LTPrint(plist);
	LTPushFront(plist, 2);
	LTPrint(plist);
	LTPushFront(plist, 3);
	LTPrint(plist);
	LTPushFront(plist, 4);
	LTPrint(plist);*/
}

int main()
{
	test();

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