线性表的定义和特点
1、线性表的定义:
线性表是具有相同特性的数据元素的一个有限序列
(a1, a2, a3, … , ai-1, ai, ai+1, … , an)
其中,a1即首元、线性表的起点,称之为起始结点;线性终点an我们称之为终端结点;任意一个结点(中间元素)ai,其前一个结点ai-1为ai的直接前驱,后一个ai+1为ai的直接后继;i为下标,是元素的序号,表示元素在表中的位置;n为元素总个数,即表长,当n=0时称为空表。
2、线性表的逻辑特征:
(1) 在非空的线性表,有且仅有一个开始结点a1,它没有直接前驱,仅有后置前驱a2。
(2) 有且仅有一个终端结点an,它没有直接后继,而仅有一个直接前驱。
(3) 内部结点都有且仅有一个直接前驱和一个直接后驱。
(4) 线性表是一种典型的线性结构。
案例引入
一元多项式的运算
1、一元多项式的运算:实现两个多项式加、减、乘运算
Pn(x) = p0 + p1x + p2x² + … + pnxⁿ
思路:
(1) 将系数视为一组线性表,其指数与系数下标一致,可通过系数表示。
例:Pn(x) = 10 + 5x - 4x² + 3x³
指数(下标i) | 系数p[i] |
---|---|
0 | 10 |
1 | 5 |
2 | -4 |
3 | 3 |
(2) 将指数(下标)相同的系数相加,可得新的线性表即为新的一元多项式。
2、稀疏多项式(特殊一元多项式)
例:S(x) = 1 + 3x^10000 + 2x^20000
(1) 若按上一方法储存,需要20001个空间,将会造成存储空间浪费。可只存储系数不为零的项,如下表。
指数(下标i) | 系数p[i] |
---|---|
0 | 1 |
10000 | 3 |
20000 | 2 |
例:A(x) = 7 + 3x + 9x^8 + 5x^17
下标i | 系数p[i] | 指数 |
---|---|---|
0 | 7 | 0 |
1 | 3 | 1 |
2 | 9 | 8 |
3 | 5 | 17 |
线性表A = ((7,0),(3,1),(9,8),(5,17))
B(x) = 8x + 22x^7 - 9x^18
下标i | 系数p[i] | 指数 |
---|---|---|
0 | 8 | 1 |
1 | 22 | 7 |
2 | -9 | 18 |
线性表B = ((8,1),(22,7),(-9,18))
(2)
①创建一个新数组C
②分别从头遍历比较A和B的每一项:
若指数相同,则对应系数相加并加入c项;
若指数不相同,则将指数较小的项复制到C中。
③当一个多项式已遍历完成时,将另一个剩余项依次复制到C中即可。
例:根据方法得
线性表A = ((7,0),(11,1),)(22,7),(9,8),(5,17),(-9,18))
即C(x) = 7 + 11x + 22x^7 + 9x^8 + 5x^17 - 9x^18
由上述案例得知
顺序存储结构存在问题:①存储空间分配不灵活 ②运算的空间复杂度高
因此我们可以使用链式存储结构
图书管理系统
对于有ISBN、书名、定价等记录的图书完成查找、插入、删除、修改、排序、计数功能。
思路:将图书表抽象为线性表,表中每本图书抽象线性表中的元素。
总结
1、线性表中数据元素的类型可以为简单类型,也可以为复杂类型。
2、许多实际应用问题所涉的基本操作有很大相似性。
3、从具体应用中抽象出共性的逻辑结构和基本操作(抽象数据类型),然后实现其存储和基本操作。
线性表的类型定义
抽象数据类型线性表的定义如下:
ADT List{ //定义了一个叫做List(线性表)的抽象类型
数据对象:D = {ai|ai属于元素集合(Elemset),(i=1,2,…,n,n≥0)}
数据关系:R = {<ai-1,ai>|ai-1,ai属于D,(i=1,2,…,n,n≥0)}//序偶关系,表示前驱和后继的关系
基本操作:
InitList(&L); //初始化线性表
DestroyList(&L); //销毁线性表
ListInsert(&L,i,e); ListDelete(&L,i,e);
……等
}ADT List
线性表的基本操作
线性表的初始化
操作名称: InitList(&L)
操作结果:构造一个空的线性表L
线性表的销毁和重置
1、操作名称:DestroyList(&L)
初始条件:线性表L已经存在
操作结果:销毁线性表L
2、操作名称:ClearList(&L)
初始条件:线性表L已经存在
操作结果:将线性表L重置为空表
3、线性表的销毁和重置的区别
销毁指的是将线性表删除,重置指的是将线性表中的元素删除,线性表仍然存在,为空表。
判断线性表是否为空
操作名称:ListEmpty(&L)
初始条件:线性表L已经存在
操作结果:若线性表L为空表,则返回TURE;否则返回FALSE
测量线性表的长度
操作名称:ListLength(&L)
初始条件:线性表L已经存在
操作结果:返回线性表L中的数据元素个数
获取线性表中元素
操作名称:GetElem(L,i,&e)
初始条件:线性表L已经存在,1≤i≤ListLength(&L)
操作结果:用e返回线性表L中的第i个数据元素的值
查找定位线性表中元素
操作名称:LocateElem(L,i,compare)
初始条件:线性表L已经存在,compare()是数据元素判定函数
操作结果:返回L中第1个与e满足compare()的数据元素的位序,若这样的数据元素不存在则返回0
求线性表中元素前驱
操作名称:PriorElem(L,cur_e,&pre_e)
初始条件:线性表L已经存在
操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱;否则操作失败,pre_e无意义
求线性表中元素前驱和后继
1、求线性表中元素前驱
操作名称:PriorElem(L,cur_e,&pre_e)
初始条件:线性表L已经存在
操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱;否则操作失败,pre_e无意义
2、求线性表中元素后继
操作名称:NextElem(L,cur_e,&next_e)
初始条件:线性表L已经存在
操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的前驱;否则操作失败,next_e无意义
在线性表中插入和删除元素
1、在线性表中插入
操作名称:ListInsert(&L,i,e)
初始条件:线性表L已经存在,1≤i≤ListLength(&L)+1
操作结果:在线性表L中的第i个位置前插入新的数据元素e,L的长度加一
2、在线性表中插入
操作名称:ListDelete(&L,i,&e)
初始条件:线性表L已经存在,1≤i≤ListLength(&L)
操作结果:删除线性表L中的第i个数据元素,并用e返回其值,L的长度减一
遍历线性表的元素
操作名称:ListTraverse(&L,visited())
初始条件:线性表L已经存在
操作结果:依次对线性表中每个元素调用visited()
线性表的顺序表示和实现
【总结】
- 顺序表(线性表的顺序存储结构)的特点
(1)利用数据元素的存储位置表示线性表中相邻数据元素之间的前后大系,即线性表的逻辑结构与存储结构一致
(2)在访问线性表时,可以快速地计算出任何一个数据元素的存储地址。因此可以粗略地认为,访问每个元素所花时间相等。这种存取元素的方法被称为随机存取法 - 优点:
⑴存储密度大(结点本身所占存储量/结点结构所占存储量)
⑵可以随机存取表中任一元素 - 缺点:
⑴在插入、删除某一元素时,需要移动大量元素
⑵浪费存储空间
⑶属于静态存储形式,数据元素的个数不能自由扩充
顺序存储结构
- 线性表的顺序表示又称为顺序存储结构或顺序映像。
- 顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构,即逻辑上相邻的元素物理上仍然相邻。
- 线性表的第1个数据元素a1的存储位置,称作线性表的起始位置或基地址。
- 依次存储,地址连续——中间没有空出存储单元即为一个典型的线性表顺序存储结构。
- 线性表顺序存储结构占用一片连续的存储空间。知道某个元素的存储位置就可以计算其他元素的存储位置。
- 顺序表的特点:
- 以物理位置相邻表示逻辑关系,其逻辑位序和物理位序相差1
- 任意元素均可随机存取(优点)
顺序表中元素存储位置的计算
由题意知,ai占用了第2000—第2007个存储单元,则ai+1的存储位置为第2008单元
假设线性表的每个元素需要占l个存储单元,则第i+1个数据元素的存储位置和第i个数据元素的存储位置之间满足关系:LOC(ai+1) = LOC(ai) + l
由此,所有数据元素的存储位置均可由第一个数据元素的存储位置得到:LOC(ai+1) = LOC(ai) + (i-1) × l(LOC(ai)为基地址,该运算称为随机存取)
顺序表的顺序存储表示
存在问题:线性表表长可变,数组长度不可动态定义
解决方法:用一变量表示顺序表的长度属性
由此可得,线性表用类C语言可定义为
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
typedef strut { //定义结构体
ElemType elem[LIST_INIT_SIZE];
int length; //当前长度
}SqList;
[注]一维数组的定义方式:类型说明符 数组名[常量表达式]
- 例:int a[10] 即定义一个数组a,其长度为10
- 说明:常量表达式中可以包含常量和符号常量,不能包含变量。即C语言中不允许对数组的大小做动态定义。
一元多项式的顺序存储结构类型定义
一元多项式:Pn(x) = p1 xe1 + p2 xe2 + … + pm xem
线性表:P = ((p1 , e1) , (p2 , e2) , … , (pm , em))
#define MAXSIZE 1000 //多项式可能达到的最大长度
typedef strut { //多项式非零项的定义(定义每一个元素的类型)
float p; //定义系数
int e; //定义指数
}Polynomial; //表示元素类型
typedef strut { //存储顺序表
Polynomial *elem;//存储空间的基地址
int length; //多项式中当前项的个数
}SqList; //多项式的顺序存储结构类型为SqList
图书表的顺序存储结构类型定义
#define MAXSIZE 1000 //书本可能达到的最大数量
typedef struct { //定义顺序表中的一个元素
char ISBN[20]; //定义书本的ISBN码
char shuming[50]; //定义书本的书名
float jiage; //定义价格
}Book; //表示元素
typedef struct{ //定义顺序表
Book *elem; //存储空间的基地址
int length; //定义图书表当前书本数量
}SqList //图书表的顺序存储结构类型为SqList
顺序表基本操作的实现
#define MAXSIZE 100
typedef struct
{
ElemType *elem:
int length;
}SqList;//定义顺序表类型
SqList L;
//定义变量L,L是SqList这种类型的,L是个顺序表
线性表的基本操作
-
IntList(&L)
//初始化操作,建立一个空的线性表L -
DestroyList(&L)
//销毁已存在的线性表L -
ClearList(&L)
//将线性表清空 -
IsEmpty(L)
//若线性表为空,返回true,否则返回false -
ListLength(L)
//返回线性表L的元素个数 -
GetElem(L,i,&e)
//将线性表L中第i各位置元素返回给e
补充:操作算法中用到的预定义常量和类型
1、函数结果状态代码
#define TRUE // 1
#define FALSE // 0
#define OK // 1
#define ERROR // 0
#define INFEASIBLE // -1
#define OVERFLOW // -2
2、
typedef int Status; //Status 是函数的类型,其值是函数结果状态代码(返回值的类型)
typedef char ElemType;//定义元素的类型
※顺序表的初始化
【算法步骤】:
①为顺序表L动态分配一个预定义大小的数组空间,使elem指向这段空间的基地址;
②将表的当前长度设为0。
【算法描述】:
Status InitList_Sq(SqList &L) //构造一个空的顺序表L
{
L.elem = new ElemType[MAXSIZE]; //为顺序表分配空间
if(!L.elem)
exit(OVERFLOW); //存储分配失败
L.length //空表长度为0
return OK
}
销毁线性表L
【注】:使用C++中函数直接销毁
void DestroyList(SqList &l){
if (L.elem)
delete L.elem; //释放存储空间
}
清空线性表L
void ClearList(SqList &l){
L.length = 0; //将线性表的长度置为0
}
求线性表的长度
int GetLength(SqList L){
return(L.length);
}
判断线性表L是否为空
int IsEmpty(SqList L){
if(L.length == 0)
return 1;
else return 0;
}
※顺序表的取值
【算法步骤】:
①判断指定的位置序号 i 值是否合理(1 ≤i ≤L.length),若不合理,则返回ERROR;
②若 i 值合理,则将第 i 个数据元素L.elem[i-1]赋给参数e,通过e返回第 i 个数据元素的传值。
【算法描述】:
int GetElem(SqList L,int i,ElemType &e){
if(i<1|i>L.length)//判断i值是否合理
return ERROR;
e = L.elem[i-1]; //第i-1的单元存储着第i个数据
return OK;
}
【算法分析】:O(1)
※顺序表的查找
【算法步骤】:
①从第一个元素起,依次和e相比较,若找到与e相等的元素L.elem[i],则查找成功,返回该元素的序号i+1;
②若查遍整个顺序表都没有找到,则查找失败,返回0。
【算法描述】:
1、用for语句描述
int LocateElem(SqList L,ELemType e){
//在线性表L中查找值为e的数据元素,返回其序号(是第几个元素)
for (i = 0;i < L.length;i++)
if(L.elem[i] == e)
return i+1;//查找成功,返回序号
return 0; //查找失败,返回0
2、用while语句描述
int LocateElem(SqList L,ELemType e){
//在线性表L中查找值为e的数据元素,返回其序号(是第几个元素)
i = 0;
while(i < L.length && L.elem[i] != e)
i++;
if(i<L.length)
return i+1;//查找成功,返回序号
return 0; //查找失败,返回0
【算法分析(for)】:O(n)
因为查找算法的基本操作为:将记录的关键字同定值进行比较,即L.elem[i] == e。
平均查找长度(ASL) :为了确定在表中的位置,需要与给定值进行比较的关键字的个数的期望值
※顺序表的插入
线性表的插入运算是指在表的第i(1 ≤i ≤n+1)个位置上,插入一个新结点e,使长度为n的线性表(a₁,…,ai-1,ai,…,an)变成长度为n+1的线性表(a₁,…,ai-1,e,ai,…,an)
【算法步骤】:
①判断插入位置 i 是否合法(1 ≤i ≤n+1),若不合法则返回ERROR;
②判断顺序表的存储空间是否已满,若满则返回ERROR;
③将第n个至第i个位置的元素依次向后移动一个位置,空出第i个位置(i=n+1时无需移动);
④将要插入的新元素e放入第i个位置;
⑤表长加1。
【算法描述】:
Status ListTnsert_Sq(SqList &L,int i, ElemType e)
{//
if((i < i)||(i > L.length+1))
return ERROR; //i值不合法
if(L.length == MAXSIZE)
return ERROR; //当前存储空间已满
for(j = L.length-1;j >=i-1;j--)
L.elem[j+i] = L.elem[j]; //从最后一个元素开始依次向后移动
L.elem[i-1] = e; //将新元素e放入第i个位置
++L.length; //表长增1
return OK;
}
【算法分析】O(n)
算法时间主要耗费在移动元素的操作上,而移动元素的个数取决于插入元素的位置。
假设pi是在第i个元素之前插入一个元素的概率,Eins为在长度为n的线性表中插入一个元素是所需移动的期望值(平均次数),则有
假定在线性表的任何位置上插入元素都是等概率的,则得
※顺序表的删除
线性表的删除操作是指将表的第i(1 ≤i ≤n+1)个元素删去,使长度为n的线性表(a₁,…,ai-1,ai,…,an)变成长度为n-1的线性表(a₁,…,ai-1,ai+1,…,an)
【算法步骤】:
①判断插入位置 i 是否合法(1 ≤i ≤n+1),若不合法则返回ERROR;
②将第i+1个至第n个位置的元素依次向后移动一个位置(i=n时无需移动);
④表长减1。
【算法描述】:
Status ListTnsert_Sq(SqList &L,int i)
{//在顺序表L中删除第 i 个元素,i值的合法范围是1≤i≤L.length
if((i < i)||(i > L.length+1))
return ERROR; //i值不合法
for(j = 1;j >L.length-1;j++)
L.elem[j-1] = L.elem[j]; //被删除元素之后的元素前移
--L.length; //表长前1
return OK;
}
【算法分析】O(n)
算法时间主要耗费在移动元素的操作上,而移动元素的个数取决于插入元素的位置。
假设pi是在第i个元素之前插入一个元素的概率,Eins为在长度为n的线性表中插入一个元素是所需移动的期望值(平均次数),则假定在线性表的任何位置上插入元素都是等概率的,可得
线性表的链式表示和存储
-
链式存储结构的优点:
1、结点空间可以动态申请和释放;
2、数据元素的逻辑次序靠结点的指针来指示,插入和删除时不需要移动数据元素。 -
链式存储结构的缺点:
存储密度小,每个节点的指针域需额外占用存储空间。每个节点的数据域所占字节不多时,指针域所占存储空间的比重显得很大。
链式存储结构
- 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
- 线性表的链式表示又称为非顺序映像或链式映像
- 用一组物理位置任意的存储单元来存放线性表的数据元素
- 这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的
- 链表中元素的逻辑次序和物理次序不一定相同。
例:
第一个元素的地址为头指针,即表的头指针H为0031,可得单链表示意图如下:
- 由于单链表是由头指针唯一确定,因此单链表可以用头指针的名字来命名。
例:26个英文小写字母表的链式存储结构
- 结点:数据元素的存储映像。各结点由两个域组成:
数据域:存储元素数值数据
指针域:存储直接后继结点的存储位置 - 链表:n个结点由指针链组成一个链表。
它是线性表的链式存储映像,称为线性表的链式存储结构
- 单链表、双链表、循环链表
⑴结点只有一个指针域的链表,称为单链表或线性链表
⑵结点由两个指针域的链表,称为双链表
⑶首尾相接的链表称为循环链表
- 头指针、头结点、首元结点
- 【注】
⑴如何表示空表
①有头结点时,头指针的指针域为空时表示空表
②无头结点时,头指针为空为空时表示空表
⑵在链表中设置头结点有什么好处
①便于首元结点的处理
②便于空表和非空表的的统一处理
⑶头结点的数据域内装的是什么
可以为空,也可存放线性表长度等附加信息,但此结点不计入链表长度值 - 链式存储结构的特点
(1)结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。
(2)访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等
单链表的定义和表示
- 单链表是由表头唯一确定,因此单链表可以用头指针的名字来命。若头指针名是L,则把链表称为表L。
- 单链表的存储结构
typedef struct Lnode{ //声明结点的类型和指向结点的指针类型
ElemType data; //结点的数据域
struct Lnode *next; //结点的指针域
}Lnode,*LinkList //LinkList为指向结构体Lnode的指针类型
//定义链表L:LinkList L;
//定义结点指针p:LNode *p;
学生表的单链表结点类型定义
存储学生学号、姓名、成绩的单链表结点类型定义如下:
typedef Struct{
char num[8]; //
char name[8]; //
int score; //
}ElemType;
typedef struct Lnode{
ElemType date; //
struct Lnode *next; //
}Lnode,*LinkList;
单链表基本操作的实现
※单链表的初始化
【算法步骤】:
⑴生成新结点作头结点,用头指针L指向头结点;
⑵将头结点的指针域置空。
【算法描述】:
Status InitList_(LinkList &L){
L = new LNode; //
L->next = NULL;
return OK;
}
判断链表是否为空
空表:列表中无元素,称为空列表(头指针和头结点仍然在)
【算法思路】:判断头结点指针域是否为空
【算法描述】:
int ListEmpty(LinkList){ //
if(L->next) //
return 0;
else
return 1;
}
【算法分析】:
①O(n)
②ASL = n-1 / 2
单链表的销毁
【算法思路】:从头指针开始,依次释放所有结点
【算法描述】:
Status DestroyList_L(LinkList &L){//
Lnode *p; //
whlie(L){
p = L;
L = L->next;
delete p;
}
return OK;
}
清空列表
链表仍存在,但链表中无元素,称为空链表(头指针和头结点仍然在)
【算法思路】:
依次释放所有结点,并将头结点指针域设置为空
【算法描述】:
Status ClearList(LinkList &L){ //
Lnode *p,*q;
p = L->next;
while(p){ //
q = p->next;
delete p;
p = q;
}
L->next = NULL; //
return OK;
}
求单链表L的表长
【算法思路】:从首元结点开始,依次计数所有结点
【算法描述】
int ListLength_L(LinkList L){ //
LinkList p;
p=L->next; //
i=0;
while(p){
i++;
p=p->next;
}
※单链表的取值
【算法思路】:从链表的头指针出发,顺着链域next逐个结点往下搜索,直到搜索到第i个结点为止。因此,链表不是随机存储。
【算法步骤】:
⑴从第1个结点(L->next)顺链扫描,用指针p指向当前扫描的结点,p初值p=L->next;
⑵j做计数器,累计当前扫描过的结点数,j初值为1;
⑶当p指向扫描的下一结点时,计数器j加1;
⑷当j==i时,p所指的结点就是要找的第i个结点。
【算法描述】
Status GetElem_L(LinkList L,int i,ElemType &e)
{//
p=L->next;
j=1; //
while(p&&j<i){ //
p=p->next;
++j;
}
if(!p||j>i)
return ERROR; //
e=p->data; //
return OK;
}//GetElem_L
【算法分析】:
ASL=n-1/2 ; O(n)
※单链表的查找
按值查找——据指定数据获取该数据位置(地址)
【算法描述】
Lnode *LocateElem_L(LinkList L,Elemtype e){
//在线性表L中查找值为e的数据元素
//找到,则返回L中值为e的元素地址,查找失败返回NULL
p=L->next;
while(p&&p->data!=e)
p=p->next;
return p;
}
【算法分析】:O(n)
按值查找——据指定数据获取该数据位置序号
【算法描述】
//在线性表L中查找值为e的数据元素的位置序号
int LocateElem_L(LinkList,Elemtype e){
//返回L中值为e的数据元素的位置信号,查找失败返回0
p=L->next;
j=1;
while(p&&p->data!=e){
p=p->next;
j++;
}
if(p)
return j;
else
return 0;
}
【算法分析】:O(n)
※单链表的插入
在第 i 个结点前插入值为 e 的新结点
【算法步骤】:
⒈首先找到ai-1的存储位置p;
⒉生成一个数据域为e的新结点s;
⒊插入新结点:①新结点的指针域指向结点ai(s->next=p->next);②结点ai-1的指针域指向新结点(p->next=s)。
【算法描述】
//在L中第i个元素之前插入数据元素e
Status ListInsert_L(LinkList &L,int i,ElemType e){
p=L;
j=0;
while(p&&j<i-1){
p=p->next;
++j;
}//寻找第i-1个结点,p指向i-1结点
if(!p||j>i-1)
return ERROR;
//i大于表长+1或者小于1,插入位置非法
s=new LNode;s->data=e;//生成新结点s,将结点s的数据域置为e
s->next=p->next; //将结点s插入L中
p->next=s;
return OK;
}//ListInsert_L
【算法分析】:O(n)
※单链表的删除
删除第i个结点
【算法步骤】:
删除单链表的第i个结点ai的具体过程如下:
①查找结点ai-1并由指针p指向该结点;
②临时保存待删除结点ai的地址在q中,以备释放;
③将结点*p的指针域指向ai的直接后继结点;
④释放结点ai的空间。
【算法描述】:
//将线性表L中第i个数据元素删除
Status ListDelete L(LinkList &L,int i,ElemType &e){
p=L;j=0;
while(p->next&&j<i-1){
p=p->next;
++j;
}//寻找第i个结点,并令p指向其前驱
if(!(p->next)llj>i-1)
return ERROR; //删除位置不合理
q=p->next; //临时保存被删结点的地址以备释放
p->next=q->next; //改变删除结点前驱结点的指针域
e=q->data; //保存删除结点的数据域
delete q; //释放删除结点的空间
return OK;
}//ListDelete L
Status GetElem L(LinkList L,int i,ElemType &e){
//获取线性表L中的某个数据元素的内容,通过变量e返回
p=L->next; j=1; //初始化
while(p&&j<i){ //向后扫描,直到p指向第i个元素或p为空
p=p->next; ++j;
}
if(!p|j>i)
return ERROR; //第i个元素不存在
e=p->data; //取第i个元素
return OK;
}//GetElem L
【算法分析】:
类似于插入算法,删除算法时间复杂度亦为O(n)。
※单链表的创建
前插法(头插法)
头插法(前插法)是通过将新结点逐个插入链表的头部(头结点之后)来创建链表,每次申请一个新结点,读入相应的数据元素值,然后将新结点插入到头结点之后。
【算法步骤】:
1、创建一个只有头结点的空链表。
2、根据待创建链表包括的元素个数n,循环n次执行以下操作:
①生成一个新结点 *p;
②输入元素值赋给新结点 *p的数据域;
③将新结点 *p插入到头结点之后。
下为头插法创建线性表(a,b,c,d,e):
【算法描述】:
void CreateList_H(LinkList &L,int n)
{//逆序位输入n各元素的值,建立带表头结点的单链表L
L = new LNode;
L->next = NULL; //先建立一个带头结点的空链表
for(i=0;i<n;++i){
p = new LNode;//生成新结点*p
cin>>p->date; //输入元素赋值给新结点*p的数据域
p->next = L->next;L->next = p;//将新结点*p插入到头结点之后
}
}
后插法
后插法是通过将新结点逐个插入链表的尾部来创建链表,每次申请一个新结点,读入相应的数据元素值,将新结点插入到表尾之后,增加一个尾指针r向链表的尾结点,。
【算法步骤】:
1、创建一个只有头结点的空链表。
2、尾指针r初始化,指向头结点。
3、根据待创建链表包括的元素个数n,循环n次执行以下操作:
①生成一个新结点 *p;
②输入元素值赋给新结点 *p的数据域;
③将新结点 *p插入到尾结点 *r之后;
④尾指针r指向新的尾结点 *p。
下为头插法创建线性表(a,b,c,d,e):
【算法描述】:
void CreateList_R(LinkList &L,int n)
{//正位序输入n个元素的值,建立带头表结点的单链表L
L = new LNode;
L-> = NULL;//先建立一个带头结点的空链表
r = L; //尾指针r指向头结点
for(i=0;i<n;i++{
p = new LNode;//生成新结点
cin>>p->date;//输入元素值赋给新结点*p的数据域
p->next = NULL; r->next = p;
//将新结点*p插入尾结点*r之后
r=p; //r指向新的尾结点*p
}
}//CreateList_R
循环列表
循环链表是一种头尾相接的链表,即表中最后一个结点的指针域指向头结点,整个链表形成一个环。循环列表从表中任一结点出发均可找到表中其他结点。
【注意】:
由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环列表那样判断p或p->next是否为空,而是判断它们是否等于头指针,即循环条件
p!=NULL → p!=L
p->next!=NULL → p->next!=L
由于以下原因,我们通常使用尾指针表示单循环链表
※带尾指针循环列表的合并
将带尾指针循环列表Tb合并在Ta之后
【算法步骤】:
【算法描述】
LinkList Connect(LinkList Ta, LinkList Tb)
{//假设Ta、Tb都是非空的单循环链表
p=Ta->next; //①p存表头结点
Ta->next=Tb->next->next; //②Tb表头连接Ta表尾或free(Tb->next)
delete Tb->next; //③释放Tb表头结点
Tb->next=p; //④修改指针
return Tb;
}
【算法分析】:时间复杂度是O(1)。
双向链表
双向链表是在单链表的每个结点里再增加一个指向其直接前驱的指针域prior,可以克服单链表查找某结点前驱结点的时间复杂度为O(n)的缺点。
双向链表的结构
由此知,双向链表的结构可定义如下:
typedef strut DuLNode{
Elemtype data;
struct DuLNode *prior,*next;
}DuLNode,*DuLinkeList;
双向链表结构的对称性(设指针p指向某一结点):
p -> prior -> next = p = p -> next ->prior
在双向链表中有些操作(如:ListLength、GetElem等),因仅涉及一个方向的指针,故它们的算法与线性链表的相同。但在插入、删除时,则需同时修改两个方向上的指针,两者的操作的时间复杂度为O(n)。
双向链表的插入
【算法分析】:
void ListInsert_DuL(DuLinkList &L, int i,ElemType e){
//在带头结点的双向循环链表L中第i个位置之前插入元素e
if(!(p=GetElemP_DuL(Li)))
return ERROR;
s=new DuLNode;
s->date=e;
s->prior=p->prior;
p->prior->next=s;
s->next=p;
p->prior=s;
return OK;
}// ListInsert_DuL
双向链表的删除
【算法描述】:
void ListDelete_DuL(DuLink &L, int i,ElemType &e)
{//删除带头结点的双向循环链表L的第i个元素,并用e返回。
if(!(p=GetElemP_DuL(L,i)))
return ERROR;
e=p-> data;
p-> prior-> next =p-> next;
p->next->prior=p-> prior;
free(p);
return OK;
} // ListDelete_DuL
双向循环链表
和单链表的循环类似,双向链表也可以有循环表
- 让头结点的前驱指针指向链表的的最后一个结点
- 让最后一个结点的后继指针指向头结点
顺序表和链表的比较
【注】:存储密度是指结点数据本身所占的存储量和整个结点结构中所占的存储量之比,即:
线性表的应用
线性表的合并
-
问题描述:
假设利用两个线性表La和Lb分别表示两个集合A和B,现要求一个新的集合A = A∪B。其中La = (7,5,3,11),La = (2,6,3),新的集合要求表示为La = (7,5,3,11,2,6) -
算法步骤:
依次取出Lb中的每个元素,执行以下操作:①在La中查找钙元素;②如果找不到,则将其插入La的最后。 -
算法描述:
void union(List &La,List Lb){
La_len=ListLength(La);
Lb_len=ListLength(Lb);
for(i=1;i<=Lb_len;i++){
GetElem(Lb,i,e);
if(!LocateElem(La,e)
ListInseert(&La,++La_len,e);
}
}
- 算法分析:
算法的时间复杂度是: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表的最后。
有序表合并——用顺序表实现
【算法思路】:
【算法描述】:
void MergeList_Sq(SqList LA,SqList LB,SqList &LC){
pa=LA.elem;
pb=LB.elem;
//指针pa和pb的初值分别指向两个表的第一个元素
LC.length=LA.length+LB.length;
//新表长度为待合并两表的长度之和
LC.elem=new ElemType[LC.length];
//为合并后的新表分配一个数组空间
pc=LC.elem; //指针pc指向新表的第一个元素
pa last=LAelem+LA.length-1
//指针pa last指向LA表的最后一个元素
pb last=LB.elem+LB.length-1;
//指针pblast指向LB表的最后一个元素
while(pa<=pa_last &&pb<=pb_last){ //两个表都非空
if(*pa<=*pb)
*pc++=*pa++; //依次“摘取” 两表中值较小的结点
else *pc++=*pb++;
}
while(pa<=pa_last)
*pc++=*pa++;//LB表已到达表尾,将LA中剩余元素加入LC
while(pb<=pb_last)
*pc++=*pb++;//LA表已到达表尾,将LB中剩余元素加入LC
}//MergeList_Sq
【算法分析】:
算法的时间复杂度是:O(ListLength(La)+ListLength(Lb))
算法的空间复杂度是:O(ListLength(La)+ListLength(Lb))
有序表合并——用链表实现
【算法思路】:
【算法描述】:
void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc){
pa=La->next;
pb=Lb->next;
pc=Lc=La; //用La的头结点作为Lc的头结点
while(pa &&pb){
if(pa->data<=pb->data){
pc->next=pa;
pc=pa;
pa=pa->next;
}
else{
pc->next=pb;
pc=pb;
pb=pb->next;
}
}
pc->next=pa?pa:pb; //插入剩余段
delete Lb; //释放Lb的头结点
}
【算法分析】:
算法的时间复杂度是:O(ListLength(La)+ListLength(Lb))