手撕双链表

> 作者简介:დ旧言~,目前大一,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。
> 望小伙伴们点赞收藏✨加关注哟 

 前言       

        前面我们已经学习了顺序表和单链表,顺序表可以存储动态的数据,但是一旦元素过少,而又要开辟空间,这样就造成空间的浪费,而单链表以节点为单位存储,不支持随机访问,只能从头到尾遍历访问,为了解决上面两个问题,人们发现了双链表,把一个一个元素以链子的形式存储,可以存储相互的地址,那双链表如何实现呢,今天咱们就实现一下--《双链表》。

手撕双链表_第1张图片

主体

咱们从三个方面实现双链表,动态管理,头插头删尾插尾删,增删查改。

手撕双链表_第2张图片

在程序中为了实现双链表,需要创建头文件List.h ,创建源文件Test.c,List.c。

手撕双链表_第3张图片

 动态管理

初始化动态双链表

既然实现双链表,初始化动态的双链表必不可少,从两个方面实现初始化动态的双链表。

1.首先我们在List.h定义动态的双链表,省得我们再定义节点(双链表)。

//定义数据类型
typedef int LTDataType;
//定义双链表初始化
typedef struct ListNode
{
	//上一个
	struct ListNode* next;
	//下一个
	struct ListNode* prev;
	LTDataType data;
}LTNode;

2.对双链表进行初始化

我们要明白,这里不像单链表一样,形成节点就行,还需要初始化。

这里采用malloc开辟空间

采用预指令判断空间是否开辟完成(没有开辟空间返回-1)

之后就是简单的初始数据

记得返回值

//初始化
LTNode* BuyLTNode(LTDataType x)
{
	//开辟空间
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	//判断开辟的空间是否为空
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	//初始化
	node->data = x;
	node->next = NULL;
	node->prev = NULL;
	return node;
}

//形成双链表
LTNode* LTInit()
{
	//使头为0
	LTNode* phead = BuyLTNode(0);
	//构成循环
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

 释放双链表内存 

双链表的内存释放与单链表的内存释放有一定的区别,这里我们分开两类,清理与销毁

清理的代码如下:

//清理
void ListClear(LTNode* phead)
{
	//断言
	assert(phead);
	
	//清理全部数据,保留头结点
	LTNode* cur = phead->next;
	
	//循环销毁
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = NULL;
		cur = next;
	}
}

销毁的代码如下:

//销毁
void ListDestory(LTNode** pphead)
{
	//断言
	assert(*pphead);

	//调用清理(函数)
	ListClear(*pphead);
	
	//释放内存
	free(*pphead);
	*pphead = NULL;
}

头插头删尾插尾删

打印元素

 打印元素就太简单了,直接上代码

//打印数据
void LTPrint(LTNode* phead)
{
	//断言
	assert(phead);

	printf("phead<=>");
	
	LTNode* cur = phead->next;
	//注意循环的结束的语句
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	
	printf("\n");
}

尾插(重点)

有了这个图就对指向的改变就轻轻松松

手撕双链表_第4张图片

//尾插(重点)
void LTPushBack(LTNode* phead, LTDataType x)
{
	//断言
	assert(phead);

	LTNode* tail = phead->prev;
	
	//给添加的元素创建节点
	LTNode* newnode = BuyLTNode(x);

    //新开辟的元素下一个节点指向尾
	newnode->prev = tail;
	//尾的上一个节点指向新的元素
    tail->next = newnode;

    //新元素的上一个节点指向头
	newnode->next = phead;
    //头的下一节点指向新的元素节点
	phead->prev = newnode;

	//本质上与尾插相似 (双向链表在pos的前面进行插入)
	//LTInsert(phead, x);
}

尾删(重点)

双链表重在画图,希望小伙伴们能看得懂。

手撕双链表_第5张图片

//尾删
void LTPopBack(LTNode* phead)
{
	//断言
	assert(phead);
	//防止没有头指向没有元素
	assert(phead->next != phead);

	//找到尾
	LTNode* tail = phead->prev;
	//找到尾前面一个元素
	LTNode* tailPrev = tail->prev;
	
	//释放内存
	free(tail);

	//(把尾的上一个元素)的上一个指针 指向头
	tailPrev->next = phead;
	//头的下一个指针指向尾的前一个元素
	phead->prev = tailPrev;

	// 双向链表删除pos位置的结点(本质上与尾删相似)
	//LTErase(phead->prev);
}

 头插(重点)

