文章目录
前言
这可能是最全的双向链表的讲解,一共整整10中常用的方法,有代码说明,更有图画展示,话不多说,大家都过来看。
前面几篇我们学习到了顺序表、单链表数据结构,下来我们学习新的数据结构—双向链表
1.双向链表
注意:这⾥的“带头”跟前面我们说的“头节点”是两个概念,实际前⾯的在单链表阶段称呼不严谨,但是为了同学们更好的理解就直接称为单链表的头节点。
带头链表里的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这里"放哨的”“哨兵位”存在的意义:
遍历循环链表避免死循环
2.双向链表的结构体定义
注:本次是在clion软件进行代码的编写和测试的
一个节点分为三个部分,分别是指向上一个和下一个的节点的指针,及其数据。
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode *next;
struct ListNode*prev;
}LTNode;
3.相关代码的实现
记住:一定要首尾相连
3.1.打印链表
打印的结束标志是遍历一圈即可。(pcur!=phead)
记住头指针是不用读取的
//打印链表
void LTPrint(LTNode* phead)
{
LTNode* pcur=phead->next;
while(pcur!=phead)
{
printf("%d->",pcur->data);
pcur=pcur->next;
}
printf("\n");
}
3.2.创建节点
LTNode *LTByNode(LTDataType x)
{
LTNode *newnode= (LTNode*)malloc(sizeof (LTNode));
if(newnode==NULL)
{
perror("LTByNode()::malloc()");
exit(-1);
}
//首尾相连
newnode->next=newnode->prev=newnode;
newnode->data=x;
return newnode;
}
3.3.初始化链表
就是创建一个头结点。
LTNode* LTInit()
{
LTNode* phead = LTByNode(-1);
return phead;
}
3.4.尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
//首先判断是否是空链表
assert(phead);
LTNode *newnode= LTByNode(x);
//phead pheea->next newnode
newnode->prev=phead->prev;
newnode->next=phead;
phead->prev->next=newnode;
phead->prev=newnode;
}
3.5.头插
oid LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode *newnode= LTByNode(x);
//phead phead->next newnode
newnode->next=phead->next;
newnode->prev=phead;
phead->next->prev=newnode;
phead->next=newnode;
}
3.6.尾删
void LTPopBack(LTNode* phead)
{
//链表必须有效且链表不能为空(只有一个哨兵位)
assert(phead && phead->next != phead);
//先将要删除的节点保存起来
LTNode* del=phead->prev;
//phead del del->prev
del->prev->next=phead;
phead->prev=del->prev;
free(del);
del=NULL;
}
3.6.头删
void LTPopFront(LTNode* phead)
{
assert(phead&&phead->next);
LTNode *del=phead->next;
del->next->prev=phead;
phead->next=del->next;
free(del);
del=NULL;
}
3.7.销毁链表
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
3.8.查找节点
思路和打印链表一样,就是遍历一次整个链表。
LTNode *LTFind(LTNode* phead,LTDataType x)
{
LTNode *pcur=phead->next;
while(pcur!=phead)
{
if(pcur->data==x)
return pcur;
pcur=pcur->next;
}
return NULL;
}
3.9.在指定位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode *newnode= LTByNode(x);
//pos pos->next newnode
newnode->next=pos->next;
newnode->prev=pos;
pos->next->prev=newnode;
pos->next=newnode;
}
3.10.在指定位置的节点
void LTErase(LTNode* pos)
{
assert(pos);
//pos->prev pos pos->next
pos->prev->next=pos->next;
pos->next->prev=pos->next;
free(pos);
pos=NULL;
}
4.参考代码
List.h文件
//
// Created by Lenovo on 2024/4/22.
//
#ifndef C_CLION_LIST_H
#define C_CLION_LIST_H
#endif //C_CLION_LIST_H
#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();
//LTNode* LTInit();
void LTDestroy(LTNode* phead);
void LTPrint(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);
LTNode *LTFind(LTNode* phead,LTDataType x);
List.c文件
//
// Created by Lenovo on 2024/4/22.
//
#include "List.h"
//创建节点
LTNode *LTByNode(LTDataType x)
{
LTNode *newnode= (LTNode*)malloc(sizeof (LTNode));
if(newnode==NULL)
{
perror("LTByNode()::malloc()");
exit(-1);
}
//首尾相连
newnode->next=newnode->prev=newnode;
newnode->data=x;
return newnode;
}
//初始化链表
LTNode* LTInit()
{
LTNode* phead = LTByNode(-1);
return phead;
}
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
//打印链表
void LTPrint(LTNode* phead)
{
LTNode* pcur=phead->next;
while(pcur!=phead)
{
printf("%d->",pcur->data);
pcur=pcur->next;
}
printf("\n");
}
bool LTEmpty(LTNode* phead);
//尾\头 插\删
void LTPushBack(LTNode* phead, LTDataType x)
{
//首先判断是否是空链表
assert(phead);
LTNode *newnode= LTByNode(x);
//phead pheea->next newnode
newnode->prev=phead->prev;
newnode->next=phead;
phead->prev->next=newnode;
phead->prev=newnode;
}
void LTPopBack(LTNode* phead)
{
//链表必须有效且链表不能为空(只有一个哨兵位)
assert(phead && phead->next != phead);
//先将要删除的节点保存起来
LTNode* del=phead->prev;
//phead del del->prev
del->prev->next=phead;
phead->prev=del->prev;
free(del);
del=NULL;
}
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode *newnode= LTByNode(x);
//phead phead->next newnode
newnode->next=phead->next;
newnode->prev=phead;
phead->next->prev=newnode;
phead->next=newnode;
}
void LTPopFront(LTNode* phead)
{
assert(phead&&phead->next);
LTNode *del=phead->next;
del->next->prev=phead;
phead->next=del->next;
free(del);
del=NULL;
}
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode *newnode= LTByNode(x);
//pos pos->next newnode
newnode->next=pos->next;
newnode->prev=pos;
pos->next->prev=newnode;
pos->next=newnode;
}
void LTErase(LTNode* pos)
{
assert(pos);
//pos->prev pos pos->next
pos->prev->next=pos->next;
pos->next->prev=pos->next;
free(pos);
pos=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;
}
main文件
//
// Created by Lenovo on 2024/4/22.
//
#include "List.h"
void test()
{
LTNode *plist=LTInit();;
LTPushBack(plist,1);
LTPushBack(plist,2);
LTPushBack(plist,3);
LTPushBack(plist,4);
LTPrint(plist);
LTNode *find= LTFind(plist,3);
LTInsert(find,8);
LTPrint(plist);
LTErase(find);
LTPrint(plist);
// LTPopFront(plist);
// LTPrint(plist);
// LTPopFront(plist);
// LTPrint(plist);
LTDestroy(plist);
// LTPushFront(plist,6);
// LTPrint(plist);
// LTPushFront(plist,7);
// LTPrint(plist);
}
int main()
{
test();
return 0;
}