目录
链表的分类
Test.c主函数
test1头插
test2头删
test3尾插
test4尾删
test5查询
test6插入pos位置后一个
test7删除pos
Test.c总代码
DList.h头文件&函数声明
头文件
函数声明
DList.h总代码
DList.c函数实现
Createnode创建节点
LTInit初始化
LTPrint打印
LTPushFront头插
LTPopFron头删
LTPushBack尾插
LTPopBack尾删
LTFind查询
LTInsertAfter在pos后面插入
LTErase删除pos数据
LTDestroy销毁释放
DList.c总代码
今天实现一个带头双向循环链表。
- 🙂判断为NULL的情况
- 🙂判断二级指针&一级指针(想要改变实参不仅可以用指针,而且可以用return
- 🙂判断先实现哪一步
- ❌野指针的出现
- ❌修改指针用二级指针
- ❌修改结构体用一级指针
- ❌内存泄露的问题,需要释放
- ❌释放完空间,指针需要置NULL
- 🆗无头单项不循环链表适用OJ链表比较多
- 🆗带头双向不循环适用生活工作场景比较多
- 🆗C++是兼容C
- ❌【单链表】一般都不带头//需要二级指针处理//除了!链表分割
- ❌【双向链表】需要带头
- 🙂【单链表】Singly linked list(SLT)
- 🙂【双链表】Double linked list(LT)
- 🙂【顺序表】Sequention table list(SL)
链表的分类
前面我们学习了单链表。但是链表并不仅限只有【单链表】。【链表】是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
单向或者双向
带头或者不带头
循环或者非循环
这些特征组合起来可以构成:8种形态的链表。
虽然有这么多的链表的结构,但是我们实际中最常用的还是有两种结构。
【无头单项非循环链表】&【带头双向循环链表】
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。实际生活中使用很多。
在前面我们已经学过 无头单项不循环链表,这里我们来实现 带头双向循环链表
Test.c主函数
int main()
{
LTNode* phead = LTInit();//初始化
test1(phead);//头插
test2(phead);//头删
test3(phead);//尾插
test4(phead);//尾删
test5(phead);//查询某个数字
test6(phead);//插入pos位置的前一个/后一个元素
test7(phead);//删除pos位置的元素
LTDestroy(phead);//空间释放/防止内存泄露
return 0;
}
test1头插
#include"DList.h"
void test1(LTNode* phead)//头插
{
LTPushFront(phead, 7);
LTPushFront(phead, 3);
LTPushFront(phead, 4);
LTPushFront(phead, 7);
LTPushFront(phead, 8);
LTPushFront(phead, 9);
LTPrint(phead);
}
test2头删
void test2(LTNode* phead)//头删
{
LTPopFront(phead);
LTPopFront(phead);
LTPopFront(phead);
LTPrint(phead);
}
test3尾插
void test3(LTNode* phead)//尾插
{
LTPushBack(phead, 77);
LTPushBack(phead, 99);
LTPrint(phead);
}
test4尾删
void test4(LTNode* phead)//尾删
{
LTPopBack(phead);
LTPopBack(phead);
LTPopBack(phead);
LTPopBack(phead);
LTPrint(phead);
}
test5查询
void test5(LTNode* phead)//查询
{
LTNode* node = LTFind(phead, 4);
if (node == NULL)
{
printf("没找到");
}
else
{
printf("%d", node->val);
}
}
test6插入pos位置后一个
void test6(LTNode* phead)//插入pos位置的前一个/后一个元素
{
LTNode* pos = LTFind(phead, 4);
LTInsertAfter(phead, pos, 77);
LTInsertAfter(phead, pos, 66);
LTInsertAfter(phead, pos, 99);
LTPrint(phead);
}
test7删除pos
//删除pos位置的元素
void test7(LTNode* phead)
{
LTNode* pos = LTFind(phead, 4);
if(pos)//查询成功才进入
{
LTErase(phead, pos);
pos=NULL;
}
LTPrint(phead);
}
Test.c总代码
#include"DList.h"
void test1(LTNode* phead)//头插
{
LTPushFront(phead, 7);
LTPushFront(phead, 3);
LTPushFront(phead, 4);
LTPushFront(phead, 7);
LTPushFront(phead, 8);
LTPushFront(phead, 9);
LTPrint(phead);
}
void test2(LTNode* phead)//头删
{
LTPopFront(phead);
LTPopFront(phead);
LTPopFront(phead);
LTPrint(phead);
}
void test3(LTNode* phead)//尾插
{
LTPushBack(phead, 77);
LTPushBack(phead, 99);
LTPrint(phead);
}
void test4(LTNode* phead)//尾删
{
LTPopBack(phead);
LTPopBack(phead);
LTPopBack(phead);
LTPopBack(phead);
LTPrint(phead);
}
void test5(LTNode* phead)//查询
{
LTNode* node = LTFind(phead, 4);
if (node == NULL)
{
printf("没找到");
}
else
{
printf("%d", node->val);
}
}
void test6(LTNode* phead)//插入pos位置的前一个/后一个元素
{
LTNode* pos = LTFind(phead, 4);
LTInsertAfter(phead, pos, 77);
LTInsertAfter(phead, pos, 66);
LTInsertAfter(phead, pos, 99);
LTPrint(phead);
}
//删除pos位置的元素
void test7(LTNode* phead)
{
LTNode* pos = LTFind(phead, 4);
if(pos)//查询成功才进入
{
LTErase(phead, pos);
pos=NULL;
}
LTPrint(phead);
}
int main()
{
LTNode* phead = LTInit();//已经被初始化了
test1(phead);//头插
test2(phead);//头删
test3(phead);//尾插
test4(phead);//尾删
test5(phead);//查询某个数字
test6(phead);//插入pos位置的前一个/后一个元素
test7(phead);//删除pos位置的元素
LTDestroy(phead);//空间释放/防止内存泄露
phead=NULL;
return 0;
}
DList.h头文件&函数声明
头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
assert(//)//括号里是可以出现的情况为真 若括号里的表达式为假那就❌
函数声明
- 声明节点
typedef int LTDateType;
//声明节点
typedef struct DListNode
{
LTDateType val;
struct DListNode* prev;
struct DListNode* next;
}LTNode;
- 初始化(可以用指针修改实参/也可以用返回值改变实参)
LTNode* LTInit();
- 头插
void LTPushFront(LTNode*phead, LTDateType x);
- 头删
void LTPopFront(LTNode* phead);
- 尾插
void LTPushBack(LTNode* phead,LTDateType x);
- 尾删
void LTPopBack(LTNode* phead);
- 打印
void LTPrint(LTNode* phead);
- 查询
LTNode* LTFind(LTNode* phead);
- 在pos的后面插入一个元素
void LTInsertAfter(LTNode* phead, LTNode* pos, LTDateType x);
- 删除pos位置的元素
void LTErase(LTNode* phead, LTNode* pos);
- 销毁释放
void LTDestroy(LTNode* phead);
DList.h总代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDateType;
//定义节点
typedef struct DListNode
{
LTDateType val;
struct DListNode* prev;
struct DListNode* next;
}LTNode;
//初始化
LTNode* LTInit();//不用二级指针/用返回值改变实参
void LTPushFront(LTNode*phead, LTDateType x);//头插
void LTPopFront(LTNode* phead);//头删
void LTPushBack(LTNode* phead,LTDateType x);//尾插
void LTPopBack(LTNode* phead);//尾删
void LTPrint(LTNode* phead);//打印
LTNode* LTFind(LTNode* phead);//查询
void LTInsertAfter(LTNode* phead, LTNode* pos, LTDateType x);//在pos的后面插入一个元素
void LTErase(LTNode* phead, LTNode* pos);//删除pos位置的元素
void LTDestroy(LTNode* phead);//销毁释放
DList.c函数实现
//初始化
LTNode* LTInit()
{
LTNode* phead = Createnode(-1);
phead->prev = phead;
phead->next = phead;
return phead;
}
Createnode创建节点
#include"DList.h"
//创造节点
LTNode* Createnode(LTDateType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc");
exit(-1);//程序停止
}
newnode->val = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
LTInit初始化
- 通过形参改变实参可以使用 【指正】
- 通过形参改变实参可以使用 【return】
//初始化
LTNode* LTInit()
{
LTNode* phead = Createnode(-1);
phead->prev = phead;
phead->next = phead;
return phead;
}
LTPrint打印
//打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead;
printf("<=>");
printf("%d<=>", cur->val);
cur = cur->next;
while (cur != phead)
{
printf("%d<=>", cur->val);
cur = cur->next;
}
printf("\n");
}
LTPushFront头插
//头插
void LTPushFront(LTNode* phead,LTDateType x)
{
assert(phead);
LTNode* newnode = Createnode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
LTPopFron头删
- 一定不能把头节点哨兵位给删除了会出现野指针的问题❌
//头删
void LTPopFront(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
LTNode* next = cur->next;
assert(cur != phead); //考虑如果链表为NULL
phead->next = next;
next->prev = phead;
free(cur);
cur = NULL;
}
LTPushBack尾插
//尾插
void LTPushBack(LTNode* phead, LTDateType x)
{
assert(phead);
LTNode* newnode = Createnode(x);
newnode->prev = phead->prev;
phead->prev->next = newnode;
newnode->next = phead;
phead->prev = newnode;
}
LTPopBack尾删
- 一定不能把头节点哨兵位给删除了会出现野指针的问题❌
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->prev;
LTNode* next = cur->prev;
assert(cur != phead);
phead->prev = next;
next->next = phead;
free(cur);
cur = NULL;
}
LTFind查询
- 查询到某个数据的位置意味着可以修改这个位置前后的数据
//查询
//查询某个数,存在返回节点地址,不存在返回NULL
LTNode* LTFind(LTNode* phead, LTDateType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
LTInsertAfter在pos后面插入
//在pos的后面插入一个元素
void LTInsertAfter(LTNode* phead, LTNode*pos, LTDateType x)
{
assert(phead);
LTNode* newnode = Createnode(x);
newnode->next = pos->next;
pos->next->prev = newnode;
pos->next = newnode;
newno
LTErase删除pos数据
//删除pos位置的元素//
void LTErase(LTNode* phead, LTNode* pos)
{
assert(phead);
assert(phead->next != phead);
LTNode* cur = pos->prev;
LTNode* next = pos->next;
cur->next = next;
next->prev = cur;
free(pos);
pos = NULL;
}
LTDestroy销毁释放
//销毁
void LTDestroy(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* tmp = cur->next;
free(cur);
cur = tmp;
}
}
DList.c总代码
#include"DList.h"
//创造节点
LTNode* Createnode(LTDateType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc");
exit(-1);//程序停止
}
newnode->val = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
//初始化
LTNode* LTInit()
{
LTNode* phead = Createnode(-1);
phead->prev = phead;
phead->next = phead;
return phead;
}
//打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead;
printf("<=>");
printf("%d<=>", cur->val);
cur = cur->next;
while (cur != phead)
{
printf("%d<=>", cur->val);
cur = cur->next;
}
printf("\n");
}
//头插
void LTPushFront(LTNode* phead,LTDateType x)
{
assert(phead);
LTNode* newnode = Createnode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
LTNode* next = cur->next;
assert(cur != phead); //考虑如果链表为NULL
phead->next = next;
next->prev = phead;
free(cur);
cur = NULL;
}
//尾插
void LTPushBack(LTNode* phead, LTDateType x)
{
assert(phead);
LTNode* newnode = Createnode(x);
newnode->prev = phead->prev;
phead->prev->next = newnode;
newnode->next = phead;
phead->prev = newnode;
}
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->prev;
LTNode* next = cur->prev;
assert(cur != phead);
phead->prev = next;
next->next = phead;
free(cur);
cur = NULL;
}
//查询
//查询某个数,存在返回节点地址,不存在返回NULL
LTNode* LTFind(LTNode* phead, LTDateType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//在pos的后面插入一个元素
void LTInsertAfter(LTNode* phead, LTNode*pos, LTDateType x)
{
assert(phead);
LTNode* newnode = Createnode(x);
newnode->next = pos->next;
pos->next->prev = newnode;
pos->next = newnode;
newnode->prev = pos;
}
//删除pos位置的元素//
void LTErase(LTNode* phead, LTNode* pos)
{
assert(phead);
assert(phead->next != phead);
LTNode* cur = pos->prev;
LTNode* next = pos->next;
cur->next = next;
next->prev = cur;
free(pos);
pos = NULL;
}
//销毁
void LTDestroy(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* tmp = cur->next;
free(cur);
cur = tmp;
}
cur=NULL;
}
//形式参数这里就可以置NULL
//实际参数到主函数里面置NULL
✔✔✔✔✔最后感谢大家的阅读,若有错误和不足,欢迎指正!乖乖敲代码哦!
代码---------→【唐棣棣 (TSQXG) - Gitee.com】
联系---------→【邮箱:2784139418@qq.com】