链表尾删
尾删就是删除最后一个节点,所以我们先要找到最后一个节点的前一个节点(phead->prev->prev),然后将最后一个节点删除(释放),最后将最后一个节点的前一个节点与头节点连接即可。
如果此时只有一个节点,也是一样的删,没有其它的麻烦,多想想就明白了(自行体会)。
如果此时没有节点,这是判空的作用就来了,没有节点当然就是不给删咯,直接assert断言暴打。
下面是相关接口函数功能实现:
// 双向链表尾删 void ListPopBack(ListNode* phead) { // 如果为空返回真 ,!一下为假 assert(phead && !ListEmpty(phead)); // 要删除的尾节点的指针 ListNode* cur = phead->_prev; // 尾节点的前一个结点 ListNode* prev = cur->_prev; free(cur); // 连接 prev->_next = phead; phead->_prev = prev; }
链表头插
头插就是在头节点的后面插入,得到一个节点后,只管连接即可,轻轻松松。
当然这里要注意的是,如果不存放当前头节点的下一个节点的地址,那么需要先将新的节点与头节点的下一个节点连接,然后才与头节点连接,因为先与头节点连接的话,就找不到下一个节点了,此时就会连接中断。
如果存放当前头节点的下一个节点的地址的话,是可以随意连接的,没有主次。
下面是相关接口函数功能实现:
// 双向链表头插 void ListPushFront(ListNode* phead, LTDataType x) { assert(phead); // 得到一个节点 ListNode* newnode = BuyListNode(x); // 这里是存放下一个节点的地址 ListNode* cur = phead->_next; // 连接 newnode->_next = cur; cur->_prev = newnode; phead->_next = newnode; newnode->_prev = phead; }
链表头删
链表头删是删除头节点的下一个节点,为了删除之后,能够正常连接,所以需要存放此时头节点的下一个节点的下一个节点的地址,然后将头节点的下一个节点删除,最后再将头节点与存放的那个地址指向的节点连接即可。
注意,既然是删除,那就要复用判空函数判断链表是否为空,不然没有节点了还去删除,就会出问题。
下面是相关接口函数功能实现:
// 双向链表头删 void ListPopFront(ListNode* phead) { // 判空 assert(phead && !ListEmpty(phead)); // 存放当前链表头节点的下一个节点的地址 ListNode* cur = phead->_next; // 存放当前链表头节点的下一个节点的下一个节点的地址 ListNode* next = cur->_next; // 删除(释放) free(cur); // 连接 phead->_next = next; next->_prev = phead; }
链表查找
- 这里的查找是你给定一个数据,然后在链表中寻找
data
与这个数据相等的节点,最终返回这个节点的地址。 - 如果没有找到,就返回
NULL
。
- 如何查找?很简单,遍历一遍链表即可。
- 如果链表为空,也就是只有一个头节点,这时
phead
的next
就是自己,判断条件为假,返回NULL
。
下面是相关接口函数功能实现:
// 双向链表查找 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; }
链表修改
链表的修改就是将链表中的某一个节点的数据修改成你想要的值。
这里通过传递某一个节点的地址来进行修改,言外之意就是可以先使用查找函数查找到你想要修改的那个节点,然后将查找函数的返回值作为要修改的那个节点的参数,最后进行修改即可。
如果此时链表为空,在查找函数就直接会返回NULL,因此这里需要断言一下pos的有效性。
下面是相关接口函数功能实现:
// 修改某一节点的数据 void ListModify(ListNode* pos, LTDataType x) { // 判断pos的有效性 assert(pos); // 直接修改数据即可 pos->_data = x; }
任意插入
这里的任意插入,是将你要插入的节点插入在你想要插入的位置的前面。
同样的,这里需要传递一个节点的指针(pos)代表你要插入的位置,所以这里搭配查找函数会更好。
前面学习了头插尾插,相信大家对插入的连接已经了如指掌了,任意位置插入也不难。
既然是在想插入的位置的前面插入,那么这里就需要存放一下该位置的前一个节点的地址,然后进行连接即可。
下面是相关接口函数功能实现:
// 双向链表在pos的前面进行插入 void ListInsert(ListNode* pos, LTDataType x) { // 判断pos是否有效 assert(pos); // 得到一个节点 ListNode* newnode = BuyListNode(x); // 存放pos的前一个节点 ListNode* prev = pos->_prev; // 连接 newnode->_next = pos; pos->_prev = newnode; prev->_next = newnode; newnode->_prev = prev; }
有了任意插入的函数,前面的头插尾插都可以改为复用噢!(在后面整体代码中展示)
任意删除
任意删除,是删除pos位置,这个pos就是你指定要删除的那个节点的地址。
既然是删除pos位置,那么就需要存放一下pos的前一个节点的地址和pos的下一个节点的地址,以便于连接。最后将pos位置的节点释放即可。
任意删除函数实际上是不需要判空的,直接判断pos的有效性就可以了,加入pos为NULL,就说明要么没找到你要删的那个节点,要么链表为空,所以空的情况已经包含在内了。
下面是相关接口函数功能实现:
// 双向链表删除pos位置的节点 void ListErase(ListNode* pos) { // 判断pos的有效性 assert(pos); // 存放pos的下一个节点的地址 ListNode* next = pos->_next; // 存放pos的前一个节点的地址 ListNode* prev = pos->_prev; // 删除(释放)pos free(pos); // 连接 prev->_next = next; next->_prev = prev; }
有了任意删除函数接口,前面的头删尾删就可以复用该函数接口了。
整体代码
DList.h:
#include <stdio.h> #include <assert.h> #include <stdlib.h> #include <stdbool.h> // 带头+双向+循环链表增删查改实现 typedef int LTDataType; typedef struct ListNode { LTDataType _data; struct ListNode* _next; struct ListNode* _prev; }ListNode; // 创建返回链表的头结点. ListNode* ListCreate(); // 得到一个结点 ListNode* BuyListNode(LTDataType x); // 双向链表清理 void ListClear(ListNode* phead); // 双向链表销毁 void ListDestory(ListNode* phead); // 双向链表打印 void ListPrint(ListNode* phead); // 判空 bool ListEmpty(ListNode* phead); // 双向链表尾插 void ListPushBack(ListNode* phead, LTDataType x); // 双向链表尾删 void ListPopBack(ListNode* phead); // 双向链表头插 void ListPushFront(ListNode* phead, LTDataType x); // 双向链表头删 void ListPopFront(ListNode* phead); // 双向链表查找 ListNode* ListFind(ListNode* phead, LTDataType x); // 修改某一节点的数据 void ListModify(ListNode* pos, LTDataType x); // 双向链表在pos的前面进行插入 void ListInsert(ListNode* pos, LTDataType x); // 双向链表删除pos位置的节点 void ListErase(ListNode* pos);
DList.c:
#include "DList.h" // 创建返回链表的头结点. ListNode* ListCreate() { ListNode* head = BuyListNode(-1); return head; } // 得到一个结点 ListNode* BuyListNode(LTDataType x) { ListNode* newnode = (ListNode*)malloc(sizeof(ListNode)); assert(newnode); newnode->_next = newnode; newnode->_prev = newnode; newnode->_data = x; return newnode; } // 双链表清理 void ListClear(ListNode* phead) { // 避免传NULL的情况,phead是NULL就不能操作了 assert(phead); // 从phead的下一个节点开始操作 ListNode* cur = phead->_next; while (cur != phead) { // 存放当前节点的下一个节点的地址 ListNode* next = cur->_next; free(cur); cur = next; } } // 双向链表销毁 void ListDestory(ListNode* phead) { // 判断phead是否是NULL assert(phead); // 复用 ListClear(phead); // 最后释放头节点 free(phead); } // 双向链表打印 void ListPrint(ListNode* phead) { assert(phead); ListNode* cur = phead->_next; while (cur != phead) { printf("%d ", cur->_data); cur = cur->_next; } printf("\n"); } // 判空 bool ListEmpty(ListNode* phead) { assert(phead); return phead->_next == phead; } // 双向链表尾插 void ListPushBack(ListNode* phead, LTDataType x) { assert(phead); //ListNode* newnode = BuyListNode(x); //ListNode* cur = phead->_prev; //cur->_next = newnode; //newnode->_prev = cur; //newnode->_next = phead; //phead->_prev = newnode; insert(phead, x); } // 双向链表尾删 void ListPopBack(ListNode* phead) { assert(phead && !ListEmpty(phead)); //ListNode* cur = phead->_prev; //ListNode* prev = cur->_prev; //free(cur); //prev->_next = phead; //phead->_prev = prev; erase(phead->prev); } // 双向链表头插 void ListPushFront(ListNode* phead, LTDataType x) { assert(phead); //ListNode* newnode = BuyListNode(x); //ListNode* cur = phead->_next; //newnode->_next = cur; //cur->_prev = newnode; //phead->_next = newnode; //newnode->_prev = phead; insert(phead->next, x); } // 双向链表头删 void ListPopFront(ListNode* phead) { assert(!ListEmpty(phead)); //ListNode* cur = phead->_next; //ListNode* next = cur->_next; //free(cur); //phead->_next = next; //next->_prev = phead; erase(phead->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; } // 修改某一节点的数据 void ListModify(ListNode* pos, LTDataType x) { assert(pos); pos->_data = x; } // 双向链表在pos的前面进行插入 void ListInsert(ListNode* pos, LTDataType x) { assert(pos); ListNode* newnode = BuyListNode(x); ListNode* prev = pos->_prev; newnode->_next = pos; pos->_prev = newnode; prev->_next = newnode; newnode->_prev = prev; } // 双向链表删除pos位置的节点 void ListErase(ListNode* pos) { assert(pos); ListNode* next = pos->_next; ListNode* prev = pos->_prev; free(pos); prev->_next = next; next->_prev = prev; }
test.h:
#include "DList.h" void test1() { ListNode* plist = ListCreate(); ListPushBack(plist, 1); ListPushBack(plist, 2); ListPushBack(plist, 3); ListPushBack(plist, 4); ListPushBack(plist, 5); ListPushBack(plist, 6); ListPrint(plist); ListPopBack(plist); ListPrint(plist); ListPopBack(plist); ListPrint(plist); ListPopFront(plist); ListPrint(plist); ListPopFront(plist); ListPrint(plist); ListPopBack(plist); ListPrint(plist); ListPopBack(plist); ListPrint(plist); ListDestory(plist); plist = NULL; } void test2() { ListNode* plist = ListCreate(); ListPushBack(plist, 1); ListPushBack(plist, 2); ListPushBack(plist, 3); ListPushBack(plist, 4); ListPushBack(plist, 5); ListPushBack(plist, 6); ListPrint(plist); ListErase(ListFind(plist, 3)); ListPrint(plist); ListInsert(ListFind(plist, 4), 3); ListPrint(plist); ListDestory(plist); plist = NULL; } int main() { //test1(); test2(); return 0; }
😳写在最后
💝带头双向循环链表可以说实现起来是比较简单了,虽然它的结构比较复杂。所以以后在学习的过程中,看到复杂的东西不要怕,说不定还挺简单呢,如果看到不复杂的,也别太掉以轻心,说不定它展开来却很难呢。
❤️🔥后续将会持续输出有关数据结构的文章,你们的支持就是我写作的最大动力!
感谢阅读本小白的博客,错误的地方请严厉指出噢~