Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 97

第4章 串、数组和广义表

教学内容
4.1 串的定义
4.2 案例引入
4.3 串的类型定义、存储结构及其运算
4.4 数组
4.5 广义表
4.6 案例分析与实现
4.7 小结
教学目标
 了解串的存储方法,理解串的两种模式匹配
算法,重点掌握 KMP 算法。
 明确数组和广义表这两种数据结构的特点,
掌握数组地址计算方法,了解几种特殊矩阵
的压缩存储方法。
 掌握广义表的定义、性质及其 GetHead 和
GetTail 的操作。
4.1 串的定义
 串的定义
即字符串,由零个或多个字符组成的有限序列
是数据元素为单个字符的特殊线性表

s = ''a1a2a3a4…….. an'' (n≥0 )

串名串值(用 '' '' 括起来)

串长
 串中字符个数( n ≥ 0 )。 n = 0 时称为空串 ,
 。
空白串
 由一个或多个空格符组成的串。
4.1 串的定义
 串的术语
子串
 串中任意个连续的字符组成的子序列
主串
 包含子串的串
字符位置
 字符在串中的序号
子串的位置
 子串的第一个字符在主串中的序号
串相等
 串长度相等,且对应位置上字符相等。
4.1 串的定义
练 1 :串是由 字符组成的序
0 个或多个
列,一般记为
S=''a1a2……an''

练 2 :现有以下 4 个字符串:
a =''BEI'' b =''JING'' c = ''BEIJING'' d = ''BEI JING''
问:① 他们各自的长度?La =3 , Lb =4 , Lc = 7 , Ld=8
② a 是哪个串的子串?在主串中的位置是多少?
a 是 c 和 d 的子串,在 c 和 d 中的位置都是 1

练 3 :空串和空白串有无区别?
答:有区别。空串 (Null String) 是指长度为零的串;而空白串
(Blank String), 是指包含一个或多个空白字符‘ ’ ( 空格
键 ) 的字符串。
4.3 串的类型定义、存储结构及其运算
 4.3.1 串的抽象类型定义
