第二章线性表

Download Report

Transcript 第二章线性表

Data_Structure = (D,S)
数据结构的三个方面:
线性结构
线性表
栈
队列
串及数组
数据的逻辑结构
非线性结构
树形结构
图形结构
顺序存储
数据的存储结构
链式存储
索引存储
散列存储
数据的运算:检索、排序、插入、删除、修改等
第2章 线性表
 了解线性表的特点及类型定义;
 掌握线性表的顺序表示及实现,掌握线性表的链
式表示及实现(单链表、双向链表和循环链表);
 算法设计(层次3):熟练掌握两种线性表表示
的创建、插入、删除和查找等基本操作;链表合
并与分解,有序表插入;(灵活应用,习题集
2.33之前)
 了解一元多项式的表示和相加。
2
第2章 线性表
线性结构特点:在数据元素的非空有限集中
存在唯一的一个被称作“第一个”的数据元素
 存在唯一的一个被称作“最后一个”的数据元
素
 除第一个外,集合中的每个数据元素均只有一
个前驱
 除最后一个外,集合中的每个数据元素均只有
一个后继


2.1 线性表的逻辑结构

定义:一个线性表是n个数据元素的有限序列
如a1 , a2, , ai , an 
例 英文字母表(A,B,C,…..Z)是一个线性表
例

学号
001
002
……
姓名
张三
李四
……
年龄
18
19
……
特征:
• 元素个数n—表长度,n=0空表
• 1<i<n时
• ai的直接前驱是ai-1,a1无直接前驱
• ai的直接后继是ai+1,an无直接后继
• 元素同构,且不能出现缺项
数据元素
元素<-数据项
记录->文件
抽象数据类型线性表的定义P19
类C描述
建空表,求长度,定位,插入等基本操作
利用基本操作实现复杂操作 如线性表合并
P20 例2-1 线性表合并 union 算法2.1
ListLength,GetElem,LocateElem,ListInsert
时间复杂度 for X LocateElem
即O(ListLength(Lb)xListLength(La))
P21 例2-2 有序表合并到第三个表 MergeList 算法2.2
时间复杂度 O( ListLength(Lb)+ListLength(La) )
void Union(List &La, List Lb) { // 算法2.1
// 将所有在线性表Lb中但不在La中的数据元素插入到La中
int La_len,Lb_len,i;
ElemType e;
La_len = ListLength(La);
// 求线性表的长度
Lb_len = ListLength(Lb);
for (i=1; i<=Lb_len; i++) {
GetElem(Lb, i, e);
// 取Lb中第i个数据元素赋给e
if (!LocateElem(La, e, equal))
// La中不存在和e相同的数据元素
ListInsert(La, ++La_len, e); // 插入
}
} // union
6
void MergeList(List La, List Lb, List &Lc) { // 算法2.2
// 已知线性表La和Lb中的元素按值非递减排列。
// 归并La和Lb得到新的线性表Lc,Lc的元素也按值非递减排列。
InitList(Lc);
i=j=1; k=0;
La_len = ListLength(La);
Lb_len = ListLength(Lb);
while ((i <= La_len) && (j <= Lb_len)) { // La和Lb均非空
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);
}
} // MergeList
7

2.2 线性表的顺序存储结构

顺序表:
• 定义:用一组地址连续的存储单元存放一个线性表叫~
• 元素地址计算方法:
• LOC(ai)=LOC(a1)+(i-1)*L
• LOC(ai+1)=LOC(ai)+L
• 其中:
• L—一个元素占用的存储单元个数
• LOC(ai)—线性表第i个元素的地址
P22 图2.2
• 特点:
• 实现逻辑上相邻—物理地址相邻
• 实现随机存取
• 实现:可用C语言的一维数组实现
V数组下标
内存
0
1
a1
a2
n-1
an
M-1
元素序号 类C描述 定义结构体类型SqList
#define LIST_INIT_SIZE 100
#define LISTINCREMENT 10
1
typedef struct{
2
ElemType *elem; //存储空间基址
int length; //当前长度
int listsize; //当前分配的存储容量
}SqList
n
P23 例 算法2.3 初始化操作
Status InitList_Sq(SqList &L) {
// 构造一个空的线性表L。
空 L.elem = (ElemType *) malloc
闲 (LIST_INIT_SIZE*sizeof(ElemType));
if (!L.elem) return OK;
// 存储分配失败
L.length = 0;
// 空表长度为0
L.listsize = LIST_INIT_SIZE; //初始存储容量
return OK;
} // InitList_Sq
 插入
