目录
前言
今天学习八卦掌的下篇。今天学习带头双向链表循环。
如八卦掌招数有问题,或可以优化,望各位大侠进行斧正。(ง •̀_•́)ง(ง •̀_•́)ง(ง •̀_•́)ง
提示:以下是本篇文章正文内容,下面案例可供参考
一、为什么学带头双向循环链表?
链表的样式多种多样。有很多种,而带头双向链表结构最复杂。当你学过高数再去看初中数学是不是相当简单。当我们学习了复杂的链表结构,并且进行了实现,当遇见其他的链表直接就可以实现了。并且有了单链表作为基础很快便能学会。
二、带头双向循环链表
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; //没有找到
}
总结
- 在进行插入和删除操作时,要考虑到边界情况和异常情况的处理。
- 在使用链表时,要注意避免指针的野指针和内存泄漏等问题
至此八卦掌的掌法就此完结。