ADT String {
数据对象: D = { ai |ai∈CharacterSet,i=1,2,...,n,
n≥0}
数据关系: R = { < ai-1, ai >SubString
| ai-1, ai ∈D, i=2,...,n}
(&Sub, S, pos, len)
StrAssign (&T, chars)
基本操作:
StrCopy (&T, S) // 有 13 种 Index (S, T, pos)

StrEmpty (S) Replace (&S, T, V)


StrInsert (&S, pos, T)
StrCompare (S, T)
StrLength (S) StrDelete (&S, pos, len)
ClearString (&S) DestroyString (&S)
Concat (&T, S1, S2)
4.3.1 串的抽象类型定义
4.3.1 串的抽象类型定义
Index ( S, T, pos) ( 定位函数 )
初始条件:
串 S 和 T 存在,且 T 是非空串,
1≤pos≤StrLength(S) 。
操作结果:
若主串 S 中存在和串 T 值相同的子串,
则返回它在主串 S 中从第 pos 个字符起第一次出
现的位置 ;
否则返回 0 。
4.3.1 串的抽象类型定义
 “ 子串在主串中的位置”意指子串中的第一
个字符在主串中的序号 。

假设 S = ‘abcaabcaaabc’, T = ‘bca’
Index(S, T, 1) = 2;
Index(S, T, 3) = 6;
Index(S, T, 8) = 0;
4.3.2 串的存储结构
 串的顺序存储

 串的链式存储
4.3.2 串的存储结构
 串的顺序存储
串的定长顺序存储结构
#define MAXLEN 255 // 用户可用的最大串长
typedef struct {
char ch[MAXLEN+1]; // 存储串的一维数组
int length; // 串的当前长度
}SString;

typedef
串的堆式顺序存储结构
struct {
char *ch; // 若非空串 , 按串长分配空间 ; 否则 ch = NULL
int length; // 串长度
}HString;
串指派
Status StrAssign( HString &T, char *chars ) {
if (T.ch) delete []T.ch;
for (i=0, c=chars; *c; ++i, ++c); // 求串长度
if (!i) {T.ch = NULL; T.length = 0;}
else{
if (!(T.ch = new char[i]))
exit(OVERFLOW);
T.ch[0..i-1] = chars[0..i-1];
T.length =i;
}
return OK;
}
求子串
Status SubString ( HString& Sub, HString S,int pos, int len )
{ // 用 Sub 返回串 S 的第 pos 个字符起长度为 len 的子串。
// 其中 ,1<=pos<= StrLength (S) 且 0<=len<=StrLength(S)-
pos+1 。
if ( pos < 1 || pos>S.length || len<0 || len>S.length-pos+1)
return ERROR; // 参数不合法
if ( Sub.ch) delete [] Sub.ch; // 释放旧空间
if (len==0) { Sub.ch = NULL; Sub.length = 0; } // 空子串
else { // 完整子串
Sub.ch =new char[len];
Sub.ch[0..len-1] = S.ch [ pos-1.. pos+len-2] ;
Sub.length = len;
}
return OK;
}
4.3.2 串的存储结构
 串的链式存储
‘ABCDEFGHI’
4.3.2 串的存储结构
 串的链式存储结构
#define CHUNKSIZE 80 // 可由用户定义的块大小
typedef struct Chunk{
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;

typedef struct{
Chunk *head,*tail; // 串的头指针和尾指针
int curlen; // 串的当前长度
}LString;
尾指针的目的是为了便于进行联结操作。
4.3.2 串的存储结构
 串的链式存储
优点
 操作方便
=串值所占的存储位
缺点
存储密度
实际分配的存储位
 存储密度较低

head

A B C D E F G H I # # #

head

A B C ... I
4.3.3 串的模式匹配算法
 模式匹配 (Pattern Matching)
即子串定位运算 ( Index(S,T,pos) 函数 )
算法目的
 确定主串中所含子串在 pos 后第一次出现的位置
初始条件
 串 S 和 T 存在, T 是非空串, 1≤pos≤StrLength(S)
操作结果
若主串 S 第 pos 个字符起存在和串 T 值相同的子串,
则返回它在主串 S 中第一次出现的位置,否则返回
0。
注: S 称为被匹配的串, T 称为模式串。若 S 包含串 T ,则

“ 匹配成功”,否则称 “匹配不成功” 。
4.3.3 串的模式匹配算法
 算法种类
BF 算法
 又称古典、经典、朴素、穷举
KMP 算法
 Knuth Morris Pratt
 特点:速度快
4.3.3 串的模式匹配算法
 BF 算法设计思想
将主串 S 的第 pos 个字符和模式 T 的第 1 个字
符进行比较,
 若相等,继续逐个比较后续字符;
 若不等,从主串 S 的下一字符( pos+1 )起,重新与
T 的第一个字符进行比较。
直到主串 S 的一个连续子串字符序列与模式 T 相
等。返回值为 S 中与 T 匹配的子序列第一个字符
的序号,即匹配成功。否则,匹配失败,返回值
0.
BF 算法
i 回 i

主串 S
si ……

模式 T
tj
j j

回溯
BF 算法
例:主串 S="ababcabcacbab" ,模式 T="abcac"

i=3 , j=3 失败;


i i i
i 回溯到 2 , j 回溯
第 到1
1
趟 a b a b c a b c a c b a b

a b c a c
j j j
BF 算法
例:主串 S="ababcabcacbab" ,模式 T="abcac"

i=2 , j=1 失败
i 回溯到 3 , j 回溯
到1
i

2
趟 a b a b c a b c a c b a b

a b c a c
j
BF 算法

例:主串 S="ababcabcacbab" ,模式 T="abcac"

i=7 , j=5 失败
i 回溯到 4 , j 回溯
到1
i i i i i

3 a b a b c a b c a c b a b

a b c a c
j j j j j
BF 算法

例:主串 S="ababcabcacbab" ,模式 T="abcac"

i=4 , j=1 失败
i 回溯到 5 , j 回溯
i 到1

第 a b a b c a b c a c b a b
4

a b c a c
j
BF 算法

例:主串 S="ababcabcacbab" ,模式 T="abcac"

i=5 , j=1 失败
i 回溯到 6 , j 回溯
到1
i

5 a b a b c a b c a c b a b

a b c a c
j
BF 算法

例:主串 S="ababcabcacbab" ,模式 T="abcac"

i=11 , j=6 , T 中全部字


符都比较完毕,匹配成功。

i i i i i

第 a b a b c a b c a c b a b
6

a b c a c
j j j j j
4.3.3 串的模式匹配算法
 BF 算法
int Index(SString S,SString T,int pos){
i=pos; j=1;
while (i<=S.length && j <=T.length){
if ( S.ch[ i ] == T.ch[ j ]) {++i; ++j; }
else { i=i-j+2; j=1; } 匹配成功后指针仍要回溯!因为要
返回的是被匹配的首个字符位置。

}
if ( j>T.length) return i-T.length;
else return 0;
}
4.3.3 串的模式匹配算法
 BF 算法的时间复杂度
若 n 为主串长度, m 为子串长度,则串的 BF 匹
配算法最好、最坏情况下需比较字符的总次数是
多少?
 最好情况
 一配就中! 只比较了 m 次。
 最坏情况
 主串前面 n-m 个位置都部分匹配到子串的最后一位,即这 n-
m 位比较了 m 次。另外最后 m 位也各比较了一次。所以总
次数为: (n-m)*m+m = (n-m+1)*m
 时间复杂度: O(m*n)
 例如:
» S=‘aaaaaaaaaaab’ n=12
» T=‘aaab’ m=4
KMP 算法
 KMP 算法设计思想
尽量利用已经部分匹配的结果信息,让 i 不要回
溯,加快模式串滑动的速度。可提速到 O(n+m)

i i
S=‘aa b a b c a b c a c b a S=‘a b a b c a b c a c b a
T=‘a
b’ a b c a c’ i i i
b’ T=‘a b c a c’
k k
S=‘a b a b c a b c a c b a
b’ T=‘a b c a c’
k
i-T.length

KMP 算法的返回值应为 6
KMP 算法
 KMP 算法设计思想

因 p1=p3,s3=p3, 所以必有 s3=p1 。因此 , 第二次匹配可


直接从 i=4, j=2 开始。

改进:每趟匹配过程中出现字符比较不等时,不回溯
主指针 i ,利用已得到的“部分匹配”结果将模式滑
动一段距离,继续进行比较。
怎么计算距离?
KMP 算法的推导过程:(见教材 P94 )
k 是追求的新起点
请抓住部分匹配时的两个特征:
(1) i
设目前打算与 P 的第 k 字符开始比较
S=‘a b a b c a b c a c b a
则 P 的 1 ~ k-1 位= S 的 i-(k-1) ~
b’ P=‘a b c a c’ i-1 位
k ‘P1…Pk-1’
i
(2)
S=‘a b a b c a b c a c b a 刚才肯定是在 S 的 i 处和 P 的第 j 字符处失
P=‘a b c a c’ 则P的 配 j-(k-1) ~ j-1 位= S 的 i-(k-1) ~
b’ i-1 位
k j
但 k 有限制, 1<k<j

两式联立可得:‘ P1…Pk-1’=‘Pj-(k-1) …Pj-1’


注意: j 为当前已知的失配位置,我们的目标是计算新起点 k 。
式中仅剩一个未知数 k ,理论上已可解!
奇妙的结果: k 仅与模式串 P 有
KMP 算法
为此,定义 next[j] 函数,表明当模式中第 j 个字符与
主串中相应字符“失配”时,在模式中需重新和主串
中该字符进行比较的字符的位置。
取 P 首与 pj-1 处最大的相同子

max{ k|1<k<j, 且“ P1…Pk-1”=“Pj-k+1…Pj-1” }
当此集合非空时
next[j]=
0 当 j=1 时
1 其他情况
例:
模 式 串 P: a b a a b c a c
可能失配位 j : 1 2 3 4 5 6 7 8 next[j] 与 s 无关,
可以预先计算
新匹配位 k=next[j] : 0 1 1 2 2 3 1 2
0 当j=1时
刚才已归纳: next[ j ] = max { k |1<k<j 且‘ P1…Pk-1’=‘Pj-(k-1) …Pj-1’ }
讨论: 1 其他情况
j=1 时 , next[ j ]≡ 0 ; // 属于“ j=1” 情况 ;
j=2 时 , next[ j ]≡ 1 ; // 找不到 1<k<j 的 k ,属于“其他情
况”;
j=3 时 , k={2} ,只需查看‘ P1’=‘P2’ 成立否, No 则属于其他情
j=4
况 时 , k={2 , 3} ,要查看‘ P1’=‘P3’ 及‘ P1P2’=‘P2 P3’ 是否成
j=5
立 时 , k={2 , 3 , 4} ,要查看‘ P1’=‘P4’ ,‘ P1P2’=‘P3P4’

以此类推,可得后续 next[j] 值。 ‘P1P2P3’=‘P2P3P4’
从两头往中间比较
next[ j ] 计算方法

•当 j=1 时, Next[j]=0 ;
//Next[j]=0 表示根本不进行字符比较
•当 j>1 时, Next[j] 的值为:模式串的位置从 1 到 j-1
构成的串中所出现的首尾相同的子串的最大长度加
1。
无首尾相同的子串时 Next[j] 的值为 1 。
// Next[j]=1 表示从模式串头部开始进行字符比较
KMP 算法
 KMP 算法的实现
int Index_KMP (SString S,SString T, int pos)
{
i= pos, j =1;
while (i<=S.length && j<=T.length) {
if (j==0 || S.ch[i]==T.ch[j]) {++i; ++j; }
else
j=next[j]; /*i 不变 ,j 后退 */
}
if (j>T.length) return i-T.length; /* 匹配成功 */
else return 0; /* 返回不匹配标志 */
}
KMP 算法
 如何求 next 函数值
由定义得知, next[1] = 0;
设 next[j] = k ,则 next[j+1] = ?
① 若 pk=pj ,则有“ p1…pk-1pk”=“pj-k+1…pj-1pj” , 如果在
j+1 发生失配,说明 next[j+1] = k+1 。
② 若 pk≠pj ,可把求 next 值问题看成是一个模式匹配
问题,整个模式串既是主串,又是子串。
KMP 算法

若 pk’=pj ,则有“ p1…pk’”=“pj-k’+1…pj” ,


next[j+1]=k’+1.
若 pk”=pj ,则有“ p1…pk””=“pj-k”+1…pj” ,
next[j+1]=k”+1.
若不存在满足条件的串,则 next[j+1]=1.
KMP 算法
 求 next 函数值的算法next 数组求法:第一位 next 值为 0 ,第二位
void get_next(SString P, int &next[ ]) 的 next 值为 1 ,后面求解每一位的 next 值时,
根据前一位进行比较。
{
 首先将前一位字符与其 next 值对应的字符
j= 1; next[1] = 0; k = 0;
进行比较,如果相等,则该位的 next 值就
while( j< P.length){ 是前一位的 next 值加 1 ;
if(k==0 || P.ch[j] == P.ch[k]){  如果不等,向前继续寻找 next 值对应的字
++j; ++k; 符来与前一位进行比较,直到找到某个位
next[j] = k; 上内容的 next 值对应的内容与前一位相等
} 为止,则这个位对应的 next 值加 1 即为所
else 求 next 值;
k = next[k];  如果找到第一位都没有找到与前一位相等
}} 的内容,那么所求的位上的 next 值为 1 。

j 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
模式串 a b c a a b b c a b c a a b d a
b
next[j]
0 1 1 1 2 2 3 11 2 3 4 5 6 7 1 2
KMP 算法
 KMP 算法的时间复杂度
设主串 s 的长度为 n, 模式串 t 长度为 m, 在 KMP
算法中求 next 数组的时间复杂度为 O(m), 在后
面的匹配中因主串 s 的下标不减即不回溯 , 比较
次数可记为 n, 所以 KMP 算法总的时间复杂度为
O(n+m) 。
KMP 算法的用途
因为主串指针 i 不必回溯,所以从外存输入文件时可以
做到边读入边查找——“流水作业” !
注意:由于 BF 算法在一般情况下的时间复杂度也近似
于 O(n+m) ,所以至今仍被广泛采用。
讨论: next [ j ] 是否完美无缺?
前面定义的 next 函数在某些情况下还是有缺陷的,
例如模式 aaaab 与主串 aaabaaaab 匹配时的情况:
先计算 j:12345
next[j] : T:aaaab 似乎慢了一点?
next[j] : 0 1 2 3 4 能否再提速?
i: 123456789
S: aaabaaaab
T: a a aa aa aba ab
abaa ab b
此时效率不高的原因为:
当 Pj==Pnext[j] 时,则
如果 Si != Pj , == 》 Si != Pnext[j]

因此, Si 没有必要继续与 Pnext[j] 进行比较,


而应该直接和 Pnext[j] 的下一个字符 Pnext[next[j]]
进行比较。
因此,在计算 next 函数时,
如果出现 Pj=Pnext[j] = Pk

next[j]=next[k]=next[next[j
]]
KMP 算法
 next 函数的改进
void get_nextval(SString P, int &nextval[ ])
{
j= 1; nextval[1] = 0; k = 0;
while( j< P.length){
if(k==0 || P.ch[j] == P.ch[k]){
++j; ++k;
if(P.ch[j] != P.ch[k]) nextval[j] = k;
else nextval[j] = nextval[k];
}
else k = nextval[k];

}}

j 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
模式串 a b c a a b b c a b c a a b d a
b
next[j]
nextval[j] 00 1 1 1 2 2 3 1 1 2 3 4 5 6 7 1 2
1 1 0 2 1 3 1 0 1 1 0 2 1 7 0 1
习题

• 模式串 P= ‘ abaabcac ’ 的 next 函数


01122312 。
值序列为 ________

 字符串’ ababaaab’ 的 nextval 函数


值为 ________
0 112 34 22
01010421
特殊线性表
特殊线性表

栈 比 较 队 列 串

逻辑结构 存储结构 逻辑结构 存储结构 逻辑结构 存储结构

⑴ 栈的定义 顺 链 ⑴ 队列定义 循 链 ⑴ 串的定义 顺 链


⑵ 操作特性 序 比较 ⑵ 操作特性 环
比较 队 ⑵ 基本概念 序 式
⑶ADT 定义 栈 栈 ⑶ADT 定义 队

⑶ADT 定义 存 存

储 储

⑴ 基本操作的实现 ⑴ 基本操作的实现
⑵ 时间性能 ⑵ 时间性能 模式匹配
复习:串的模式匹配
 1. 什么是模式匹配?
S: ababcabcacbab
T: abcab

即:串的定位运算。在主串 S 中查找和模式 T
相匹配的串。
复习:串的模式匹配
 2. 有哪些模式匹配算法?
BF 算法
S: ababcabcacbab
T: abcab
(1) abc
(2) a
(3) abcab
KMP 算法
复习:串的模式匹配
 2. 有哪些模式匹配算法?
BF 算法
KMP 算法
若主串为 : acabaabaabcacaabc
模式串为 : abaabcac
next() 函数值: 01122312
pos=1
请根据 next() 值写出其模式匹配过程。
next() 和 nextval()

j 1 2 3 4 5 6 7 8
模式串 a b a a b c a c

next(j) 0 1 1 2 2 3 1 2

nextval(j)
0 1 0 2 1 3 0 2

next[i]=k :意味着模式串 T[1:i-1] 中包含长度为 k-1 的前后


最大相同子串 T[1:k-1]=T[i-k+1:i-1]
j 1 2 3 4 5 6 7 8 9 10
模式串 a a b c a a b c a c
next(j) 0 1 2 1 1 2 3 4 5 6

nextval(j) 0 0 2 1 0 0 2 1 0 6

void get_next(SString P, int &next[ ]) void get_nextval(SString P, int &nextval[ ])


{ {
j= 1; next[1] = 0; k = 0; j= 1; nextval[1] = 0; k = 0;
while( j< P.length){
while( j< P.length){
if(k==0 || P.ch[j] == P.ch[k]){
if(k==0 || P.ch[j] == P.ch[k]){ ++j; ++k;
++j; ++k; if(P.ch[j] != P.ch[k])
next[j] = k; nextval[j] = k;
} else nextval[j] = nextval[k];
else }
k = next[k]; else k = nextval[k];

}} }}
next() 和 nextval()

j 1 2 3 4 5 6 7 8
模式串 a b a b a a a b

next(j) 0 1 1 2 3 4 2 2

nextval(j)
0 1 0 1 0 4 2 1

next[i]=k :意味着模式串 T[1:i-1] 中包含前后最大相同子串


T[1:k-1]=T[i-k+1:i-1]
特殊线性表
线性表——具有相同类型的数据元素的有限序列。
限制插入、删除位置

栈——仅在表尾进行插入和删除操作的线性表。

特殊线性

队列——在一端进行插入操作,而另一端进行
删除操作的线性表。
串——零个或多个字符组成的有限序列 。

限制元素类型为字符
广义线性表

线性表——具有相同类型的数据元素的有限序列。

将元素的类型进行扩充

(多维)数组——线性表中的数据元素可以是

广义线性

线性表,但所有元素的类型相同。
广义表——线性表中的数据元素可以是线性表,
且元素的类型可以不相同。
4.4 数组
 4.4.1 数组的类型定义

 4.4.2 数组的顺序存储

 4.4.3 特殊矩阵的压缩存储
4.4.1 数组的类型定义
数组
数组是由类型相同的数据元素构成的有序集合,
每个元素称为数组元素。
特点:元素本身也可是一个数据结构
 本章所讨论的数组与高级语言中数组的区别
高级语言中的数组是顺序结构;
而本章的数组既可是顺序结构,也可是链式结
构,用户可根据需要选择。
4.4.1 数组的类型定义
 一维数组
C/C++ 定义
 int a[100];
a, i=0
LOC(i) =
a + i*l, i > 0
0 1 2 3 4 5 6 7 8 9
a 35 27 49 18 60 54 77 83 41 02

l l l l l l l l l l
l=4
4.4.1 数组的类型定义
 二维数组
可以看成数组元素是线  a11 a12  a1n 
性表的线性表。 a a  a 
Amn   21 22 2n 
A  (1 , 2 , , p ) (p  m或n)    
 
 i  ( ai1 , ai 2 ,, ain ) 1  i  m am1 am 2  amn 

 a11 a12  a1n 


a a  a 
Amn   21 22 2n 
 j  ( a1 j , a2 j ,, amj ) 1  j  n    
 
am1 am 2  amn 
4.4.1 数组的类型定义
 C/C++ 中的二维数组
一个二维数组类型可以定义为其分量类型为一维
数组类型的一维数组类型,即
typedef int Array2[m][n];
等价于:
typedef int Array1[n];
typedef Array1 Array2[m];

一个 n 维数组类型可以定义为其数据元素为 n-1


维数组类型的一维数组类型。
数组一旦被定义,它的维数和维界就不再改变。
4.4.1 数组的类型定义
n>0 ,为数组的维数, ji 是
 数组的抽象数据类型定义
数组的第 i 维下标, 0≤ji≤bi-
ADT Array { 1 , bi 为数组第 i 维的长度
数据对象:

数据关系:
R  {R 1 ,R 2 , ,R n }
R i  { aj1 ji j n ,aj1 ji 1j n  |
0  jk  bk  1, 1  k  n,且k  i,
0  ji  bi  2,
aj1 ji j n ,aj1 ji 1j n  D ,i  1, ,n }
4.4.1 数组的类型定义
 基本操作
InitArray (&A, n, bound1, ..., boundn)
操作结果:若维数 n 和各维长度合法,则构造
相应的数组 A ,并返回 OK 。

DestroyArray(&A)
操作结果:销毁数组
A。
4.4.1 数组的类型定义
 基本操作
Value(A, &e, index1, ..., indexn)
初始条件: A 是 n 维数组, e 为元素变量,随
后是 n 个下标值。
操作结果:若各下标不超界,则 e 赋值为所指
定的 A 的元素值,并返回 OK 。
Assign(&A, e, index1, ..., indexn)
初始条件: A 是 n 维数组, e 为元素变量,随
后是 n 个下标值。
操作结果:若下标不超界,则将 e 的值赋给所
指定的 A 的元素,并返回 OK 。
4.4.2 数组的顺序存储
 数组一般不做插入或删除操作,一旦建立了
数组,则结构中的数据元素个数和元素之间
的关系就不再发生变动。
因此,数组采用顺序存储结构比较合适。

 由于存储空间是一维的结构,而数组可能是
多维的结构,则用一组连续存储单元存放数
组元素就有次序约定问题。
4.4.2 数组的顺序存储
 二维数组的顺序存储
以行序为主序
以列序为主序
4.4.2 数组的顺序存储
 二维数组的行序优先表示
int a[m][n] ;
 a[0][0] a[0][1]  a[0][n  1] 
 
 a[1][0] a[1][1]  a[1][n  1] 
a   a[2][0]
 a[2][1]  a[2][n  1] 
     
 
 a[m  1][0] a[m  1][1]  a[m  1][n  1]

则行列索引下标为 i 和 j 对应的元素首地址
为:
LOC( i , j) = a + i * n + j
4.4.2 数组的顺序存储
 三维数组
按页 / 行 / 列存放,页优先的顺序存储

u1

l1
l2 l3 u3

u2 ②


4.4.2 数组的顺序存储
 三维数组
a[m1][m2] [m3]
 各维元素个数为 m1, m2, m3
下标为 i1, i2, i3 的数组元素的存储位置:
LOC ( i1, i2, i3 ) = a +
  i1* m2 * m3 + i2* m3 + i3
前 i1 页 第 i1 页 第 i2 行的前
总 的 i3 列元素个数
元素个数 前 i2 行
总元素个
4.4.2 数组的顺序存储
 n 维数组
a[b1][b2]…[bn]
各维元素个数分别为 b1, b2, b3, …, bn
则下标为 j1, j2, j3, …, jn 的数组元素的存储位置
LOC  j1 , j2 , , jn   a  ( j1  b2  b3   bn 
 j2  b3  b4   bn    jn 1  bn  jn ) L
 n 1 n

 a    ji   bk  jn  L
 i 1 k i 1 
 n 
LOC[ j1 , j2 , , jn ]  LOC[0, 0, , 0]    ci ji  Cn=L, Ci-1=bi×Ci , 1<i≤n
 i 1 
4.4.2 数组的顺序存储
 练习 1
设有一个二维数组 A[m][n] 按行优先顺序存储,
假设 A[0][0] 存放位置在 644 , A[2][2] 存放位置
在 676 ,每个元素占一个空间,问 A[3][3] 存放
在什么位置?
设数组元素 A[i][j] 存放在起始地址为 Loc ( i, j ) 的存
储单元中
∵ Loc ( 2, 2 ) = Loc ( 0, 0 ) + 2 * n + 2 = 644 + 2 * n + 2 = 676.
∴ n = ( 676 - 2 - 644 ) / 2 = 15
∴ Loc ( 3, 3 ) = Loc ( 0, 0 ) + 3 * 15 + 3 = 644 + 45 + 3 = 692.
练习
 练习 2
设有二维数组 A[10,20] ,其每个元素占两个字
节, A[0][0] 存储地址为 100 ,若按行优先顺序
存储,则元素 A[6,6] 的存储地址为 ,
按列优先顺序存储,元素 A[6,6] 的存储地址为

100+(6*20+6)*2=352

100+(6*10+6)*2=232
4.4.2 数组的顺序存储
 练习 3
一个二维数组 A ,行下标的范围是 1 到 6 ,
列下标的范围是 0 到 7 ,每个数组元素用相邻的
6 个字节存储,存储器按字节编址。那么,这个
数组总共占多少个字节?
 48*6=288
已知二维数组 Am,m 按行存储的元素地址公式是:
Loc(aij)= Loc(a11)+[(i-1)*m+(j-1)]*K
按列存储的公式是?
 Loc(aij)=Loc(a11)+[(j-1)*m+(i-1)]*K
4.4.2 数组的顺序存储
 补充
链式存储方式:用带行指针向量的单链表表示

a11 a12 … a1n ^


am1 am2 … amn ^


^
4.4.3 特殊矩阵的压缩存储
 为什么进行压缩存储
不是所有矩阵都能进行压缩存储
压缩存储的对象
 存在大量元素具有相同取值的矩阵
 存在大量零元的矩阵

对称矩阵,对角矩阵,三角矩阵 (特殊矩阵)
 稀疏矩阵
» 矩阵中非零元素的个数较少(一般小于 5% )
什么是压缩存储?
 为多个值相同的元只分配一个存储空间
 零元不分配存储空间
4.4.3 特殊矩阵的压缩存储
 对称矩阵的压缩存储
3 6 4 7 8
6   2   8   4   2a a …. … ….. a
A 11 12
4   8   1   6   9a a …….. ……. a
1n

= 7 4 6 0 5
21 22 2n

8 2 9 5 7 ……………….
对称矩阵特点: aij=aji an1 an2 …….. ann

按行序为主序: a11 a21 a22 a31 a32 …... an1 …... ann
k=0 1 2 3 4 n(n-1)/2 n(n+1)/2-1
i(i  1) / 2  j  1,i  j
k  
 j(j  1) / 2  i  1,i  j
4.4.3 特殊矩阵的压缩存储
 下三角矩阵的压缩存储
a11 0 0 …….. 0
a21 a22 0 …….. 0

…………………. 0
an1 an2 an3…….. ann

按行序为主序: a11 a21 a22 a31 a32 …... an1 …... ann
k=0 1 2 3 4 n(n-1)/2 n(n+1)/2-1

i(i-1)
Loc(aij)=Loc(a11)+[ +(j-1)]*L
2
4.4.3 特殊矩阵的压缩存储
 对角矩阵的压缩存储
a11 a12 0 …………… . 0
a21 a22 a23 0 …………… 0
0 a32 a33 a34 0 ……… 0
……………………………
0 0 … an-1,n-2 an-1,n-1 an-1,n
0 0 … …an,n-1 ann

Loc(aij)=Loc(a11)+[3(i-1)+(j-i)]*L
4.4.3 特殊矩阵的压缩存储
 稀疏矩阵的压缩存储
稀疏矩阵的定义
 矩阵中只有少量的非零元,且这些非零元在矩阵中的
分布没有一定规律。
稀疏因子
 在 mn 矩阵中,有 t 个非零元,则  = t/(m*n)
称为稀疏因子
 通常认为  ≤ 0.05 的矩阵为稀疏矩阵
实现方法
 将每个非零元用一个三元组( i , j , aij )表示,则
每个稀疏矩阵可用一个三元组线性表表示。
4.4.3 特殊矩阵的压缩存储
 例:写出下图所示稀疏矩阵的压缩存储形式

矩阵 M 的三元组线性表表示为
 {(1,2,12), (1,3,9), (3,1,-3), (3,6,14), (4,3,24), (5,2,18),
(6,1,15), (6,4,-7) }
 以行优先顺序存储
4.4.3 特殊矩阵的压缩存储
 稀疏矩阵的三元组顺序表存储表示
#define MAXSIZE=12500 // 假设非零元个数的最大值为 12500
typedef struct {
    int i, j;   // 该非零元的行号和列号
    ElemType e; // 该非零元的值
} Triple; // 三元组

typedef struct {
  Triple data[MAXSIZE + 1]; // 非零元三元组表, data[0] 未用
  int mu, nu, tu;   // 矩阵的行数、列数和非零元的个数
} TSMatrix;    // 三元组顺序表
4.5 广义表
 4.5.1 广义表的定义

 4.5.2 广义表的存储结构
4.5.1 广义表的定义
 定义
广义表是线性表的推广,也称为列表( lists )
 记为: LS = (a1, …,ai,…, an )
 ai 可以是单个数据元素,也可以是广义表
 例子: A=(a, (b , c , d))
广义表与线性表的区别和联系?
 广义表中元素既可以是原子类型,也可以是列表;
 当每个元素都为原子且类型相同时,就是线性表。
常用约定
 用小写字母表示原子类型,用大写字母表示列表。
 B=(b,c,d)
 A=(a,B)
4.5.1 广义表的定义
 广义表常用术语
长度
 广义表中元素的个数 n 。 n=0 时称为空表。
深度
 定义为括号嵌套的最深层次。
 (a, (b , c , d))
表头
 对于任意一个非空广义表 LS=(a1,a2,…,an) ,它的第一
个数据元素 a1 被称为广义表的表头。
表尾
 对于任意一个非空广义表,除表头外,由剩余数据元
素构成的广义表 (a2,a3,...,an) 被称为广义表的表尾。
4.5.1 广义表的定义
 广义表示例

A =() n=0 ,因为 A 是空表

B = (e) n=1 ,表中元素 e 是原子


C = (a, (b , c , n=2 , a 为原子, (b,c,d) 为子表
d)) n=3 , 3 个元素都是子表
D = (A, B, C) n=2 , a 为原子, E 为子表
E = (a, E) n=1, 表中元素为空表 ( )
F = (( ))
E=(a,E)=(a,(a,E))= (a,(a,(a,…….))) , E 为递归表
4.5.1 广义表的定义
 广义表的图形表示
A =() D

B = (e)
C = (a, (b , c , A B C
d))
D = (A, B, C) e a

b c d
4.5.1 广义表的定义
 广义表的特点
广义表是一个多层次的结构
 元素可以是子表,而子表的元素还可以是子表
广义表可以为其他子表所共享
广义表可以是一个递归的表,即广义表可以是其
本身的子表。
 E = (a, E)
4.5.1 广义表的定义
 广义表的基本运算
求表头 GetHead[L]
 非空广义表的第一个元素,可以是单个元素,也可以
是一个子表。
求表尾 GetTail[L]
 非空广义表除去表头元素以外其它元素所构成的表。
表尾一定是一个表。
4.5.1 广义表的定义
 练习:给出下列广义表的基本操作结果
1. GetTail((b, k, p, h)) = (k, p, h)
;
(a,b)
2. GetHead(((a,b), (c,d) )) =
((c,d))
;
(b
3. GetTail(((a,b), (c,d) )) =

; ()
5. GetTail((e)) =
;
4. GetTail(GetHead(((a,b),(c,d))))
() =
;
6. GetHead ((( ))) =
()
.
4.5.1 广义表的定义
练习 已知广义表 L=((x, y, z), a, (u, t, w)) ,从 L 表中取出原子
u 的运算是:
A) head(tail(tail(L))) B) tail(head(head(tail(L))))
C) head(tail(head(tail(L)))) D) head(head(tail(tail(L))))
解:取出原子 u 的过程如下:
1 )用 tail 运算去掉表头 (x, y, z) ,即: tail(L) = (a, (u, t, w))
2 )再用 tail 运算去掉表头 a ,即: tail(tail(L)) = ((u, t, w))
3 )用 head 运算取出表头 (u, t, w) ,即:
head(tail(tail(L))) = (u, t, w)
4 )再用 head 运算取出表头 u ,即: head(head(tail(tail(L))))
4.5.2 广义表的存储结构
 头尾链表的存储结构
