基于C语言的双向循环带头链表实现
Posted 胖仙人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于C语言的双向循环带头链表实现相关的知识,希望对你有一定的参考价值。
->Gitee源代码点击这里<-
结构说明:
①一个结点中有三部分
a.prev指针:存放前一个结点的指针
b.data:存放有效值
c.next指针:存放下一个结点的指针
②链表有一个头结点,其中不存放有效值,但存放了头结点的指针和尾结点的指针
③之所以成为循环,是因为尾结点的下一个结点不再为NULL,而是指向头结点
④当链表尾空时,头结点的prev部分和next部分都存放头结点自己的地址
根据结构描述创建结点的结构体
typedef int DLDataType;
typedef struct DoubleLinkedListNode
typedef struct DoubleLinkedListNode* prev;
DLDataType data;
typedef struct DoubleLinkedListNode* next;
DLNode;
插入数据就需要创建结点,所以第一步要设计创建结点的接口
void BuyDLNode(DLDataType data);
void BuyDLNode(DLDataType data)
DLNode* node = (DLNode*)malloc(sizeof(DLNode));
if (node == NULL)
printf("Buy new node failed\\n");
exit(-1);
node->prev = NULL;
node->next = NULL;
node->data = data;
return node;
链表未插入数据之前,头结点就已经存在,即头结点的存在就象征了双链表的存在,所以我们要对头结点进行初始化,并在main函数中实际创建一个头结点
DLNode* PheadInit();
DLNode* PheadInit()
DLNode* phead = BuyDLNode(-1);
phead->prev = phead;
phead->next = phead;
return phead;
int main()
DLNode* phead = PheadInit();
数据处理:
一、头插数据
插入时和单链表的原理类似,区别在于双链表头插时需要处理的指针关系更多
void DoubleLinkedPushFront(DLNode* phead, DLDataType data);
void DoubleLinkedPushFront(DLNode* phead,DLDataType data)
assert(phead);
DLNode* newnode = BuyDLNode(data);
DLNode* firstnode = phead->next; //找到原来的第一个结点
//处理newnode和firstnode之间的关系
newnode->next = firstnode;
firstnode->prev = newnode;
//处理phead和newnode之间的关系
phead->next = newnode;
newnode->prev = phead;
在设计插入数据的接口时,我们通常还需要考虑极端情况,如:头插入第一个结点时,上述代码是否适用
示意图:
当链表中无结点时,此时找第一个结点 first = phead->next
即为phead自己本身,通过示意图可以看到,上述代码对于极端情况也同样适用
二、尾插数据
由于双向和循环,双向循环链表在尾插时则不再需要通过遍历来找寻尾结点,phead->prev
即为尾结点的地址
尾插示意图:
void DoubleLinkedPushBack(DLNode* phead, DLDataType data);
void DoubleLinkedPushBack(DLNode* phead, DLDataType data)
DLNode* newnode = BuyDLNode(data);
DLNode* tail = phead->prev;
//处理tail和newnode之间的关系
tail->next = newnode;
newnode->prev = tail;
//处理phead和newnode之间的关系
phead->prev = newnode;
newnode->next = phead;
同样,也要对极端情况进行考虑,当链表中没有结点时,tail即phead自己本身
上述代码也同样适用
三、打印接口
打印接口可以帮助我们更直观的看到链表内的数据,方便接口测试
void DoubleLinkedPrint(DLNode* phead);
void DoubleLinkedPrint(DLNode* phead)
DLNode* cur = phead->next;
printf("phead->");
while (cur != phead)
printf("%d->", cur->data);
cur = cur->next;
printf("phead\\n");
四、头删数据
void DoubldeLinkedPopFront(DLNode* phead);
void DoubldeLinkedPopFront(DLNode* phead)
assert(phead);
assert(phead->next != phead);
DLNode* first = phead->next;
phead->next = first->next;
first->next->prev = phead;
free(first);
first = NULL;
五、尾删数据
void DoubleLinkedPopBack(DLNode* phead);
void DoubleLinkedPopBack(DLNode* phead)
assert(phead);
assert(phead->prev != phead);
DLNode* tail = phead->prev;
DLNode* newtail = tail->prev;
phead->prev = newtail;
newtail->next = phead;
free(tail);
tail = NULL;
六、在任意位置 **_pos _**前插入数据
由于双向链表结点的双指向性,我们可以设计单链表不方便实现的接口。而由于链表不能随机访问,所以这里仍然要配合查找接口的使用来获取我们想要的 pos
查找接口:
找到返回pos,否则返回NULL
DLNode* DoubleLinkedFind(DLNode* phead,DLDataType data);
DLNode* DoubleLinkedFind(DLNode* phead, DLDataType data)
DLNode* cur = phead->next;
while (cur != phead)
if (cur->data == data)
return cur;
else
cur = cur->next;
return NULL;
插入接口:
void DoubleLinkedInsert(DLNode* pos, DLDataType data);
void DoubleLinkedInsert(DLNode* pos, DLDataType data)
DLNode* prev = pos->prev;
DLNode* newnode = BuyDLNode(data);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
有了此接口,我们还可以通过对该接口的复用来实现对头插和尾插接口的简化
头插
void DoubleLinkedPushFront(DLNode* phead,DLDataType data)
DoubleLinkedInsert(phead->next,data);
//尾插
//说明:phead的前一个结点就是尾结点
void DoubleLinkedPushBack(DLNode* phead,DLDataType data)
DoubleLinkedInsert(phead,data);
七、删除 pos 位置的结点
void DoubleLinkedDelete(DLNode* pos);
void DoubleLinkedDelete(DLNode* pos)
DLNode* prev = pos->prev;
DLNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
同样,我们也可以借助该接口实现对头删和尾删的简化
void DoubldeLinkedPopFront(DLNode* phead)
assert(phead->next!=phead); //调用和删除接口结点内必须有结点
DoubleLinkedDelete(phead->next);
void DoubldeLinkedPopBack(DLNode* phead)
assert(phead->prev!=phead); //调用和删除接口结点内必须有结点
DoubleLinkedDelete(phead->prev);
八、链表的销毁
由于结点是动态开辟,所以双链表使用结束时,需要释放空间
这里需要注意,phead需要调用者手动指控;想要改变phead的值,实际上时需要传二级指针作为参数,但是为了保证接口参数的一致性,所以选择传一级指针
void DoubleLinkedListDestroy(DLNode* phead);
void DoubleLinkedListDestroy(DLNode* phead)
DLNode* cur = phead->next;
while (cur != phead)
DLNode* next = cur->next;
free(cur);
cur = next;
free(phead);
以上是关于基于C语言的双向循环带头链表实现的主要内容,如果未能解决你的问题,请参考以下文章
数据结构C语言版 —— 链表增删改查实现(单链表+循环双向链表)