本篇博客主要是介绍带头双向循环链表的一些基本的增删改查的操作。
如上图所示,我们所说的带头双向循环链表常指的是带有哨兵位头节点head
,前一个结点的尾指针指向后一个节点,后一个节点的头指针指向前一个节点,并且最后一个节点的尾指针指向哨兵位节点,哨兵位节点的头指针指向最后一个节点,这就是双向带头循环链表。
要实现带头双向循环链表,我们首先就需要创建出该相应的结构体。
typedef int LTDataType;
typedef struct ListNode
{LTDataType _data;struct ListNode* _next;struct ListNode* _prev;
}ListNode;
之后我们创建这个链表的哨兵位头节点并返回,我们用ListCreate()
函数实现,如下代码。
ListNode* ListCreate()
{ListNode* head = (ListNode*)malloc(sizeof(ListNode));head->_data = 0;head->_next = head;head->_prev = head;return head;
}
接下来就是对带头双向循环链表的插入,插入可以分为头插,尾插和随机插,我们用三个函数来实现。我们先进行头插ListPushFront(ListNode* pHead, LTDataType x);
,如下代码。
ListNode* BuyNode(LTDataType x)
{ListNode* new = (ListNode*)malloc(sizeof(ListNode));new->_data = x;new->_next = NULL;new->_prev = NULL;return new;
}
void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* p = BuyNode(x);p->_next = pHead->_next;pHead->_next->_prev = p;p->_prev = pHead;pHead->_next = p;
}
接下来我们进行尾插,尾插我们用函数ListPushBack(ListNode* pHead, LTDataType x);
来实现,如下代码。
ListNode* BuyNode(LTDataType x)
{ListNode* new = (ListNode*)malloc(sizeof(ListNode));new->_data = x;new->_next = NULL;new->_prev = NULL;return new;
}
void ListPushBack(ListNode* pHead, LTDataType x)
{ListNode* p = BuyNode(x);ListNode* ret = pHead->_prev;p->_next = pHead;ret->_next = p;p->_prev = ret;pHead->_prev = p;
}
无论是头插还是尾插,只要我们找准哨兵位节点,谨慎使用好哨兵位节点的各个指针,就可以很简单的完成带头双向循环链表的头插和尾插。在完成头插和尾插后,最后我们还剩下一个随机插函数,随机插入我们用ListInsert(ListNode* pos, LTDataType x);
函数完成,如下代码。
ListNode* BuyNode(LTDataType x)
{ListNode* new = (ListNode*)malloc(sizeof(ListNode));new->_data = x;new->_next = NULL;new->_prev = NULL;return new;
}
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* p = BuyNode(x);p->_next = pos;p->_prev = pos->_prev;pos->_prev->_next = p;pos->_prev = p;
}
跟前面的插入一样,删除我们也有3种删除方式,分别是头删,尾删和随机删,接下来我们会一一将代码放出来。头删我们用函数ListPopFront(ListNode* pHead);
来完成,代码如下。
void ListPopFront(ListNode* pHead)
{assert(pHead);ListNode* p = pHead->_next;p->_next->_prev = pHead;pHead->_next = p->_next;free(p);
}
尾删我们用函数ListPopBack(ListNode* pHead);
来完成,代码如下。
void ListPopBack(ListNode* pHead)
{assert(pHead);ListNode* p = pHead->_prev;p->_prev->_next = pHead;pHead->_prev = p->_prev;free(p);
}
带头双向循环链表的头删和尾删都比较简单,关键点还是在于找准哨兵位头节点的头尾指针。接下来就是链表的随机删的实现,随机删除我们用函数ListErase(ListNode* pos);
来完成,如下代码。
void ListErase(ListNode* pos)
{assert(pos);if (pos == pos->_next){return;}pos->_prev->_next = pos->_next;pos->_next->_prev = pos->_prev;free(pos);
}
在完成了链表的增加和删除后,剩下的就是链表的搜索和修改,链表的搜索我们用函数ListFind(ListNode* pHead, LTDataType x);
来完成,如下代码。
ListNode* ListFind(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* p = pHead->_next;while (p != pHead){if (p->_data == x){return p;}p = p->_next;}return NULL;
}
链表的修改我们用函数ListModify(ListNode* pHead, LTDataType x)
来完成,如下代码。
ListNode* ListFind(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* p = pHead->_next;while (p != pHead){if (p->_data == x){return p;}p = p->_next;}return NULL;
}
void ListModify(ListNode* pHead, LTDataType x)
{ListNode* p = ListFind(pHead, x);scanf("%d", &p->_data);
}
到此为止,带头双向循环链表的增删改查就全完成了,带头双向循环链表是链表中使用起来最方便,功能最全的链表,因此我们合理的应用带头双向循环链表可以帮助我们事半功倍,解决不少问题。另外题外话,刚刚🐏康归来,好久都没写博客了,被这个疫情搞得一团糟,中招了是真难受啊,小伙伴们自己注意防护吧。
上一篇:深度学习:04 神经网络的搭建
下一篇:shell流程控制之条件判断练习