当前位置: 首页 > news >正文

【数据结构】双向链表“0”基础知识讲解 + 实战演练

一、概念与结构

如图所示:双向链表是由一个一个的结点组成,这里的结点有三个组成部分:


struct ListNode
{int data;struct ListNode* next;//指向后一个结点的指针struct ListNode* prev;//指向前一个结点的指针
};

注意:这里的“带头”跟前面我们说的“头结点”是两个概念,实际前面的在单链表阶段称呼不严谨,但是为了同学们更好的理解就直接称为单链表的头结点。

带头链表里的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这里“放哨的”

双向链表为空:他还是要满足带头、双向、循环这几个特点,实际上他只有一个哨兵位指针,他的next指针指向自己,prev指针也指向自己。

二、实现双向链表

2.1、List.h
#pragma once#define _CRT_SECURE_NO_WARNINGS#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;//双向链表的初始化
//要改变头结点plist就要传入二级指针否则形参改变不了实参
void LTInit(LTNode** pphead);//尾插
//在双向链表中,增删改查都不会改变哨兵位结点
void LTPushBack(LTNode* phead, LTDataType x);//头插
//注意这里的头插是将newnode放到head之后,node1之前
//如果只是单纯的放在head前面那就和尾插无异了
void LTPushFront(LTNode* phead, LTDataType x);//判断链表是否为空
bool LTEmpty(LTNode* phead);//打印双向链表
//只需要打印node1、node2、node3...不需要打印phead这个哨兵位
void LTPrint(LTNode* phead);//尾删
void LTPopBack(LTNode* phead);//头删
//删除的是head后面的结点,并不是删除哨兵位
void LTPopFront(LTNode* phead);//查找
LTNode* LTFind(LTNode* phead, LTDataType x);//任意位置的插入
//双向链表我们知道其中一个的位置就可以找到任意位置的数据
void LTInsert(LTNode* pos, LTDataType x);//在指定位置之前插入数据
void LTInsertFront(LTNode* pos, LTDataType x);//删除pos位置的结点
void LTErase(LTNode* pos);//销毁双向链表
//销毁的实际也是改变,要使用二级指针
void LTDesTroy(LTNode** pphead);
2.2、List.c
#include"List.h"//申请一个新结点
LTNode* LTBuyNode(LTDataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = newnode->prev = newnode;return newnode;
}//初始化双向链表
void LTInit(LTNode** pphead)
{*pphead = (LTNode*)malloc(sizeof(LTNode));if (*pphead == NULL){perror("malloc fail!");exit(1);}(*pphead)->data = -1;//我们可以随意的先赋个值,不使用(*pphead)->next = (*pphead)->prev = *pphead;//现在就是一个空的双向链表//等价代码: *pphead = LTBuyNode(-1);
}//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);//phead phead->prev newnodenewnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode;
}//头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);//phead newnode phead->nextnewnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}//判断链表是否为空
bool LTEmpty(LTNode* phead)
{assert(phead);//一个双向链表如果他指向自己那就是空链表return phead->next == phead;
}//打印双向链表
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){printf("%d -> ", pcur->data);pcur = pcur->next;}printf("\n");
}//尾删
void LTPopBack(LTNode* phead)
{//双向链表不能为空assert(!LTEmpty(phead));LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}//头删
void LTPopFront(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->next;del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);//双向链表中的头结点是哨兵位后面的结点LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}//未找到return NULL;
}//任意位置的插入
//这里我们写在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);//pos newnode pos->nextnewnode->prev = pos;newnode->next = pos->next;pos->next->prev = newnode;pos->next = newnode;
}//在指定位置之前插入数据
void LTInsertFront(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos;newnode->prev = pos->prev;pos->prev->next = newnode;pos->prev = newnode;
}//删除pos位置的结点
void LTErase(LTNode* pos)
{assert(pos);//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}//销毁双向链表
void LTDesTroy(LTNode** pphead)
{LTNode* pcur = (*pphead)->next;while (pcur != *pphead){LTNode* next = pcur->next;free(pcur);pcur = next;}//销毁头结点free(*pphead);*pphead = NULL;
}

这里为了保持接口的一致性,我们可以改一下初始化和销毁的代码方便我们后期更好的使用和记忆

//初始化2
LTNode* LTInit2()
{LTNode* phead = LTBuyNode(-1);return phead;
}//销毁2
void LTDesTroy2(LTNode* phead)
{LTNode* pcur = (phead)->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}//销毁头结点free(phead);phead = NULL;
}
2.3、test.c
#include"List.h"void test01()
{LTNode* plist = NULL;LTInit(&plist);//尾插LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);//头插/*LTPushFront(plist, 1);LTPushFront(plist, 2);LTPushFront(plist, 3);LTPushFront(plist, 4);*///尾删/*LTPopBack(plist);LTPopBack(plist);LTPopBack(plist);LTPopBack(plist);LTPrint(plist);*///头删/*LTPopFront(plist);LTPopFront(plist);LTPopFront(plist);LTPopFront(plist);LTPrint(plist);*///查找LTNode* pos = LTFind(plist, 2);/*if (pos){printf("找到了!\n");}else{printf("未找到!\n");}*///在pos之后插入数据//LTInsert(pos, 416);//LTPrint(plist);//在pos之前插入数据LTInsertFront(pos, 416);LTPrint(plist);//删除pos位置的数据LTErase(pos);LTPrint(plist);//销毁LTDesTroy(&plist);}int main()
{test01();return 0;
}

三、顺序表与链表的分析

其实大家现在都处于一个代码的初级阶段,会经常碰到不会写的题目,可能一个题目思考半个多小时也没有什么思路,我们这时候要做的就是“死磕”代码,多去画图理解结构与理清思路,没有一蹴而就的成就,只有千锤百炼的沉淀,共勉之!

http://www.dtcms.com/a/494733.html

相关文章:

  • 药品经营许可证识别技术:通过深度学习算法实现资质文件的自动化识别与核验
  • 网站的建设思想企业app定制
  • YOLOv3 目标检测算法核心技术
  • MySQL 主从复制故障排查及解决方案
  • 告别单打独斗:多智能体协作如何解决复杂问题?
  • Netty连接断开检测:Epoll与NIO的对比及实战解决方案
  • 神经网络之反向传播
  • el-table-column show-overflow-tooltip 只能显示纯文本,无法渲染 <p> 标签
  • (定时任务)接上篇:定时任务的分布式执行与分布式锁使用场景
  • 广州网站制作哪家专业凡科互动游戏可以作弊码
  • caching_sha2_password认证插件说明
  • 13.继承(一)
  • vue3:el-progress的圆形无线滚动,心跳、呼吸效果,加载中的效果
  • 高速光耦:电子系统的卓越赋能者
  • 鸿蒙HAP文件数字签名提取与解析
  • 《宋代水墨国漫3D:动态镜头笔触连贯的开发拆解》
  • Fast-Agent:重新定义AI Agent开发的“快“与“简“
  • 做电力的系统集成公司网站个人简历在线制作免费
  • 如何查网站是那家做的用什么做视频网站比较好的
  • SQL UPDATE 语句详解
  • 一个基于BiTCN-BiLSTM混合神经网络的时间序列预测MATLAB程序
  • Python开发的自我修养之数据类型的选择策略
  • Day02_刷题niuke20251017
  • [嵌入式系统-135]:主流AIOT智能体开发板
  • 设计模式---观察者模式
  • 【软考备考】 高并发场景如何做负载均衡知识点四
  • LOFAR物理频谱特征提取及实现
  • excel拼接数据库
  • 23ICPC杭州vp补题
  • 做网站不难吧长兴网站建设