꧁ 各位大佬们好!很荣幸能够得到您的访问,让我们一起在编程道路上任重道远!꧂
☙ 博客专栏:【数据结构初阶】❧
⛅ 本篇内容简介:数据结构初阶中的双向循环带头链表的实现!
⭐ 了解作者:励志成为一名编程大牛的学子,目前正在升大二的编程小白。
✍励志术语:编程道路的乏味,让我们一起学习变得有趣!
文章目录
✀ 前言
在前面中,简单介绍了带头双向循环链表的结构,在这里我们来一起实现一下这个双向链表,我们先来看一下这个的结构:
其中head是作为哨兵位的头节点,不存储数据(包括链表的长度)。因为链表存储的数据可能不仅仅是int类型。
✂ 双向链表接口的实现
✀ 双向链表的结构
从上面的结构中,我们可以看到有:
1. 数据的存储
2. 一个前指针
3. 一个后指针
代码实现:
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
LTDataType Data;
struct ListNode* next;
}ListNode;
✀ 双向链表的初始化
//双向链表的初始化 不用二级指针,利用返回值
ListNode* ListInit();
带头节点链表的初始化就是,创建一个哨兵位的头结点,头节点的prev指向自己,next也指向自己。在这里我们可以利用一级指针,也可以用二级指针,如果利用一级指针,我们就需要初始化函数的返回值。
图解结构:
代码实现:
//双向链表的初始化 不用二级指针,利用返回值
ListNode* ListInit()
{
//创造哨兵位的头节点
ListNode* guard = (ListNode*)malloc(sizeof(ListNode));
if (guard == NULL)
{
perror("malloc fail");
exit(-1);
}
guard->next = guard;
guard->prev = guard;
return guard;
}
✀ 双向链表的创建一个新节点
//节点的创建
ListNode* BuyListNode(LTDataType x);
//节点的创建
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
//判空
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->Data = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
✀ 双向链表的打印
//双向链表的打印
void ListPrint(ListNode* phead);
思路:打印函数的实现,就是直接遍历,定义一个phead后一个的指针,当cur != phead时就打印Data值。
图解:
代码实现:
//双向链表的打印
void ListPrint(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
printf("guard<=>");
while (cur != phead)
{
printf("%d<=>", cur->Data);
cur = cur->next;
}
printf("\n");
}
✀ 双向链表的尾插
//双向链表的尾插
void ListPushBack(ListNode* phead, LTDataType x);
思路:双向链表的尾插非常简单,因为头节点的prev就尾,先定义一个尾节点tail,然后直接将新节点链接到tail节点上即可。
图解:
代码实现:
//双向链表的尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
//创造节点
ListNode* newnode = BuyListNode(x);
ListNode* tail = phead->prev;
// 记录尾的前一个
phead->prev = newnode;
newnode->next = phead;
newnode->prev = tail;
tail->next = newnode;
}
测试尾插(将1,2,3,4数据尾插进入链表):
✀ 双向链表的尾删
//双向链表的尾删
void ListPopBack(ListNode* phead);
思路:我们需要记录尾节点的前一个,将phead的prev链接到尾节点的前一个。注:(这里需要对链表进行判空处理)
图解:
代码实现:
//双向链表的尾删
void ListPopBack(ListNode* phead)
{
assert(phead);
//判空
assert(!ListEmpty(phead));
ListNode* tail = phead->prev;
//记录前一个
ListNode* prev = tail->prev;
phead->prev = prev;
prev->next = phead;
free(tail);
tail = NULL;
}
测试尾删(成功尾删):
✀ 双向链表的头插
//双向链表的头插
void ListPushFront(ListNode* phead, LTDataType x);
思路:我们需要记录phead的下一个(next),再将newnode链接到phead与next之间。
图解:
代码实现:
//双向链表的头插
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = BuyListNode(x);
//记录phead的后一个
ListNode* next = phead->next;
phead->next = newnode;
newnode->next = next;
next->prev = newnode;
newnode->prev = phead;
}
测试头插(尾插10,20,30,40):
✀ 双向链表的头删
//双向链表的头删
void ListPopFront(ListNode* phead);
思路:我们需要记录phead的下一个(first),再记录first的下一个(second),将phead与second链接,最后释放first节点。
图解:
代码实现:
//双向链表的头删
void ListPopFront(ListNode* phead)
{
assert(phead);
//判空
assert(!ListEmpty(phead));
//记录第一个与第二个
ListNode* first = phead->next;
ListNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}
测试头删(成功头删):
✀ 双向链表的判空
//双向链表的判空
bool ListEmpty(ListNode* phead);
思路:判断phead->next等不等于phead,如果不等不为空,等于为空。
代码实现:
//双向链表的判空
bool ListEmpty(ListNode* phead)
{
assert(phead);
return phead->next == phead;
}
✀ 求双向链表的长度
//双向链表的长度
size_t ListSize(ListNode* phead);
思路:采用计数器遍历的方法,cur为phead的下一个,!= phead,count++。
代码实现:
//双向链表的长度
size_t ListSize(ListNode* phead)
{
assert(phead);
size_t count = 0;
ListNode* cur = phead->next;
while (cur != phead)
{
count++;
cur = cur->next;
}
return count;
}
✀ 求双向链表的查找
//双向链表的查找
ListNode* ListFind(ListNode* phead, LTDataType x);
思路:也是采用遍历的方法,Data值==x,返回cur的地址,迭代往后走。
代码实现:
//双向链表的查找
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;
}
✀ 双向链表在pos之前插入
//双向链表在pos之前插入
void ListInsert(ListNode* pos, LTDataType x);
思路:记录pos位置的前一个(prev),在prev节点与pos之间链接newnode节点。
图解:
代码实现:
//双向链表在pos之前插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
//记录pos的前一个
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->next = pos;
pos->prev = newnode;
newnode->prev = prev;
}
测试在pos位置之前插入(和ListFind函数配合使用):
✀ 双向链表删除pos位置
//双向链表删除pos位置
void ListErase(ListNode* pos);
思路:记录pos位置的前一个(prev),和pos位置的后一个(next),将prev节点与next节点链接,最后释放pos节点。
图解:
测试删除pos位置节点(例:pos为10位置的节点):
✀ 双向链表的销毁
//双向链表的销毁
void ListDestroy(ListNode* phead);
思路:定义cur指针为phead的next,next指针为cur的next,释放cur,cur再等于next,迭代往后走。最后guard哨兵位的节点,我们手动制空。
图解:
代码实现:
//双向链表的销毁
void ListDestroy(ListNode* phead)
{
assert(phead);
//遍历销毁
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
✂ 项目源代码
✀ List.h文件
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
LTDataType Data;
struct ListNode* next;
}ListNode;
//双向链表的初始化 不用二级指针,利用返回值
ListNode* ListInit();
//节点的创建
ListNode* BuyListNode(LTDataType x);
//双向链表的打印
void ListPrint(ListNode* phead);
//双向链表的尾插
void ListPushBack(ListNode* phead, LTDataType x);
//双向链表的尾删
void ListPopBack(ListNode* phead);
//双向链表的头插
void ListPushFront(ListNode* phead, LTDataType x);
//双向链表的头删
void ListPopFront(ListNode* phead);
//双向链表的判空
bool ListEmpty(ListNode* phead);
//双向链表的长度
size_t ListSize(ListNode* phead);
//双向链表的查找
ListNode* ListFind(ListNode* phead, LTDataType x);
//双向链表在pos之前插入
void ListInsert(ListNode* pos, LTDataType x);
//双向链表删除pos位置
void ListErase(ListNode* pos);
//双向链表的销毁
void ListDestroy(ListNode* phead);
✀ List.c文件
#include"List.h"
//双向链表的初始化 不用二级指针,利用返回值
ListNode* ListInit()
{
//创造哨兵位的头节点
ListNode* guard = (ListNode*)malloc(sizeof(ListNode));
if (guard == NULL)
{
perror("malloc fail");
exit(-1);
}
guard->next = guard;
guard->prev = guard;
return guard;
}
//节点的创建
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
//判空
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->Data = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
//双向链表的打印
void ListPrint(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
printf("guard<=>");
while (cur != phead)
{
printf("%d<=>", cur->Data);
cur = cur->next;
}
printf("\n");
}
//双向链表的尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
创造节点
//ListNode* newnode = BuyListNode(x);
//ListNode* tail = phead->prev;
记录尾的前一个
//phead->prev = newnode;
//newnode->next = phead;
//newnode->prev = tail;
//tail->next = newnode;
ListInsert(phead, x);
}
//双向链表的尾删
void ListPopBack(ListNode* phead)
{
assert(phead);
//判空
assert(!ListEmpty(phead));
//ListNode* tail = phead->prev;
记录前一个
//ListNode* prev = tail->prev;
//phead->prev = prev;
//prev->next = phead;
//free(tail);
//tail = NULL;
ListErase(phead->prev);
}
//双向链表的判空
bool ListEmpty(ListNode* phead)
{
assert(phead);
return phead->next == phead;
}
//双向链表的长度
size_t ListSize(ListNode* phead)
{
assert(phead);
size_t count = 0;
ListNode* cur = phead->next;
while (cur != phead)
{
count++;
cur = cur->next;
}
return count;
}
//双向链表的头插
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
//ListNode* newnode = BuyListNode(x);
记录phead的后一个
//ListNode* next = phead->next;
//phead->next = newnode;
//newnode->next = next;
//next->prev = newnode;
//newnode->prev = phead;
ListInsert(phead->next,x);
}
//双向链表的头删
void ListPopFront(ListNode* phead)
{
assert(phead);
//判空
assert(!ListEmpty(phead));
记录第一个与第二个
//ListNode* first = phead->next;
//ListNode* second = first->next;
//phead->next = second;
//second->prev = phead;
//free(first);
//first = NULL;
ListErase(phead->next);
}
//双向链表的销毁
void ListDestroy(ListNode* phead)
{
assert(phead);
//遍历销毁
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
//双向链表的查找
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;
}
//双向链表在pos之前插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
//记录pos的前一个
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->next = pos;
pos->prev = newnode;
newnode->prev = prev;
}
//双向链表删除pos位置
void ListErase(ListNode* pos)
{
assert(pos);
//记录前一个与后一个
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
pos = NULL;
}
✀ test.c文件
#include"List.h"
void test1()
{
ListNode* plist = ListInit();
//测试尾插
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//打印
ListPrint(plist);
//测试尾删
ListPopBack(plist);
ListPopBack(plist);
ListPopBack(plist);
ListPrint(plist);
//测试头插
ListPushFront(plist, 10);
ListPushFront(plist, 20);
ListPushFront(plist, 30);
ListPushFront(plist, 40);
ListPrint(plist);
//测试头删
ListPopFront(plist);
ListPrint(plist);
//测试在pos之前插入
ListInsert(ListFind(plist, 20), 50);
ListPrint(plist);
//删除pos位置
ListErase(ListFind(plist, 10));
ListPrint(plist);
ListDestroy(plist);
plist = NULL;//手动制空
}
int main()
{
test1();
return 0;
}
✂ 结束语
相信各位大佬们都已经开学了,那我们就放下假期玩耍的心,一起开心写代码叭!开学第一遍博客— 双向带头循环链表的实现送给大家,祝各位大佬在学业上节节攀升!
【写在最后·想告诉你】
在互联网这个行业里
任何时候都要学好技术
永远都是 技术为王