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

数据结构-线性表的顺序表示

目录

  • 前言
  • 一、线性表
    • 1.1 线性表的概念
    • 1.2 线性表的逻辑特征
  • 二、线性表的抽象数据类型
  • 三、线性表的顺序表示和实现
    • 3.1 线性表的顺序表示
    • 3.2 基本操作的实现
    • 3.3 线性表的顺序表示的优缺点
  • 总结

前言

本篇文章介绍线性表的基本概念,并使用顺序存储结构实现线性表。
本篇文章使用的程序设计语言为C语言,需要读者掌握C语言的指针、结构体和动态内存管理的知识。
如果您对C语言的指针不熟悉,请阅读https://blog.csdn.net/pyc68/article/details/135181228
如果您对C语言的结构体不熟悉,请阅读https://blog.csdn.net/pyc68/article/details/135211015
如果您对C语言的动态内存管理不熟悉,请阅读https://blog.csdn.net/pyc68/article/details/135737056

一、线性表

1.1 线性表的概念

概念:线性表是具有相同特性的数据元素的一个有限序列
( a 1 , a 2 , ⋯ , a i − 1 , a i , a i + 1 , ⋯ , a n ) ⏟ \underbrace{(a_1,a_2,\cdots,a_{i-1},a_i,a_{i+1},\cdots,a_n)} (a1,a2,,ai1,ai,ai+1,,an)

数据元素

数据元素 a 1 a_1 a1称为线性表的起始结点
数据元素 a n a_n an称为线性表的终端结点
数据元素 a i − 1 a_{i-1} ai1称为数据元素 a i a_i ai直接前驱
数据元素 a i + 1 a_{i+1} ai+1称为数据元素 a i a_i ai直接后继
下标 i i i,称为数据元素的序号,表示数据元素在线性表中的位置,称为第 i i i个数据元素 ( 1 ≤ i ≤ n ) (1 \leq i \leq n) 1in
n n n表示数据元素的总个数,即线性表的长度,当 n = 0 n=0 n=0时,称为空表;当 n > 0 n>0 n>0时,称为非空线性表

1.2 线性表的逻辑特征

在非空线性表中,线性表有以下特征

  • 有且仅有一个起始结点 a 1 a_1 a1,它没有直接前驱,仅有一个直接后继 a 2 a_2 a2
  • 有且仅有一个终端结点 a n a_n an,它没有直接后继,仅有一个直接前驱 a n − 1 a_{n-1} an1
  • 其余的内部结点 a i ( 2 ≤ i ≤ n ) a_i(2 \leq i \leq n) ai2in都有且仅有一个直接前驱 a i − 1 a_{i-1} ai1和一个直接后继 a i + 1 a_{i+1} ai+1

二、线性表的抽象数据类型

线性表的抽象数据类型定义如下:

ADT List
{数据对象: D={ai | ai属于数据元素集合,(i=1,2,...n,n ≥ 0)};数据关系: S={<a(i-1),ai>| a(i-1),ai属于D(i=2,3,...,n)}基本操作:createNullList(&L)操作结果:创建一个空表destroyList(&L)初始条件:线性表L存在操作结果:销毁一个线性表LclearList(&L)初始条件:线性表L存在操作结果:将线性表L重置为一个空表isEmpty(L)初始条件:线性表L存在操作结果:判断线性表L是否为空表listLength(L)初始条件:线性表L存在操作结果:返回线性表L中的元素个数		getElem(L,i,&e)初始条件:线性表L存在,1<= i <= listLength(L)操作结果:用e返回线性表L中第i个数据元素的值locateElem(L,e,compare())初始条件:线性表L存在,compare()是数据元素判断函数操作结果:返回L中第1个与e满足compare()的数据元素的序号i(1 <= i <= listLength(L))若这样的数据元素不存在则返回0	priorElem(L,cur_e,&pre_e)初始条件:线性表L存在操作结果:若cur_e是L的数据元素,且不是起始结点,则用pre_e返回它的前驱结点否则操作失败,pre_e无意义nextElem(L,cur_e,&next_e)初始条件:线性表L存在操作结果:若cur_e是L的数据元素,且不是终端结点,则用next_e返回它的后继结点否则操作失败,next_e无意义				insertElem(&L,i,e)初始条件:线性表L存在,1<= i <= ListLength(L)+1操作结果:在线性表的第i个位置插入一个元素e,L的长度加1deleteElem(&L,i)初始条件:线性表L存在,1<= i <= listLength(L)操作结果:删除线性表的第i个元素,L的长度减1traverseList(&L,visited())初始条件:线性表L存在操作结果:依次对线性表中每个元素调用visited()}ADT List

