双向链表(数据结构与算法)

请添加图片描述

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿
🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟
🌟🌟 追风赶月莫停留 🌟🌟
🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀
🌟🌟 平芜尽处是春山🌟🌟
🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟
🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅

🍌双向链表的定义与结构

在这里插入图片描述
双向链表:双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。(两个指针就是一个指向下一个数据,另一个指针指向上一个数据。如图中head头指针和tail尾指针)

在这里插入图片描述
在双向链表中还有一个特殊点,就是头指针,双向链表是带头的。在上次我们说的单链表中是不带头的,一旦涉及到头,我们就是先判断是不是为空,而在双向链表中就是不会涉及到这个问题了,不用再去一个一个的判断,就会省去很多的麻烦。

带头的双向链表嘴尾常用,所以我们这里就介绍带头的双向链表

🍌双向链表增删查改(有头+双向+循环链表增删查改实现)

🍍其它接口

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

typedef int LTdatetype;

typedef struct ListNode
{
	LTdatetype date;
	struct ListNode* next;
	struct ListNode* tail;
}ListNode;

定义一些接口

🍍创建返回链表的头结点

//创建返回链表的头结点
ListNode* Create(LTdatetype x)
{
	ListNode* cur =(ListNode*)malloc(sizeof(ListNode));
	if (cur == NULL)
	{
		perror("malloc  faild");
		exit(-1);
	}
	cur->next = NULL;
	cur->tail = NULL;
	cur->date = x;
	return cur;
}

ListNode* Inia()
{
	ListNode* node = Create(0);
	node->next = node;
	node->tail = node;

	return node;
}

Create函数就是创建一个节点。而Inia函数本来是进行初始化,而想要变成循环的链表,形参改变实参那就要用到二级指针,因为这里是双向链表,涉及头指针的时候不需要考虑二级指针,所以在这里就只有初始化需要用到头指针,所以我们就干脆用返回值,这样就不看起来违和了,当然用二级指针也没有什么问题,看自己习惯即可。

🍍双向链表销毁

// 双向链表销毁
void Destreoy(ListNode* ps)
{
	assert(ps);
	ListNode* cur = ps->next;
	while (cur != ps)
	{
		cur = cur->next;
		free(cur->tail);
	}
	free(ps);
}

🍍双向链表打印

// 双向链表打印
void Print(ListNode* ps)
{
	assert(ps);
	ListNode* cur = ps->next;
	while (cur != ps)
	{
		printf("%d<=>", cur->date);
		cur = cur->next;
	}
}

在这里插入图片描述

在这里打印,我们就不能使用和单链表打印的方法了,因为我们这里是循环链表,并且我们的ps里面没有存有数据,所以我们要从ps的下个数据开始,也就是图中cur的位置,然后在cur到ps的时候跳出循环就可以了。

🍍双向链表尾插

// 双向链表尾插
void LTpushbake(ListNode* ps, LTdatetype x)
{
	assert(ps);
	ListNode* cur = Create(x);
	ListNode *node = ps->tail ;
	
	//ps的头要指向cur
	node->next = cur;
	//cur的尾要指向ps
	cur->tail = node;

	//ps的尾要指向cur
	ps->tail = cur;
	//cur的头要指向ps
	cur->next = ps;
}

在这里插入图片描述

之所以要这样的指向,是因为这是一个循环的双向链表

🍍双向链表尾删

// 双向链表尾删
void LTpopbake(ListNode* ps)
{
	assert(ps);
	assert(ps->next != ps);
	ListNode* cur = ps->tail;
	ListNode* curtail = cur->tail;

	free(cur);
	cur->next = cur->tail = NULL;

	curtail->next = ps;
	ps->tail = curtail;

}

在这里插入图片描述
在这里因为这是循环链表所以不用遍历去找尾结点,当然用遍历也是可以的

🍍双向链表头插

// 双向链表头插
void LTpushfront(ListNode* ps, LTdatetype x)
{
	assert(ps);
	ListNode* node = Create(x);

	ListNode* cur = ps->next;

	ps->next = node;
	node->tail = ps;

	node->next = cur;
	cur->tail = node;

}

在这里插入图片描述
带头的链表中,头插是最方便的,不用像不带头的单链表中一个一个去判断,大大的节省了我们的时间

🍍双向链表头删

// 双向链表头删
void LTpopfront(ListNode* ps)
{
	assert(ps);
	assert(ps->next != ps);
	ListNode* node = ps->next;
	ListNode* node_next = node->next;

	free(node);

	ps->next = node_next;
	node_next->tail = ps;
}

在这里插入图片描述
带头的链表中,头删也很方便了。

🍍双向链表查找

