> 作者简介:დ旧言~,目前大一,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。
> 望小伙伴们点赞👍收藏✨加关注哟💕💕
🌟前言
前面我们已经学习了顺序表和单链表,顺序表可以存储动态的数据,但是一旦元素过少,而又要开辟空间,这样就造成空间的浪费,而单链表以节点为单位存储,不支持随机访问,只能从头到尾遍历访问,为了解决上面两个问题,人们发现了双链表,把一个一个元素以链子的形式存储,可以存储相互的地址,那双链表如何实现呢,今天咱们就实现一下--《双链表》。
🌙主体
咱们从三个方面实现双链表,动态管理,头插头删尾插尾删,增删查改。
在程序中为了实现双链表,需要创建头文件List.h ,创建源文件Test.c,List.c。
🌠动态管理
💤初始化动态双链表
既然实现双链表,初始化动态的双链表必不可少,从两个方面实现初始化动态的双链表。
1.首先我们在List.h定义动态的双链表,省得我们再定义节点(双链表)。
//定义数据类型
typedef int LTDataType;
//定义双链表初始化
typedef struct ListNode
{
//上一个
struct ListNode* next;
//下一个
struct ListNode* prev;
LTDataType data;
}LTNode;
2.对双链表进行初始化
我们要明白,这里不像单链表一样,形成节点就行,还需要初始化。
💦这里采用malloc开辟空间
💦采用预指令判断空间是否开辟完成(没有开辟空间返回-1)
💦之后就是简单的初始数据
💦记得返回值
//初始化
LTNode* BuyLTNode(LTDataType x)
{
//开辟空间
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
//判断开辟的空间是否为空
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
//初始化
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
//形成双链表
LTNode* LTInit()
{
//使头为0
LTNode* phead = BuyLTNode(0);
//构成循环
phead->next = phead;
phead->prev = phead;
return phead;
}
💤释放双链表内存
双链表的内存释放与单链表的内存释放有一定的区别,这里我们分开两类,清理与销毁
清理的代码如下:
//清理
void ListClear(LTNode* phead)
{
//断言
assert(phead);
//清理全部数据,保留头结点
LTNode* cur = phead->next;
//循环销毁
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = NULL;
cur = next;
}
}
销毁的代码如下:
//销毁
void ListDestory(LTNode** pphead)
{
//断言
assert(*pphead);
//调用清理(函数)
ListClear(*pphead);
//释放内存
free(*pphead);
*pphead = NULL;
}
🌠头插头删尾插尾删
💤打印元素
打印元素就太简单了,直接上代码
//打印数据
void LTPrint(LTNode* phead)
{
//断言
assert(phead);
printf("phead<=>");
LTNode* cur = phead->next;
//注意循环的结束的语句
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
💤尾插(重点)
有了这个图就对指向的改变就轻轻松松
//尾插(重点)
void LTPushBack(LTNode* phead, LTDataType x)
{
//断言
assert(phead);
LTNode* tail = phead->prev;
//给添加的元素创建节点
LTNode* newnode = BuyLTNode(x);
//新开辟的元素下一个节点指向尾
newnode->prev = tail;
//尾的上一个节点指向新的元素
tail->next = newnode;
//新元素的上一个节点指向头
newnode->next = phead;
//头的下一节点指向新的元素节点
phead->prev = newnode;
//本质上与尾插相似 (双向链表在pos的前面进行插入)
//LTInsert(phead, x);
}
💤尾删(重点)
双链表重在画图,希望小伙伴们能看得懂。
//尾删
void LTPopBack(LTNode* phead)
{
//断言
assert(phead);
//防止没有头指向没有元素
assert(phead->next != phead);
//找到尾
LTNode* tail = phead->prev;
//找到尾前面一个元素
LTNode* tailPrev = tail->prev;
//释放内存
free(tail);
//(把尾的上一个元素)的上一个指针 指向头
tailPrev->next = phead;
//头的下一个指针指向尾的前一个元素
phead->prev = tailPrev;
// 双向链表删除pos位置的结点(本质上与尾删相似)
//LTErase(phead->prev);
}
💤头插(重点)
博主在这里采用三种方法,希望大家至少学会一种
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
//方法一
//初始化
//LTNode* newnode = BuyLTNode(x);
//newnode->next = phead->next;
//phead->next->prev = newnode;
//phead->next = newnode;
//newnode->prev = phead;
//方法二
//初始化
LTNode* newnode = BuyLTNode(x);
LTNode* first = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
//方法三
// 双向链表删除pos位置的结点(本质上就是头插)
//LTInsert(phead->next, x);
}
💤头删(重点)
双链表会自带梢兵位(这个到后期博主会讲)
//头删(重点)
void LTPopFront(LTNode* phead)
{
//断言
assert(phead);
//头指向不能为空
assert(phead->next != phead);
//找到梢兵位的节点
LTNode* first = phead->next;
//找到头后面一个元素
LTNode* second = first->next;
//释放内存
free(first);
//梢兵位的节点指向头后面一个元素
phead->next = second;
//后面一个元素指向梢兵位的节点
second->prev = phead;
//双向链表删除pos位置的结点(本质上和头删一样)
//LTErase(phead->next);
}
🌠增删查改
💤统计双链表元素个数
这个函数还是比较简单的,注意循环的停止条件。
//统计双链表元素个数
int LTSize(LTNode* phead)
{
//断言
assert(phead);
int size = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
++size;
cur = cur->next;
}
return size;
}
💤双向链表在pos的前面进行插入
这里大家就看图理解就行啦
// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x)
{
//断言
assert(pos);
LTNode* posPrev = pos->prev;
//为插入的元素开辟空间
LTNode* newnode = BuyLTNode(x);
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
💤双向链表删除pos位置的结点
这里大家就看图理解就行啦
// 双向链表删除pos位置的结点
void LTErase(LTNode* pos)
{
//断言
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
//释放内存
free(pos);
posPrev->next = posNext;
posNext->prev = posPrev;
}
🌙代码总结
🌠主函数
//包含文件
#include"List.h"
void TestList1()
{
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPushBack(plist, 5);
LTPrint(plist);
LTPushFront(plist, 10);
LTPushBack(plist, 10);
LTPrint(plist);
}
void TestList2()
{
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPushBack(plist, 5);
LTPrint(plist);
LTPopBack(plist);
//LTPopFront(plist);
LTPrint(plist);
//LTPopFront(plist);
//LTPopFront(plist);
//LTPopFront(plist);
//LTPopFront(plist);
LTPrint(plist);
}
//void TestList3()
//{
// LTNode* plist = LTInit();
// LTPushBack(plist, 1);
// LTPushBack(plist, 2);
// LTPushBack(plist, 3);
// LTPushBack(plist, 4);
// LTPushBack(plist, 5);
// LTPrint(plist);
//
// LTPushFront(plist, 10);
// LTPushFront(plist, 20);
// LTPushFront(plist, 30);
// LTPushFront(plist, 40);
// LTPrint(plist);
//}
//
//void TestList4()
//{
// LTNode* plist = LTInit();
// LTPushBack(plist, 1);
// LTPushBack(plist, 2);
// LTPushBack(plist, 3);
// LTPushBack(plist, 4);
// LTPushBack(plist, 5);
// LTPrint(plist);
//
// LTPopFront(plist);
// LTPrint(plist);
//
// LTPopBack(plist);
// LTPrint(plist);
//}
int main()
{
TestList1();
return 0;
}
🌠List.h头文件
//包含头文件
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
//定义数据类型
typedef int LTDataType;
//定义双链表初始化
typedef struct ListNode
{
//上一个
struct ListNode* next;
//下一个
struct ListNode* prev;
LTDataType data;
}LTNode;
//初始化
LTNode* BuyLTNode(LTDataType x);
//形成双链表
LTNode* LTInit();
//打印数据
void LTPrint(LTNode* phead);
//尾插(重点)
void LTPushBack(LTNode* phead, LTDataType x);
//尾删(重点)
void LTPopBack(LTNode* phead);
//头插(重点)
void LTPushFront(LTNode* phead, LTDataType x);
//头删(重点)
void LTPopFront(LTNode* phead);
//统计双链表元素个数
int LTSize(LTNode* phead);
// 双向链表查找(在双链表中不合适)
LTNode* LTFind(LTNode* phead, LTDataType x);
// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x);
// 双向链表删除pos位置的结点
void LTErase(LTNode* pos);
//清理
void ListClear(LTNode* phead);
//销毁
void ListDestory(LTNode** pphead);
🌠List.c源文件
//包含文件
#include"List.h"
//初始化
LTNode* BuyLTNode(LTDataType x)
{
//开辟空间
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
//判断开辟的空间是否为空
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
//初始化
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
//形成双链表
LTNode* LTInit()
{
//使头为0
LTNode* phead = BuyLTNode(0);
//构成循环
phead->next = phead;
phead->prev = phead;
return phead;
}
//打印数据
void LTPrint(LTNode* phead)
{
//断言
assert(phead);
printf("phead<=>");
LTNode* cur = phead->next;
//注意循环的结束的语句
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
//尾插(重点)
void LTPushBack(LTNode* phead, LTDataType x)
{
//断言
assert(phead);
LTNode* tail = phead->prev;
//给添加的元素创建节点
LTNode* newnode = BuyLTNode(x);
//新开辟的元素下一个节点指向尾
newnode->prev = tail;
//尾的上一个节点指向新的元素
tail->next = newnode;
//新元素的上一个节点指向头
newnode->next = phead;
//头的下一节点指向新的元素节点
phead->prev = newnode;
//本质上与尾插相似 (双向链表在pos的前面进行插入)
//LTInsert(phead, x);
}
//尾删
void LTPopBack(LTNode* phead)
{
//断言
assert(phead);
//防止没有头指向没有元素
assert(phead->next != phead);
//找到尾
LTNode* tail = phead->prev;
//找到尾前面一个元素
LTNode* tailPrev = tail->prev;
//释放内存
free(tail);
//(把尾的上一个元素)的上一个指针 指向头
tailPrev->next = phead;
//头的下一个指针指向尾的前一个元素
phead->prev = tailPrev;
// 双向链表删除pos位置的结点(本质上与尾删相似)
//LTErase(phead->prev);
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
//方法一
//初始化
//LTNode* newnode = BuyLTNode(x);
//newnode->next = phead->next;
//phead->next->prev = newnode;
//phead->next = newnode;
//newnode->prev = phead;
//方法二
//初始化
LTNode* newnode = BuyLTNode(x);
LTNode* first = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
//方法三
// 双向链表删除pos位置的结点(本质上就是头插)
//LTInsert(phead->next, x);
}
//头删(重点)
void LTPopFront(LTNode* phead)
{
//断言
assert(phead);
//头指向不能为空
assert(phead->next != phead);
//找到梢兵位的节点
LTNode* first = phead->next;
//找到头后面一个元素
LTNode* second = first->next;
//释放内存
free(first);
//梢兵位的节点指向头后面一个元素
phead->next = second;
//后面一个元素指向梢兵位的节点
second->prev = phead;
//双向链表删除pos位置的结点(本质上和头删一样)
//LTErase(phead->next);
}
//统计双链表元素个数
int LTSize(LTNode* phead)
{
//断言
assert(phead);
int size = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
++size;
cur = cur->next;
}
return size;
}
// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x)
{
//断言
assert(pos);
LTNode* posPrev = pos->prev;
//为插入的元素开辟空间
LTNode* newnode = BuyLTNode(x);
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
// 双向链表删除pos位置的结点
void LTErase(LTNode* pos)
{
//断言
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
//释放内存
free(pos);
posPrev->next = posNext;
posNext->prev = posPrev;
}
//清理
void ListClear(LTNode* phead)
{
//断言
assert(phead);
//清理全部数据,保留头结点
LTNode* cur = phead->next;
//循环销毁
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = NULL;
cur = next;
}
}
//销毁
void ListDestory(LTNode** pphead)
{
//断言
assert(*pphead);
//调用清理(函数)
ListClear(*pphead);
//释放内存
free(*pphead);
*pphead = NULL;
}
🌟结束语
今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小说手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。文章来源:https://www.toymoban.com/news/detail-723932.html
文章来源地址https://www.toymoban.com/news/detail-723932.html
到了这里,关于手撕双链表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!