目录
1.带头双向循环链表的特点
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势
2.带头双向循环链表的结构和结构体定义
我们用两个指针prev 和 next 分别指向结点的前一个结点和下一个结点
(头结点的front指向链表的最后一个结点)
(链表的最后一个结点的next指向头结点)
typedef int Datetype;
typedef struct DoubleList
{
Datetype Data;
struct DoubleList* prev;
struct DoubleList* next;
}DL;
3.各接口函数实现
3.1 创建一个新的带头双向循环链表的结点
DL* Buynewnode(Datetype x);
跟创建普通链表结点一样,只不过多了一个prev结构体指针
DL* Buynewnode(Datetype x)
{
DL* newnode = (DL*)malloc(sizeof(DL));
if (newnode == NULL)
{
perror("malloc file\n");
exit(-1);
}
newnode->Data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
3.2 创建返回链表的头结点
DL* DoubleListInit();
因为需要链表带一个头结点(头结点不存放任何数据,这里默认放一个-1)
就需要先创建一个单头结点,并返回头结点地址
DL* DoubleListInit()
{
DL*plist = Buynewnode(-1);
plist->next = plist;
plist->prev = plist;
return plist;
}
3.3 带头双向循环链表尾插和尾删
尾插:
void DoubleListPushBack(DL* plist, Datetype x);
只需要将尾结点的next指向新结点,新结点的prev指向尾结点
头结点的prev指向新结点,新结点的next指向头结点
这样新结点就成为尾结点了
void DoubleListPushBack(DL* plist, Datetype x)
{
assert(plist);
DL* newnode = Buynewnode(x);
DL* tail = plist->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = plist;
plist->prev = newnode;
//DoubleListInsert(plist, x); DoubleListInsert函数的尾插复用
}
尾删:
void DoubleListPopBack(DL* plist);
需要判断链表中至少有一个除头结点之外的有效数据结点
尾删只需要把尾结点的前一个结点的next指向头结点头结点的prev指向尾结点的前一个结点
void DoubleListPopBack(DL* plist)
{
assert(plist);
assert(plist->next != plist);//判空,如果链表中只有一个哨兵位,free(tail) 会把哨兵位释放掉
DL* tail = plist->prev;
tail->prev->next = plist;
plist->prev = tail->prev;
free(tail);
//DoubleListErase(plist->prev); DoubleListErase函数的尾删复用
}
3.4 带头双向循环链表头插和头删
头插:
void DoubleListPushFront(DL* plist, Datetype x);
头插是将新结点插在头结点和头结点的下一个结点之间
头插需要先动新结点的prev和next,使其指向头结点和头结点的下一个结点
然后再将头结点的下一个结点的prev指向新结点,头结点的next指向新结点
void DoubleListPushFront(DL* plist, Datetype x)
{
assert(plist);
DL* newnode = Buynewnode(x);
newnode->prev = plist;
newnode->next = plist->next;
plist->next->prev = newnode;
plist->next = newnode;
//DoubleListInsert(plist->next, x); DoubleListInsert函数的头插复用
}
头删:
void DoubleListPopFront(DL* plist);
需要判断链表中至少有一个除头结点之外的有效数据结点
将头结点的next指向第二个有效数据结点再将第二个有效数据结点的prev指向头结点
void DoubleListPopFront(DL* plist)
{
assert(plist);
assert(plist->next);
DL* p = plist->next;
plist->next = p->next;
p->next->prev = plist;
free(p);
//DoubleListErase(plist->next); DoubleListErase函数的头删
}
3.5 带头双向循环链表打印
void DoubleListPrint(DL* plist);
循环需要从头结点之后的第一个结点cur开始,不能从头结点开始
判断当cur不为头结点时,打印,为头结点时则证明循环已循环一圈
void DoubleListPrint(DL* plist)
{
assert(plist);
DL* cur = plist->next;
while (cur != plist)
{
printf("%d ",cur->Data);
cur = cur->next;
}
printf("\n");
}
3.6 带头双向循环链表查找
DL* DoubleListFind(DL* plist,Datetype x);
跟打印一样遍历,遇到x就返回结点地址 ,没有找到就返回NULL
DL* DoubleListFind(DL* plist,Datetype x)
{
assert(plist);
DL* cur = plist->next;
while (cur != plist)
{
if (cur->Data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
3.7 带头双向循环链表在pos的前面进行插入
void DoubleListInsert(DL* pos, Datetype x);
可以复用头插,尾插函数
void DoubleListInsert(DL* pos, Datetype x)
{
assert(pos);
DL* prev = pos->prev;
DL* newnode = Buynewnode(x);
newnode->prev = prev;
newnode->next = pos;
prev->next = newnode;
pos->prev = newnode;
}
3.8 带头双向循环链表删除pos位置的节点
void DoubleListErase(DL* pos);
可以复用尾删,头删函数
//注:传pos不能为头 C++中能更好的解决
//
// 带头双向循环链表删除pos位置的节点
void DoubleListErase(DL* pos)
{
assert(pos);
DL* prev = pos->prev;
DL* next = pos->next;
free(pos);
prev->next = next;
next->prev = prev;
}
3.9 判空
bool DoubleListEmpty(DL* plist);
bool DoubleListEmpty(DL* plist)
{
assert(plist);
//if (plist->next == plist)
// return true;
//else
// return false;
return plist->next == plist;
}
3.10 带头双向循环链表销毁
void DoubleListDestroy(DL* plist);
void DoubleListDestroy(DL* plist)
{
assert(plist);
DL* cur = plist->next;
while (cur != plist)
{
DL* next = cur->next;
free(cur);
cur = next;
}
free(plist);
}