typedef enum {ATOM, LIST} ElemTag;
  // ATOM==0: 原子, LIST==1: 子表

typedef struct GLNode {


ElemTag tag; // 公共部分,用于区分原子结点和表结点
union { // 原子结点和表结点的联合部分
  AtomType atom; // atom 是原子结点的值域, AtomType 由用户定义
struct {struct GLNode *hp, *tp;} ptr;
// ptr 是表结点的指针域, ptr.hp 和 ptr.tp 分别指向表头和表尾

};
} *GList; 表结点 tag=1 hp tp

原子结点 tag=0 atom


4.5.2 广义表的存储结构
给出下列广义表的存储结构
A=( ) 表结点 tag=1 hp tp
B=(e)
C=(a,(b,c,d)) 原子结点 tag=0 data
D=(A,B,C)
A=NULL
B 1 /\
0 e

C 1 1 /\

0 a 1 1 1 /\

0 b 0 c 0 d
4.5.2 广义表的存储结构
广义表的存储结构

E
1 1 ∧

0 a
E = (a, E)
F = (( ))
F
1 ∧ ∧
广义线性表
广义线性表

多维数组 广义表

逻辑结构 存储结构 逻辑结构 存储结构

⑴ 数组的定义 顺 压 ⑴ 基本概念 链
⑵ 基本操作 序 缩 · 广义表定义 接
⑶ADT 定义 存 存 · 表头、表尾 存
储 储 · 长度、深度 储
⑵ADT 定义
按 按 特殊矩阵 稀疏矩阵 头尾表示法
行 列 · 对称矩阵
优 优
· 三角矩阵
先 先
· 对角矩阵