三、线性表的顺序表示和实现

3.1 线性表的顺序表示

顺序表的顺序表示又称为顺序存储结构或顺序映像
顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构

线性表 ( a 1 , a 2 , ⋯ , a i − 1 , a i , a i + 1 , ⋯ , a n ) (a_1,a_2,\cdots,a_{i-1},a_i,a_{i+1},\cdots,a_n) (a1,a2,,ai1,ai,ai+1,,an)
顺序存储 ⋯ ∣ a 1 ∣ a 2 ∣ ⋯ ∣ a i − 1 ∣ a i ∣ a i + 1 ∣ ⋯ ∣ a n ∣ ⋯ ‾ ‾ \underline{\overline{\cdots|a_1|a_2|\cdots|a_{i-1}|a_i|a_{i+1}|\cdots|a_n|\cdots}} a1a2ai1aiai+1an
a 1 a_1 a1存储位置称为线性表的起始位置或基地址
线性表顺序存储结构占用一片连续的存储空间,知道某个元素的存储位置就可以计算其他元素的存储位置
假设线性表的每个元素占用 c c c个存储单元,则第 i + 1 i+1 i+1个数据元素的存储位置和第 i i i个数据元素的存储位置之间满足关系(用 l o c ( a i ) loc(a_i) loc(ai)表示数据元素的存储位置) l o c ( a i + 1 ) = l o c ( a i ) + c loc(a_{i+1})=loc(a_i)+c loc(ai+1)=loc(ai)+c
由此,所有数据元素的存储位置均可由第一个数据元素的存储位置得出:
l o c ( a i ) = l o c ( a 1 ) + ( i − 1 ) × c loc(a_i)=loc(a_1)+(i-1) \times c loc(ai)=loc(a1)+(i1)×c

3.2 基本操作的实现

基本操作的实现,在C语言使用函数表示基本操作
使用结构体类型定义一个线性表

//定义返回值状态码
#define SUCCESS 1
#define ERROR   0//这里假设元素的数据类型为char
typedef char ElemType;//定义一个线性表
struct SeqList {int MAXNUM;			//线性表的最大容量(元素个数)int n;				//线性表中实际的元素个数,n <= MAXNUMElemType* element;	//存放线性表元素,element[0],element[1]...element[n-1]
};//定义一个SeqList的指针类型
typedef struct SeqList* PSeqList;