•定义:线性表的插入是指在第i(1i  n+1)
个元素之前插入一个新的数据元素x,使长度
为n的线性表
a
1
, a2, , ai 1 , ai , an 
变成长度为n+1的线性表
a1 , a2, , ai 1 , x, ai , an 
需将第i至第n共(n-i+1)个元素后移
•P24 算法2.4
// 算法2.4
Status ListInsert_Sq(SqList &L, int i, ElemType e) {
// 在顺序线性表L的第i个元素之前插入新的元素e,
// i的合法值为1≤i≤ListLength_Sq(L)+1
if (i < 1 || i > L.length+1) return ERROR; // i值不合法
if (L.length >= L.listsize) { // 当前存储空间已满,增加容量
ElemType *newbase = (ElemType *)realloc(L.elem,
(L.listsize+LISTINCREMENT)*sizeof (ElemType));
if (!newbase) return ERROR; // 存储分配失败
L.elem = newbase;
// 新基址
L.listsize += LISTINCREMENT; // 增加存储容量
}
ElemType *q = &(L.elem[i-1]); // q为插入位置
for (p = &(L.elem[L.length-1]); p>=q; --p) *(p+1) = *p;
// 插入位置及之后的元素右移
*q = e;
// 插入e
++L.length; // 表长增1
return OK;
} // ListInsert_Sq
11
V数组下标
内存 元素序号
0
a1
1
1
a2
2
i-1
ai
i
ai+1
V数组下标
内存 元素序号
0
a1
1
1
a2
2
i
i-1
x
i
i+1
i
ai
i+1
ai+1
n-1
n
an
n
n-1
an-1
n
n+1
n
an
n+1
•算法时间复杂度T(n)
• 设Pi是在第i个元素之前插入一个元素的概率,则在
长度为n的线性表中插入一个元素时,所需移动的
元素次数的平均次数为:
Eis 
n 1
 P (n  i  1)
i 1
i
1
若认为Pi 
n 1
1 n 1
n
则Eis 
(n  i  1) 

n  1 i 1
2
 T n   On 
 删除
•定义:线性表的删除是指将第i(1i  n)个
元素删除,使长度为n的线性表
a
1
, a2, , ai 1 , ai , an 
变成长度为n-1的线性表
a
1
, a2, , ai 1 , ai 1 , an 
需将第i+1至第n共(n-i)个元素前移
•P24 算法2.5
Status ListDelete_Sq(SqList &L, int i, ElemType &e)
{ // 算法2.5
// 在顺序线性表L中删除第i个元素,并用e返回其值。
// i的合法值为1≤i≤ListLength_Sq(L)。
if (i<1 || i>L.length) return ERROR; // i值不合法
p = &(L.elem[i-1]);
// p为被删除元素的位置
e = *p;
// 被删除元素的值赋给e
q = L.elem+L.length-1;
// 表尾元素的位置
for (++p; p<=q; ++p) *(p-1) = *p;
// 被删除元素之后的元素左移
--L.length;
// 表长减1
return OK;
} // ListDelete_Sq
15
V数组下标
内存 元素序号
0
a1
1
1
a2
2
i-1
ai
i
n-1
n
V数组下标
内存 元素序号
0
a1
1
1
a2
2
i
i-1
ai+1
i
ai+1
i+1
i
ai+2
i+1
an
n
n-2
an
n-1
n+1
n-1
n
•算法评价
• 设Qi是删除第i个元素的概率,则在长度为n的线性
表中删除一个元素所需移动的元素次数的平均次数
为:
E de 
n
 Q (n  i )
i 1
i
1
若认为Qi 
n
1 n
n 1
则E de   ( n  i ) 
n i 1
2
 T n   O n 
故在顺序表中插入或删除一个元素时,平均移
动表的一半元素,当n很大时,效率很低
 顺序存储结构的优缺点
 优点
•逻辑相邻,物理相邻
•可随机存取任一元素
•存储空间使用紧凑
 缺点
•插入、删除操作需要移动大量的元素
•预先分配空间需按最大空间分配,利用不充分
•表容量难以扩充
 2.3
线性表的链式存储结构
特点:
•用一组任意的存储单元存储线性表的数据
元素
•利用指针实现了用不相邻的存储单元存放
逻辑上相邻的元素
•每个数据元素a ,除存储本身信息外,还
需存储其直接后继的信息
结点
•结点
i
数据域
•数据域:元素本身信息
•指针域:指示直接后继的存储位置
指针域
例 线性表 (ZHAO,QIAN,SUN,LI,ZHOU,WU,ZHENG,WANG)
存储地址
1
7
13
19
25
31
37
43
头指针
H
31
数据域
指针域
LI
43
QIAN
SUN
13
1
WANG
WU
ZHAO
ZHENG
ZHOU
NULL
37
7
19
25
H
ZHAO
QIAN
SUN
LI
ZHOU
WU
ZHENG
WANG
^
 线性链表
