数据结构与算法教程,数据结构C语言版教程!(第二部分、线性表详解:数据结构线性表10分钟入门)二

第二部分、线性表详解:数据结构线性表10分钟入门

线性表,数据结构中最简单的一种存储结构,专门用于存储逻辑关系为"一对一"的数据。

线性表,基于数据在实际物理空间中的存储状态,又可细分为顺序表(顺序存储结构)和链表(链式存储结构)。

本章还会讲解顺序表和链表的结合体——静态链表,不仅如此,还会涉及循环链表、双向链表、双向循环链表等链式存储结构。

三、顺序表的基本操作(C语言详解版)

我们学习了《二、顺序表(顺序存储结构)及初始化详解》一节,本节学习有关顺序表的一些基本操作,以及如何使用 C 语言实现它们。

1、顺序表插入元素

向已有顺序表中插入数据元素,根据插入位置的不同,可分为以下 3 种情况:

  1. 插入到顺序表的表头
  2. 在表的中间位置插入元素;
  3. 尾随顺序表中已有元素,作为顺序表中的最后一个元素;

虽然数据元素插入顺序表中的位置有所不同,但是都使用的是同一种方式去解决,即:通过遍历,找到数据元素要插入的位置,然后做如下两步工作

  • 将要插入位置元素以及后续的元素整体向后移动一个位置;
  • 将元素放到腾出来的位置上;

例如,在 {1,2,3,4,5} 的第 3 个位置上插入元素 6,实现过程如下:

  • 遍历至顺序表存储第 3 个数据元素的位置,如图 1 所示:

    找到目标元素位置

    图 1 找到目标元素位置

  • 将元素 3 以及后续元素 4 和 5 整体向后移动一个位置,如图 2 所示:

    将插入位置腾出

    图 2 将插入位置腾出

  • 将新元素 6 放入腾出的位置,如图 3 所示:

    插入目标元素

    图 3 插入目标元素

因此,顺序表插入数据元素的 C 语言实现代码如下:

//插入函数,其中,elem为插入的元素,add为插入到顺序表的位置

table addTable(table t,int elem,int add)

{

        //判断插入本身是否存在问题(如果插入元素位置比整张表的长度+1还大(如果相等,是尾随的情况),或者插入的位置本身不存在,程序作为提示并自动退出)

        if (add>t.length+1||add<1) {

                printf("插入位置有问题\n");

                return t;

        }

        //做插入操作时,首先需要看顺序表是否有多余的存储空间提供给插入的元素,如果没有,需要申请

        if (t.length==t.size) {

                t.head=(int *)realloc(t.head, (t.size+1)*sizeof(int));

                if (!t.head) {

                        printf("存储分配失败\n");

                        return t;

                }

                t.size+=1;

        }

        //插入操作,需要将从插入位置开始的后续元素,逐个后移

        for (int i=t.length-1; i>=add-1; i--) {

                t.head[i+1]=t.head[i];

        }

        //后移完成后,直接将所需插入元素,添加到顺序表的相应位置

        t.head[add-1]=elem;

        //由于添加了元素,所以长度+1

        t.length++;

        return t;

}

注意,动态数组额外申请更多物理空间使用的是 realloc 函数。并且,在实现后续元素整体后移的过程,目标位置其实是有数据的,还是 3,只是下一步新插入元素时会把旧元素直接覆盖。

2、顺序表删除元素

从顺序表中删除指定元素,实现起来非常简单,只需找到目标元素,并将其后续所有元素整体前移 1 个位置即可。

后续元素整体前移一个位置,会直接将目标元素删除,可间接实现删除元素的目的。

例如,从 {1,2,3,4,5} 中删除元素 3 的过程如图 4 所示:

图 4 顺序表删除元素的过程示意图

因此,顺序表删除元素的 C 语言实现代码为:

table delTable(table t,int add){

        if (add>t.length || add<1) {

                printf("被删除元素的位置有误\n");

                return t;

        }

        //删除操作

        for (int i=add; i<t.length; i++) {

                t.head[i-1]=t.head[i];

        }

        t.length--;

        return t;

}

3、顺序表查找元素

顺序表中查找目标元素,可以使用多种查找算法实现,比如说《第九部分:三:二分查找(折半查找)算法详解(C语言实现)》、插值查找算法等。

这里,我们选择《第九部分:二:顺序查找算法详解(包含C语言实现代码)》,具体实现代码为:

