【数据结构和算法初阶(C语言)】双向循环带头链表的增删查改详解(天才设计的链表结构,应用简单逆天!!!!!)

目录

​编辑​编辑

1.双向链表的定义:前赴后继 

2.带头链表的定义-----哨兵位 

3.增删查改

3.1创建新节点函数----方便后续增加节点调用

3.2创建哨兵位----创建头结点 

3.3增加节点,尾部插入数据 

3.4尾删除 

3.5查找函数----遍历+对比,返回节点地址 

3.6头删函数

3.7头插函数 

3.8 计算数组的长度

3.9在任意位置pos前插入 

3.10在任意位置删除

3.11链表的销毁 

 4、结语及整个源码含测试用例


1.双向链表的定义:前赴后继 

typedef int LTDataTYpe;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataTYpe data;
}LTNode;

2.带头链表的定义-----哨兵位 

3.增删查改

先写一个打印函数来方便后续测试:

 

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur= phead->next;
	while (cur != phead)
	{
		//printf("phead");
		printf("%d<=>", cur->data);
		cur = cur->next;

	}

}

3.1创建新节点函数----方便后续增加节点调用

申请节点,将前后指针置空同时可以给节点赋值,返回这个节点的地址,也就是指向这个节点的指针

LTNode* BuyNode(LTDataTYpe x)
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	phead->data = x;
	phead->next = NULL;
	phead->prev = NULL;
	return phead;
}

3.2创建哨兵位----创建头结点 

后续所有节点可以增加在哨兵位的前后就对应前插、尾插。

最开始的哨兵位前后指针都应该指向自己。

LTNode* LTInit()
{
	
	LTNode* phead = BuyNode(-1);//首先有一个节点
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

3.3增加节点,尾部插入数据 

两种情况一样:

 


void LTPushBack(LTNode* phead, LTDataTYpe x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* newnode = BuyNode(x);
	newnode->prev = tail;
	tail->next = newnode;
	newnode->next = phead;
	phead->prev = newnode;
	
}

3.4尾删除 


void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* tail = phead->prev;
	LTNode* tailprev = tail->prev;
	free(tail);
	tail->next = phead;
	phead->prev = tailprev;
}

3.5查找函数----遍历+对比,返回节点地址 

LTNode* LTFind(LTNode* phead, LTDataTYpe x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur!= phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

3.6头删函数

void LTPodFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* after = phead->next;
	LTNode* tail = after->next;
	phead->next = tail;
	tail->prev = phead;
	free(after);
}

3.7头插函数 

 

void LTPushFront(LTNode* phead, LTDataTYpe x)
{
	assert(phead);
	LTNode* d1 = phead->next;
	LTNode* newnode = BuyNode(x);
	phead->next = newnode;
	newnode->next = d1;
	d1->prev = newnode;
	newnode->prev = phead;

}

 

3.8 计算数组的长度

 这里对于大家可能有一个误区,我们的头结点也就是哨兵位,数据领域没有保存值,就会有伙伴像在数据域保存链表的大小,增加就+1,减少就-1,但是这种情况只针对数据域是整型的情况,如果数据领域是浮点数或者字符型,加1可能就导致数据不准确,

方法:遍历+计数

int LTSize(LTNode* phead)
{
	assert(phead);//不能是空指针
	int size = 0;
	LTNode* cur = phead->next;//定义遍历指针
	while (cur != phead)
	{
		size++;
		cur = cur->next;

	}
	return size;
}

3.9在任意位置pos前插入 

如果pos=head就是尾插

如果pos = head->next就是头插

void LTInsert(LTNode* pos, LTDataTYpe x)
{
	//首先要查找的节点不能为空
	assert(pos);
	LTNode* newnode = BuyNode(x);
	LTNode* prv = pos->prev;
	prv->next = newnode;
	newnode->prev = prv;
	newnode->next = pos;
	pos->prev = newnode;

}

3.10在任意位置删除

删除一定注意保护头结点---使用断言

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

也是可以复用实现头删尾删

3.11链表的销毁 

链表不是连续的空间就不会像那种顺序表找到头一下子释放而是注意释放

 

