数据结构——双链表(八卦掌)下部(附源码)

目录

前言

一、为什么学带头双向循环链表?        

二、带头双向循环链表

1.定义

2.八卦掌下式 

2.1走桩

2.1.1:扩容

2.2趟泥步

2.2.1:初始化

2.3下塌掌

2.3.1:尾插

2.4单换掌

2.4.1:头插

2.5拖天掌

2.5.1:在指定位置插入

2.6抱月掌

2.6.1:尾删,头删,在指定位置删除 

2.7指天划地掌

2.7.1:打印 

  2.7.2:查找

总结  


前言

         今天学习八卦掌的下篇。今天学习带头双向链表循环。

   如八卦掌招数有问题,或可以优化,望各位大侠进行斧正。(ง •̀_•́)ง(ง •̀_•́)ง(ง •̀_•́)ง


提示:以下是本篇文章正文内容,下面案例可供参考

一、为什么学带头双向循环链表?        

        链表的样式多种多样。有很多种,而带头双向链表结构最复杂。当你学过高数再去看初中数学是不是相当简单。当我们学习了复杂的链表结构,并且进行了实现,当遇见其他的链表直接就可以实现了。并且有了单链表作为基础很快便能学会。

二、带头双向循环链表

1.定义

        下面就是带头双向循环链表的图片, 其中的head表示哨兵位的头节点。

        特点:第一个车厢的头指向最后一个车厢的尾,而最后一个车厢的尾指向第一个车厢的头,前后两个车厢相互指向。

        head作为哨兵位的头节点,是链表前起始结点,不储存任何数据。只是作为链表的开始。以此作为描点,可以通过哨兵位的头结点,来寻找到这个链表。

        这个双向循环链表的基本结构。我们通过上面的图可以看出,结构体具有两个指针,一个指向后面的结构体。一个指向前面的结构体。

typedef int d_l_type;

typedef struct d_l_l_node
{
	struct d_l_l_node* front;	//指向前面的节点
	struct d_l_l_node* behind;	//指向后面的节点
	d_l_type data;			//数据的类型
}d_l_node;

2.八卦掌下式 

2.1走桩

2.1.1:扩容

         就如同上部的单链表一般,在链表在进行增添数据的时候,需要增加一个结构体(车厢),也就是扩容

d_l_node* exp(d_l_type x)	// 扩容
{
	d_l_node* new = (d_l_node*)malloc(sizeof(d_l_node));
	if (new == NULL)
	{
		perror("malloc file");
		return NULL;
	}
	new->behind = NULL;
	new->front = NULL;
	new->data = x;
}

2.2趟泥步

2.2.1:初始化

        带头双向循环链表,作为有头结点的链表,和单链表不同,它具有一个哨兵位,所以我们需要对带头双向循环链表进行初始化,也就是对其进行初始化。在这里,我们定义一个函数使他的数据为零,让它成为哨兵位的头结点

d_l_node* l_init() //双向带头循环 初始化
{
	d_l_node* p = exp(0);
	p->front = p; 
	p->behind = p;
	return p;
}

2.3下塌掌

2.3.1:尾插

         先实现第一个尾插,在数据的最后一个位置插入,如下图所示。基于此来实现代码。这里有一个非常重要的点,我们传进来的参数的首地址是哨兵位的头结点,而这个首地址的头指向的是最后一个车厢的位置,所以我们可以根据首地址的头指针找到最后一个车厢,而不是像单链表意义靠循环去找位置。在数据量很大的时候节约的大量的时间。之后将最后一个车厢的尾巴指向新车厢,新车厢的头指向原先最后一个的车厢。新车厢的尾巴指向哨兵位,哨兵位的头指向新车厢。

void node_push_back(d_l_node* p, d_l_type x)	//尾插
{
	assert(p);
	d_l_node* d_new = exp(x);	// 扩容
	d_l_node* text = p->front; // 这个的意义
	text->behind = d_new;	// 尾巴指向后一个
	d_new->front = text;	// 后一个的头指向前一个
	p->front = d_new;	// 哨兵位的头指向最后一个
	d_new->behind = p;	// 最后一个的尾巴指向哨兵位的头
}