//查找函数,其中,elem表示要查找的数据元素的值

int selectTable(table t,int elem){

        for (int i=0; i<t.length; i++) {

                if (t.head[i]==elem) {

                        return i+1;

                }

        }

        return -1;//如果查找失败,返回-1

}

4、顺序表更改元素

顺序表更改元素的实现过程是:

  1. 找到目标元素;
  2. 直接修改该元素的值

顺序表更改元素的 C 语言实现代码为:

//更改函数,其中,elem为要更改的元素,newElem为新的数据元素

table amendTable(table t,int elem,int newElem){

        int add=selectTable(t, elem);

        t.head[add-1]=newElem;//由于返回的是元素在顺序表中的位置,所以-1就是该元素在数组中的下标

        return t;

}

以上是顺序表使用过程中最常用的基本操作,这里给出本节完整的实现代码:

#include <stdio.h>

#include <stdlib.h>

#define Size 5

typedef struct Table{

int * head;

int length;

int size;

}table;

table initTable(){

        table t;

        t.head=(int*)malloc(Size*sizeof(int));

        if (!t.head) {

                printf("初始化失败\n");

                exit(0);

        }

        t.length=0;

        t.size=Size;

        return t;

}

table addTable(table t,int elem,int add)

{

        if (add>t.length+1||add<1) {

                printf("插入位置有问题\n");

                return t;

        }

        if (t.length>=t.size) {

                t.head=(int *)realloc(t.head, (t.size+1)*sizeof(int));

                if (!t.head) {

                        printf("存储分配失败\n");

                }

                t.size+=1;

        }

        for (int i=t.length-1; i>=add-1; i--) {

                t.head[i+1]=t.head[i];

        }

        t.head[add-1]=elem;

        t.length++;

         return t;

}

table delTable(table t,int add){

        if (add>t.length || add<1) {

                printf("被删除元素的位置有误\n");

                return t;

        }

        for (int i=add; i<t.length; i++) {

                t.head[i-1]=t.head[i];

        }

        t.length--;

        return t;

}

int selectTable(table t,int elem){

        for (int i=0; i<t.length; i++) {

                if (t.head[i]==elem) {

                        return i+1;

                }

        }

        return -1;

}

table amendTable(table t,int elem,int newElem){

         int add=selectTable(t, elem);

         t.head[add-1]=newElem;

         return t;

}

void displayTable(table t){

         for (int i=0;i<t.length;i++) {

                 printf("%d ",t.head[i]);

         }

         printf("\n");

}

int main(){

         table t1=initTable();

         for (int i=1; i<=Size; i++) {

                t1.head[i-1]=i;

                t1.length++;

        }

         printf("原顺序表:\n");

         displayTable(t1);

         printf("删除元素1:\n");

         t1=delTable(t1, 1);

         displayTable(t1);

         printf("在第2的位置插入元素5:\n");

         t1=addTable(t1, 5, 2);

         displayTable(t1);

         printf("查找元素3的位置:\n");

         int add=selectTable(t1, 3);

         printf("%d\n",add);

         printf("将元素3改为6:\n");

         t1=amendTable(t1, 3, 6);

         displayTable(t1);

         return 0;

}

程序运行结果为:

原顺序表:
1 2 3 4 5
删除元素1:
2 3 4 5
在第2的位置插入元素5:
2 5 3 4 5
查找元素3的位置:
3
将元素3改为6:
2 5 6 4 5

 四、什么是单链表,链式存储结构详解

前面详细地介绍了《二、顺序表(顺序存储结构)及初始化详解》,本节给大家介绍另外一种线性存储结构——链表。

链表,别名链式存储结构或单链表,用于存储逻辑关系为 "一对一" 的数据。与顺序表不同,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的。

例如,使用链表存储 {1,2,3},数据的物理存储状态如图 1 所示:

链表随机存储数据

图 1 链表随机存储数据

我们看到,图 1 根本无法体现出各数据之间的逻辑关系。对此,链表的解决方案是,每个数据元素在存储时都配备一个指针,用于指向自己的直接后继元素。如图 2 所示:

各数据元素配备指针

图 2 各数据元素配备指针

像图 2 这样,数据元素随机存储,并通过指针表示数据之间逻辑关系的存储结构就是链式存储结构。

1、链表的节点

