目录
1->前言
2->链表的概念和结构
2.1链表概念
2.2->带头双向循环链表结构
3->模拟实现带头双向循环链表
3.1定义链表结点 struct ListNode
3.2创建链表结点 CreateLTNode 函数
3.3链表初始化函数 ListInit函数
3.4链表打印函数 ListPrint函数
3.5链表判空函数 ListEmpty函数
3.6链表增删改查之尾部插入 ListPushBack 函数
3.7链表增删改查之头部插入 ListPushFront 函数
3.8链表增删改查之尾部删除 ListPopBack 函数
3.9链表增删改查之头部删除 ListPopFront 函数
3.10链表增删改查之查找数据 ListFind 函数
3.11链表增删改查之在pos位置之前插入
3.12链表增删改查之删除pos位置的值
3.13链表增删改查之销毁链表
4->您的专属鼓励师
1->前言
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
上一个篇章我们讲解了无头单向非循环链表,这节我们讲解带头双向循环链表
2->链表的概念和结构
2.1链表概念
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
2.2->带头双向循环链表结构
3->模拟实现带头双向循环链表
3.1定义链表结点 struct ListNode
数据域_data 指针域_prev指向前一个结点,_next指向后一个结点
#pragma once
//使用c语言模拟实现双向带头循环链表
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct ListNode
{
struct ListNode* _prev;//指向前一个结点
struct ListNode* _next;//指向后一个结点
int _data; //存储数据
}LTNode;
3.2创建链表结点 CreateLTNode 函数
注意:我们的结点动态申请内存在堆上,所以后边释放节点一定要free
//1.创建链表结点
LTNode* CreateLTNode(int num)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc failed");
return NULL;
}
//到这里申请空间成功
newnode->_prev = NULL;
newnode->_next = NULL;
newnode->_data = num;
return newnode;
}
3.3链表初始化函数 ListInit函数
个人总结:任何数据结构或变量在使用之前一定要初始化给予初始值,让他处于一个明确的状态,否则会带来很多未知的错误,会浪费很多时间
//2.链表初始化
LTNode* ListInit()
{
LTNode* phead = CreateLTNode(-1);//创建头结点
phead->_prev = phead; //让头结点指向自己
phead->_next = phead; //让头结点指向自己
return phead;
}
3.4链表打印函数 ListPrint函数
因为我们有头结点,头结点里的数据不算作有效数据不要打印,并且用头结点作为while循环的判断条件
//3.链表打印
void ListPrint(LTNode* phead)
{
assert(phead);
printf("head<==>");
LTNode* cur = phead->_next;//从头结点下一个节点开始有效数据打印
while (cur != phead)//cur不是头结点才能进循环
{
printf("%d<==>", cur->_data);
cur = cur->_next;
}
printf("head\n");
}
3.5链表判空函数 ListEmpty函数
//4.判断链表是否为空
//为空返回 true,不为空返回 false
bool ListEmpty(LTNode* phead)
{
assert(phead);
return phead->_next == phead;
}
3.6链表增删改查之尾部插入 ListPushBack 函数
//5.链表尾部插入
void ListPushBack(LTNode* phead, int num)
{
assert(phead);
//创建新结点并且找到最后一个结点
LTNode* newnode = CreateLTNode(num);
LTNode* tail = phead->_prev;
//修改指针达到尾部插入效果
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = phead;
phead->_prev = newnode;
}
我们写代码测试下是否正确:
//1.测试双链表尾部插入
void test1()
{
LTNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPrint(plist);
}
看控制台输出:结果正确:
3.7链表增删改查之头部插入 ListPushFront 函数
//6.链表头部插入
void ListPushFront(LTNode* phead, int num)
{
assert(phead);
//创建新结点并且找到第一个结点
LTNode* newnode = CreateLTNode(num);
LTNode* next = phead->_next;
//修改指针达到插入数据效果
phead->_next = newnode;
next->_prev = newnode;
newnode->_prev = phead;
newnode->_next = next;
}
我们写代码测试下是否正确:
//2.测试双链表头部插入
void test2()
{
LTNode* plist = ListInit();
ListPushFront(plist, 1);
ListPushFront(plist, 2);
ListPushFront(plist, 3);
ListPushFront(plist, 4);
ListPrint(plist);
}
看控制台输出:结果正确:
3.8链表增删改查之尾部删除 ListPopBack 函数
//7.链表尾部删除
void ListPopBack(LTNode* phead)
{
//判断链表存在并且有数据才能删除
assert(phead);
assert(!ListEmpty(phead));
//找到最后一个结点和最后一个结点的前一个结点
LTNode* tail = phead->_prev;
LTNode* prev_tail = tail->_prev;
free(tail);
//修改指针
prev_tail->_next = phead;
phead->_prev = prev_tail;
}
我们写代码测试下是否正确:
//3.测试双链表尾部删除
void test3()
{
LTNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPrint(plist);
ListPopBack(plist);
ListPopBack(plist);
ListPrint(plist);
}
看控制台输出:结果正确:
3.9链表增删改查之头部删除 ListPopFront 函数
//8.链表头部删除
void ListPopFront(LTNode* phead)
{
//判断链表存在并且有数据才能删除
assert(phead);
assert(!ListEmpty(phead));
//找到第一个结点和第二个结点
LTNode* next = phead->_next;
LTNode* next_next = next->_next;
//释放头部结点
free(next);
//修改指针
phead->_next = next_next;
next_next->_prev = phead;
}
我们写代码测试下是否正确:
//4.测试双链表头部删除
void test4()
{
LTNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPrint(plist);
ListPopFront(plist);
ListPopFront(plist);
ListPrint(plist);
}
看控制台输出:结果正确:
3.10链表增删改查之查找数据 ListFind 函数
//9.链表查找数据,简单遍历就行
LTNode* ListFind(LTNode* phead, int num)
{
assert(phead);
LTNode* cur = phead->_next;
while (cur != phead)
{
if (cur->_data == num)
return cur;
cur = cur->_next;
}
//到这里表示链表遍历一遍但是仍然没有找到数据,那就返回空
return NULL;
}
我们写代码测试下是否正确:
//5.测试链表的查找数据
void test5()
{
LTNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPrint(plist);
LTNode* ptr = ListFind(plist, 3);
printf("我查找的数据 : %d",ptr->_data);
}
看控制台输出:结果正确:
3.11链表增删改查之在pos位置之前插入
//10.在pos之前插入
void LTInsert(LTNode* pos, int x)
{
assert(pos);
LTNode* prev = pos->_prev;
LTNode* newnode = CreateLTNode(x);
// prev newnode pos
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = pos;
pos->_prev = newnode;
}
3.12链表增删改查之删除pos位置的值
//11.删除pos位置的值
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* posPrev = pos->_prev;
LTNode* posNext = pos->_next;
posPrev->_next = posNext;
posNext->_prev = posPrev;
free(pos);
}
3.13链表增删改查之销毁链表
//12.销毁链表
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->_next;
while (cur != phead)
{
LTNode* next = cur->_next;
free(cur);//销毁存储有效数据结点
cur = next;
}
//销毁头结点
free(phead);
}
4->您的专属鼓励师
有些事情,你永远都没有办法做到“顶尖”,因为智力跟不上.但是所有的事情,你都可以做到“高段”,因为它需要的是时间的累积和精力的打磨.不聪明与聪明之间的区别,是很微妙的.有时候我们只会通过一次两次的结果,来判断整个人、整件事,其实这是不明智的.从小,邻居和亲戚在谈论我的时候,都会觉得我很聪明。但是只有我自己知道,我从来没有聪明过,只是看上去比较聪明而已.
修行之路确实枯燥,但是我们把问题搞懂以后就发现他是那样的美妙!一遍学不会没关系吖,多看几遍,我也是学了好多遍呢,小伙伴们肯定学的又快又好!!!最后希望写的内容对小伙伴们有所帮助,我写的如果有哪里不对的地方请在评论区或者私信指出来哦!让我们一起进步吖,任何疑问包括心情不好都可以找我聊聊,我很乐意当你的倾听者吖.