前言
📚作者简介:爱编程的小马,正在学习C/C++,Linux及MySQL。
📚本文收录与初阶数据结构系列,本专栏主要是针对时间、空间复杂度,顺序表和链表、栈和队列、二叉树以及各类排序算法,持续更新!
📚相关专栏C++及Linux正在发展,敬请期待!
目录
前言
1. 带头双向循环链表的实现
1.1 概念
1.2 链表的实现
1.2.1 链表的基本搭建
1.2.2 结点动态内存开辟的函数
1.2.3 链表的初始化
1.2.4 链表的打印
1.2.5 链表的尾插
1.2.6 链表的头插
1.2.7 链表的尾删
1.2.8 链表的头删
1.2.9 链表的查找
1.2.10 链表的任意位置前插
1.2.11 链表在指定位置的删除
1.2.12 释放链表
2. 完整代码
2.1 List.c
2.2 List.h
2.3 test.c
总结
1. 带头双向循环链表的实现
1.1 概念
之前我给大家讲过不带头单向不循环链表,这次我再给大家介绍一个也同样很重要的,叫做带头双向循环链表。
带头双向循环链表,顾名思义,就是带哨兵位,有前后指针,有循环的链表。
结构如下:
1.2 链表的实现
1.2.1 链表的基本搭建
typedef int LTtypeData;typedef struct LTNode
{struct LTNode* prev;struct LTNode* next;LTtypeData data;}LTNode;
首先,双向表示需要两个指针,一个是prev指针,可以找到前一个结点,一个是next指针,可以找到下一个结点,还有一个data来存放数据。
1.2.2 结点动态内存开辟的函数
这个地方很重要,因为后面基本都是要通过这个函数来申请结点,
LTNode* BuyLTNode(LTtypeData x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc failed");return NULL;}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
1.2.3 链表的初始化
链表的初始化其实很简单,申请一个结点,然后让结点自己指向自己,构成循环。看下图
代码实现:
LTNode* LTcreate()
{LTNode* phead = BuyLTNode(-1);assert(phead);phead->prev = phead;phead->next = phead;return phead;
}
为什么要返回结构体指针?因为外面结构体指针想改变,是不是要么就是传外面结构体的地址,要么就是带返回值,很明显后者更简单。
1.2.4 链表的打印
这个带循环的链表就不是到空指针就结束这么判断了,而是不等于头结点就继续,等于是不是代表已经循环完一圈了,就结束。
void LTPrint(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;printf("guard<==>");while (cur != phead){printf("%d<==>", cur->data);cur = cur->next;}printf("\n");
}
1.2.5 链表的尾插
尾插需不需要像单链表一样找尾, 是不是不需要啊?哪里是尾?是不是phead->prev是尾,看图
代码实现:
void LTPushBack(LTNode* phead, LTtypeData x)
{assert(phead);LTNode* newnode = BuyLTNode(x);LTNode* tail = phead->prev;newnode->prev = tail;tail->next = newnode;phead->prev = newnode;newnode->next = phead;
}
首先我们先创建一个newnode新节点,要尾插的结点,然后将尾部和新结点相连接是不是就可以了,在把新结点和头结点相连接是不是就尾插成功了?
1.2.6 链表的头插
头插怎么做?是不是需要找到phead,和记录phead的next,也就是下一个,然后让newnode结点和phead与next相连接就ok。
void LTPushFront(LTNode* phead, LTtypeData x)
{assert(phead);LTNode* newnode = BuyLTNode(x);LTNode* next = phead->next;phead->next = newnode;newnode->prev = phead;newnode->next = next;next->prev = newnode;
}
1.2.7 链表的尾删
删除要特别注意,链表为空就不要删了。还需不需要像单链表一样找尾,再找尾的前一个?不需要,看图:
代码实现:
void LTPopBack(LTNode* phead)
{assert(phead);//没有再删是要出问题的assert(!IsEmpty(phead));LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;free(tail);phead->prev = tailPrev;tailPrev->next = phead;
}
在这里介绍一下这个bool函数,这个可以表示是真还是假,那么函数设置
bool IsEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}
这个代码的意思是,如果返回值是真,说明一件事,就是确实是空链表,那assert断言的时候加一个逻辑非即可表示false,便于我们判断是不是空链表。
1.2.8 链表的头删
这个就很简单了,找到链表的下一个和下下一个,让链表指向下下一个结点即可,看图:
代码实现:
void LTPopFront(LTNode* phead)
{//方法一 一个指针assert(phead);assert(!IsEmpty(phead));LTNode* next = phead->next;phead->next = next->next;next->next->prev = phead;
}
1.2.9 链表的查找
这个就更加的简单了,输入要找的值,返回这个地方的结构体指针就ok
LTNode* LTFind(LTNode* phead, LTtypeData x)
{assert(phead);assert(!IsEmpty(phead));LTNode* cur = phead->next;while (cur != phead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}
1.2.10 链表的任意位置前插
在pos前插,是不是需要找到pos前面的位置,互相链接即可。
void ListInsert(LTNode* pos, LTtypeData x)
{assert(pos);LTNode* posprev = pos->prev;LTNode* newnode = BuyLTNode(x);posprev->next = newnode;newnode->prev = posprev;newnode->next = pos;pos->prev = newnode;
}
那么我就问大家两个问题,如何用任意位置的前插来表示头插和尾插?
首先来讲下头插:
头插是不是传phead->next是不是就可以啊,为什么?
这个是标准的头插。
2、尾插传什么参数?
尾插是不是要找尾,谁的prev是尾,是不是phead,是不是传phead就可以了?是的。
1.2.11 链表在指定位置的删除
是不是很简单,为什么?因为找指定位置的前一个和后一个,最后链接起来是不是就可以了?
void ListErase(LTNode* pos)
{assert(pos);assert(!IsEmpty(pos));LTNode* posprev = pos->prev;LTNode* posnext = pos->next;free(pos);posprev->next = posnext;posnext->prev = posprev;
}
调用这个函数,如何实现尾删和头删?
尾删:是不是传phead->prev就可以?是的
头删:是不是传phead->next就可以 ?是的
1.2.12 释放链表
这个就是和单链表相同,一前一后指针即可。
void Destory(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}
2. 完整代码
2.1 List.c
#define _CRT_SECURE_NO_WARNINGS 1#include"List.h"LTNode* BuyLTNode(LTtypeData x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc failed");return NULL;}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}bool IsEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}LTNode* LTcreate()
{LTNode* phead = BuyLTNode(-1);assert(phead);phead->prev = phead;phead->next = phead;return phead;
}void LTPushBack(LTNode* phead, LTtypeData x)
{assert(phead);/*LTNode* newnode = BuyLTNode(x);LTNode* tail = phead->prev;newnode->prev = tail;tail->next = newnode;phead->prev = newnode;newnode->next = phead;*/ListInsert(phead, x);
}void LTPrint(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;printf("guard<==>");while (cur != phead){printf("%d<==>", cur->data);cur = cur->next;}printf("\n");
}void LTPushFront(LTNode* phead, LTtypeData x)
{assert(phead);/*LTNode* newnode = BuyLTNode(x);LTNode* next = phead->next;phead->next = newnode;newnode->prev = phead;newnode->next = next;next->prev = newnode;*/ListInsert(phead->next, x);}void LTPopBack(LTNode* phead)
{assert(phead);//没有再删是要出问题的assert(!IsEmpty(phead));/*LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;free(tail);phead->prev = tailPrev;tailPrev->next = phead;*/ListErase(phead->prev);
}void LTPopFront(LTNode* phead)
{//方法一 一个指针assert(phead);assert(!IsEmpty(phead));/*LTNode* next = phead->next;phead->next = next->next;next->next->prev = phead;*/ListErase(phead->next);
}LTNode* LTFind(LTNode* phead, LTtypeData x)
{assert(phead);assert(!IsEmpty(phead));LTNode* cur = phead->next;while (cur != phead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}void ListInsert(LTNode* pos, LTtypeData x)
{assert(pos);LTNode* posprev = pos->prev;LTNode* newnode = BuyLTNode(x);posprev->next = newnode;newnode->prev = posprev;newnode->next = pos;pos->prev = newnode;
}void ListErase(LTNode* pos)
{assert(pos);assert(!IsEmpty(pos));LTNode* posprev = pos->prev;LTNode* posnext = pos->next;free(pos);posprev->next = posnext;posnext->prev = posprev;
}void Destory(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}
2.2 List.h
#pragma once#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int LTtypeData;typedef struct LTNode
{struct LTNode* prev;struct LTNode* next;LTtypeData data;}LTNode;//创建新结点
LTNode* LTcreate();//判断是否没有数据
bool IsEmpty(LTNode* phead);
//开始插入数据了
//尾插
void LTPushBack(LTNode* phead, LTtypeData x);//头插
void LTPushFront(LTNode* phead, LTtypeData x);//尾删
void LTPopBack(LTNode* phead);//头删
void LTPopFront(LTNode* phead);
//打印数据
void LTPrint(LTNode* phead);//查找
LTNode* LTFind(LTNode* phead, LTtypeData x);//在pos前面进行插入
void ListInsert(LTNode* pos, LTtypeData x);//删除pos位置的值void ListErase(LTNode* pos);//销毁链表
void Destory(LTNode* phead);
2.3 test.c
这个就留给大家自己去测试函数了。
总结
1、大家一定要自己来写一下带头双向循环链表,因为真的很实用而且很简单
2、下一节给大家更新栈和队列
如果这份博客对大家有帮助,希望各位给小马一个大大的点赞鼓励一下,如果喜欢,请收藏一下,谢谢大家!!!
制作不易,如果大家有什么疑问或给小马的意见,欢迎评论区留言