博主在这里采用三种方法,希望大家至少学会一种

手撕双链表_第6张图片

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	//方法一
	//初始化
	//LTNode* newnode = BuyLTNode(x);
	//newnode->next = phead->next;
	//phead->next->prev = newnode;
	//phead->next = newnode;
	//newnode->prev = phead;

	//方法二
	//初始化
	LTNode* newnode = BuyLTNode(x);
	LTNode* first = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;

	//方法三
	// 双向链表删除pos位置的结点(本质上就是头插)
	//LTInsert(phead->next, x);
}

 头删(重点)

双链表会自带梢兵位(这个到后期博主会讲)

//头删(重点)
void LTPopFront(LTNode* phead)
{
	//断言
	assert(phead);
	//头指向不能为空
	assert(phead->next != phead);

	//找到梢兵位的节点
	LTNode* first = phead->next;
	//找到头后面一个元素
	LTNode* second = first->next;

	//释放内存
	free(first);

	//梢兵位的节点指向头后面一个元素
	phead->next = second;
	//后面一个元素指向梢兵位的节点
	second->prev = phead;

	//双向链表删除pos位置的结点(本质上和头删一样)
	//LTErase(phead->next);
}

  增删查改

 统计双链表元素个数

这个函数还是比较简单的,注意循环的停止条件。

//统计双链表元素个数
int LTSize(LTNode* phead)
{
	//断言
	assert(phead);

	int size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

 双向链表在pos的前面进行插入

这里大家就看图理解就行啦

手撕双链表_第7张图片

// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x)
{
	//断言
	assert(pos);

	LTNode* posPrev = pos->prev;
	//为插入的元素开辟空间
	LTNode* newnode = BuyLTNode(x);

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

 双向链表删除pos位置的结点

这里大家就看图理解就行啦

手撕双链表_第8张图片

// 双向链表删除pos位置的结点
void LTErase(LTNode* pos)
{
	//断言
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;

	//释放内存
	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}

代码总结

主函数

//包含文件
#include"List.h"

void TestList1()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPushBack(plist, 5);
	LTPrint(plist);

	LTPushFront(plist, 10);
	LTPushBack(plist, 10);

	LTPrint(plist);
}

void TestList2()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPushBack(plist, 5);
	LTPrint(plist);

	LTPopBack(plist);
	//LTPopFront(plist);
	LTPrint(plist);

	//LTPopFront(plist);
	//LTPopFront(plist);
	//LTPopFront(plist);
	//LTPopFront(plist);
	LTPrint(plist);
}

//void TestList3()
//{
//	LTNode* plist = LTInit();
//	LTPushBack(plist, 1);
//	LTPushBack(plist, 2);
//	LTPushBack(plist, 3);
//	LTPushBack(plist, 4);
//	LTPushBack(plist, 5);
//	LTPrint(plist);
//
//	LTPushFront(plist, 10);
//	LTPushFront(plist, 20);
//	LTPushFront(plist, 30);
//	LTPushFront(plist, 40);
//	LTPrint(plist);
//}
//
//void TestList4()
//{
//	LTNode* plist = LTInit();
//	LTPushBack(plist, 1);
//	LTPushBack(plist, 2);
//	LTPushBack(plist, 3);
//	LTPushBack(plist, 4);
//	LTPushBack(plist, 5);
//	LTPrint(plist);
//
//	LTPopFront(plist);
//	LTPrint(plist);
//
//	LTPopBack(plist);
//	LTPrint(plist);
//}

int main()
{
	TestList1();

	return 0;
}

List.h头文件

//包含头文件
#include
#include
#include

//定义数据类型
typedef int LTDataType;
//定义双链表初始化
typedef struct ListNode
{
	//上一个
	struct ListNode* next;
	//下一个
	struct ListNode* prev;
	LTDataType data;
}LTNode;

//初始化
LTNode* BuyLTNode(LTDataType x);

//形成双链表
LTNode* LTInit();

//打印数据
void LTPrint(LTNode* phead);

//尾插(重点)
void LTPushBack(LTNode* phead, LTDataType x);
//尾删(重点)
void LTPopBack(LTNode* phead);
//头插(重点)
void LTPushFront(LTNode* phead, LTDataType x);
//头删(重点)
void LTPopFront(LTNode* phead);

