本期带大家一起用C语言实现双向链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的;简单来说,线性表的链式存储结构生成的表,称作“链表”。
每个元素本身由两部分组成:
1、本身的信息,称为“数据域”
2、指向直接后继的指针,称为“指针域”
1、带哨兵位或者不带哨兵位
2、循环或者不循环3、单向或者双向
这里先建立三个文件:
1️⃣ :SeqList.h文件,用于函数声明
2️⃣ :SeqList.c文件,用于函数的定义
3️⃣ :Test.c文件,用于测试函数
建立三个文件的目的: 将顺序表作为一个项目来进行书写,方便我们的学习与观察。
双向链表的结构为
存储的有效数据data
指向后一个数据地址的指针next
指向前一个数据指针的数据prev
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
这里我们使用typedef对我们所存储的数据,以及双向链表结构体重命名,方便我们后续修改
LTNode* CreatNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
LTNode* LTInit()
{
LTNode* phead = CreatNode(-1);
phead->next = phead;
phead->prev = phead;
}
创建哨兵位节点
对链表进行尾插操作,添加数据到链表的尾部 ☘️
void LTPushBack(LTNode* phead,LTDataType x)
{
assert(phead);
LTNode* newnode = CreatNode(x);
LTNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
phead->prev = newnode;
newnode->next = phead;
}
首先我们需要创建一个新的节点,然后将我们的双向链表和我们创建的新节点进行链接起来
对链表进行头插操作,添加数据到链表的头部
但注意的是:不是添加到哨兵位的前面
而是添加到哨兵位下一个节点的前面
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* node = CreatNode(x);
node->next = phead->next;
phead->next->prev = node;
phead->next = node;
node->prev = phead;
}
bool LTEmpty(LTNode* phead)
{
return phead == phead->next;
}
对于 双向链表 的尾删,只要找到尾结点的前一个节点改变它和哨兵位的连接关系即可
如果要找到尾结点的前一个节点,那么我只需要通过
哨兵位 的 prev 找到 尾,在通过 尾 的 prev 就可以找到 尾结点的前一个节点。然后调整这个节点和哨兵位的链接关系,然后 释放尾结点 就可以了
但是要注意,当链表只有哨兵位的时候不能进行删除操作!!!✈️
如图所示
void LTPopBack(LTNode* phead)
{
assert(phead);
if (LTEmpty(phead))
{
printf("链表为空\n");
return;
}
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
上面说过,如果当链表只有哨兵位的时候不能进行删除操作!!!
所以我们通过了一个函数来检查当前链表是否只有一个哨兵位✈️ ✈️
bool LTEmpty(LTNode* phead)
{
return phead == phead->next;
}
对于头删来说,我需要删除 哨兵位的 next 节点 ,我们
需要连接哨兵位和哨兵位next的next,然后释放哨兵位的next
但是同样需要注意的是:链表当中只有哨兵位的时候我们不能对其删除!!!
void LTPopFront(LTNode* phead)
{
assert(phead);
if (LTEmpty(phead))
{
printf("链表为空\n");
return;
}
LTNode* next = phead->next->next;
phead->next = next;
phead->next->prev = phead;
}
当我们需要查找链表当中是否存在某个元素的时候,我们需要链表,看是否存在该个元素
但是带头双向循环链表当中不存在NULL
所以停止遍历的时候我们的终止条件为!=·phead
如果找到,返回该节点的地址;如果找不到返回 NULL
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
在 pos 位置之前插入,那么 通过 pos 的 prev 找到 pos 位置的上一个节点 posPrev ,然后改变 posPrev 和 新节点 newnode 之间的链接和 newnode 和 pos 之间的链接
需要同LTFind一起使用
void LTInsert(LTNode* pos, LTDataType x)
{
LTNode* node = CreatNode(x);
LTNode* posPrev = pos->prev;
posPrev->next = node;
node->next = pos;
node->prev = posPrev;
pos->prev = node;
}
代码实现逻辑图如下所示
那么很好,有了这个接口之后,可以把它 复用 于 尾插 和 头插。
对于 尾插 来说, pos 位置就是 phead ,因为 phead 的前面就是
链表的尾,在 phead 位置前插入,就是尾插:
void LTPushBack(LTNode* phead,LTDataType x)
{
assert(phead);
LTInsert(phead, x);
}
对于 头插 来说, pos 位置就是 phead->next
为第一个节点的前面,在 phead->next 位置前插入,就是头插:
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead->next, x);
}
在 pos 位置删除, 只要找到 pos 的前一个节点 posPrev
然后找到 pos 的后一个节点 posNext ,然后将这两个节点的 prev 和 next 建立正确的链接关系。然后释放 pos 节点,pos 节点置空。
void LTErase(LTNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
}
同样的,这个接口也能复用于 尾删 和 头删 。
对于 尾删 来说,, pos 位置就是 phead->prev
为链表的尾,删除 phead->prev 位置,就是尾删:
为了避免空指针异常,我们需要进行判断
void LTPopBack(LTNode* phead)
{
assert(phead);
if (LTEmpty(phead))
{
printf("链表为空\n");
return;
}
LTErase(phead->prev);
}
对于 头删 来说, pos 位置就是 phead->next
为链表的头,删除 phead->next 位置,就是头删:
为了避免空指针异常,我们需要进行判断
void LTPopFront(LTNode* phead)
{
assert(phead);
if (LTEmpty(phead))
{
printf("链表为空\n");
return;
}
LTErase(phead->next);
}
打印整个链表,就只需要遍历链表,控制好循环的停止条件:
循环终止条件设置为!=phead
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
printf("哨兵位->");
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
我需要把 哨兵位 ,链表的节点 全部删除,那么我就要使用循环来删除。
循环的结束条件为 != phead。在销毁的过程中,每次记住我当前节点的下一个节点,以便迭代
但是由于我们的 形参是一级指针
所以不能够将哨兵位正常销毁,我们需要在主函数当中手动将哨兵位置为NULL
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(cur);
}
#pragma once
#include
#include
#include
#include
#include
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
//初始化
LTNode* LTInit();
//打印链表
void LTPrint(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead,LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead,LTDataType x);
//pos之前插入
void LTInsert(LTNode* pos, LTDataType x);
//删除pos位置的值
void LTErase(LTNode* pos);
//销毁
void LTDestroy(LTNode* phead);
#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
LTNode* CreatNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
LTNode* LTInit()
{
LTNode* phead = CreatNode(-1);
phead->next = phead;
phead->prev = phead;
}
bool LTEmpty(LTNode* phead)
{
return phead == phead->next;
}
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
printf("哨兵位->");
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
void LTPushBack(LTNode* phead,LTDataType x)
{
assert(phead);
//LTNode* newnode = CreatNode(x);
//LTNode* tail = phead->prev;
//tail->next = newnode;
//newnode->prev = tail;
//phead->prev = newnode;
//newnode->next = phead;
LTInsert(phead, x);
}
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
//LTNode* node = CreatNode(x);
//node->next = phead->next;
//phead->next->prev = node;
//phead->next = node;
//node->prev = phead;
LTInsert(phead->next, x);
}
void LTPopBack(LTNode* phead)
{
assert(phead);
/*if (LTEmpty(phead))
{
printf("链表为空\n");
return;
}
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;*/
if (LTEmpty(phead))
{
printf("链表为空\n");
return;
}
LTErase(phead->prev);
}
void LTPopFront(LTNode* phead)
{
assert(phead);
/*if (LTEmpty(phead))
{
printf("链表为空\n");
return;
}
LTNode* next = phead->next->next;
phead->next = next;
phead->next->prev = phead;*/
if (LTEmpty(phead))
{
printf("链表为空\n");
return;
}
LTErase(phead->next);
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
void LTInsert(LTNode* pos, LTDataType x)
{
LTNode* node = CreatNode(x);
LTNode* posPrev = pos->prev;
posPrev->next = node;
node->next = pos;
node->prev = posPrev;
pos->prev = node;
}
void LTErase(LTNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
}
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(cur);
}
#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
void test1()
{
printf("尾插:");
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPrint(plist);
LTDestroy(plist);
printf("\n");
}
void test2()
{
printf("头插:");
LTNode* plist = LTInit();
LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPushFront(plist, 3);
LTPushFront(plist, 4);
LTPushFront(plist, 5);
LTPrint(plist);
LTDestroy(plist);
plist = NULL;
printf("\n");
}
void test3()
{
printf("尾删:");
LTNode* plist = LTInit();
LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPopBack(plist);
LTPopBack(plist);
LTPopBack(plist);
LTPopBack(plist);
LTPrint(plist);
LTDestroy(plist);
printf("\n");
}
void test4()
{
printf("头删:");
LTNode* plist = LTInit();
LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPopFront(plist);
LTPopFront(plist);
LTPopFront(plist);
LTPopFront(plist);
LTPrint(plist);
LTDestroy(plist);
printf("\n");
}
void test5()
{
printf("pos之前插入");
LTNode* plist = LTInit();
LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPushFront(plist, 3);
LTPushFront(plist, 4);
LTPushFront(plist, 5);
LTNode* pos = LTFind(plist,5);
if (pos != NULL)
{
LTInsert(pos, 9);
}
LTPrint(plist);
LTDestroy(plist);
printf("\n");
}
void test6()
{
printf("删除pos的值:");
LTNode* plist = LTInit();
LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPushFront(plist, 3);
LTPushFront(plist, 4);
LTPushFront(plist, 5);
LTNode* pos = LTFind(plist, 3);
if (pos != NULL)
{
LTErase(pos);
}
LTPrint(plist);
LTDestroy(plist);
printf("\n");
}
int main()
{
test1();
test2();
test3();
test4();
test5();
test6();
return 0;
}
如果大家通过本篇博客收获了,对顺序表有了新的了解的话
那么希望支持一下哦如果还有不明白的,疑惑的话,或者什么比较好的建议的话,可以发到评论区,
我们一起解决,共同进步 ❗️❗️❗️
最后谢谢大家❗️❗️❗️