1
第二章 线性表
线性表的定义
线性表的顺序表示
线性表的链式表示
一元多项式的运算
数
据
结
构
之
线
性
表
2
2.1 线性表的类型定义
?线性表
? 定义: n 个数据元素的有限序列。
( A , B , C , ...... , X , Y , Z )
( a 1 , a 2 , ... , a i -1 , a i , a i + 1, ... , a n - 1 , a n )
? 线性表的逻辑特性:
?线性表中的所有数据元素,其数据类型是
一致的;
?数据元素在表中的位置只取决于它们自己
的序号,数据元素之间的相对位置是线性
的
2
数
据
结
构
之
线
性
表
3
?基本术语
? 直接前驱: a i - 1 针对 a i
? 直接后继: a i + 1,
?线性表的长度: 是指线性表中数据
元素的个数, n = 0 时为空表。
?位序: 下标i是数据元素 ai , 在线
性表中的位序。
数
据
结
构
之
线
性
表
4
?线性表抽象数据类型的定义
? ADT List {数据对象 :D= {a i | a i ∈ ElemSet , i
= 1,2,...,n, n ≥ 0}
数据关系 :R1={<a i -1 ,a i >|a i -1 ,a i ∈D, i = 1,2,...,n }
? 基本操作: & 符号说明函数参数是引用型
InitList(&L) ListLength(L)
DestroyList(&L) GetElem(L , i , &e)
ClearList(&L) LocateElem(L , e)
ListEmpty(&L) PriorElem(L , cur_e , &pre_e)
ListInsert(&L , i , e) ListDelete(&L , i , &e)
.......}
3
数
据
结
构
之
线
性
表
5
? 求 A = A ∪ B 的算法
? 分析:建立线性表 La、 Lb,将 La中不存在
的 bi插入 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); /* O(1) */
if(!LocateElem(La,e)) /* O(La_len ) */
ListInsert(La,+ +La_len,e); /* O(1 ) */
} }
? 算法分析:考虑算法的最费时执行状况,作
为算法的时间复杂度 O(La_len*Lb_len)
数
据
结
构
之
线
性
表
6
?求 LC = LA+ LB 的算法 :La和 Lb的元素按升序
排列,将 La和 Lb归并为 Lc。
void MergeList( List La ,List Lb,List &Lc){
InitList(Lc); i = j = 1; k = 0;
La_len=ListLength(La);Lb_len=ListLength(Lb);
While ((i<=La_len)&&(j<=Lb_len)) {
GetElem(La,i,ai); GetElem(Lb,j,bj);
if(ai<=bj) { ListInsert(Lc,+ +k,ai);++i}
else { ListInsert(Lc,+ +k,bj);++j} }
while (i<=La_len ) {
GetElem(La,i++,ai); ListInsert(Lc,+ +k,ai); }
while (j<=Lb_len) {
GetElem(Lb,j++,bj); ListInsert(Lc,+ +k,bj); } }
算法分析:时间复杂度 O(La_len+Lb_len)
4
数
据
结
构
之
线
性
表
7
?线性表的存储结构
?顺序存储的线性表,叫
顺序表
?链式存储的线性表,叫
链表
数
据
结
构
之
线
性
表
8
2.2 线性表的顺序表示和实现
? 概念
? 用一组地址连续的存储单元依次存储线性
表的数据元素。
? 顺序表的特点是:逻辑关系上相邻的两个
元素在物理位置上也相邻。
? 顺序表是随机存取的存储结构:
可以随机存取(所需时间相同)表中任一
元素,因为它的存储位置可用一个简单、直观
的公式(位序的线性函数)来表示。
5
数
据
结
构
之
线
性
表
9
? 数据元素 a i 的存储位置(内存地址):
(不同于位序 i )
Loc ( a i )=Loc ( a i -1 ) + L
Loc ( a i )=Loc ( a 1 ) +( i -1 )*L
Loc(a i )是 a i 在内存中的第一个字节的
地址。
L 是一个元素的字节数。
例如:整型线性表存储在内存中的地址是
C000,表中的第7个元素的存储地址是
C000+2*6=C00C
数
据
结
构
之
线
性
表
10
?顺序存储结构
? 定义
typedef int ElemType ;
typedef Elemtype ET;
typedef struct{
ElemType *elem ; //动态空间基址
int length ; //实际元素个数
int listsize ; //当前分配的存储
}SqList ; //容量 (以 sizeof(ElemType)为单位 )
SqList 是顺序表的类型名。
6
数
据
结
构
之
线
性
表
11
存储地址 内存状态 元素序号
空闲
a
a
a
a
1
2
i
n
:
:
:
:
:
:
:
b
b+L
b+(i-1)L
b+(n-1)L
B+(maxlen-1)L
1
2
i
n
线性表的顺序存储结构示意图
数
据
结
构
之
线
性
表
12
?重新分配动态存储空间的函数
realloc(原动态区首址,字节数 )
?其功能为:
?申请新的动态存储空间(成功或失败);
?将原动态区的数据拷贝到新动态区;
?释放原动态存储区;
?返回新存储区首地址(无类型)。
?用途:当原动态区不够用时,追加动态
存储区;
7
数
据
结
构
之
线
性
表
13
?顺序表的操作
? 构造一个空的线性表 L
#define List_Init_Size 100
#define ListIncrement 10
Status InitList_Sq( SqList &L){
L.elem=(ET*)malloc(List_Init_Size*sizeof(ET));
if(L.elem==NULL)
exit(OVERFLOW); /*存储分配失败 */
L.length=0; /* 空表的长度 */
L.listsize=List_Init_Size; /* 初始存储容量 */
return OK;
}
数
据
结
构
之
线
性
表
14
? 插入操作: 在顺序表L中第i个位置上插
入一个新的元素e。
?形式参数为:&L ,i , e ;
?算法步骤如下:
?对输入参数的安全性检查: 插入位置 i 应落在
表长+1范围内,即: 1 ≤ i ≤ L.length+1
?存储空间的处理:若原表的存储空间已满,应追
加存储空间的分配;
?数据块的搬移:将表中从i到L.length位置上的
所有元素往后移动一个位置;
?在第i个位置上存储新的元素e,表长增1;
?注意:逻辑位置(序号)i 对应的存储下
标是i-1。
8
数
据
结
构
之
线
性
表
15
?顺序表 ----插入操作算法描述之一
Status ListInsert_Sq(SqList &L , int i , ET e){
if ( i<1 || i>L.length+1) return ERROR;
if(L.length >= L.listsize){
p=(ET*)realloc(L.elem,(L.listsize+10)*sizeof(ET));
if (p==NULL) exit(OVERFLOW);
L.elem=p; L.listsize+=ListIncrement;
}
for( j=L.length ; j>=i ; --j )
L.elem[j]=L.elem[j-1];
L.elem[j]=e ; ++L.length ;
return OK; }
数
据
结
构
之
线
性
表
16
?顺序表 ----插入操作算法描述之二
Status ListInsert_Sq(SqList &L , int i , ET e){
if ( i<1 || i>L.length+1) return ERROR;
if (L.length >= L.listsize){
p=(ET*)realloc(L.elem,(L.listsize+10)*sizeof(ET));
if (p==NULL) exit(OVERFLOW);
L.elem=p; L.listsize+=ListIncrement;
}
q=&(L.elem[i-1]); /* q=L.elem+(i-1) 插入位置 */
for (p=&(L.elem[L.length-1]);p>=q;--p)
*(p+1)=*p;
*q=e ; ++L.length ;
return OK;
}
9
数
据
结
构
之
线
性
表
17
?插入操作的算法性能分析
?空间复杂度是 O( 1 )
?时间复杂度: O(n) , 其中 n= L.length
?步骤(1)、(2)、(4)的时
间复杂度都是常数,即: O ( 1 );
?步骤(3)的基本操作是数据移动,
最坏的情况下(在第一个位置上插入元素),
需要移动n个元素;
平均情况下(等概率),需移动表中 n/2
个元素,最好的情况是不移动元素。
因此,插入操作算法的时间复杂度是 O(n);
数
据
结
构
之
线
性
表
18
? 删除操作:长为n的线性表L(a
1
,a
2
, … ,
a
n
), 删除a
i
,并把它送入某单元e
e
a
i
a
1
a
2
a
3
a
i-1
a
i
a
n
:
:
1
2
3
i-1
i
n-1
i+1
n
:
:
a
1
a
2
a
3
a
i-1
a
n
:
:
1
2
3
i-1
i
n-1
i+1
n
:
:
a
i+1
顺序存储结构的删除操作
10
数
据
结
构
之
线
性
表
19
?形式参数为 : &L ,i , &e ;
?算法步骤如下:
?对输入参数的安全性检查: 删除位置 i
应落在表长范围内,即: 1≤ i ≤ L.length
?取出元素值赋给 e:
?数据块的搬移: 将表中从 i+1到 L.length
位置上的所有元素往前移动一个位置;
?表长减1 : --L.length;
数
据
结
构
之
线
性
表
20
?算法
Status ListDelete_Sq(SqList &L , int i , ET &e){
if(i<1 || i>L.length) return ERROR;
e=L.elem[i-1];
for( j=i ; j<L.length;j++)
L.elem[j-1]=L.elem[j];
L.length--;
}
算法分析 :基本操作是什么?时间复杂度
是多少?
11
数
据
结
构
之
线
性
表
21
? 线性链表
? 概念
?顺序表的特点:可以随机存取表中的任一元素;
在作插入和删除操作时,需移动大量的元素。
?线性表的链式存储结构:用任意的存储单元存储
线性表的数据元素;
?用链指针体现线性表中数据元素之间的逻辑关系:
?逻辑结构中的数据元素ai对应存储结构中的结点
ai (数据域)
ai+1结点的地址 (指针域)
2.3 线性表的链式表示和实现
数
据
结
构
之
线
性
表
22
?N个结点连成一个链
表:
?假设元素类型是字符
型,每个结点占用 3
个字节存储空间。
?单链表可由头指针唯
一确定;
地址 数据域指针域
0300 A 0605
0303 B 0306
0306 C NULL
030F D 0303
......
0600 E 060A
0605 F 030F
060A G 0300
......
0800 H 0600
Head
Head H E G A F D B C ^
12
数
据
结
构
之
线
性
表
23
?线性表的单链表存储结构
/*typedef char ElemType;*/
typedef struct LNode{
ElemType data;
struct LNode *next;
} Lnode, *LinkList;
Lnode 是结点类型名,
LinkList是结点指针类型名;
数
据
结
构
之
线
性
表
24
?头结点
头指针 L 是 LinkList类型,头结点是
Lnode类型;
非空表:
空表:
头结点的位序为0,它不是线性表中的
元素,头结点的数据域可用于存储线性
表的长度;
?单链表是非随机存取的存储结构。
头 5 8 3 6 ^L
头 ^L
13
数
据
结
构
之
线
性
表
25
? 单链表基本操作的算法
?在链表 L中, 读取 第 i个元素的值赋给参数 e
Status GetElem_L(LinkList L,int i,ElemType &e){
p=L → next ; j=1 ; /* O(1) */
while ( p && j < i ){ p=p → next ; j++;} /* O(n)
if ( p==NULL || j > i ) return ERROR; /* O(1)
e = p → data; /* O(1)
return OK;
}
时间复杂度为: O(n)
问题:在顺序表中, GetElem_Sq时间复杂度?
数
据
结
构
之
线
性
表
26
?构造一个空的单链表 , 算法描述: L 必须是
引用型
Status InitList_L(LinkList &L){
L=( LinkList ) malloc ( sizeof ( Lnode ) );
if (L==NULL) exit (OVERFLOW);
L → next=NULL; L → data=0;
return OK; }
对应的 C程序如下: L指向主函数中链表头指针变量
Status InitList_L(LinkList *L){ // 程序中,L是双指针
*L=( LinkList ) malloc ( sizeof ( Lnode ) );
if ( *L==NULL) exit (OVERFLOW);
(*L) → next=NULL; (*L) → data=0;
return OK ; }
14
数
据
结
构
之
线
性
表
27
?单链表算法中的关键步骤描述:
?指针 p在链表上依次滑动: p=head;
while( p->next ) {p=p->next; }
?前驱指针 q 和指针 p在链表上同步滑动:
q=head ; p=q->next;
while( p){q=p ; p=q->next;}
例如: int ListLength_L( LinkList L) {p=L;j=0;
while(p->next){j++;p=p->next;} return(j);}
Status PriorElem_L(LinkList L,ET e, ET &pre){
q=L; p=q->next;
while(p && p->data!=e){q=p;p=q->next;} …}
Status NextElem_L(LinkList L,ET e, ET &next){…}
数
据
结
构
之
线
性
表
28
?在单链表 L中的第 i 个结点之前的插
入操作
?步骤如下: ( i > 0 )
?寻找第 i-1个结点; //O(n)
?测试 已知量的合法性; //O(1)
?申请一个结点存储空间,并给其数据域
赋值; // O(1)
?新结点插入链表中。 //O(1)
?该算法的时间复杂度是: O(n)
15
数
据
结
构
之
线
性
表
29
?单链表的插入操作算法描述
参数 L可以不是引用型,因为, L指向头结点
Status ListInsert_L(LinkList L,int i,ElemType e){
p=L, j=0;
while ( p && j< i -1 ){p=p → next; j++;} //寻找
if (p==NULL || i<1) return ERROR;
s = ( LinkList ) malloc ( sizeof ( Lnode ) );
if (s == NULL) exit (OVERFLOW);
s → data=e;
s → next=p → next ; // 在 p结点之后插入结点 s
p → next = s ; /* L → data++ ;链表长度计数 */
return OK; }
数
据
结
构
之
线
性
表
30
? 单链表的删除操作: 删除第i个结点
?步骤
?若头指针为空,则退出;否则执行下面的操作。
?找到要删除结点的直接前驱,将该结点的指针域指向它
的直接后继。若不能找到,则返回。
?释放该结点所占用的内存空间。
BAT CAT
HAT WAT
L
q
^
第 i个结点
p
16
数
据
结
构
之
线
性
表
31
?算法描述
void delete(LinkList L, int i, char *e){
p=L;
j=0;
while(p->next && j<i-1){
p=p->next;
++j;
}
if(!(p->next)|| j>i-1)
return ERROR;
q=p->next;
p->next=q->next;
e=q->data;
free(q);
return OK; }
数
据
结
构
之
线
性
表
32
? 将指针 s所指的一串结点链接在线性
链表 L 的最后一个结点之后
?问题分析
?找到链表 L的最后一个结点;
?进行链接。
?算法描述:
Status Append(LinkList L , LinkList s ){
…/* L 带表头结点, s 不带表头结点 */
}
?算法分析
17
数
据
结
构
之
线
性
表
33
Status Append(LinkList L , LinkList s ){
if (s) {
p=L;
while(p->next) p=p->next;
p->next=s;
}
}
数
据
结
构
之
线
性
表
34
? 更合理的线性链表类型定义
typedef struct LNode{ //结点类型
ElemType data ;
struct LNode *next ;
} *Link , *Position ;
typedef struct{//
Link head ,tail ;//分别指向链表的 头结点 和 最后一
个结点
int len ; // 链表中数据元素的个数
} LinkL ;
参阅 Page 37, 请同学们自行完成该类型定义的第
37-38页所列各基本操作的算法。
18
数
据
结
构
之
线
性
表
35
? 基于 LinkL类型,将线性链表 s 链接在
线性链表 L 的最后一个结点之后
?解题分析:链表头尾已知,只需直接链接 .
?算法描述:
Status Append(LinkL &L , LinkL &s ){
if (s.head != s.tail){
L.tail → next =s.head → next;
L.tail=s.tail;L.len+=s.len;
s.head → next=NULL;
s.tail=s.head; } }
?算法分析:时间复杂度 O(1)
数
据
结
构
之
线
性
表
36
? 基于 LinkL类型,将指针 s 所指的一串
结点链接在线性链表 L 的最后一个结点
之后
?解题分析:链接之后,需找出 s 的最后一个结点 .
?算法描述:
Status Append(LinkL &L , Link s ){
L.tail → next =s;
while(s → next){
s =s → next ;L.len++; } // O(s_len)
L.tail=s ;
}
?算法分析:时间复杂度 O(s_len)
19
数
据
结
构
之
线
性
表
37
? 静态链表
?用数组描述的链表,表示链的是数组下标
(整型)。
?结构类型定义:
#define MaxSize 13
typedef struct{
ElemType data ;
int cur;
}SLinkList [MaxSize];
SLinkList space;//space是有 13个元素的数组名
数
据
结
构
之
线
性
表
38
?循环链表和双向链表
? 循环链表:
非空循环链表:
空循环链表:
头 5 8 3 6L
头L
20
数
据
结
构
之
线
性
表
39
? 双向链表
?定义
结点的物理形式
空双向循环链表
prior data next
H
ABC
P
非空双向循环链表
^ 头 8 3 6 ^L
数
据
结
构
之
线
性
表
40
?用 C语言描述如下:
typedef struct Dulnode{
ElemType data; /*定义数据类型 *;
struct Dulnode *prior; /*指向直接前驱 */
struct Dulnode *next; /*指向直接后继 */
}DuLNode, *DuLinkList; /*定义该双向链表 */
21
数
据
结
构
之
线
性
表
41
?删除操作: 删除指针P所指向的结点
?步骤
?判断 p是否为头结点,若是,则退出。
?将 p的直接前驱的直接后继指向 p的直接后继。
?将 p的直接后继的直接前驱指向 p的直接前驱。
?释放该结点所占用的存储空间。
^
L
^
……
p
1
2
数
据
结
构
之
线
性
表
42
?算法描述
void delete(DuLinkList L, DuLinkList P)
{
if (p= =L)
return;
else{
p->prior->next=p->next;
p->next->prior=p->prior;
}
free(p);
return;
}
22
数
据
结
构
之
线
性
表
43
?插入操作: 在某双向链表的第i个结点
前插入元素e
?步骤
?取第 i个结点的指针 p;若该元素不存在,
则返回;
?建立新的结点 q,并赋值;
?将 q的前驱指向 p的前驱 , q的后继指向 p;
?将 p的前驱的后继指向 q, p的前驱指向 q;
?返回。
^
p
^
……
L
e
q
第 i个结点
数
据
结
构
之
线
性
表
44
void ListInsert_DuL(DuLinkList L,int i, ElemType e){
if(!(p=GetElem_DuL(L,i))) /*取第 i个元素的地址 p*/
return ERROR;
if(!(q=(DuLinkList)malloc(sizeof(DuLNode))))
return ERROR;
q->data=e; /*新结点赋值 */
q->prior=p->prior; /*q的前驱指向 p的前驱 */
q->next=p; /*q的后继指向 p*/
p->prior->next=q; /*p的前驱的后继指向 q*/
p->prior=q; /*p的前驱指向 q*/
return OK; }
23
数
据
结
构
之
线
性
表
45
1. 3 一元多项式的表示及相加
? 不缺项的一元多项式 Pn( x )
? 数学式: Pn( x )= p0 + p1 x +p2 x +……+ pn x
用线性表: P = (p0 , p1, p2 , … ,pn) 存储每项系数。
例如: P5( x )= 3+6.7x+5.2x + 4x +0x +9x
P=( 3 , 6.7 , 5.2 , 4 , 0 , 9 )
? 两个一元多项式相加
m < n , 求 Rn( x)= Pn(x) + Qm(x)
R=(p0+q0, p1+q1, p2+q2, ... , pm + qm , pm+1, ... , pn)
? 采用顺序存储结构,可以方便地解决不缺项
的一元多项式相加的问题。
2
n
234 5
数
据
结
构
之
线
性
表
46
? 特殊的一元多项式 :大幅度缺项的一元多项式
S(x)=1+3x + 2x
? 数学式: Pn( x )= p1 xa1 + p2 xa2 +... + pm xa
0≤ a1 ≤ a2 ≤ a3 ≤……≤ am
? 线性表: P=( ( p1, a1 ) , ( p2 , a2 ) , ... ,( pm ,
am) )
?顺序存储结构:用于对多项式进行“求值”;
?链式存储结构:用于两个多项式相加。
m
1000
2500
24
数
据
结
构
之
线
性
表
47
? 特殊的一元多项式链式存储结构类型定义:
typedef struct{float coef; //表元素类型定义
int expn;}ElemType;
typedef struct LNode{ //结点及其指针类型定义
ElemType data ;
struct LNode *next ;
}*Link, Lnode ;
typedef struct{ //链表类型定义
Link head , tail ;
int len ; } LinkPoly;
数
据
结
构
之
线
性
表
48
A(x)=7+3x+9x +5x
head
tail
4
A
head
tail
3
B
head
tail
4
C
+
||
B(x)=8x+22x - 9x
C(x)=7+11x+22x +5x
78
717
8.0 1 22.0 7 -9.0 8 ^头结点
7.0 0 3.0 1 9.0 8
5.0 17 ^
817
头结点
7.0 0 11.0 1 5.0 17 ^22.0 7头
25
数
据
结
构
之
线
性
表
49
Void AddPoly(LinkPoly &Pa , LinkPoly &Pb) {
ha=Pa.head; hb=Pb. head; qa=ha->next;qb=hb->next;
while (!Empty(Pa) && !Empty(Pb)){
a=qa->data;b=qb->data;
switch(compare(a.expn , b.expn)){
case 0: sum=a.coef+b.coef;
if(sum) {qa->data.coef=sum; ha=qa;}
else{ DelFirst(Pa, ha,qa);free(qa);}
DelFirst(Pb,hb,qb);free(qb);
qa=ha->next;qb=hb->next;} break;
case -1:ha=qa;qa=ha->next;break;
case 1:DelFirst(Pb,hb,qb);InsFirst(Pa, ha,qb);
ha=qb; qb=hb->next;}
if(!Empty(Pb)) Append(Pa,qb); Free(hb);}
数
据
结
构
之
线
性
表
50
? 本章重点
? 线性表的逻辑结构
? 线性表的物理结构
?顺序表:创建、插入、删除等操作的实现
?链表
?单链表:创建、插入、删除等操作的实现
?循环链表
?双向链表:插入、删除等操作的实现
? 一元多项式的表示和运算