目录
特点:相比于无头单向不循环链表来说,该链表结构比较复杂,但是功能实现比较简单。
功能实现:
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
1、链表的创建(创建并返回链表的头节点)
链表的头节点需要用调用malloc函数开辟,然后要将开辟出的指针强制类型转换为带头双向循环链表型。
ListNode* ListCreate()
{
ListNode* pHead = (ListNode*)malloc(sizeof(ListNode));
if (pHead == NULL)
{
printf("malloc is fail \n");
exit(-1);
}
pHead->next = pHead;
pHead->prev = pHead;
return pHead;
}
2、链表的销毁
假设该链表为以下情况,链表的销毁不仅需要销毁到链表的头,还需要销毁链表的数据,定义一个指针指向数据的第一个,还需要定义一个指针用来迭代cur,知道数据销毁完毕。但是需要注意的是结束循环的条件不是cur!=NULL,由图可知当next->next指向pHead时,链表的数据已经到尾了。
void ListDestory(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
ListNode* next = cur->next;
free(cur);
cur =next;
}
}
3、链表的打印
跟链表的销毁一个逻辑,但是不需要记录cur的next,因为cur指向的空间不会被释放。
// 双向链表打印
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
4、尾插
尾插时需要开辟一个新的结点,然后将结点的之前一个指向头,头的下一个指向结点。结点的下一个指向也只向头,头的前一个指向结点。
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
ListNode* newnode = BuyDListNode(x);
ListNode* tail = pHead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = pHead;
pHead->prev = newnode;
}
5、尾删
尾删需要考虑两种情况,正常情况下,链表是不为空的,当链表为空时,就不能删除数据了,所以需要加上一个断言,判断链表不为空。因为链表是一个循环列表,所以在判断链表是否为空的时候,只需要判断pHead的next是否指向自己。
由图可知,双向链表的尾结点就是头节点的前一个,为了增加代码的可读性,可以定义一个尾结点和尾结点的前一个结点,只需要将prev的下一个指向pHead,pHead的前一个指向prev,就可以组成一个新的链表,最后释放tail所指向的结点,就完成删除。
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* tail = pHead->prev;
ListNode* prevtail = tail->prev;
prevtail->next = pHead;
pHead->prev= prevtail;
free(tail);
}
6、头插
头插时需要在定义一个指针记录刚开始链表第一个数据的位置,然后插入。
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyDListNode(x);
ListNode* Next = pHead->next;
pHead->next = newnode;
newnode->prev = pHead;
newnode->next = Next;
Next->prev = newnode;
}
7、头删
头删同样需要考虑链表为空的情况,操作与尾插一样。
需要确定链表的头位置与第二个位置。
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* head = pHead->next;
ListNode* next = head->next;
pHead->next = next;
next->prev = pHead;
free(head);
}
8、查找
查找的思维跟打印其实是一样的,找到了就返回地址,找不到就返回空,同时也需要知道截至循环的条件,也是cur的next指向头节点。
// 双向链表查找
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;
}
9、在pos的前面进行插入
在插入之前需要记住pos的前一个位置。
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* newcode = BuyDListNode(x);
ListNode* prev = pos->prev;
prev->next = newcode;
newcode->prev = prev;
newcode->next = pos;
pos->prev = newcode;
}
10、在pos位置删除
需要记得pos的前一个位置和pos的后一个位置,然后让这两个位置互相指向。
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}