目录
一、概念
双向带头循环链表是具有哨兵位头节点的链表,并且它的每个结点中都有两个指针,分别指向直接后继和直接前驱。
二、结构
typedef int DoubleListDataType;
//带头双向循环链表
typedef struct DoubleListNode
{
struct DoubleListNode* previous; //指向直接前趋的指针域
DoubleListDataType data; //数据域
struct DoubleListNode* next; //指向直接后继的指针域
}DoubleListNode;
三、双向循环链表的接口
1、创建节点
//创建节点
DoubleListNode* BuyDoubleListNode(DoubleListDataType x)
{
//动态开辟节点空间
DoubleListNode* newnode = (DoubleListNode*)malloc(sizeof(DoubleListNode));
//判断动态开辟空间是否成功
assert(newnode);
//节点的初始化
newnode->previous = NULL;
newnode->data = x;
newnode->next = NULL;
return newnode;
}
2、初始化
//初始化--头结点的创建
DoubleListNode* DoubleListInit()
{
//创建头节点
DoubleListNode* phead = BuyDoubleListNode(-1);
//头节点初始化
phead->previous = phead;
phead->next = phead;
return phead;
}
3、打印链表
//打印链表
void DoubleListPrint(DoubleListNode* phead)
{
//链表不为空
assert(phead);
DoubleListNode* current = phead->next; //当前节点
while (current!=phead)
{
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
4、链表的尾插
//尾插
void DoubleListPushBack(DoubleListNode* phead, DoubleListDataType x)
{
//链表不为空
assert(phead);
//创建新节点
doublelistnode* newnode = buydoublelistnode(x);
//原链表的尾节点
doublelistnode* endnode = phead->previous;
//修改新节点的指向前趋的指针域(指向原尾节点)
newnode->previous = endnode;
//修改新节点的指向后继的指针域(指向头节点)
newnode->next = phead;
//修改原尾节点的指向后继的指针域(指向新节点)
endnode->next = newnode;
//修改头节点的指向前趋的指针域(指向新节点)
phead->previous = newnode;
}
5、链表的尾删
//尾删
void DoubleListPopBack(DoubleListNode* phead)
{
//链表不为空
assert(phead);
//链表不能只包含头节点
assert(phead->next != phead);
//尾节点
DoubleListNode* EndNode = phead->previous;
//倒数第二个节点
DoubleListNode* PrevEndNode = EndNode->previous;
//修改倒数第二个节点的指向后继的指针域(指向头节点)
PrevEndNode->next = phead;
//修改头节点的指向前趋的指针域(指向新的尾节点)
phead->previous = PrevEndNode;
//释放原来的尾节点
free(EndNode);
EndNode = NULL;
}
6、链表的头插
//头插
void DoubleListPushFront(DoubleListNode* phead, DoubleListDataType x)
{
//链表不为空
assert(phead);
//创建新节点
DoubleListNode* NewNode = BuyDoubleListNode(x);
//第二个节点(头节点的下一个节点)
DoubleListNode* SecondNode = phead->next;
//修改新节点的两个指针域
NewNode->previous = phead;
NewNode->next = SecondNode;
//修改第二个节点的指向前趋的指针域
SecondNode->previous = NewNode;
//修改头节点的指向后继的指针域
phead->next = NewNode;
}
7、链表的头删
//头删
void DoubleListPopFront(DoubleListNode* phead)
{
//链表不为空
assert(phead);
//链表不能只包含头节点
assert(phead->next != phead);
//第二个节点(头节点的下一个节点)
DoubleListNode* SecondNode = phead->next;
//第三个节点(头节点之后的第二个节点)
DoubleListNode* ThirdNode = SecondNode->next;
//修改头节点与第三个节点的指针域
ThirdNode->previous = phead;
phead->next = ThirdNode;
//释放第二个节点
free(SecondNode);
}
8、在 pos 节点之前插入新节点
//在 pos 节点之前插入
void DoubleListInsert(DoubleListNode* pos, DoubleListDataType x)
{
//创建新节点
DoubleListNode* NewNode = BuyDoubleListNode(x);
//pos节点之前的节点
DoubleListNode* PrevPosNode = pos->previous;
//修改指针域
NewNode->previous = PrevPosNode;
NewNode->next = pos;
PrevPosNode->next = NewNode;
pos->previous = NewNode;
}
9、删除 pos 节点
//删除 pos 节点
void DoubleListErase(DoubleListNode* pos)
{
assert(pos);
//pos节点之前的节点
DoubleListNode* PrevPosNode = pos->previous;
//pos节点之后的节点
DoubleListNode* NextPosNode = pos->next;
//修改指针域
PrevPosNode->next = NextPosNode;
NextPosNode->previous = PrevPosNode;
//释放pos
free(pos);
pos = NULL;//这一句实际无意义,因为参数类型为 DoubleListNode* ,而不是DoubleListNode**
}
10、销毁链表
//销毁
void DoubleListErase(DoubleListNode** pos)
{
assert(pos);
assert(*pos);
DoubleListNode* cur = (*pos)->next;
while (cur != (*pos))
{
DoubleListNode* next = cur->next;
DoubleListErase(cur);
cur = next;
}
free(*pos);
*pos = NULL;
}
四、优缺点
优点:1、在任意位置插入删除效率高,时间复杂度为O(1)。2、按需申请空间
缺点:不支持随机访问,一些快排,二分查找在这种结构上不适用