记录数据结构双向带头循环链表相关知识点 记录数据结构双向带头循环链表相关知识点 记录数据结构双向带头循环链表相关知识点
文章目录
一、双向带头循环链表的认识
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结 构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都 是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
二、链表的实现
1、链表的创建
我们这里采用分模块来写,创建List.h、List.c、Test.c三个文件 。 List.h中进行函数声明和头文件包含,List.c中实现链表,Test.c用作写main函数测试链表。
头文件:LIst.h
#pragma once #define _CRT_SECURE_NO_WARNINGS #pragma warning(disable:6031) #include<stdio.h> #include<stdlib.h> #include<assert.h> // 带头+双向+循环链表增删查改实现 typedef int LTDataType; typedef struct ListNode { LTDataType _data; struct ListNode* _next; struct ListNode* _prev; }ListNode; ListNode* ListCreate();// 创建返回链表的头结点. void ListDestory(ListNode* pHead);// 双向链表销毁 void ListPrint(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 ListInsert(ListNode* pos, LTDataType x);// 双向链表在pos的前面进行插入 void ListErase(ListNode* pos);// 双向链表删除pos位置的节点
2、结点的创建
为了使用方便我们在List.c中封装一个创建结点的函数,后续创建结点直接调用即可。
源文件:LIst.c
ListNode* BuyListNode(int x) { ListNode* new = (ListNode*)malloc(sizeof(ListNode)); assert(new); new->_data = x; new->_next = NULL; new->_prev = NULL; return new; }
3、初始化链表
双向带头循环链表需要初始化哨兵位的头结点,让哨兵位的prev和next都指向自己
源文件:LIst.c
ListNode* ListCreate() { ListNode* head = (ListNode*)malloc(sizeof(ListNode)); assert(head); head->_next = head; head->_prev = head; return head; }
主文件(测试文件):Test.c
#include"List.h" void test1() { ListNode* p=ListCreate(); } int main() { test1(); return 0; }
4、尾插
单向不循环链表的尾插需要我们去遍历找到尾,而双向循环就很简单,只需要用到哨兵位pHead->prev就可以找到链表的尾结点,将尾结点的next指向新结点,将新结点的prev指向尾结点,然后让头结点的prev指向要插入的结点,再将新结点的next指向头,轻松搞定。
源文件:LIst.c
void ListPushBack(ListNode* pHead, LTDataType x) { assert(pHead); /*ListNode* tail = pHead->_prev; ListNode* new = BuyListNode(x); assert(new); tail->_next = new; new->_prev = tail; new->_next = pHead; pHead->_prev = new;*/ ListInsert(pHead, x);//Insert复用 }
5、头插
源文件:LIst.c
void ListPushFront(ListNode* pHead, LTDataType x) { assert(pHead); /*ListNode* new = BuyListNode(x); assert(new); pHead->_next->_prev = new; new->_next = pHead->_next; new->_prev = pHead; pHead->_next = new;*/ ListInsert(pHead->_next, x);//复用 }
新结点创建完成后,将其链接在头结点后面即可。
6、打印链表
这里一定要注意循环停止的条件应该是不等于头指针,等于头指针的时候说明已经遍历一遍。
源文件:LIst.c
void ListPrint(ListNode* pHead) { assert(pHead); ListNode* cur = pHead->_next; while (cur != pHead) { printf("%d<->", cur->_data); cur = cur->_next; } printf("NULL\n"); }
写好打印函数后我们测试一下:
主文件(测试文件):Test.c
#include"List.h" void test1() { ListNode* p=ListCreate(); ListPushBack(p, 1); ListPushBack(p, 2); ListPushBack(p, 3); ListPushFront(p,4); ListPrint(p); ListDestory(p); } int main() { test1(); return 0; }
这里看到和我们预想的结果一样,证明代码正确,接下来写结点的删除。
7、尾删
尾删这里一定要判断链表是否为空
源文件:LIst.c
void ListPopBack(ListNode* pHead) { assert(pHead); assert(pHead->_next != pHead); /*ListNode* tail = pHead->_prev; assert(tail); tail->_prev->_next = pHead; pHead->_prev = tail->_prev; free(tail);*/ ListErase(pHead->_prev);//复用 }
8、头删
同样要判断链表是否为空,不进行判断就会把哨兵位都给释放啦!!!
源文件:LIst.c
void ListPopFront(ListNode* pHead) { assert(pHead); assert(pHead->_next != pHead); /*ListNode* delhead = pHead->_next; assert(delhead); delhead->_next->_prev = pHead; pHead->_next = delhead->_next; free(delhead);*/ ListErase(pHead->_next);//复用 }
9、在链表中查找
查找数据无捷径,需要进行遍历,找到数据就返回结点地址,找不到返回NULL;
源文件:LIst.c
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; }
10、在pos之前插入数据
新建一个指针用来记录pos之前结点的地址。然后将新结点链接上即可。
源文件:LIst.c
void ListInsert(ListNode* pos, LTDataType x) { assert(pos); ListNode* prev = pos->_prev; ListNode* new = BuyListNode(x); prev->_next = new; new->_prev = prev; new->_next = pos; pos->_prev = new; }
11、删除pos位置的结点
使用两个指针来记录pos位置的上一个和下一个结点,free掉pos后,将记录下来的结点链接起来
源文件:LIst.c
void ListErase(ListNode* pos) { assert(pos); ListNode* pos_prev = pos->_prev; ListNode* pos_next = pos->_next; pos_prev->_next = pos_next; pos_next->_prev = pos_prev; free(pos); pos = NULL; }
12、链表销毁
在使用完链表之后我们需要对其进行销毁,否则会造成内存泄漏
源文件:LIst.c
void ListDestory(ListNode* pHead) { assert(pHead); ListNode* cur = pHead->_next; while (cur != pHead) { ListNode* next = cur->_next; free(cur); cur = next; } free(pHead); pHead = NULL; }
三、完整代码
源文件:LIst.c
#include"List.h" ListNode* ListCreate() { ListNode* head = (ListNode*)malloc(sizeof(ListNode)); assert(head); head->_next = head; head->_prev = head; return head; } ListNode* BuyListNode(int x) { ListNode* new = (ListNode*)malloc(sizeof(ListNode)); assert(new); new->_data = x; new->_next = NULL; new->_prev = NULL; return new; } void ListPrint(ListNode* pHead) { assert(pHead); ListNode* cur = pHead->_next; while (cur != pHead) { printf("%d<->", cur->_data); cur = cur->_next; } printf("NULL\n"); } void ListPushBack(ListNode* pHead, LTDataType x) { assert(pHead); /*ListNode* tail = pHead->_prev; ListNode* new = BuyListNode(x); assert(new); tail->_next = new; new->_prev = tail; new->_next = pHead; pHead->_prev = new;*/ ListInsert(pHead, x); } void ListPopBack(ListNode* pHead) { assert(pHead); assert(pHead->_next != pHead); /*ListNode* tail = pHead->_prev; assert(tail); tail->_prev->_next = pHead; pHead->_prev = tail->_prev; free(tail);*/ ListErase(pHead->_prev); } void ListPushFront(ListNode* pHead, LTDataType x) { assert(pHead); /*ListNode* new = BuyListNode(x); assert(new); pHead->_next->_prev = new; new->_next = pHead->_next; new->_prev = pHead; pHead->_next = new;*/ ListInsert(pHead->_next, x); } void ListPopFront(ListNode* pHead) { assert(pHead); assert(pHead->_next != pHead); /*ListNode* delhead = pHead->_next; assert(delhead); delhead->_next->_prev = pHead; pHead->_next = delhead->_next; free(delhead);*/ ListErase(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 ListInsert(ListNode* pos, LTDataType x) { assert(pos); ListNode* prev = pos->_prev; ListNode* new = BuyListNode(x); prev->_next = new; new->_prev = prev; new->_next = pos; pos->_prev = new; } void ListErase(ListNode* pos) { assert(pos); ListNode* pos_prev = pos->_prev; ListNode* pos_next = pos->_next; pos_prev->_next = pos_next; pos_next->_prev = pos_prev; free(pos); pos = NULL; } void ListDestory(ListNode* pHead) { assert(pHead); ListNode* cur = pHead->_next; while (cur != pHead) { ListNode* next = cur->_next; free(cur); cur = next; } free(pHead); pHead = NULL; }
头文件:LIst.h
#pragma once #define _CRT_SECURE_NO_WARNINGS #pragma warning(disable:6031) #include<stdio.h> #include<stdlib.h> #include<assert.h> // 带头+双向+循环链表增删查改实现 typedef int LTDataType; typedef struct ListNode { LTDataType _data; struct ListNode* _next; struct ListNode* _prev; }ListNode; // 创建返回链表的头结点. ListNode* ListCreate(); // 双向链表销毁 void ListDestory(ListNode* pHead); // 双向链表打印 void ListPrint(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); // 双向链表在pos的前面进行插入 void ListInsert(ListNode* pos, LTDataType x); // 双向链表删除pos位置的节点 void ListErase(ListNode* pos);
主文件:Test.c
#include"List.h" void test1() { ListNode* p=ListCreate(); ListPushBack(p, 1); ListPushBack(p, 2); ListPushBack(p, 3); ListPushFront(p,4); ListPrint(p); ListDestory(p); } int main() { test1(); return 0; }
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
主文件:Test.c
#include"List.h" void test1() { ListNode* p=ListCreate(); ListPushBack(p, 1); ListPushBack(p, 2); ListPushBack(p, 3); ListPushFront(p,4); ListPrint(p); ListDestory(p); } int main() { test1(); return 0; }
谢谢浏览,喜欢的记得三连,感谢~!