由于线性表的基本操作较多,这里只介绍一些常见的操作,文章末尾提供了完整代码的gitee地址
由于是使用顺序存储结构实现线性表,此时可以称线性表为顺序表

  1. 创建空顺序表
    step1:首先使用malloc()函数创建一个struct SeqList大小的空间
    step2:然后使用malloc()函数创建一个(sizeof(ElemType)*m)大小的空间
    step3:将MAXNUM设置为m,将n设置为0(表示当前顺序表为空)

    //2.1 创建一个空顺序表
    PSeqList createNullList_seq(int m)
    {PSeqList palist = (PSeqList)malloc(sizeof(struct SeqList));if (NULL == palist)				//判断申请空间是否成功{printf("malloc Fail!\n");return NULL;}palist->element = (ElemType*)malloc(sizeof(ElemType) * m);if (NULL == palist->element)	{printf("malloc Fail!\n");free(palist);palist = NULL;return NULL;}palist->MAXNUM = m;palist->n = 0;return palist;
    }
    
  2. 销毁顺序表
    由于这里使用malloc()创建SeqList结点,则需要使用二级指针才能完成

    //2.2 销毁一个顺序表
    void destroyList_seq(PSeqList* palist)
    {assert(*palist);assert((*palist)->element);free((*palist)->element);(*palist)->element = NULL;free(*palist);*palist = NULL;
    }
    
  3. 顺序表的查找
    从表的一端开始,逐个进行比较。找到返回该元素的位置序号,否则返回ERROR(表示未找到)

    //2.6 查找与指定值e相同数据元素的位置
    int locateElem_seq(PSeqList palist, ElemType e)
    {assert(palist);int i = 0;for (i = 0; i < palist->n; i++)if (e == palist->element[i])return i + 1;            //i+1表示第几个元素,即element[i]return ERROR;
    }
    

    算法分析
    平均查找长度ASL(Average Search Length):为确定数据元素在表中的位置,需要与给定值进行比较的关键字的个数的期望叫做查找算法的平均查找长度
    对于n个数据元素的表,查找成功时 A S L = ∑ n i = 1 P i C i ASL=\underset{i=1}{\overset{n}{\sum}}P_iC_i ASL=i=1nPiCi
    P i P_i Pi为第i个数据元素被查找的概率
    C i C_i Ci找到第 i i i个数据元素需要比较的次数
    顺序查找的平均查找长度: A S L = P 1 + 2 P 2 + ⋯ + ( n − 1 ) P n − 1 + n P n ASL=P_1+2P_2+\cdots+(n-1)P{n-1}+nP_n ASL=P1+2P2++(n1)Pn1+nPn
    根据以上得出被查找元素位置 i i i与查找次数的关系 C i = i C_i=i Ci=i
    假设每个数据元素被查找的概率相等,有 n n n个数据元素,则 P i = 1 n P_i=\frac{1}{n} Pi=n1
    那么可得,
    A S L s s = ∑ n i = 1 P i C i = 1 n ∑ n i = 1 i = n + 1 2 ASL_{ss}=\underset{i=1}{\overset{n}{\sum}}P_iC_i=\frac{1}{n}\underset{i=1}{\overset{n}{\sum}}i=\frac{n+1}{2} ASLss=i=1nPiCi=n1i=1ni=2n+1
    其中 ∑ n i = 1 i = n ( n + 1 ) 2 其中\underset{i=1}{\overset{n}{\sum}}i=\frac{n(n+1)}{2} 其中i=1ni=2n(n+1)
    算法时间复杂度 T ( n ) = O ( n ) T(n) = O(n) T(n)=O(n)
    算法空间复杂度 S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1)

  4. 顺序表的插入
    顺序表的插入运算是指在表中的第 i ( 1 ≤ i ≤ n + 1 ) i(1\leq i \leq n+1) i(1in+1)个位置上,插入一个新结点e,使长度为 n n n的顺序表变成长度为 n + 1 n+1 n+1的顺序表
    ( a 1 , ⋯ , a i − 1 , a i , ⋯ , a n ) ⟶ ( a 1 , ⋯ , a i − 1 , e , a i , ⋯ , a n ) (a_1,\cdots,a_{i-1},a_i,\cdots,a_n)\longrightarrow(a_1,\cdots,a_{i-1},\color{blue} {e}\color{black},a_i,\cdots,a_n) (a1,,ai1,ai,,an)(a1,,ai1,e,ai,,an)
    算法思想:
    step1:判断插入位置 i i i是否合法(合法值 1 ≤ i ≤ n + 1 1\leq i \leq n+1 1in+1
    step2:判断顺序表的存储空间是否已满,若满则返回ERROR
    step3:将第n至第i个位置的数据元素依次向后移动一个位置,空出第 i i i个位置
    step4将要插入的新元素 e e e放入第 i i i个位置
    step5:表长加1,插入成功返回SUCCESS

    代码实现如下:

    //2.7 在指定位置插入一个数据元素
    //假设插入位置为i,插入到第i个位置,即插入到下标为i-1的位置
    int insertElem_seq(PSeqList palist, int i, ElemType e)
    {assert(palist);if (i < 1 || i > (palist->n + 1))return ERROR;if (palist->n == palist->MAXNUM)return ERROR;int j = 0;for (j = (palist->n - 1); j >= (i - 1); j--)    //将下标为i-1以及后面的元素往后移动palist->element[j + 1] = palist->element[j];palist->element[i - 1] = e; //将下标为i-1的位置放入epalist->n += 1;    //顺序表元素个数+1return SUCCESS;
    }
    

    插入算法的分析:
    算法时间主要消耗在数据元素移动的操作上
    假设顺序表中有n个数据元素,那么
    插入位置 i = 1 i=1 i=1时,移动元素的次数为n
    插入位置 i = 2 i=2 i=2时,移动元素的次数为n-1,…
    插入位置 i = n + 1 i=n+1 i=n+1时,移动元素的次数为0
    根据以上得出插入位置 i i i和数据元素移动次数的关系 C i = n + 1 − i C_i=n+1-i Ci=n+1i
    设每个位置被插入的概率相同,插入位置为 n + 1 n+1 n+1个,则 P i = 1 n + 1 P_i=\frac{1}{n+1} Pi=n+11
    那么可得,
    ∑ n + 1 i = 1 P i C i = 1 n + 1 ∑ n + 1 i = 1 ( n + 1 − i ) = 1 n + 1 ( n + ⋯ + 1 + 0 ) = 1 n + 1 n ( n + 1 ) 2 = n 2 \begin{aligned} \underset{i=1}{\overset{n+1}{\sum}}P_iC_i =\frac{1}{n+1}\underset{i=1}{\overset{n+1}{\sum}}(n+1-i) &=\frac{1}{n+1}(n+\cdots+1+0) \\ &=\frac{1}{n+1}\frac{n(n+1)}{2}\\ &=\frac{n}{2} \end{aligned} i=1n+1PiCi=n+11i=1n+1(n+1i)=n+11(n++1+0)=n+112n(n+1)=2n
    算法时间复杂度 T ( n ) = O ( n ) T(n)=O(n) T(n)=O(n)
    算法空间复杂度 S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1)

  5. 顺序表的删除
    顺序表的删除运算是指将表的第 i i i个数据元素删除,是长度为 n n n的顺序表变成长度为 n − 1 n-1 n1的顺序表
    ( a 1 , ⋯ , a i − 1 , a i , a i + 1 , ⋯ , a n ) ⟶ ( a 1 , ⋯ , a i − 1 , a i + 1 , ⋯ , a n ) (a_1,\cdots,a_{i-1},\color{red}a_i\color{black},a_{i+1},\cdots,a_n)\longrightarrow(a_1,\cdots,a_{i-1},a_{i+1},\cdots,a_n) (a1,,ai1,ai,ai+1,,an)(a1,,ai1,ai+1,,an)
    算法思想:
    step1:判断删除的位置 i i i是否合法(合法值 1 ≤ i ≤ n 1\leq i \leq n 1in
    step2:将第 i + 1 i+1 i+1至第n位的数据元素依次向前移动一个位置
    step3:表长减1,删除成功返回SUCCESS

    代码实现

    //2.8 在指定位置删除一个数据元素
    //假设删除位置为i,删除第i个位置,即删除下标为i-1位置的元素
    int deleteElem_seq(PSeqList palist, int i)
    {assert(palist);if (i < 1 || i > palist->n)return ERROR;int j = 0;for (j = i; j <= palist->n-1; j++)palist->element[j - 1] = palist->element[j];  //将下标为i以及后面的元素往前移palist->n -= 1;  //顺序表元素个数-1return SUCCESS;
    }
    

    删除算法的分析:
    算法时间主要消耗在数据元素移动的操作上
    假设顺序表中有n个数据元素,那么
    删除位置 i = 1 i=1 i=1时,需要往前移动n-1次
    删除位置 i = 2 i=2 i=2时,需要往前移动n-2次,…
    删除位置 i = n i=n i=n时,需要往前移动0次
    根据以上得出删除位置和数据元素移动次数的关系 C i = n − i C_i=n-i Ci=ni
    设每个位置被删除的概率相同,删除位置为 n n n个,则 P i = 1 n P_i=\frac{1}{n} Pi=n1
    那么可得,
    ∑ n i = 1 P i C i = 1 n ∑ n i = 1 ( n − i ) = 1 n ( n − 1 + ⋯ + 1 + 0 ) = 1 n n ( n − 1 ) 2 = n − 1 2 \begin{aligned} \underset{i=1}{\overset{n}{\sum}}P_iC_i =\frac{1}{n}\underset{i=1}{\overset{n}{\sum}}(n-i) &=\frac{1}{n}(n-1+\cdots+1+0) \\ &=\frac{1}{n}\frac{n(n-1)}{2}\\ &=\frac{n-1}{2} \end{aligned} i=1nPiCi=n1i=1n(ni)=n1(n1++1+0)=n12n(n1)=2n1
    算法时间复杂度 T ( n ) = O ( n ) T(n)=O(n) T(n)=O(n)
    算法空间复杂度 S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1)