// 双向链表查找
ListNode * LTfind(ListNode* ps, LTdatetype x)
{
	assert(ps);
	ListNode* cur = ps->next;
	while (cur != ps)
	{
		if (cur->date == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

关于这个查找还是要注意下,因为我们这里是循环链表,所以要注意记得要设置跳出循环的条件

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

// 双向链表在pos的前面进行插入
void LTinsert(ListNode *pos, LTdatetype x)
{
	assert(pos);
	ListNode* node = Create(x);
	ListNode* pos_tail = pos->tail;

	pos_tail->next = node;
	node->tail = pos_tail;

	node->next = pos;
	pos->tail = node;

}

在这里插入图片描述
在pos前插入和头插,尾插差不多的意思

🍍双向链表删除pos位置的节点

// 双向链表删除pos位置的节点
void LTdelete(ListNode* pos)
{
	assert(pos);
	ListNode* pos_tail = pos->tail;
	ListNode* pos_next = pos->next;

	free(pos);

	pos_tail->next = pos_next;
	pos_next->tail = pos_tail;
}

在这里插入图片描述

删除pos位置,也差不多和前面意思都差不多

🍌双向链表整体代码的实现

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

typedef int LTdatetype;


//创建返回链表的头结点
typedef struct ListNode
{
	LTdatetype date;
	struct ListNode* next;
	struct ListNode* tail;
}ListNode;

ListNode* Create(LTdatetype x)
{
	ListNode* cur = (ListNode*)malloc(sizeof(ListNode));
	if (cur == NULL)
	{
		perror("malloc  faild");
		exit(-1);
	}
	cur->next = NULL;
	cur->tail = NULL;
	cur->date = x;
	return cur;
}

ListNode* Inia()
{
	ListNode* node = Create(0);
	node->next = node;
	node->tail = node;

	return node;
}


// 双向链表销毁
void Destreoy(ListNode* ps)
{
	assert(ps);
	ListNode* cur = ps->next;
	while (cur != ps)
	{
		cur = cur->next;
		free(cur->tail);
	}
	free(ps);
}

// 双向链表打印
void LTprint(ListNode* ps)
{
	assert(ps);
	ListNode* cur = ps->next;
	printf("ps<=>");
	while (cur != ps)
	{
		printf("%d<=>", cur->date);
		cur = cur->next;
	}
	printf("\n");
}

// 双向链表尾插
void LTpushbake(ListNode* ps, LTdatetype x)
{
	assert(ps);
	ListNode* cur = Create(x);
	ListNode* node = ps->tail;

	node->next = cur;
	cur->tail = node;

	ps->tail = cur;
	cur->next = ps;
}

// 双向链表尾删
void LTpopbake(ListNode* ps)
{
	assert(ps);
	assert(ps->next != ps);
	ListNode* cur = ps->tail;
	ListNode* curtail = cur->tail;

	free(cur);

	curtail->next = ps;
	ps->tail = curtail;

}

// 双向链表头插
void LTpushfront(ListNode* ps, LTdatetype x)
{
	assert(ps);
	ListNode* node = Create(x);

	ListNode* cur = ps->next;

	ps->next = node;
	node->tail = ps;

	node->next = cur;
	cur->tail = node;

}


// 双向链表头删
void LTpopfront(ListNode* ps)
{
	assert(ps);
	assert(ps->next != ps);
	ListNode* node = ps->next;
	ListNode* node_next = node->next;

	free(node);

	ps->next = node_next;
	node_next->tail = ps;
}

// 双向链表查找
ListNode * LTfind(ListNode* ps, LTdatetype x)
{
	assert(ps);
	ListNode* cur = ps->next;
	while (cur != ps)
	{
		if (cur->date == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}


// 双向链表在pos的前面进行插入
void LTinsert(ListNode *pos, LTdatetype x)
{
	assert(pos);
	ListNode* node = Create(x);
	ListNode* pos_tail = pos->tail;

	pos_tail->next = node;
	node->tail = pos_tail;

	node->next = pos;
	pos->tail = node;

}

// 双向链表删除pos位置的节点
void LTdelete(ListNode* pos)
{
	assert(pos);
	ListNode* pos_tail = pos->tail;
	ListNode* pos_next = pos->next;

	free(pos);

	pos_tail->next = pos_next;
	pos_next->tail = pos_tail;
}


void test1()//测试
{
	ListNode* ps = Inia();
	LTpushbake(ps, 1);//尾插
	LTpushbake(ps, 2);//尾插
	LTpushbake(ps, 3);//尾插
	LTpushbake(ps, 4);//尾插
	LTprint(ps);//打印

	LTpopbake(ps);//尾删
	LTprint(ps);//打印

	LTpushfront(ps, 10);//头插
	LTpushfront(ps, 11);//头插
	LTpushfront(ps, 12);//头插
	LTpushfront(ps, 13);//头插
	LTprint(ps);//打印

	LTpopfront(ps);//头删
	LTprint(ps);//打印

	ListNode *pos = LTfind(ps, 11);//双向链表查找
	if (pos == NULL)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了,为:%d\n", pos->date);
	}

	LTinsert(pos, 100);// 双向链表在pos的前面进行插入
	LTprint(ps);//打印

	LTdelete(pos);//双向链表删除pos位置的节点
	LTprint(ps);//打印

	Destreoy(ps);//双向链表销毁
}

int main()
{
	test1();//测试
	return 0;
}

带头的双向链表实现起来还是非常简单的,大家如果对于文章中的某一个点有问题,欢迎私聊我。

请添加图片描述

  • 77
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 49
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 49
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悦心无谓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值