void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	//可以外部置空,不设置返回值和二级指针。

}

 4、结语及整个源码含测试用例

双向链表是一个快捷使用的数据结构,非常实用,不过对于初学建议画图来理解增删查改。很大程度上增删查改避免了二级指针的传参,就是因为使用了哨兵位的好处。以上就是本期的所有内容,知识含量蛮多,大家可以配合解释和原码运行理解。创作不易,大家如果觉得还可以的话,欢迎大家三连,有问题的地方欢迎大家指正,一起交流学习,一起成长,我是Nicn,正在c++方向前行的奋斗者,数据结构内容持续更新中,感谢大家的关注与喜欢。

源码:

test.c

#include"List.h"
void test()
{

	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	
	LTPushBack(plist, 2);
	
	LTPushBack(plist, 3);
	LTPodFront(plist);
	LTPushFront(plist, 1);
	
	LTPrint(plist);
	printf("\n");
	//LTInsert(plist,1);
	//LTPrint(plist);
}
int main()
{

	test();
	return 0;
}

 List.h

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

typedef int LTDataTYpe;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataTYpe data;
}LTNode;

//创建新节点函数
LTNode* BuyNode(LTDataTYpe x);
//初始化创建哨兵位
LTNode* LTInit();
//尾插一下
void LTPushBack(LTNode* phead, LTDataTYpe x);
//打印链表
void LTPrint(LTNode* phead);
//尾删
void LTPopBack(LTNode* phead);
//计算链表的大小或者记录链表的大小
int LTSize(LTNode* phead);
//头删函数
void LTPodFront(LTNode* phead);
//头插一下
void LTPushFront(LTNode* phead, LTDataTYpe x);
//查找函数
LTNode* LTFind(LTNode* phead, LTDataTYpe x);
//在寻找到的任意pos位置之前插入
void LTInsert(LTNode* pos, LTDataTYpe x);

//删除任意位置,pos
void LTErase(LTNode* pos);
//链表的销毁
//链表不是连续的空间就不会像那种顺序表找到头一下子释放而是注意释放
void LTDestory(LTNode* phead);

List.c

LTNode* BuyNode(LTDataTYpe x)
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	phead->data = x;
	phead->next = NULL;
	phead->prev = NULL;
	return phead;
}


LTNode* LTInit()
{
	
	LTNode* phead = BuyNode(-1);//首先有一个节点
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

void LTPushBack(LTNode* phead, LTDataTYpe x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* newnode = BuyNode(x);
	newnode->prev = tail;
	tail->next = newnode;
	newnode->next = phead;
	phead->prev = newnode;
	
}
void LTPushFront(LTNode* phead, LTDataTYpe x)
{
	assert(phead);
	LTNode* d1 = phead->next;
	LTNode* newnode = BuyNode(x);
	phead->next = newnode;
	newnode->next = d1;
	d1->prev = newnode;
	newnode->prev = phead;

}

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur= phead->next;
	while (cur != phead)
	{
		//printf("phead");
		printf("%d<=>", cur->data);
		cur = cur->next;

	}

}

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* tail = phead->prev;
	LTNode* tailprev = tail->prev;
	free(tail);
	tail->next = phead;
	phead->prev = tailprev;
}



int LTSize(LTNode* phead)
{
	assert(phead);//不能是空指针
	int size = 0;
	LTNode* cur = phead->next;//定义遍历指针
	while (cur != phead)
	{
		size++;
		cur = cur->next;

	}
	return size;
}

LTNode* LTFind(LTNode* phead, LTDataTYpe x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur!= phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}



void LTInsert(LTNode* pos, LTDataTYpe x)
{
	//首先要查找的节点不能为空
	assert(pos);
	LTNode* newnode = BuyNode(x);
	LTNode* prv = pos->prev;
	prv->next = newnode;
	newnode->prev = prv;
	newnode->next = pos;
	pos->prev = newnode;

}


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

void LTPodFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* after = phead->next;
	LTNode* tail = after->next;
	phead->next = tail;
	tail->prev = phead;
	free(after);
}

void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	//可以外部置空,不设置返回值和二级指针。

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值