带头双向循环链表的节点有两个指针域,一个指针域prev存放节点前面节点的地址,另一个指针域next存放节点后面节点的地址。其中头节点的prev存放尾节点的地址,尾节点的next存放头节点的地址,达到循环的效果。
节点的定义、创建、初始化、销毁和打印
typedef int LTDataType;//重命名结构体数据类型,方便链表后续扩展
typedef struct ListNode {
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
//创建新节点
ListNode* BuyNode(LTDataType x) {
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL) {
perror("BuyNode::malloc");
exit(-1);
}
newnode->data = x;
newnode->next = newnode->prev = NULL;
return newnode;
}
//销毁
void ListDestroy(ListNode* phead) {
assert(phead);
ListNode* cur = phead->next;
while (cur != phead) {
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
//初始化
ListNode* ListInit() {
//哨兵位的头节点
ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
if (phead == NULL) {
perror("ListInit::malloc");
exit(-1);
}
phead->next = phead;
phead->prev = phead;
return phead;
}
//打印
void ListPrint(ListNode* phead) {
assert(phead->next != NULL);
ListNode* cur = phead->next;
while (cur != phead) {
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
链表的增加
尾插
//尾插
void ListPushBack(ListNode* phead, LTDataType x) {
assert(phead);
ListInsert(phead, x);
//ListNode* tail = phead->prev;
//ListNode* newnode = BuyNode(x);
//tail->next = newnode;
//newnode->prev = tail;
//phead->prev = newnode;
//newnode->next = phead;
}
首先记录下尾节点地址,再创建一个新节点。
将尾节点的next指向新节点,新节点的prev指向尾节点。
头节点的prev指向新节点,新节点的next指向头节点.
头插
//头插
void ListPushFront(ListNode* phead, LTDataType x) {
assert(phead);
ListInsert(phead->next, x);
//ListNode* newNode = BuyNode(x);
//ListNode* next = phead->next;
//
//phead->next = newNode;
//newNode->next = next;
//next->prev = newNode;
//newNode->prev = phead;
}
创建一个新节点,并记录下头节点的地址(也就是哨兵位的下一个节点)。
再将哨兵位的next指向新节点,新节点的next指向头节点。
头节点的prev指向新节点,新节点的prev指向哨兵位的地址。
在指定节点前面插入
//在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) {
assert(pos);
ListNode* pre = pos->prev;
ListNode* newNode = BuyNode(x);
pre->next = newNode;
newNode->prev = pre;
newNode->next = pos;
pos->prev = newNode;
//ListPushFront(pre, x);
}
记录下pos位置的前面一个节点的地址,创建一个新节点。
pos位置的前面一个节点的next指向新节点。
新节点的prev指向pos位置的前面一个节点,新节点的next指向pos位置。
pos位置的prev指向新节点。
节点的删除
头删
//头删
void ListPopFront(ListNode* phead) {
assert(phead);
assert(phead->next != phead);//判断链表是否为空
ListErase(phead->next);
//ListNode* newNext = phead->next->next;
//free(phead->next);
//phead->next = newNext;
//newNext->prev = phead;
}
首先记录下头节点(就是哨兵位的下一个节点)的下一个节点的地址。
再释放掉头节点。哨兵位的next指向记录的头节点的下一个节点的地址。
记录下的头节点的下一个节点的prev指向哨兵位。
尾删
//尾删
void ListPopBack(ListNode* phead) {
assert(phead);
assert(phead->next != phead);//当phead->next == phead时,链表为空
ListErase(phead->prev);
//ListNode* newTail = phead->prev->prev;
//free(phead->prev);
//phead->prev = newTail;
//newTail->next = phead;
}
记录下尾节点前面一个节点的位置。
释放掉尾节点。再将哨兵位的prev指向记录下的尾节点前面一个节点的位置。
记录下的尾节点的前面一个节点的next指向哨兵位。
删除指定节点
//删除pos位置的节点
void ListErase(ListNode* pos) {
assert(pos);
ListNode* pre = pos->prev;
ListNode* next = pos->next;
free(pos);
pre->next = next;
next->prev = pre;
}
首先记录下pos位置前面一个节点pre和pos位置后面一个节点next;
释放掉pos位置的节点,再将pre节点的next指向next,next节点的prev指向pre节点。
节点的查找
//查找
ListNode* ListFind(ListNode* phead, LTDataType x) {
assert(phead);
ListNode* cur = phead->next;
while (cur != phead) {
if (cur->data == x) {
return cur;
}
cur = cur->next;
}
return NULL;
}
节点的修改
节点的修改是指修改节点中的数据域,这里可以嵌套节点的查找函数,得到对应节点的地址就能进行操作了,完成对数据域的修改。
总结
带头双向循环链表的结构比单链表的结构更加复杂,但是带头双向循环链表的删除,插入更为关键。因为单链表的删除和插入的实现,使得带头双向循环链表的头插、头删、尾插和尾删更加方便,头插、头删、尾插和尾删的操作嵌套删除和插入的函数就能完成了。