寻址的计算方法
本章小结
 串是内容受限的线性表,其限定表中的元素
为字符。要求掌握串的存储方法,理解串的
两种模式匹配算法,尤其是 KMP 算法。
 明确数组和广义表这两种数据结构的特点
 掌握数组地址计算方法,了解几种特殊矩阵
的压缩存储方法。
 掌握广义表的定义、性质及其 GetHead 和
GetTail 的操作。
练习
 假设以行序为主序存储二维数组 A=array[1..100,1..100] ,
设每个数据元素占 2 个存储单元,基地址为 10 ,则
LOC[5,5]=
B ( )。
A . 808 B . 818 C . 1010 D . 1020
 设有数组 A[i,j] ,数组的每个元素长度为 3 字节, i 的值为 1
到 8 , j 的值为 1 到 10 ,数组从内存首地址 BA 开始顺序存
放,当用以列为主存放时,元素 A[5,8] 的存储首地址为( B
)。
A . BA+141 B . BA+180 C . BA+222 D . BA+225
 设有一个 10 阶的对称矩阵 A ,采用压缩存储方式,以行序
为主存储, a11 为第一元素,其存储地址为
B 1 ,每个元素占
一个地址空间,则 a85 的地址为( )。
A . 13 B . 33 C . 18 D . 40
练习
 若对 n 阶对称矩阵 A 以行序为主序方式将其下三角形的元素