2.4单换掌

2.4.1:头插

        下图就是头插,直接就可以写代码了,这个比尾插还要简单。我们要明白一点,那就是哨兵位的位置是不变的,头插指的是在哨兵位后面进行插入。 

void node_push_front(d_l_node* p, d_l_type x)	//头插
{
	assert(p);
	d_l_node* d_new = exp(x);
	d_l_node* text = p->behind;	// 下一个数据的位置
	p->behind = d_new;	// 哨兵位的尾巴指向新车厢
	d_new->front = p;	//新车厢的头指向哨兵位
	d_new->behind = text;	// 新车厢的位指向d1
	text->front = d_new;	// di的头指向新车厢
}

2.5拖天掌

2.5.1:在指定位置插入

         在任意位置插入,我们根据pos的头结点得到pos位置。将新车厢放进pos的位置。

void node_sert(d_l_node* pos, d_l_type x)	// 在pos位置插入
{
	d_l_node* new = exp(x);
	d_l_node* text = pos->front;	//得到pos前面的位置
	text->behind = new;
	new->front = text;
	new->behind = pos;
	pos->front = new;
}

2.6抱月掌

2.6.1:尾删,头删,在指定位置删除 

        下一步便是删除。 我这里将头删,尾删,在指定位置删除三个删除融合到一起了。但在进行头/尾删的时候我们需要判断当只有哨兵位情况。因为哨兵位是没有数据的。所以我们要写一个判断哨兵位的子函数,之后将它放在头/尾删里面。在指定位置插入就不用判断了,因为它能在指定位置插入说明它是有数据的。

   

尾删

 

头删

 

 在指定位置删除

bool d_empty(d_l_node* p)
{
	assert(p);
	if (p->behind == p)
	{
		return NULL;
	}
}

void node_out_back(d_l_node* p)	//尾删
{
	assert(p);
	assert(d_empty(p));
	d_l_node* text = p->front;
	text->front->behind = p;
	p->front = text->front;
	free(text);
	text = NULL;
}

void node_out_front(d_l_node* p)	//头删
{
	assert(p);
	assert(d_empty(p));
	d_l_node* text = p->behind;
	p->behind = text->behind;
	text->behind->front = p;
	free(text);
	text = NULL;
}

void node_sert(d_l_node* pos, d_l_type x)	//在pos位置删除
{
	d_l_node* text = pos->front;
	text->behind = pos->behind;
	pos->behind->front = text;
	free(pos);
	pos = NULL;
}

 

2.7指天划地掌

2.7.1:打印 

        我们要验证链表,怎么验证,当然要对链表进行打印了。

void node_printf(d_l_node* p)	//打印
{
	assert(p);
	printf("<=head=>");
	d_l_node* print = p->behind;
	while (print != p)
	{
		printf("%d<=>", print->data);
		print = print->behind;
	}
	printf("\n");
}
  2.7.2:查找

       在进行查找的时候我们要判断两种情况,一种是链表里面有这个数据,一种是链表里面没有这个数据。同时查找还可以和在pos位置插入进行联动。

void* node_find(d_l_node* p, d_l_type x)	//查找
{
	assert(p);	//断言
	d_l_node* cur = p->behind;	//哨兵位的下一个位置
	while (cur != p)	//当cur为哨兵位的时候,遍历了一遍结构体
	{
		if (cur->data == x)	// 找到了
		{
			return cur;
		}
		cur = cur->behind;	
	}
	return NULL;	//没有找到
}

总结  

  1. 在进行插入和删除操作时,要考虑到边界情况和异常情况的处理。
  2. 在使用链表时,要注意避免指针的野指针和内存泄漏等问题

        至此八卦掌的掌法就此完结。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值