3.3 线性表的顺序表示的优缺点

  • 优点
    存储密度大(结点本身所占存储量/结点结构所占存储量)
    可以随机存取任意一个数据元素,即可以根据基地址计算其他数据元素的存储位置后进行存取
  • 缺点
    在插入、删除一个数据元素时,需要进行数据元素移动操作,时间复杂度较高
    属于静态存储形式,数据元素个数不可随意扩充,一次性申请一块sizeof(ElemType)*MAXNUM大小的存储空间,当数据元素的个数等于MAXNUM时,无法再进行插入新的数据元素;当数据元素的个数小于MAXNUM时,又会剩余存储空间,导致存储空间的浪费。

线性表的顺序表示适合对数据元素进行大量的随机存取操作,但不适合进行大量的插入、删除操作。

总结

完整代码:https://gitee.com/PYSpring/data-structure/tree/master

相关文章:

  • Webstorm vue项目@路径不能跳转到对应资源,提示Cannot find declaration to go to
  • Android记录19-朋友圈动态发布时间计算
  • 事件传播机制 与 责任链模式
  • Matlab 入门学习
  • .net core使用EPPlus设置Excel的页眉和页脚
  • G7易流赋能化工物流,实现安全、环保与效率的共赢
  • Java延迟初始化Logger日志对象
  • 【C++11 之nullptr关键字 用以消除空指针和0歧义】基础知识必须了解
  • 【Python教程】压缩PDF文件大小
  • Vue3中的常见组件通信之`provide`、`inject`
  • webkit 的介绍
  • 大模型网信办备案全网最详细说明(付附件)
  • Docker部署Nginx1.21.5(保姆级图文教程)
  • Mybatis框架的缓存
  • Excel导出实例
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • ES6 学习笔记(一)let,const和解构赋值
  • Leetcode 27 Remove Element
  • Redux 中间件分析
  • REST架构的思考
  • 不发不行!Netty集成文字图片聊天室外加TCP/IP软硬件通信
  • 和 || 运算
  • 理解在java “”i=i++;”所发生的事情
  • 使用 5W1H 写出高可读的 Git Commit Message
  • 通信类
  • 用简单代码看卷积组块发展
  • No resource identifier found for attribute,RxJava之zip操作符
  • hi-nginx-1.3.4编译安装
  • ​​​​​​​开发面试“八股文”:助力还是阻力?
  • #define
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • (03)光刻——半导体电路的绘制
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (el-Transfer)操作(不使用 ts):Element-plus 中 Select 组件动态设置 options 值需求的解决过程
  • (poj1.3.2)1791(构造法模拟)
  • (windows2012共享文件夹和防火墙设置
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (切换多语言)vantUI+vue-i18n进行国际化配置及新增没有的语言包
  • (原創) 博客園正式支援VHDL語法著色功能 (SOC) (VHDL)
  • (转)EOS中账户、钱包和密钥的关系
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .Net 4.0并行库实用性演练
  • .NET 8.0 发布到 IIS
  • .Net 基于IIS部署blazor webassembly或WebApi
  • .Net6使用WebSocket与前端进行通信
  • .NET8 动态添加定时任务(CRON Expression, Whatever)
  • .NETCORE 开发登录接口MFA谷歌多因子身份验证
  • .net开发引用程序集提示没有强名称的解决办法
  • .NET业务框架的构建
  • .sdf和.msp文件读取
  • @AliasFor 使用
  • @ComponentScan比较
  • @RequestParam详解