•定义:结点中只含一个指针域的链表叫~,也叫
单链表
•实现 typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode, *LinkList;
LNode *h,*p;
data
p
next
结点(*p)
(*p)表示p所指向的结点
(*p).datap->data表示p指向结点的数据域
(*p).nextp->next表示p指向结点的指针域
生成一个LNode型新结点:p=(LNode *)malloc(sizeof(LNode));
系统回收p结点:free(p)
头结点:在单链表第一个结点前附设一个结点叫~
头结点指针域为空表示线性表为空
头结点
h
h
a1
^
空表
a2
…...
an
^
•单链表的基本运算
• 取元素:GetElem_L
• P29 算法 2.8
Status GetElem_L(LinkList &L,int i, ElemType &e) { // 算法2.8
// L为带头结点的单链表的头指针。
// 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
p = L->next; j = 1;
// 初始化,p指向第一个结点,j为计数器
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
• 时间复杂度 T(n) = O(n)
• 插入:在线性表两个数据元素a和b间插入x,已知
p指向a
p
b
a
p->next=s;
s
x
s->next=p->next;
• P30 算法 2.9
Status ListInsert_L(LinkList &L, int i, ElemType e) { // 算法2.9
// 在带头结点的单链线性表L的第i个元素之前插入元素e
p = L; j = 0;
while (p && j < i-1) { p = p->next; ++j; } // 寻找第i-1个结点
if (!p || j > i-1) return ERROR;
// i小于1或者大于表长
s = (LinkList)malloc(sizeof(LNode)); // 生成新结点
s->data = e; s->next = p->next;
// 插入L中
p->next = s;
return OK;
} // LinstInsert_L
• 时间复杂度 T(n) = O(n)
24
•删除:单链表中删除b,设p指向a
p
a
b
c
p->next=p->next->next;
• P30 算法2.10
Status ListDelete_L(LinkList &L, int i, ElemType &e) {
// 在带头结点的单链线性表L中,删除第i个元素,并由e返回其值
p = L; j = 0;
while (p->next && j < i-1) { // 寻找第i个结点,并令p指向其前趋
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;
• 时间复杂度 T(n) = O(n)
} // ListDelete_L
动态建立单链表算法:设线性表n个元素已存放在数
组a中,建立一个单链表,h为头指针

CreateList_L P30 算法 2.11
hh
h
头结点
头结点
头结点
000 ^
aanan-1
1a2 ^
ana…...
2 ^
an-1
…...
ann
^
void CreateList_L(LinkList &L, int n) { // 算法2.11
// 逆位序输入n个元素的值,建立带表头结点的单链线性表L
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
// 先建立一个带头结点的单链表
for (i=n; i>0; --i) {
p = (LinkList)malloc(sizeof(LNode)); // 生成新结点
scanf(&p->data);
//输入元素值
p->next = L->next; L->next = p; // 插入到表头
}
} // CreateList_L
•算法评价
T n   On
合并有序链表MergeList_L
P31 算法 2.12
void MergeList_L(LinkList &La, LinkList &Lb, LinkList
&Lc) {
// 已知单链线性表La和Lb的元素按值非递减排列。
// 归并La和Lb得到新的单链线性表Lc,Lc的元素也按值非递减排列。
pa = La->next; pb = Lb->next;
Lc = pc = 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; // 插入剩余段
free(Lb);
// 释放Lb的头结点
} // MergeList_L
算法2.2 的两种实现:算法2.7 顺序,算法2.12 链接
27
•单链表特点
•它是一种动态结构,整个存储空间为多个
链表共用
•不需预先分配空间
•指针占用额外存储空间
•不能随机存取,查找速度慢
静态链表 无指针类型语言 BASIC
大数组 用游标(指示器cur)代替指针
P32 图2.10
备用链表:未使用的分量(数组元素)
P33 例2-3 (A-B)U(B-A)
算法2.14-2.17
图2.11 数组=静态链表1 + 备用链表 0
 循环链表(circular
linked list)
•循环链表是表中最后一个结点的指针指向头结点,
使链表构成环状
•特点:从表中任一结点出发均可找到表中其他结
点,提高查找效率
•操作与单链表基本一致,循环条件不同
• 单链表p或p->next=NULL
• 循环链表p或p->next=H
h
h
空表
temp = B->next;
B->next = A->next;
尾指针 合并简化 P35 图2.13
A->next = temp->next;
A = B;
 双向链表(double
linked list)
单链表具有单向性的缺点
•结点定义
typedef struct DulNode {
ElemType data;
struct DulNode *prior,*next;
}DulNode, *DulLinkList;
prior
element next
空双向循环链表:L
非空双向循环链表:
a L
A c
b
p
p->prior->next= p= p->next->proir;
B
•删除
p->prior->next=p->next;
a
c
b
p->next->prior=p->prior;
P
•P37 算法2.19 ListDelete_DuL
Status ListDelete_DuL(DuLinkList &L, int i, ElemType &e) {
// 删除带头结点的双链循环线性表L的第i个元素,i的合法值为1≤i≤表长
if (!(p = GetElemP_DuL(L, i))) // 在L中确定第i个元素的位置指针p
return ERROR;
// p=NULL, 即第i个元素不存在
e = p->data;
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
return OK;
} // ListDelete_DuL
•算法评价:T(n)=O(n)
DuLinkList GetElemP_DuL(DuLinkList L, int i) {
// L为带头结点的双向链表的头指针。
// 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
DuLinkList p;
p = L->next;
int j = 1; // 初始化,p指向第一个结点,j为计数器
while (p!=L && j<i) {
//顺指针向后查找,直到p指向第i个元素或p为空
p = p->next;
++j;
}
if (p==L || j<i) return NULL; // 第i个元素不存在
else return p;
} // GetElem_L
32
P
•插入
a
S
b
x
• ListInsert_DuL
1. s->prior = p->prior;
2. p->prior->next = s;
3. s->next = p;
4. p->prior = s;
P36 算法2.18
Status ListInsert_DuL(DuLinkList &L, int i, ElemType e) {
// 在带头结点的双链循环线性表L的第i个元素之前插入元素e,
// i的合法值为1≤i≤表长+1。
if (!(p = GetElemP_DuL(L, i))) // 在L中确定第i个元素的位置指针p
return ERROR;
// p=NULL, 即第i个元素不存在
if (!(s = (DuLinkList)malloc(sizeof(DuLNode))))
return ERROR;
s->data = e;
s->prior = p->prior; p->prior->next = s;
s->next = p; p->prior = s;
return OK;
} // ListInsert_DuL
• 算法评价:T(n)=O(n)
小结:带头结点的线性链表 取消参数i 位序
算法改写 算法2.20 2.21
34

2.4 线性表的应用举例
一元多项式的表示及相加
 一元多项式的表示:
Pn ( x)  P0  P1 x  P2 x 2    Pn x n
可用线性表P表示 P  ( P0 , P1 , P2 ,, Pn )
1000
 2x 20000
但对S(x)这样的多项式浪费空间 S ( x)  1  3x
一般 Pn ( x)  P1 x
e1
 P2 x e 2    Pm x em
其中 0  e1  e2  em(Pi为非零系数)
用数据域含两个数据项的线性表表示
P1,e1,
P2,e2,
Pm,em
其存储结构可以用顺序存储结构,也可以用单链表

单链表的结点定义
typedef struct node
{ int coef,exp;
struct node *next;
}JD;

coef
next
exp
一元多项式相加
A( x )  7  3 x  9 x 8  5 x17
B ( x )  8 x  22x 7  9 x 8
C ( x)  A( x )  B ( x )  7  11x  22x 7  5 x17
A
-1
7
0
3
B
-1
8
1
22 7
-9 8 ^
C
-1
7
0
11 1
22 7
1
9
8
5 17 ^
5 17 ^

运算规则
设p,q分别指向A,B中某一结点,p,q初值是第一结点
比较
p->exp与q->exp
p->exp < q->exp: p结点是和多项式中的一项
p后移,q不动
p->exp > q->exp: q结点是和多项式中的一项
将q插在p之前,q后移,p不动
p->exp = q->exp: 系数相加
0:从A表中删去p,
释放p,q,p,q后移
0:修改p系数域,
释放q,p,q后移
直到p或q为NULL
若q==NULL,结束
若p==NULL,将B中剩余部分连到A上即可

算法描述
pre
p pre
p
p
p
pa
pa
-1
-1
7
7
00
11
3 111
11
99
pb
pb
-1
-1
8
8
11
22
22 77
-9
-9 88 ^^
q
q
pa
p pre
-1
7
0
q
pre
11 1
22 7
88
55 17
17 ^^
q=NULL
5 17 ^
作业
2.3
2.5
2.8
2.13