数据结构——双链表

「C++ 40 周年」主题征文大赛(有机会与C++之父现场交流!) 10w+人浏览 460人参与

目录

一、链表的分类

二、双向链表的概念与结构

概念:

结构:

三、双向链表的代码实现

1.定义双向链表的结构

2.申请新节点

3.初始化

4.打印链表

5.尾插

6.头插

7.判断链表是否为空(是否只有一个哨兵位节点)

8.尾删

9.头删

10.查找值为pos的数据

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

12.删除pos位置的数据

13.销毁链表

四、完整代码分享

List.h

List.c

test.c

五、博主手记

前言:

前两篇文章我们介绍了单链表,这篇文章来介绍一下双链表(带头双向循环链表),再分享双链表的知识和代码实现之前,我们来简单介绍一下链表的分类,那么就让我们开启今天这篇文章吧!

一、链表的分类

链表的分类有:

带头与不带头、单向与双向、循环与不循环。

因此,链表的种类就有8种。

但是我们最常用的只有两种:不带头单向不循环链表带头双向循环链表,也就是我们这两篇文章所说的单链表和双链表。

二、双向链表的概念与结构

概念:

双向链表就是带头双向循环链表,如图所示。

结构:

和单链表一样,物理结构:连续;  逻辑结构:不一定连续。

三、双向链表的代码实现

1.定义双向链表的结构

双向链表中有三个结构:data、*next、*prev

data :保存有效数据

struct ListNode* next :指针保存下⼀个结点的地址

struct ListNode* prev :指针保存前⼀个结点的地址

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

2.申请新节点

//申请新节点
ListNode* LTBuyNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

3.初始化

初始化就是把头节点设置为存储无效数据,以便后面“放哨”用。

//初始化
ListNode* LTInit()
{
    ListNode* phead = LTBuyNode(-1);
	return phead;
}

4.打印链表

//打印链表
void LTPrint(ListNode* phead)
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

5.尾插

//尾插
void LTPushBack(ListNode* phead, LTDataType x)
{
	ListNode* newnode = LTBuyNode(x);
	newnode->next = phead;
	newnode->prev = phead->prev;
	phead->prev->next = newnode;
	phead->prev = newnode;
}

6.头插

//头插
void LTPushFront(ListNode* phead, LTDataType x)
{
	ListNode* newnode = LTBuyNode(x);
	newnode->next = phead->next;
	newnode->prev = phead;
	phead->next->prev = newnode;
	phead->next = newnode;
}

7.判断链表是否为空(是否只有一个哨兵位节点)

//判断链表是否为空(是否只有一个哨兵位节点)
bool LTEmpty(ListNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

8.尾删

删除操作之前需要判断链表内是否为空,否则无法进行删除。

//尾删
void LTDelBack(ListNode* phead)
{
	assert(!LTEmpty(phead));
	ListNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	free(del);
	del = NULL;
}

9.头删

//头删
void LTDelFront(ListNode* phead)
{
	assert(!LTEmpty(phead));
	ListNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}

10.查找值为pos的数据

//查找数据
ListNode* LTFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

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

//在pos之后插入节点
void LTInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = LTBuyNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}

12.删除pos位置的数据

//删除pos节点
void LTErase(ListNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

13.销毁链表

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

进行完销毁链表函数之后,虽然已经将链表内的数据都释放掉了,但还需要将链表的头节点置为NULL,防止后面成为野指针被使用。

四、完整代码分享

List.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

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

//初始化
ListNode* LTInit();
//申请新节点
ListNode* LTBuyNode(LTDataType x);
//打印链表
void LTPrint(ListNode* phead);
//判断链表是否为空(是否只有一个哨兵位节点)
bool LTEmpty(ListNode* phead);
//尾插
void LTPushBack(ListNode* phead, LTDataType x);
//头插
void LTPushFront(ListNode* phead, LTDataType x);
//尾删
void LTDelBack(ListNode* phead);
//头删
void LTDelFront(ListNode* phead);
//查找数据
ListNode* LTFind(ListNode* phead, LTDataType x);
//在pos之后插入节点
void LTInsert(ListNode* pos, LTDataType x);
//删除pos节点
void LTErase(ListNode* pos);
//销毁链表
void LTDestory(ListNode* phead);

List.c

#include"List.h"

//申请新节点
ListNode* LTBuyNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

//初始化
ListNode* LTInit()
{
	ListNode* phead = LTBuyNode(-1);
	return phead;
}

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

//打印链表
void LTPrint(ListNode* phead)
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

//尾插
void LTPushBack(ListNode* phead, LTDataType x)
{
	ListNode* newnode = LTBuyNode(x);
	newnode->next = phead;
	newnode->prev = phead->prev;
	phead->prev->next = newnode;
	phead->prev = newnode;
}

//头插
void LTPushFront(ListNode* phead, LTDataType x)
{
	ListNode* newnode = LTBuyNode(x);
	newnode->next = phead->next;
	newnode->prev = phead;
	phead->next->prev = newnode;
	phead->next = newnode;
}

//判断链表是否为空(是否只有一个哨兵位节点)
bool LTEmpty(ListNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
//尾删
void LTDelBack(ListNode* phead)
{
	assert(!LTEmpty(phead));
	ListNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	free(del);
	del = NULL;
}

//头删
void LTDelFront(ListNode* phead)
{
	assert(!LTEmpty(phead));
	ListNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}

//查找数据
ListNode* LTFind(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之后插入节点
void LTInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = LTBuyNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}

//删除pos节点
void LTErase(ListNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

test.c

test,.c也就是测试函数,用来测试函数的功能。

#include"List.h"

void test1()
{
	 ListNode* plist=LTInit();
	 LTPushBack(plist, 1);
	 LTPushBack(plist, 2);
	 LTPushBack(plist, 3);
	 LTPushBack(plist, 4);
	 LTPrint(plist);

	 //LTPushFront(plist, 1);
	 //LTPrint(plist);
	 //LTPushFront(plist, 2);
	 //LTPrint(plist);
	 //LTPushFront(plist, 3);
	 //LTPrint(plist);
	 //LTPushFront(plist, 4);
	 //LTPrint(plist);

	 //LTDelBack(plist);
	 //LTPrint(plist);
	 //LTDelBack(plist);
	 //LTPrint(plist);
	 //LTDelBack(plist);
	 //LTPrint(plist);
	 //LTDelBack(plist);
	 //LTPrint(plist);
	 //LTDelBack(plist);
	 //LTPrint(plist);

	 //LTDelFront(plist);
	 //LTPrint(plist);
	 //LTDelFront(plist);
	 //LTPrint(plist);
	 //LTDelFront(plist);
	 //LTPrint(plist);
	 //LTDelFront(plist);
	 //LTPrint(plist);
	 //LTDelFront(plist);
	 //LTPrint(plist);

	 ListNode* pos = LTFind(plist, 3);
	 //if (pos != NULL)
	 //{
		// printf("找到了");
	 //}
	 //LTInsert(pos, 100);
	 //LTPrint(plist);
	 //
	 //LTErase(pos);
	 //LTPrint(plist);

	 LTDestory(plist);
	 plist = NULL;


}

int main()
{
	test1();
	return 0;
}

五、博主手记

这一部分是将博主记的笔记和解题思路分享给大家,大家可以参考一下思路。

结语:

双向链表对于单链表来说确实使用起来更加的方便,但是考虑到占用空间的问题,大家还是不能盲目的使用,还应该具体情况具体分析,来使用合适的结构完成。

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值