//统计双链表元素个数
int LTSize(LTNode* phead);
// 双向链表查找(在双链表中不合适)
LTNode* LTFind(LTNode* phead, LTDataType x);

// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x);
// 双向链表删除pos位置的结点
void LTErase(LTNode* pos);

//清理
void ListClear(LTNode* phead);
//销毁
void ListDestory(LTNode** pphead);

List.c源文件



//包含文件
#include"List.h"

//初始化
LTNode* BuyLTNode(LTDataType x)
{
	//开辟空间
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	//判断开辟的空间是否为空
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	//初始化
	node->data = x;
	node->next = NULL;
	node->prev = NULL;
	return node;
}

//形成双链表
LTNode* LTInit()
{
	//使头为0
	LTNode* phead = BuyLTNode(0);
	//构成循环
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

//打印数据
void LTPrint(LTNode* phead)
{
	//断言
	assert(phead);

	printf("phead<=>");
	
	LTNode* cur = phead->next;
	//注意循环的结束的语句
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	
	printf("\n");
}

//尾插(重点)
void LTPushBack(LTNode* phead, LTDataType x)
{
	//断言
	assert(phead);

	LTNode* tail = phead->prev;

	//给添加的元素创建节点
	LTNode* newnode = BuyLTNode(x);

	//新开辟的元素下一个节点指向尾
	newnode->prev = tail;
	//尾的上一个节点指向新的元素
	tail->next = newnode;

	//新元素的上一个节点指向头
	newnode->next = phead;
	//头的下一节点指向新的元素节点
	phead->prev = newnode;

	//本质上与尾插相似 (双向链表在pos的前面进行插入)
	//LTInsert(phead, x);
}

//尾删
void LTPopBack(LTNode* phead)
{
	//断言
	assert(phead);
	//防止没有头指向没有元素
	assert(phead->next != phead);

	//找到尾
	LTNode* tail = phead->prev;
	//找到尾前面一个元素
	LTNode* tailPrev = tail->prev;
	
	//释放内存
	free(tail);

	//(把尾的上一个元素)的上一个指针 指向头
	tailPrev->next = phead;
	//头的下一个指针指向尾的前一个元素
	phead->prev = tailPrev;

	// 双向链表删除pos位置的结点(本质上与尾删相似)
	//LTErase(phead->prev);
}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	//方法一
	//初始化
	//LTNode* newnode = BuyLTNode(x);
	//newnode->next = phead->next;
	//phead->next->prev = newnode;
	//phead->next = newnode;
	//newnode->prev = phead;

	//方法二
	//初始化
	LTNode* newnode = BuyLTNode(x);
	LTNode* first = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;

	//方法三
	// 双向链表删除pos位置的结点(本质上就是头插)
	//LTInsert(phead->next, x);
}

//头删(重点)
void LTPopFront(LTNode* phead)
{
	//断言
	assert(phead);
	//头指向不能为空
	assert(phead->next != phead);

	//找到梢兵位的节点
	LTNode* first = phead->next;
	//找到头后面一个元素
	LTNode* second = first->next;

	//释放内存
	free(first);

	//梢兵位的节点指向头后面一个元素
	phead->next = second;
	//后面一个元素指向梢兵位的节点
	second->prev = phead;

	//双向链表删除pos位置的结点(本质上和头删一样)
	//LTErase(phead->next);
}

//统计双链表元素个数
int LTSize(LTNode* phead)
{
	//断言
	assert(phead);

	int size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x)
{
	//断言
	assert(pos);

	LTNode* posPrev = pos->prev;
	//为插入的元素开辟空间
	LTNode* newnode = BuyLTNode(x);

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

// 双向链表删除pos位置的结点
void LTErase(LTNode* pos)
{
	//断言
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;

	//释放内存
	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}


//清理
void ListClear(LTNode* phead)
{
	//断言
	assert(phead);
	
	//清理全部数据,保留头结点
	LTNode* cur = phead->next;
	
	//循环销毁
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = NULL;
		cur = next;
	}
}

//销毁
void ListDestory(LTNode** pphead)
{
	//断言
	assert(*pphead);

	//调用清理(函数)
	ListClear(*pphead);
	
	//释放内存
	free(*pphead);
	*pphead = NULL;
}

结束语

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小说手给博主一键三连,有你们的支持是我最大的动力,回见。

你可能感兴趣的:(数据结构)