链表的分类
双向链表是逻辑简单和代码简单
带头不带头,单向双向,循环不循环,两两组合·
双向链表是包括自己的后一个和前一个组成
知道一个就可以全部知道
循环还是不循环是将不循环的原本的头节点的前置为尾结点,尾结点后置为头节点
带头不带头不是指的第一个有效结点,这里的头节点是哨兵位置,不保存任何有效数据,是纯粹的占位置,如果链表之中只有头节点,那么称呼为空链表,作用是不用判断其是否为空链表
常用是单向不循环不带头链表,这里是双向带头循环链表
之后就可以完全实现了
前者结构简单,后者代码简单
这里就是结构图
head的前驱是尾节点,尾结点时头节点
代码实现
目录
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int LTDatatype;
//定义双向链表(结点)结构
typedef struct ListNode {
LTDatatype data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
//初始化
LTNode* LTInit();
//void LTDesTroy(LTNode** pphead);
//接口一致性,传一级,在函数调用结束后需要手动将实参置为NULL
void LTDesTroy(LTNode* phead);
//void LTInit(LTNode** pphead);
void LTPrint(LTNode* phead);
bool LTEmpty(LTNode* phead);
//尾插
//phead节点不会发生改变,参数就为一级
//phead节点发生改变,参数就为二级
void LTPushBack(LTNode* phead, LTDatatype x);
void LTPushFront(LTNode* phead, LTDatatype x);
//尾删
void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);
//在pos位置之后插⼊数据----pos位置之前插入代码基本是一样的(双向的)--课下可以实现以下在pos之前插入
void LTInsert(LTNode* pos, LTDatatype x);
void LTErase(LTNode* pos);
LTNode* LTFind(LTNode* phead, LTDatatype x);
这些就是代码实现
定义双向链表就是属于将本身的数据完成,同时还要完全存储指向下一个结点的指针还有指向下一个结点的指针
接下来就是代码实现了
同时还要测试,因为如果只要申请空间的话那么就是和单链表没有区别
创建
LTNode* LTInit()
{
LTNode* LTInit()
{
//创建头结点
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
phead->data = -1;
phead->next = phead->prev = phead;
//LTNode* phead = buyNode(-1);
return phead;
}
先是先申请空间,之后都全部指向自己
同时完成数据存储
这样就可以完完全全满足定义
另外一种方式时自己定义一个变量传输过去
第二种方式就是给一个地址,然后再进行一个创建
void LTInit(LTNode** pphead)
{
assert(pphead);
*pphead = (LTNode*)malloc(sizeof(LTNode));
(*pphead)->data = -1;
(*pphead)->next = (*pphead)->prev = *pphead;
}
推荐第一种
也就是当前是一个空链表
插入
尾插
void LTPushBack(LTNode* phead, LTDatatype x)
{
assert(phead);
LTNode* newnode = buyNode(x);
//phead phead->prev newnode
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}
之前单链表是二级指针,直接指向最初的地址,二当前的话是因为不会再有
当前的问题是pehead会不会发生改变,哨兵位不会发生改变
尾插是插到哨兵位的前一个,哨兵位不应该发生改变,里面的不会改变,单链表里面主要会有插入头节点和删除头节点,头节点会发生改变,这里哨兵位的话完完全全不会发生改变
需要先断言保证不是一个null
双向链表的尾插是首先是要有一个数据插入,那么就是显示器一个结点,对于影响来说,首先是最后一个指针(原本)的尾指针发生了改变,还有哨兵位的头节点,那么就是将原本的结点全部改变,但是可能会影响原本链表,也就是数据,那么就是先改变要插入数据的
那么就是将新节点的指针完成改变,之后就是改变原结点,那么知道哨兵位,那么就是将哨兵位的前置结点就是尾节点,改变为节点的next,
这样就是时间复杂度是o(1)
包装
LTNode* buyNode(LTDatatype x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
node->data = x;
node->next = node->prev = node;
return node;
}
这个就是尾插
void LTPushFront(LTNode* phead, LTDatatype x)
{
assert(phead);
LTNode* newnode = buyNode(x);
//phead newnode phead->next
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
头部插入
和尾插的逻辑差不多,知道哨兵位,先改自己,之后改原本的数据,原本的改pre,之后改哨兵
注意:在哨兵位之前插入不叫头插,叫尾插,哨兵位置原本就是为了方便警戒
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
接下来就是输出打印,保证不循环打印就可以,因为肯定没有空,同时可以确定的是最后一个数据的后置结点时哨兵位
首先要确定的就是初始化不是哨兵位
之后循环打印就可以
删除
首先是判断是否为空
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
尾删
首先是将收到印象的指针进行改变
将原本倒数第二的指针到哨兵位,哨兵位到倒数第二
先改变再删除
void LTPopBack(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
//phead del->prev del
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
先将del代之尾节点
之后将del的前一个结点的尾节点到哨兵位
哨兵位的前置结点到原本尾部结点的前结点
之后将空间释放,之后就是指向null,防止变成野指针
头善
void LTPopFront(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->next;
//phead del del->next
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
将头指针为del
之后就是将第二指针(del->next)的前置指针为哨兵位
之后就是将哨兵位后置位第二位
指定
指定数据查找位置
就是直接循环
没有找到就是NULL
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
之后指定位置
插入
void LTInsert(LTNode* pos, LTDatatype x)
{
assert(pos);
LTNode* newnode = buyNode(x);
//pos newnode pos->next
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
给一个pos,因为有前去也有后继,那么就是可以直接传,不用遍历
首先是先改变没有影响的,那么就是直接和头插尾插一样
删除
void LTErase(LTNode* pos)
{
assert(pos);
//pos->prev pos pos->next
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
因为指定位置前后都一样,所以就不在多余说
销毁
首先是需要传输的就是连哨兵位都要全部销毁,所以是二级指针
void LTDesTroy(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
首先是遍历销毁,需要保存一下下一个指针的位置
最后将哨兵位置为空,同时需要将空间释放
但是因为需要保持接口一致性,所以基本上最好都是一级指针,那么就是需要再函数调用之后将参数置为空
这里的时间复杂度就是o(1)