【C语言简单实现数据结构】带头循环双向链表
心有所想,日复一日,必有精进
前言
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。
接下来,在实现过程中就会体会到,如果少一个条件实现就会麻烦不少,但这些条件正好实现起来是非常巧妙。
之前已经实现了单链表,具体原理差不多,就不一一赘述:
双向链表接口
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
typedef int ListDataType;
typedef struct ListNode{
ListDataType data;
struct ListNode* prev;
struct ListNode* next;
}ListNode;
//初始化
ListNode* ListInit(void);
//打印
void ListPrint(ListNode* phead);
//头插
void ListPushFront(ListNode* phead, ListDataType x);
//尾插
void ListPushBack(ListNode* phead, ListDataType x);
//尾删
void ListPopBack(ListNode* phead);
//头删
void ListPopFront(ListNode* phead);
//插入pos结点
void ListInsert(ListNode* pos, ListDataType x);
//删除pos结点
void ListErase(ListNode* pos);
//销毁
void ListDestory(ListNode* phead);
具体实现
开辟新结点
//开辟新结点
ListNode* newNode(ListDataType x){
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
if ( newNode == NULL){
perror("malloc fail");
exit(-1);
}
newNode->next = NULL;
newNode->prev = NULL;
newNode->data = x;
return newNode;
}
初始化
//初始化
ListNode* ListInit(void){
ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
phead->next = phead;
phead->prev = phead;
return phead;
}
打印
//打印
void ListPrint(ListNode* phead){
assert(phead);
ListNode* cur = phead->next;
printf("guard<=>");
while (cur != phead){
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
头插
//头插
void ListPushFront(ListNode* phead, ListDataType x){
/*assert(phead);
ListNode* newnode = newNode(x);
ListNode* cur = phead->next;
phead->next = newnode;
newnode->next = cur;
newnode->prev = phead;
cur->prev = newnode;*/
ListInsert(phead->next,x);
}
尾插
//尾插
void ListPushBack(ListNode* phead, ListDataType x){
/*assert(phead);
ListNode* newnode = newNode(x);
ListNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;*/
ListInsert(phead, x);
}
尾删
//尾删
void ListPopBack(ListNode* phead){
/*assert(phead);
ListNode* tail = phead->prev;
phead->prev = tail->prev;
tail->prev->next = phead;
free(tail);
tail = NULL;*/
ListErase(phead->prev);
}
头删
//头删
void ListPopFront(ListNode* phead){
/*assert(phead);
ListNode* cur = phead->next;
phead->next = cur->next;
cur->next->prev = phead;
free(cur);*/
ListErase(phead->next);
}
查找
//查找
void ListFind(ListNode* phead ,ListDataType x){
ListNode* cur = phead->next;
while (cur != phead){
if (cur->data == x)
return cur;
cur = cur->next;
}
return -1;
}
在pos结点之前插入结点
//在pos结点之前插入结点
void ListInsert(ListNode* pos, ListDataType x){
ListNode * newnode = newNode(x);
ListNode *cur = pos->prev;
cur->next = newnode;
newnode->prev = cur;
newnode->next = pos;
pos->prev = newnode;
}
删除pos结点
//删除pos结点
void ListErase(ListNode* pos){
ListNode *pre = pos->prev;
ListNode *cur = pos->next;
pre ->next = cur;
cur->prev = pre;
free(pos);
}
判空/求链表长度
bool ListEmpty(ListNode* phead)
{
assert(phead);
/*if (phead->next == phead)
return true;
else
return false;*/
return phead->next == phead;
}
size_t ListSize(ListNode* phead)
{
assert(phead);
size_t n = 0;
ListNode* cur = phead->next;
while (cur != phead)
{
++n;
cur = cur->next;
}
return n;
}
销毁
//销毁
// 可以传二级,内部置空头结点
// 建议:也可以考虑用一级指针,让调用ListDestory的人置空 (保持接口一致性)
void ListDestory(ListNode* phead){
ListNode* cur = phead->data;
while (cur != phead){
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
总结
带头双向循环链表,已经解决了大多数顺序表的缺点,我们对顺序表对链表进行对比:
不同点 | 顺序表 | 链表 |
---|---|---|
存储空间 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持O(1) | 不支持:O(N) |
任意位置插入或者删除元素 | 可能需要搬移元素,效率低O(N) | 只需修改指针指向 |
插入 | 动态顺序表,空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |
缓存利用率 | 高 | 低 |
备注:缓存利用率参考存储体系结构 以及 局部原理性
全部代码放在了Gitee这个链接上了,有需要的可以当作参考