( 包括主对角线上所有元素 ) 依次存放于一维数组 B[1..
(n(n+1))/2] 中,则在 B 中确定 aij ( i>=j )的位置 k 的关系
为(A )。
A . i*(i-1)/2+j B . j*(j-1)/2+i C . i*(i+1)/2+j D . j*(j+1)/2+i
 A[N , N] 是对称矩阵,将下面三角(包括对角线)以行序
存储到一维数组 T[1..N(N+1)/2] 中,则对任一上三角元素
a[i][j] 对应 T[k] 的下标B k 是( )。
A . i(i-1)/2+j B . j(j-1)/2+i C . i(j-i)/2+1 D . j(i-1)/2+1
 设二维数组 A[1.. m , 1.. n] (即 m 行 n 列)按行存储在数
组 B[1.. m*n] 中,则二维数组元素 A[i,j] 在一维数组 B 中的
下标为(
A )。
A . (i-1)*n+j B . (i-1)*n+j-1 C . i*(j-1) D . j*m+i-1
练习
 数组 A[0..4,-1..-3,5..7] 中含有元素的个数(B )。
A . 55 B . 45 C . 36 D . 16
 广义表 A=(a,b,(c,d),(e,(f,g))) ,则
Head[Tail[Head[Tail[Tail[A]]]]] 的值为(D )。
A . (g) B . (d) C.c D.d
 广义表 ((a,b,c,d)) 的表头是(C ),表尾是(
B )。
A.a B.() C . (a,b,c,d) D . (b,c,d)
 设广义表 L=((a,b,c)) ,则 L 的长度和深度分别为(
C )。
A.1和1 B.1和3 C.1和2 D.2和2
练习
 设任意 n 个整数存放于数组 A(1:n) 中,试编
写算法,将所有正数排在所有负数前面(要
求算法复杂性为 O(n) )。
void Arrange(int A[],int n)
//n 个整数存于数组 A 中,本算法将数组中所有正数排在所有负数的前

{
int i=0,j=n-1,x; // 用类 C 编写,数组下标从 0 开始
while(i<j)
{
while(i<j && A[i]>0) i++;
while(i<j && A[j]<0) j--;
if(i<j) {x=A[i]; A[i++]=A[j]; A[j--]=x; }// 交换 A[i] 与
A[j]
}
}// 算法 Arrange 结束 .

You might also like