从图 2 可以看到,链表中每个数据的存储都由以下两部分组成:

  1. 数据元素本身,其所在的区域称为数据域
  2. 指向直接后继元素的指针,所在的区域称为指针域

即链表中存储各数据元素的结构如图 3 所示:

图 3 节点结构

图 3 所示的结构在链表中称为节点。也就是说,链表实际存储的是一个一个的节点,真正的数据元素包含在这些节点中,如图 4 所示:

链表中的节点

图 4 链表中的节点

因此,链表中每个节点的具体实现,需要使用 C 语言中的结构体,具体实现代码为

typedef struct Link{

        char elem; //代表数据域

        struct Link * next; //代表指针域,指向直接后继元素

}link; //link为节点名,每个节点都是一个 link 结构体

提示,由于指针域中的指针要指向的也是一个节点,因此要声明为 Link 类型(这里要写成 struct Link* 的形式)。

2、头节点,头指针和首元节点

其实,图 4 所示的链表结构并不完整。一个完整的链表需要由以下几部分构成:

  1. 头指针:一个普通的指针,它的特点是永远指向链表第一个节点的位置。很明显,头指针用于指明链表的位置,便于后期找到链表并使用表中的数据;
  2. 节点:链表中的节点又细分为头节点、首元节点和其他节点
    • 头节点其实就是一个不存任何数据的空节点,通常作为链表的第一个节点。对于链表来说,头节点不是必须的,它的作用只是为了方便解决某些实际问题;
    • 首元节点由于头节点(也就是空节点)的缘故,链表中称第一个存有数据的节点为首元节点。首元节点只是对链表中第一个存有数据节点的一个称谓,没有实际意义;
    • 其他节点:链表中其他的节点;

因此,一个存储 {1,2,3} 的完整链表结构如图 5 所示:

完整的链表示意图

图 5 完整的链表示意图

注意:链表中有头节点时,头指针指向头节点;反之,若链表中没有头节点,则头指针指向首元节点。

明白了链表的基本结构,下面我们来学习如何创建一个链表。

3、链表的创建(初始化)

创建一个链表需要做如下工作:

  1. 声明一个头指针(如果有必要,可以声明一个头节点);
  2. 创建多个存储数据的节点,在创建的过程中,要随时与其前驱节点建立逻辑关系;

例如,创建一个存储 {1,2,3,4} 且无头节点的链表,C 语言实现代码如下:

link * initLink(){

        link * p=NULL;//创建头指针

        link * temp = (link*)malloc(sizeof(link));//创建首元节点

        //首元节点先初始化

        temp->elem = 1;

        temp->next = NULL;

        p = temp;//头指针指向首元节点

        //从第二个节点开始创建

        for (int i=2; i<5; i++) {

        //创建一个新节点并初始化

                link *a=(link*)malloc(sizeof(link));

                a->elem=i;

                a->next=NULL;

                //将temp节点与新建立的a节点建立逻辑关系

                temp->next=a;

                //指针temp每次都指向新链表的最后一个节点,其实就是 a节点,这里写temp=a也对

                temp=temp->next;

        }

        //返回建立的节点,只返回头指针 p即可,通过头指针即可找到整个链表

        return p;

}

如果想创建一个存储 {1,2,3,4} 且含头节点的链表,则 C 语言实现代码为:

link * initLink(){

        link * p=(link*)malloc(sizeof(link));//创建一个头结点

        link * temp=p;//声明一个指针指向头结点,

        //生成链表

        for (int i=1; i<5; i++) {

                link *a=(link*)malloc(sizeof(link));

                a->elem=i;

                a->next=NULL;

                temp->next=a;

                temp=temp->next;

        }

        return p;

}

我们只需在主函数中调用 initLink 函数,即可轻松创建一个存储 {1,2,3,4} 的链表,C 语言完整代码如下:

#include <stdio.h>

#include <stdlib.h>

//链表中节点的结构

typedef struct Link{

        int elem;

        struct Link *next;

}link;

//初始化链表的函数

link * initLink();

//用于输出链表的函数

void display(link *p);

int main() {

        //初始化链表(1,2,3,4)

        printf("初始化链表为:\n");

        link *p=initLink();

        display(p);

        return 0;

}

