数据结构:发掘双链表的灵活优势

✨✨小新课堂开课了,欢迎欢迎~✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:http://t.csdnimg.cn/oHJAK(数据结构与算法)

小新的主页:编程版小新-CSDN博客

 前言:

前面我们学习了单链表,实现了它的增删查改等功能。但是单链表有一个缺点,单链表只能找到该节点的下一个节点,找不到该节点的前一个节点,要想找到前一个节点,只能遍历原链表。我们今天要讲到的双链表就很好的解决了这个问题,并且双链表十分的灵活。

1.具体形式

双链表是带头双向循环链表的简称。头就是我们之前提到过的哨兵位。

具体是这个模样。

2.双链表的功能 

1.初始化双链表

2.对双链表进行尾插(在末尾插入数据)

3.对双链表进行尾删(删除末尾的数据)

4.打印双链表

5.对双链表进行头插(在第一个有效节点之前插入数据)ps:即哨兵位后面插入

6.对双链表进行头删(删除第一个有效节点)

7.查找指定节点

8.在指定位置之后插入数据

9.删除指定位置的节点

10.销毁双链表

3.定义双链表

typedef int LTDataType;
//定义双链表的节点
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;//下一个节点
	struct ListNode* prev;//前一个节点
}LTNode;

 双链表中的数据比单链表中的数据多了一个指向某节点前一个节点的指针,使用双链表能直接找到前一个节点,相比单链表要遍历原链表找前一个节点十分的方便。

4.双链表功能的实现

如果在看下面代码有疑问的话,一定要回去看上面双链表的具体形式的图,仔细分析一下某一个节点的前一个节点以及后一个节点的指向。哨兵位和“尾”节点(形式上的最后一个节点d3)是比较特殊的,可以先看一下。

4.1初始化双链表

初始化的过程其实就是为双链表创建一个哨兵位,哨兵位一般不给data赋值,但是因为初始化的避免不了给它赋值,我们一般就给它赋一个比较特殊的值例如-1,当然赋其他的值也可以,这更像是一个约定俗成的规矩。

初始化1
//void LTInit(LTNode** phead)
//{
//	//给双向链表创建一个哨兵位
//	*phead = LTBuynode(-1);
//}

//初始化2
LTNode* LTInit()
{
	LTNode* phead = LTBuynode(-1);
	return phead;
}

有两种初始化的方式,任选其中一种即可。哨兵位就是一个比较特殊的节点,LTBuynode()函数是用来申请一个新节点的,后面有实现。第一次调用这个函数的过程就是创建哨兵位的过程。之后再调用这个函数就是申请新的节点进行插入操作。

4.2对双链表进行尾插(在末尾插入数据)

在插入数据之前都要申请一个新的节点


//申请新的节点
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;
}

申请完成之后再实现插入操作。

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	//插入之前要申请一个新的节点
	LTNode* newnode = LTBuynode(x);
	//phead  phead->prev newnode
	//先改动newnode的指向,避免对其他造成影响
	newnode->prev = phead->prev;
	newnode->next = phead;

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

}

 双链表的增删查改等功能的实现其实就是改变节点的指向的过程。在实现任何一种插入操作的时候,我们都先改变新申请的节点的指向,避免对其他的造成影响。这样实现起来逻辑更加清晰。

4.3对双链表进行尾删(删除末尾的数据)

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead && phead->next != phead);//不能为空链表

	LTNode* del = phead->prev;//尾节点
	//phead phead->prev del
	del->prev->next = phead;
	phead->prev = del->prev;

	//删除del节点
	free(del);
	del = NULL;

}

4.4打印双链表

//打印链表
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;//哨兵位不需要打印
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");

}

4.5对双链表进行头插(在第一个有效节点之前插入数据)

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuynode(x);
	//phead newnode phead->next
	newnode->next = phead->next;
	newnode->prev = phead;
	phead->next = newnode;
	phead->next->prev = newnode;

}

