上一篇写了结构最简单的一个单链表,本篇实现最复杂的一个链表结构:带头双向循环链表,虽然它结构最复杂,但实现起来我感觉却比单链表简单。此外,此链表的头只是一个指针,不存储有效数据
带头双向链表对比顺序表的优缺点
优点:
1,头尾的插入删除很方便
2,开辟新的空间不会有空间浪费
缺点:
1,不能够随机访问
2,存储一个值还要存储链接指针
头文件
#pragma once
#include<assert.h>
#include<stdlib.h>
#include<stdio.h>
typedef int LTNodeDataType;
typedef struct ListNode
{
LTNodeDataType data;
struct ListNode* prev;
struct ListNode* next;
}LTNode;
LTNode* ListNodeInit();
void ListPrint(LTNode* phead);
LTNode* ListNodeFind(LTNode* phead, LTNodeDataType x);
LTNode* BuyListNode(LTNodeDataType x);
void LTNodePushBack(LTNode* phead,LTNodeDataType x);
void LTNodePushFront(LTNode* phead,LTNodeDataType x);
void LTNodePopFront(LTNode* phead);
void LTNodePopBack(LTNode* phead);
void LTNodeInsert(LTNode* pos,LTNodeDataType x);
void LTNodeErase(LTNode* pos);
void LTNodeDestory(LTNode* phead);
链表初始化
LTNode* ListNodeInit()
{
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
phead->prev = phead;
phead->next = phead;
return phead;
}
这里选择用传返回值来定义头指针,当然还可以用传指针的方式来定义
创造新结点
LTNode* BuyListNode(LTNodeDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
newnode->data = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
应为要频繁扩容,所以单独写一个函数来进行复用
打印
void ListPrint(LTNode* phead)
{
LTNode* cur = phead->next;
while (cur!=phead)//循环链表
{
printf("%d ", cur->data);
cur = cur->next;
}
}
注意头指针的下一个位置才有值,头指针不包含有效值
尾加
void LTNodePushBack(LTNode* phead,LTNodeDataType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
printf("malloc fail");
exit(-1);
}
newnode->data = x;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
尾删
void LTNodePopBack(LTNode* phead)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* tailprev = tail->prev;
free(tail);
tailprev->next = phead;
phead->prev = tailprev;
}
要注意节点释放的位置,如果free在前面就会造成空指针的问题,所以最好把前一个或后一个的数据额外定义一下,这样free了也不会造成相互找不到值的问题
头加
void LTNodePushFront(LTNode* phead, LTNodeDataType x)
{
assert(phead);
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
printf("malloc fail");
exit(-1);
}
newnode->data = x;
LTNode* next=phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;
}
头删
void LTNodePopFront(LTNode* phead)
{
assert(phead);
LTNode* next = phead->next;
LTNode* nextNext = next->next;
phead->next = nextNext;
nextNext->prev = phead;
free(next);
}
查找
LTNode* ListNodeFind(LTNode* phead, LTNodeDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur!=phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
插入
void LTNodeInsert(LTNode* pos,LTNodeDataType x)
{
assert(pos);
LTNode* posprev = pos->prev;
LTNode* newnode =BuyListNode(x);
posprev->next = newnode;
newnode->prev = posprev;
newnode->next = pos;
pos->prev = newnode;
}
删除
void LTNodeErase(LTNode* pos)
{
assert(pos);
LTNode* Posprev = pos->prev;
LTNode* Posnext = pos->next;
Posprev->next = Posnext;
Posnext->prev = Posprev;
free(pos);
pos = NULL;
}
销毁
void LTNodeDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur!=phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
当然链表头尾的增删可以用插入和删除来复用,可以在短时间内就可以写出一个链表
尾加
LTNodeInsert(phead, x);//不是phead-》prev
一开始误认为尾是phead->prev,其实phead就是尾
尾删
LTNodeErase(phead->prev);
头加
LTNodeInsert(phead->next,x);
头删
LTNodeErase(phead->next);
本文介绍了如何实现带头双向循环链表,包括节点结构、初始化、操作方法(如添加、删除、查找)以及其相对于顺序表的优缺点。重点强调了头尾插入删除的便捷性和空间效率,但提到了随机访问的限制和额外存储链接指针的代价。
127

被折叠的 条评论
为什么被折叠?