link * initLink(){

        link * p=NULL;//创建头指针

        link * temp = (link*)malloc(sizeof(link));//创建首元节点

        //首元节点先初始化

        temp->elem = 1;

        temp->next = NULL;

        p = temp;//头指针指向首元节点

        for (int i=2; i<5; i++) {

                link *a=(link*)malloc(sizeof(link));

                a->elem=i;

                a->next=NULL;

                temp->next=a;

                temp=temp->next;

        }

        return p;

}

void display(link *p){

        link* temp=p;//将temp指针重新指向头结点

        //只要temp指针指向的结点的next不是Null,就执行输出语句。

        while (temp) {

                printf("%d ",temp->elem);

                temp=temp->next;

        }

        printf("\n");

}

程序运行结果为:

初始化链表为:
1 2 3 4

注意,如果使用带有头节点创建链表的方式,则输出链表的 display 函数需要做适当地修改

void display(link *p){

        link* temp=p;//将temp指针重新指向头结点

        //只要temp指针指向的结点的next不是Null,就执行输出语句。

        while (temp->next) {

                temp=temp->next;

                printf("%d",temp->elem);

        }

        printf("\n");

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/282712.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用anaconda创建爬虫spyder工程

1.由于每个工程使用的环境都可能不一样&#xff0c;因此一个好的习惯就是不同的工程都创建属于自己的环境&#xff0c;在anaconda中默认的环境是base&#xff0c;我们现在来创建一个名为spyder的环境&#xff0c;专门用于爬虫工程&#xff1a; //括号中名字&#xff0c;代表当…

shell编程一

shell 定义 Shell 也是一种程序设计语言&#xff0c;它有变量&#xff0c;关键字&#xff0c;各种控制语句&#xff0c;有自己的语法结构&#xff0c;利用shell程序设计语 可以编写功能强、代码简短的程序 #! Shebang 定义解释器 shell的分类和切换 # cat /etc/shells /bin/sh…

ZYNQ 7020 之 FPGA知识点重塑笔记一——串口通信

目录 一&#xff1a;串口通信简介 二&#xff1a;三种常见的数据通信方式—RS232串口通信 2.1 实验任务 2.2 串口接收模块的设计 2.2.1 代码设计 2.3 串口发送模块的设计 2.3.1 代码设计 2.4 顶层模块编写 2.4.1 代码设计 2.4.2 仿真验证代码 2.4.3 仿真结果 2.4.4…

门控循环单元(GRU)-多输入回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分程序&#xff1a; 四、全部代码数据分享&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编译…

HR-net学习与实现

这里会用到预训练模型所以先了解一下预训练是什么以及它的作用是什么&#xff0c;详细内容可以参考教程 1.预训练是什么 深入理解&#xff1a;什么是预训练&#xff1f;预训练有什么作用&#xff1f;预训练和训练的本质区别&#xff1f;&#xff1f;&#xff1f;-CSDN博客 预…

机器学习、人工智能、深度学习的关系

人工智能(Artificial Intelligence&#xff0c;AI) 人工智能范围很广&#xff0c;它是一门新的科学与工程&#xff0c;是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的技术科学&#xff0c;研究内容涵盖语音识别、图像识别、自然语言处理、智能搜索和…

【node-express】在commonjs的项目中使用esm和ts开发的sdk

在commonjs的项目中使用esm和ts开发的sdk 效果实现步骤 效果 在一些demo中, 大部分代码是commonjs规范开发的&#xff0c;但是要用到的sdk是ts开发的并且仅支持esm&#xff0c; 又不想配置很复杂的工程项目&#xff0c;可以这么做。如果你有更好的建议&#xff0c;希望能得到你…

系统账号注册

登录/注册地址&#xff1a;https://id.sf.163.com/login?hshufanqz&tshufanqz&localezh_CN&referrerhttps%3A%2F%2Fcommunity.codewave.163.com%2Frest%2Fcommunity%2Flogin注册成功并登录后&#xff0c;即可进入设计器中。低代码开发者可在设计器中按需要搭建一个…

List集合格式转换

最近遇到一个任务&#xff1a; 需要把A集合数据转成 B集合的形式&#xff1a; A集合&#xff1a; B集合&#xff1a; 代码&#xff1a; package com.example.juc.test;import com.example.juc.entity.Ld; import com.example.juc.entity.Student;import java.lang.reflect.F…

Vue-Vben-Admin:打造高效中大型项目后台解决方案

Vue-Vben-Admin&#xff1a;打造高效中大型项目后台解决方案 摘要&#xff1a; Vue-Vben-Admin是一个基于Vue3.0、Vite、Ant-Design-Vue和TypeScript的开源项目&#xff0c;旨在为开发中大型项目提供一站式的解决方案。它涵盖了组件封装、实用工具、钩子函数、动态菜单、权限验…

conda环境下face_alignment.LandmarksType._2D AttributeError: _2D解决方法

1 问题描述 运行retalking模型时&#xff0c;代码抛出异常&#xff0c;信息如下所示&#xff1a; Traceback (most recent call last):File "D:/ml/video-retalking/inference.py", line 345, in <module>main()File "D:/ml/video-retalking/inference.…

【Vue2+3入门到实战】(18)VUE之Vuex状态管理器概述、VueX的安装、核心概念 State状态代码实现 详细讲解

目录 一、[Vuex](https://vuex.vuejs.org/zh/) 概述1.是什么2.使用场景3.优势4.注意&#xff1a; 二、需求: 多组件共享数据1.创建项目2.创建三个组件, 目录如下3.源代码如下 三、vuex 的使用 - 创建仓库1.安装 vuex2.新建 store/index.js 专门存放 vuex3.创建仓库 store/index…

nginx原理和配置项详解

一、nginx原理 Nginx是一个高性能的HTTP和反向代理服务器&#xff0c;也是一个IMAP/POP3/SMTP代理服务器。其工作原理和配置项如下&#xff1a; 工作原理&#xff1a; 反向代理&#xff1a;Nginx可以作为反向代理服务器&#xff0c;接收客户端的请求&#xff0c;然后将请求转…

46、激活函数 - Relu 激活

本节介绍一个在神经网络中非常常见的激活函数 - Relu 激活函数。 什么是ReLU激活函数 ReLU 英文名为 Rectified Linear Unit,又称修正线性单元,是一种简单但很有效的激活函数,它的定义如下: 即当输入 x 大于零时,输出等于他自己;当输入小于等于零时,输出为零,下面是re…

ArkTS - 组件生命周期

一、先说下自定义组件 在arkTs中&#xff0c;自定义组件分为两种&#xff08;我的总结&#xff09;&#xff1a; 一种是&#xff1a;根组件&#xff0c;就是被装饰器Entry装饰的入口组件&#xff0c;这也是自定义组件(父组件)。 另一种是&#xff1a;没有被Entry装饰的自定义…

基于Docker的软件环境部署脚本,持续更新~

使用时CtrlF搜索你想要的环境&#xff0c;如果没有你想要的环境&#xff0c;可以评论留言&#xff0c;会尽力补充。 本文提供的部署脚本默认参数仅适合开发测试&#xff0c;请根据实际情况调节参数。 数据库 MySQL version: 3.9 services:mysql:image: mysql:8.0.35container…

25、商城系统(七):商城项目基础功能pom.xml(重要),mybatis分页插件

截止这一章,我们就不把重心放在前端,后台的基础代码,因为后面都是业务层面的crud。 前端直接替换这两个文件夹即可,后台代码也直接复制: 一、重新更新一下所有的pom.xml 这个地方我踩了好多坑,最后得到一个完整的pom.xml,建议大家直接用我的pom.xml替换即可。 1.comm…

磁盘和文件系统管理

一&#xff1a;磁盘结构&#xff1a; 1.磁盘基础&#xff1a; 扇区固定大小&#xff0c;每个扇区4k。磁盘会进行磨损&#xff0c;损失生命周期。 设备文件&#xff1a; 一切皆文件 设备文件&#xff1a;关联至一个设备驱动程序&#xff0c;进而能够跟与之对应硬件设备进行通…

【深度解析C++之运算符重载】

系列文章目录 &#x1f308;座右铭&#x1f308;&#xff1a;人的一生这么长、你凭什么用短短的几年去衡量自己的一生&#xff01; &#x1f495;个人主页:清灵白羽 漾情天殇_计算机底层原理,深度解析C,自顶向下看Java-CSDN博客 ❤️相关文章❤️&#xff1a;【深度解析C之this…

Linux网络编程学习心得.4

1.epoll工作模式 水平触发 LT 边沿触发 ET 因为设置为水平触发,只要缓存区有数据epoll_wait就会被触发,epoll_wait是一个系统调用,尽量少调用 所以尽量使用边沿触发,边沿出触发数据来一次只触发一次,这个时候要求一次性将数据读完,所以while循环读,读到最后read默认带阻塞…