前面我们写过了不带头结点的单链表,由于没有头结点,在对单链表进行头插/删的时候需要传入二级指针;在需要进行尾插/删的时候,需要先根据头指针找到头结点,然后从头往后遍历找到最后一个结点再进行相应操作。而我们今天要写的双向循环带头结点链表,相对于不带头结点的单链表做增删时,将会方便许多。
(1)可以通过头结点找到链表首尾
(2)在链表中间做增删时,不需要从头遍历
(3)有头结点, 对带头结点的链表,在表的任何结点之前插入结点或删除表中任何结点,所要做的都是修改前一结点的指针域,因为任何元素结点都有前驱结点。若链表没有头结点,则首元素结点没有前驱结点,在其前插入结点或删除该结点时操作会复杂些,就像前面写的不带头结点的单链表,需要传入二级指针。另外由于头结点的数据域空闲,可以存放一些关于链表的属性信息。
双向循环带头结点链表的存储结构
typedef int DataType;
typedef struct Node
{
struct Node *next;
struct Node *prev;
DataType data;
}Node;
创建/初始化/销毁/打印
- 创建结点
Node *CreateNode(DataType data)
{
Node *newNode = (Node *)malloc(sizeof(Node));
assert(newNode);
newNode->data = data;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}
- 链表的初始化
void LinkListInit(Node *HNode)
{
assert(HNode);
HNode->data = 0;
HNode->next = HNode;
HNode->prev = HNode;
}
- 链表的打印
因为是带头结点的链表,所以应该从第二个结点开始打印
void print(const Node *HNode)
{
Node *cur = HNode->next;
printf("HNode--->");
for (; cur != HNode; cur = cur->next)
{
printf("%d", cur->data);
if (cur->next != HNode)
{
printf("--->");
}
}
printf("--->HNode");
printf("\n");
}
- 链表的销毁
先将链表清空,链表清空后仍存在一个头结点,然后再把头结点释放
void clear(Node *HNode)
{
Node *cur = HNode->next;
Node *next = NULL;
while (cur != HNode)
{
next = cur->next;
free(cur);
cur->next = NULL;
cur->prev = NULL;
next->prev = HNode;
HNode->next = next;
cur = next;
}
}
void destroy(Node *HNode)
{
clear(HNode);
free(HNode);
HNode->next = NULL;
HNode->prev = NULL;
}
其实双向循环带头结点链表的插入操作都是如出一辙的,都是仅需要更改插入/删除结点的next&prev指针指向,以及前一个结点的next和后一个结点的prev
下面以头删为例进行说明:
①
newNode->next
②
newNode->prev
③
HNode->next
④
node1->prev
而双向循环带头结点链表的删除操作比插入更加简单,只需要变更前一个结点的next和后一个结点的prev,然后释放掉该结点就OK
插入结点
//头插
void PushFront(Node *HNode, DataType data)
{
Node *node = CreateNode(data);
node->prev = HNode;
node->next = HNode->next;
HNode->next->prev = node;
HNode->next = node;
}
//尾插
void PushBack(Node *HNode, DataType data)
{
Node *node = CreateNode(data);
node->next = HNode;
node->prev = HNode->prev;
HNode->prev->next = node;
HNode->prev = node;
}
//任意位置前插入,假设pos在链表中
void Insert(Node *pos, DataType data)
{
Node *node = CreateNode(data);
node->next = pos;
node->prev = pos->prev;
pos->prev->next = node;
pos->prev = node;
}
而因为双向循环带头结点链表操作的便利性,可以将头插/尾插通过Insert来实现
void PushFront_Simplify(Node *HNode, DataType data)
{
Insert(HNode->next, data);
}
void PushBack_Simplify(Node *HNode, DataType data)
{
Insert(HNode, data);
}
测试展示:
删除节点
//头删
void PopFront(Node *HNode)
{
Node *front = HNode->next;
if (HNode->next == HNode)
{
printf("链表为空,删除失败\n");
return;
}
HNode->next = front->next;
front->next->prev = HNode;
free(front);
front->next = NULL;
front->prev = NULL;
}
//尾删
void PopBack(Node *HNode)
{
Node *back = HNode->prev;
if (HNode->next == HNode)
{
printf("链表为空,删除失败\n");
return;
}
back->prev->next = HNode;
HNode->prev = back->prev;
free(back);
back->next = NULL;
back->prev = NULL;
}
//删除任意位置,假设pos在链表中
void Erase(Node *HNode, Node *pos)
{
if (HNode->next == HNode)
{
printf("链表为空,删除失败\n");
return;
}
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos->next = NULL;
pos->prev = NULL;
}
同样,可以用Erase实现头删/尾删
void PopFront_Simplify(Node *HNode)
{
Erase(HNode, HNode->next);
}
void PopBack_Simplify(Node *HNode)
{
Erase(HNode, HNode->prev);
}
测试展示: