数据结构 | 第2章 线性表

2.1线性表的类型定义

线性表   是具有相同特性的数据元素的一个有限序列

  1. (a_{1},a_{2},……,a_{i-1},a_{i},a_{i+1},……,a_{n}),其中a_{1}线性起点起始节点),a_{i}作为数据元素,a_{i-1}a_{i}直接前趋a_{i+1}a_{i}直接后驱a_{n}线性终点终端节点),下标是元素的序号,n为元素总个数即表长。n=0时为空表。
  2. 在非空的线性表中,有且仅有一个开始节点a_{1},它没有直接前趋,仅有一个直接后继
  3. 有且仅有一个终端节点a_{n},它没有直接后继,仅有一个直接前趋
  • 类型定义:
ADT List{
    数据对象:D = {ai | ai属于Elemset,(i=1,2,……,n,n>=0)}
    数据关系:R = {<ai-1,ai> | ai-1,ai属于D,(i=2,3,……,n)}
    基本操作:
    InitList(&L);    DestroyList(&L);
    ListInsert(&L,i,e);    ListDelete(&L,i,&e);
    ……
}ADT List

InitList(&L)

  • 操作结果:构造一个空的线性表L

DestroyList(&L)

  • 初始条件:线性表L已经存在
  • 操作结果:销毁线性表L

ClearList(&L)

  • 初始条件:线性表L已经存在
  • 操作结果:将线性表L重置为空表

ListEmpty(L)

  • 初始条件:线性表L已经存在
  • 操作结果:若线性表L为空表,则返回TRUE;否则返回FALSE

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()的数据元素的位序。若这样的数据元素不存在则返回值为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无意义

ListInsert(&L,i,e)

  • 初始条件:线性表L已经存在,1<=i<=ListLength(L)+1
  • 操作结果:在L的第i个位置之前插入新的数据元素e,L的长度加1

ListDelete(&L,i,&e)

  • 初始条件:线性表L已经存在且非空,1<=i<=ListLength(L)
  • 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1

ListTraverse(L,visited())

  • 初始条件:线性表L已经存在
  • 操作结果:依次对线性表中每个元素调用visited()


2.2线性表的顺序表示和实现

线性表的顺序表示又称为顺序存储结构或顺序映像

        <1> 顺序存储:逻辑上相邻,物理上也相邻;依次存储,地址连续,中间没有空出存储单元

        <2> 线性表第1个数据元素a_{1}的存储位置,称作线性表的起始位置基地址

        <3> 假设线性表的每个元素需占L个存储单元,则第i+1个数据元素的存储位置和第i个数据元素的存储位置之间满足关系:LOC(a_{i+1})=LOC(a_{i})+l

        <4> 所有数据元素的存储位置均可由第一个数据元素的存储位置得到:LOC(a_{i})=LOC(a_{1})+(i-1)l

        <5> 随机存取

一维数组表示顺序表,用一变量表示顺序表的长度属性:

#define MAXSIZE 100  //最大长度
typedef struct{
    ElemType *elem;  //指向数据元素的基地址
    int length;      //线性表的当前长度
}SqList;

//ElemType不能直接用
//malloc(m)函数,开辟m字节长度的地址空间,并返回空间首地址
//sizeof(x)函数,计算变量x的长度
//free(p)函数,释放指针p所指变量的存储空间,即彻底删除一个变量
//需要加载头文件:<stdlib.h>

传地址方式——数组名做参数

  • 传递的是数组的首地址
  • 对形参数组所做的任何改变都将反映到实参数组中

——> SqList L;        //定义变量L,L是SqList这种类型的,L是个顺序表

线性表的初始化

销毁线性表

清空线性表

求线性表的长度

判断线性表是否为空

1.顺序表的查找操作
  • 按值查找:如果存在,输出是第几个元素;如果不存在,输出0
  • 顺序查找:从表的一端开始,逐个进行记录的关键字和给定值的比较
  • 平均查找长度ASL:为确定记录在表中的位置,需要与给定值进行比较的关键字的个数的期望值(平均值)叫做查找算法的平均查找长度
  • ASL=\sum_{i=1}^{n}P_{i}C_{i} =(n+1)/2       //P_{i}是第i个纪录被查找的概览,C_{i}是找到第i个记录需要比较的次数,假设每个记录的查找概率相等:P_{i}=1/n
  • 时间复杂度为O(n)
  • 空间复杂度为O(1)
 2.顺序表的插入操作
  • 在表的第i(1<=i<=n+1)个位置上,插入一个新结点e,使长度为n的线性表(a_{1},……,a_{i-1},a_{i},……,a_{n})变成长度为n+1的线性表(a_{1},……,a_{i-1},e,a_{i},……,a_{n})
  • 判断插入位置是否合法
  • 判断顺序表的存储空间是否已满,若已满返回ERROR(不是length)
  • 平均移动次数:n/2,n是线性表中元素个数
  • 时间复杂度为O(n)
  • 空间复杂度为O(1)
3.顺序表的删除算法
  • 线性表的删除运算是指将表的第i(1<=i<=n)个结点删除,使长度为n的线性表(a_{1},……,a_{i-1},a_{i},……,a_{n})变成长度为n-1的线性表(a_{1},……,a_{i-1},a_{i+1},……,a_{n})
  • 平均移动次数:(n-1)/2
  • 时间复杂度为O(n)
  • 空间复杂度为O(1)

顺序表优缺点:

        <1> 优点

  • 存储密度大(结点本身所占存储量/结点结构所占存储量)
  • 可以随机存取表中任一元素

        <2> 缺点

  • 在插入、删除某一元素时,需要移动大量元素
  • 浪费存储空间
  • 属于静态存储形式,数据元素的个数不能自由扩充


2.3线性表的链式表示和实现

线性表的链式表示又称为非顺序映像或链式映像

        <1> 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。这组存储单元既可以是连续的,也可以不连续,甚至零散分布在内存中的任意位置上

        <2> 单链表是由头指针唯一确定,因此单链表可以用头指针的名字来命名

        <3> 各节点由两个域组成:

  • 数据域:存储元素数值数据
  • 指针域:存储直接后继结点的存储位置

        <4> 链表:n个结点由指针链组成一个链表。它是线性表的链式存储映像,称为线性表的链式存储结构

        <5> 顺序存取法:访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花的时间不等

单链表   结点只有一个指针域的链表,也称线性链表

双链表   结点有两个指针域的链表

循环链表   首尾相接的链表

头指针   是指向链表中第一个结点的指针

首元结点   链表中存储第一个数据元素的结点

头节点   在链表的首元结点之前附设的一个结点

空表的表示

        1.无头结点时,头指针为空时表示空表

        2.有头结点时,当头结点的指针域为空时表示空表

设头结点的好处 

        1.便于首元结点的处理:首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其他位置一致,无需进行特殊处理

        2.便于空表和非空表的统一处理:无论链表是否为空,头指针都是指向头结点的非空指针,因此空表和非空表的处理统一

头结点的数据域内装的是什么

        头结点的数据域可以为空,也可存放线性表长度等附加信息,但此结点不能计入链表长度值

单链表

带头结点的单链表:单链表是由表头唯一确定,因此单链表可以用头指针的名字来命名,若头指针名是L,则把链表称为表L 

单链表的存储结构:

定义链表L:LinkList L;

定义结点指针p:LNode *p; <——> LinkList p;

1.单链表的初始化
  • 生成新结点作头结点,用头指针L指向头结点
  • 将头结点的指针域置空

补充算法1:判断链表是否为空(头结点和头指针仍然在),只要判断头结点指针域是否为空

补充算法2:单链表的销毁,销毁后不存在。从头指针开始,依次释放所有结点

Status DestroyList_L(LinkList &L){    //销毁单链表L
    Lnode *p;    //    或LinkList p
    while(L){
        p=L;
        L=L->next;
        delete p;
    }
    return OK;
}
    

补充算法3:清空链表。链表仍存在,但无元素,成空链表(头结点和头指针仍然在)。依次释放所有结点,并将头结点指针域设置为空

补充算法4:求单链表表长。从首元结点开始,依次计数所有结点

int ListLength_L(LinkList L){    //返回L中数据元素的个数
    LinkList p;
    p=L->next;    //p指向第一个结点
    i=0;
    while(p){    //遍历单链表,统计结点数
        i++;
        p=p->next;
    }
    return i;
}

 2.取值
  • 从链表的头指针出发,顺着链域next逐个结点往下搜索,直至搜索到第i个结点为止
  • 1.从第一个结点(L->next)顺链扫描,用指针p指向当前扫描到的结点,p初值p=L->next
  • 2.j做计数器,累计当前扫描过的结点数,j初值为1
  • 3.当p指向扫描到的下一结点时,计数器j加1
  • 4.j==1时,p所指的结点就是要找的第i个结点

 3.查找

按值查找——

根据指定数据获取该数据所在的位置(地址)

  • 1.从第一个结点起,依次和e相比较
  • 2.如果找到一个其值与e相等的数据元素,则返回其在链表中的“位置”或地址
  • 3.如果查遍整个链表都没有找到其值和e相等的元素,则返回0或“NULL”

 根据指定数据获取该数据位置序号

  • 时间复杂度为O(n)
 4.插入
  • 在第i个结点前插入值为e的新结点
  • 1.首先找到a_{i-1}的存储位置p
  • 2.生成一个数据域为e的新结点s
  • 3.插入新结点:<1> 新结点的指针域指向结点a_{i}    <2> 结点a_{i-1}的指针域指向新结点
  • 时间复杂度为O(1)      //因为不会随着n变大而增加遍历次数(不需要移动元素,只要修改指针)
5.删除
  • 1.首先找到a_{i-1}的存储位置p,保存要删除的a_{i}的值
  • 2.令p->next指向a_{i+1}

  • 3.释放结点a_{i}的空间

  • 时间复杂度为O(1)      //因为不会随着n变大而增加遍历次数(不需要移动元素,只要修改指针)

上述算法为简化,缺少q和i 的定义

6.建立单链表

头插法——元素插入在链表头部,也叫前插法(每次接在头结点后)

  • 1.从一个空表开始,重复读入数据
  • 2.生成新结点,将读入数据存放到新结点的数据域中
  • 3.从最后一个结点开始,依次将各结点插入到链表的前端(而不是直接在已经接好的结点后面接)

  • 时间复杂度为O(n)

尾插法——元素插入在链表尾部,也叫后插法

  • 1.从一个空表L开始,将新结点逐个插入到链表尾部,尾指针r指向链表的尾结点
  • 2.初始时,r同L均指向头结点。每读入一个数据元素则申请一个新结点,将新结点插入到尾结点后,r指向新结点

  •  时间复杂度为O(n)、

7.循环链表

——是一种头尾相接的链表(即表中最后一个结点的指针域指向头结点,整个链表形成一个环)

  • 优点:从表中任一结点出发均可1找到表中其他结点
  • 注意:由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p->next是否为空,而是判断它们是否等于头指针
  • 头指针表示单循环链表:找a_{1}的时间复杂度O(1),找a_{n}的时间复杂度O(n)——不方便
  • 尾指针表示单循环链表:a_{1}的存储位置是R->next->nexta_{n}的存储位置是R,两者的时间复杂度均为O(1)

带尾指针循环链表的合并

  1.  p存表头结点                        //p=Ta->next
  2. Tb表头连接到Ta表尾            //Ta->next=Tb->next->next
  3. 释放Tb表头结点                   //delete Tb->next
  4. 修改指针                              //Tb->next=p

