为了解决单链表的缺点,我们可以改进一下单链表,使其变的可以随机访问,并且使增删查改更加简单逻辑清晰。
为了使代码逻辑更加清晰,我使用了哨兵位头节点:意思就是当链表为空的时候,也有一个节点存在,它不存储有效数据,这样做的好处是:只需要传一级指针就可以直接改变头节点(哨兵位节点)的 next 和 pre 的值进而改变链表。
根据图片,可以知道,双向循环链表的一大特点就是链表既可以向前找也可以向后找,循环意味着没有结束,这意味着如果遍历链表的指针与phead(哨兵位头节点)相等时链表结束(遍历指针为phead->next)。
看着比较复杂,其实代码逻辑比较简单。
先拿尾插函数和打印函数示范:
struct Seqlist
{
struct Seqlist* next;
struct Seqlist* pre;
int val;
};
void Seqlist_init(struct Seqlist** plist) {
*plist = malloc(sizeof(struct Seqlist));
(*plist)->next = *plist;
(*plist)->pre = *plist;
}
void SeqlistPushback(struct Seqlist* plist,int num) {
struct Seqlist* new_node = malloc(sizeof(struct Seqlist));
struct Seqlist* tail = plist->pre;
new_node->val = num;
plist->pre = new_node;
tail->next = new_node;
new_node->next = plist;
new_node->pre = tail;
}
Seqlist是创建的结构体,其中有指向前后的指针和存储的整数。
Seqlist_init是为了创建哨兵位头节点,因为是双向循环,所以前后指针都指向自己。
SeqlistPushback是尾插函数,首先创建一个新节点,然后找到尾(即头的前一个plist->pre),给新节点的 val 赋值,最后四行代码,前两行先让 tail 和 phead 都指向 new_node,然后再让new_node 都指向 tail 和 phead 。
如果链表为空,同样适用,这时 phead 同时也是 tail ,前两行让 phead 的 next 和 pre 都指向 new_node ,然后让 new_node 的 next 和 pre 都指向 phead 。
void SeqlistPrint(struct Seqlist* plist) {
struct Seqlist* cur = plist->next;
while (cur != plist) {
printf("%d ", cur->val);
cur = cur->next;
}
}
打印函数也比较简单,让cur指针指向哨兵位头节点的下一个节点,cur=cur->next,并打印链表。如果cur等于phead,就说明已经遍历了一遍链表,退出函数。
那如果我们想随意位置的增加或删除节点呢,逻辑同样比较简单,首先我们要写一个查找链表节点返回地址的查找函数:
struct Seqlist* FindPositon(struct Seqlist* phead,int num) {
struct Seqlist* cur = phead->next;
while (cur!=phead)
{
if (cur->val == num) {
return cur;
}
cur = cur->next;
}
return -1;
}
在这个函数中,当要找的 num 等于 cur->val 时返回 cur 的地址,然后我们需要一个删除函数:
void SeqlistCancel(struct Seqlist* pos) {
struct Seqlist* pos_pre = pos->pre;
struct Seqlist* pos_next = pos->next;
pos_pre->next = pos_next;
pos_next->pre = pos_pre;
free(pos);
}
逻辑非常简单,就是把这个位置的前后链接起来最后 free 释放掉 pos。
还有内存销毁函数:
void SeqlistFree(struct Seqlist* plist) {
struct Seqlist* cur = plist->next;
while (cur != plist) {
cur = cur->next;
free(cur->pre);
}
free(plist);
}
最后展示一下结果:
这就是文章的全部内容了,希望对你有所帮助,如有错误欢迎评论。