4.6对双链表进行头删(删除第一个有效节点)

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);//不能为空链表

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

	//删除del节点
	free(del);
	del = NULL;
}

4.7查找指定节点

//查找节点
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	LTNode* pcur = phead;
	while (pcur->next != phead)//当pcur->next==phead的时候pcur就是最后一个节点
	{
		if (pcur->data == x)
		{
			return pcur;
			
		}
		pcur = pcur->next;
	}
	//没有找到
	return NULL;
}

4.8在指定位置之后插入数据

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

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

4.9删除指定位置的节点

//删除pos节点
void LTErase(LTNode* pos) 
{
	//pos理论上不能为phead,但是没有phead的参数,无法增加校验
	assert(pos);
	//pos->prev pos pos->next
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;
}

4.10销毁双链表

//销毁链表
void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//此时pcur指向phead
	free(phead);
	phead = NULL;

}

5.完整代码

List.h:

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

typedef int LTDataType;
//定义双链表的节点
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

//声明双向链表中提供的方法

//初始化
LTNode* LTInit();
//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);

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

//删除pos节点
void LTErase(LTNode* pos);

//查找节点
LTNode* LTFind(LTNode* phead, LTDataType x);

//销毁链表
void LTDestroy(LTNode* phead);

List.c:

#include"List.h"

//打印链表
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* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	//申请成功
	newnode->data = x;
	newnode->next = newnode->prev = newnode;

	return newnode;
}

初始化1
//void LTInit(LTNode** phead)
//{
//	//给双向链表创建一个哨兵位
//	*phead = LTBuynode(-1);
//}


//初始化2
LTNode* LTInit()
{
	LTNode* phead = LTBuynode(-1);
	return phead;
}


//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	//插入之前要申请一个新的节点
	LTNode* newnode = LTBuynode(x);
	//phead  phead->prev newnode
	//先改动newnode的指向,避免对其他造成影响
	newnode->prev = phead->prev;
	newnode->next = phead;

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

}


//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead && phead->next != phead);//不能为空链表

	LTNode* del = phead->prev;//尾节点
	//phead phead->prev del
	del->prev->next = phead;
	phead->prev = del->prev;

	//删除del节点
	free(del);
	del = NULL;

}


//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuynode(x);
	//phead newnode phead->next
	newnode->next = phead->next;
	newnode->prev = phead;
	phead->next = newnode;
	phead->next->prev = newnode;

}

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);

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

	//删除del节点
	free(del);
	del = NULL;
}

//查找节点
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	LTNode* pcur = phead;
	while (pcur->next != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
			
		}
		pcur = pcur->next;
	}
	//没有找到
	return NULL;
}


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

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


//删除pos节点
void LTErase(LTNode* pos) 
{
	//pos理论上不能为phead,但是没有phead的参数,无法增加校验
	assert(pos);
	//pos->prev pos pos->next
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;
}

//销毁链表
void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//此时pcur指向phead
	free(phead);
	phead = NULL;

}

我们在自己写这些代码的时候其实可以在创建一个test.c(测试文件),对我们所实现的功能进行测试,每当我们实现一个功能的时候我们都去测试一下去找错,这样下来效率会大大提高,要是全部写完再测试,运气不好的话有一次报很多错误,不方便改正的同时也会打击我们的自信心。

小新:上面是小新的一点建议,大家怎么写舒服都可以哦~

test.c:仅供参考

#include"List.h"

void ListTest01()
{
	LTNode* plist = LTInit();

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

	//LTPopBack(plist);
	//LTPushFront(plist, 5);
	//LTPopFront(plist);
	LTNode* find = LTFind(plist, 2);
	//LTInsert(find, 11);
	//LTErase(find);
	//LTPrint(plist);
	LTDestroy(plist);//销毁之后不满足打印条件,是不能通过LTPrint()打印的

}

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

结束了~

下次还记得来哦

  • 46
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值