算法时间复杂度为O(1)

8.双向链表

——在单链表的每个结点里再增加一个指向其直接前趋的指针域prior,这样链表中就形成了有两个不同方向的链

 双向循环链表

  • 让头结点的前驱指针指向链表的最后一个结点
  • 让最后一个结点的后继指针指向头结点

对称性(设指针p指向某一结点):p->prior->next=p=p->next->prior

在双向链表中有些操作(如:ListLength、GetElem等),因仅涉及一个方向的指针,故它们的算法与线性链表的相同。但在插入、删除时,则需同时修改两个方向上的指针,两者的操作时间复杂度均为O(n)

双向链表的插入

  1. s->prior=p->prior
  2. p->prior->next=s
  3. s->next=p
  4. p->prior=s

 双向链表的删除

  1. p->prior->next=p->next
  2. p->next->prior=p->prior

知道删除结点是哪一个,时间复杂度为O(1);如果需要查找删除位置在什么地方,时间复杂度为O(n)

 2.4顺序表和链表的比较

链式存储结构的优点:

  1. 结点空间可以动态申请和释放
  2. 数据元素的逻辑次序靠结点的指针来指示,插入和删除时不需要移动数据元素

缺点:

  1. 存储密度小,每个结点的指针域需额外占用存储空间。当每个结点的数据域所占字节不多时,指针域所占存储空间的比重显得很大。(存储密度 = 结点数据本身占用的空间 / 结点占用的空间总量,一般存储密度越大,存储空间利用率越高,顺序表的存储密度为1,链表小于1)
  2. 链式存储结构是非随机存取结构。对任一结点的操作都要从头指针沿指针链查找到该结点,这增加了算法复杂度

2.5线性表的应用

线性表的合并

问题描述:假设利用两个线性表La和Lb分别表示两个集合A和B,现要求一个新的集合A=A\cup B

如:La = (7,5,3,11) Lb = (2,6,3) ——>Lc = (7,5,3,11,2,6)

算法步骤:

  1. 依次取出Lb中的每个元素,执行以下操作
  2. 在La中查找该元素
  3. 如果找不到,则将其插入La的最后 

 算法时间复杂度:O(ListLength(La)*ListLength(Lb))

有序表的合并

问题描述:已知线性表La和Lb中的数据元素按值非递减有序排列,现要求将La和Lb归并为一个线性表Lc,且Lc中的数据元素仍按值非递减有序排列

如:La = (1,7,8) Lb(2,4,6,8,10,11) ——>Lc=(1,2,4,6,7,8,8,10,11)

算法步骤:

  1. 创建一个空表Lc
  2. 依次从La或Lb中“摘取”元素值较小的结点插入到Lc表的最后,直至其中一个表变空为止
  3. 继续将La或Lb其中一个表的剩余结点插在Lc表的最后

顺序表实现

 

  • 时间复杂度为O(ListLength(La)+ListLength(Lb)) 
  • 空间复杂度为O(ListLength(La)+ListLength(Lb)) 

用链表实现

  • 时间复杂度为O(ListLength(La)+ListLength(Lb)) 
  • 空间复杂度为O(1) ,不需要额外空间

2.6案例分析与实现

【案例一】一元多项式的运算:实现两个多项式加、减、乘运算

例如:

 【案例二】稀疏多项式的运算

顺序存储结构 

        1.创建一个新数组c

        2.分别从头遍历比较a和b的每一项       

  • 指数相同,对应系数相加,若其和不为0,则在c中增加一个新项
  • 指数不相同,则将指数较小的项复制到c中

        3.一个多项式已遍历完毕时,将另一个剩余项依次复制到c中即可

存在的问题:存储空间分配不灵活;运算的空间复杂度高

链式存储结构

例如:

算法步骤:(建立链表)

 (多项式相加)

【案例三】图书信息管理系统 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值