【C语言教程】双向链表学习总结和C语言代码实现!值得学习~
双向链表
定义
我们一开始学习的链表中各节点中都只包含一个指针(游标),且都统一指向直接后继节点,通常称这类链表为单向链表。
虽然使用单向链表能 100% 解决逻辑关系为 "一对一" 数据的存储问题,但在解决某些特殊问题时,单链表并不是效率最优的存储结构。比如说,如果算法中需要大量地找某指定节点的前驱节点,使用单链表无疑是灾难性的,因为单链表更适合 "从前往后" 找,而 "从后往前" 找并不是它的强项。
为了能够高效率解决类似的问题,就发明了双向链表。从名字上理解双向链表,即链表是 "双向" 的,如下图所示:
从上图中可以看到,双向链表中各节点包含以下 3 部分信息(如下图所示):
● 指针域 prior:用于指向当前节点的直接前驱节点;
● 数据域 data:用于存储数据元素。
● 指针域 next:用于指向当前节点的直接后继节点;
因此,双链表的节点结构用 C 语言实现为:
typedef struct Node {
struct Node *prior;//指向直接前驱节点
ElemType data;//数据域
struct Node *next;//指向直接后继节点
} Node;
注意:因为带头节点会更好操作,所以我的代码都有头节点。
1、双向链表的创建
同单链表相比,双链表仅是各节点多了一个用于指向直接前驱的指针域。因此,我们可以在单链表的基础轻松实现对双链表的创建。
//1、初始化双向链表(带头节点)
Status initLinkList(LinkList *list){
//创建头节点
*list = malloc(sizeof(Node));
if (*list == NULL) {
return ERROR;
}
(*list)->prior = NULL;
(*list)->data = -1;
(*list)->next = NULL;
printf("已初始化链表~\n");
return OK;
}
2、遍历双向链表
和单向链表遍历方式一模模一样样,这里就不多讲。我多加了一层使用prior指针逆序输出,相信有点基础的同学应该能一眼看明白。
//2、遍历双向链表
void printfLinkLisk(LinkList list){
printf("遍历链表:\n");
if (list == NULL || list->next == NULL) {
printf("这是一个空链表\n");
return;
}
LinkList p = list;
//判断next是否全部正确
printf("根据next从前往后遍历:");
while (p->next) {
printf("%d ",p->next->data);
p = p->next;
}
printf("\n");
//判断prior是否全部正确
printf("根据prior从后往前遍历:");
while (p != list) {
printf("%d ",p->data);
p = p->prior;
}
printf("\n");
}
3、根据索引位置添加节点
因为我的双向链表有头节点,所以只有两种添加情况:
● 添加至表的中间位置
同单链表添加数据类似,双向链表中间位置添加数据需要经过以下 4 个步骤(步骤中的顺序中 3 必须放到 1 和 2 后面,其它顺序可变),如下图所示:
● 将priorNode->next节点的prior指向新节点;
● 将新节点->next指向原来的priorNode->next;
● 将priorNode->next指向新节点;
● 新节点的prior指向priorNode。
● 添加至表尾
与添加到表中间的步骤只需要少掉步骤 1。因为priorNode->next是Null,不能用它执行操作,否则会崩溃。
//3、根据索引位置插入数据至链表中
Status insertLinkList(LinkList *list, int index, ElemType data){
if (list == NULL || index < 0) {
return ERROR;
}
int i = 0;
LinkList priorNode = *list;
//判断插入的位置,这里开始位置是0,index超过链表长度则插入末尾
while (i < index && priorNode->next != NULL) {
priorNode = priorNode->next;
i++;
}
LinkList newNode = malloc(sizeof(Node));
if (newNode == NULL) {
return ERROR;
}
newNode->data = data;
//插入操作共四步,看好了,别眨眼
//1.将priorNode->next节点的前驱指向新节点
if (priorNode->next) {
priorNode->next->prior = newNode;
}
//2.将新节点->next指向原来的priorNode->next
newNode->next = priorNode->next;
//3.将priorNode->next指向新节点
priorNode->next = newNode;
//4.新节点的前驱指向priorNode
newNode->prior = priorNode;
return OK;
}
4、根据索引位置删除节点
根据索引删除节点时,只需遍历链表找到要删除的结点,更改前驱节点的next和后继节点的prior即可。
//4、根据索引位置删除节点
Status deleteLinkListByIndex(LinkList *list, int index, ElemType *data){
if (*list == NULL || index < 0) {
return ERROR;
}
LinkList locaNode = *list;
int i = 0;
while (i <= index) {
locaNode = locaNode->next;
if (locaNode == NULL) {
printf("没有这个你想要删除的节点\n");
return ERROR;
}
i++;
}
//开始删除,只需要做两步
//1、更改前驱节点的next
locaNode->prior->next = locaNode->next;
//2、更改后继节点的prior。
if (locaNode->next) {
locaNode->next->prior = locaNode->prior;
}
*data = locaNode->data;
free(locaNode);
return OK;
}
5、根据存储的值删除节点
根据值删除节点时,只需遍历链表找到要删除的结点,更改前驱节点的next和后继节点的prior即可。
//5、根据存储的值删除节点
Status deleteLinkListByData(LinkList *list, ElemType data){
if (*list == NULL) {
return ERROR;
}
LinkList locaNode = (*list)->next;
while (locaNode) {
if (locaNode->data == data) {
break;
}
locaNode = locaNode->next;
}
if (locaNode == NULL) {
printf("没有这个你想要删除的节点\n");
return ERROR;
}
//开始删除,只需要做两步
locaNode->prior->next = locaNode->next;
if (locaNode->next) {
locaNode->next->prior = locaNode->prior;
}
free(locaNode);
return OK;
}
6、根据值查找节点
方法同单向链表
//6、查找元素
Status selectNode(LinkList list, ElemType data, LinkList *locaNode){
if (list == NULL) {
return ERROR;
}
LinkList p = list->next;
while (p) {
if (p->data == data) {
*locaNode = p;
break;
}
p = p->next;
}
if (*locaNode == NULL) {
printf("没有这个你想要的节点\n");
return ERROR;
}
else {
return OK;
}
}
其它辅助代码
#include "stdlib.h"
#define OK 1
#define ERROR 0
//元素类型
typedef int ElemType;
//状态类型
typedef int Status;
//定义节点结构体
typedef struct Node {
struct Node *prior;
ElemType data;
struct Node *next;
} Node;
typedef Node *LinkList;
int main(int argc, const char * argv[]) {
LinkList list;
initLinkList(&list);
for (int i = 0; i < 10; i ++) {
insertLinkList(&list, i, i);
}
printfLinkLisk(list);
int index, data;
printf("输入你想插入的位置(从0开始)和存储的值:");
scanf("%d %d",&index,&data);
insertLinkList(&list, index, data);
printfLinkLisk(list);
printf("输入你想删除的位置(从0开始):");
scanf("%d",&index);
deleteLinkListByIndex(&list, index, &data);
printfLinkLisk(list);
printf("输入你想删除的节点的值(只删最前的那个):");
scanf("%d",&data);
deleteLinkListByData(&list, data);
printfLinkLisk(list);
printf("\n");
return 0;
}
输出结果:
看到这里是不是又学到了很多新知识呢~
如果你很想学编程,小编推荐我的C语言/C++编程学习基地【点击进入】!
都是学编程小伙伴们,带你入个门还是简简单单啦,一起学习,一起加油~
还有许多学习资料和视频,相信你会喜欢的!
涉及:游戏开发、常用软件开发、编程基础知识、课程设计、黑客等等......
【C语言教程】双向链表学习总结和C语言代码实现!值得学习~的更多相关文章
- 电脑小白学习软件开发-C#语言基础之循环重点讲解,习题
写代码也要读书,爱全栈,更爱生活.每日更新原创IT编程技术及日常实用视频. 我们的目标是:玩得转服务器Web开发,搞得懂移动端,电脑客户端更是不在话下. 本教程是基础教程,适合任何有志于学习软件开发的 ...
- Swift语言教程中文文档
Swift语言教程中文文档 Swift语言教程(一)基础数据类型 Swift语言教程(二)基础数据类型 Swift语言教程(三)集合类型 Swift语言教程(四) 集合类型 Swift语言教程(五)控 ...
- Swift3.0语言教程使用URL字符串
Swift3.0语言教程使用URL字符串 Swift3.0语言教程使用URL字符串,和路径一样,URL其实也是字符串,我们可以将这些字符串称为URL字符串.本小节将讲解URL字符串的使用. 1.编码 ...
- Swift3.0语言教程使用路径字符串
Swift3.0语言教程使用路径字符串 Swift3.0语言教程使用路径字符串,路径其实是字符串的一种,我们称为路径字符串.本小节将讲解如何使用路径字符串. 1.组合路径 开发者可以将数组快速的组合成 ...
- Swift3.0语言教程字符串大小写转化
Swift3.0语言教程字符串大小写转化 Swift3.0语言教程字符串大小写转化,在字符串中,字符串的格式是很重要的,例如首字母大写,全部大写以及全部小写等.当字符串中字符很多时,通过人为一个一个的 ...
- Swift3.0语言教程替换子字符串
Swift3.0语言教程替换子字符串 Swift3.0语言教程替换子字符串,替换子字符串其实就是将字符串中的子字符串删除,然后再进行添加.为了让这一繁琐的过程变的简单,NSString提供了替换子字符 ...
- Swift3.0语言教程获取C字符串
Swift3.0语言教程获取C字符串 Swift3.0语言教程获取C字符串,为了让Swift和C语言可以实现很好的交互,开发者可以使用NSString的cString(using:)方法在指定编码格式 ...
- Swift3.0语言教程获取字符串长度
Swift3.0语言教程获取字符串长度 Swift3.0语言教程获取字符串长度,当在一个字符串中存在很多的字符时,如果想要计算字符串的长度时相当麻烦的一件事情,在NSString中可以使用length ...
- Swift2.0语言教程之类的方法
Swift2.0语言教程之类的方法 Swift2.0语言的方法 方法其实就是函数,只不过它被定义在了类中.在Swift中,根据被使用的方式不同,方法分为了实例方法和类型方法两种.这两种方法的定义也和O ...
- C++语言实现双向链表
这篇文章是关于利用C++模板的方式实现的双向链表以及双向链表的基本操作,在之前的博文C语言实现双向链表中,已经给大家分析了双向链表的结构,并以图示的方式给大家解释了双向链表的基本操作.本篇文章利用C+ ...
随机推荐
- java 求水仙花数
package com.yc.bean; public class ShuiXianHua { public static void main(String[] args) { /** * 题目:打印 ...
- 谈谈 mysql和oracle的使用感受 -- 差异
之前一直使用mysql作为存储数据库,虽然中间偶尔使用sqlite作为本地数据库存储,但没有感觉多少差别. 后来遇上了oracle,且以其作为主要存储,这下就不得不好好了解其东西了.oracle作为商 ...
- elo system
今天了解了一下游戏中的PVP模块的实现,大多数的游戏都使用到了ELO算法,刚开始的时候并不清楚这个算法是做什么的,对此开始大量查找有关于ELO算法的资源,功夫不负有心人,总算找到一些有用的资源了. 先 ...
- Django 页面之间传递MySql数据表(Django八)
之前实现了页面传递多个参数,但没有实现页面间传递一整个数据表 session传递几个参数:https://blog.csdn.net/qq_38175040/article/details/10496 ...
- PHP面试总结(转)
PHP面试总结 从8月15号来到北京一直到今天,一月有余.来的这段时间一直准备笔试面试,大大小小的公司,乱七八糟面了10多家,近期才安顿下来.面试的这段时间感觉自己成长了不少.初来到这个陌生的城市 ...
- Centos7,PHP7安装swoole
Swoole详细介绍及如何使用,这里暂时不做说明,可以参考一下文档:https://wiki.swoole.com/ 源码编译安装 下载地址如下: https://github.com/swoole/ ...
- 软件工程与UML作业3(互评作业)
博客班级 https://edu.cnblogs.com/campus/fzzcxy/2018SE1/ 作业要求 https://edu.cnblogs.com/campus/fzzcxy/2018S ...
- 使用Golang的singleflight防止缓存击穿
背景 在使用缓存时,容易发生缓存击穿. 缓存击穿:一个存在的key,在缓存过期的瞬间,同时有大量的请求过来,造成所有请求都去读dB,这些请求都会击穿到DB,造成瞬时DB请求量大.压力骤增. singl ...
- session、闪现、请求扩展
session 除请求对象之外,还有一个session对象.它允许你在不同请求储存特定用户的信息.它是在Cookies的基础上实现的,并且对,Cookies进行密钥签名要使用会话,你需要设置一个密钥. ...
- DC4靶机
DC-4靶机渗透 扫描内网机器,看到143是开启的,那么ok了,确定了目标主机的地址. 对其进行进一步的端口扫描,80,22端口都是开放的. 访问具体网页,进行爆破,分别为admin,happy. 里 ...