第 4 章 字符串和数组

34
第4第 第第第第第第 北北北北北北 北北北北北北 北北北 北北北北北北 北北北北北北 北北北

Upload: ken

Post on 11-Jan-2016

75 views

Category:

Documents


2 download

DESCRIPTION

第 4 章 字符串和数组. 北京师范大学 教育技术学院 杨开城. 堆存储 # define MAXSIZE 100 char str1[MAXSIZE+1]; /* 静态定义的字符数组,可容纳最大字符串长度是 MAXSIZE*/ char *pstr=NULL; /* 字符指针,将指向存放字符串的内存* / int len; scanf(“%d”,&len); /* 获得字符串的长度* / pstr=(char *)malloc(len+1); /* 根据字符串的长度分配一块内存* /. 块链存储 typedef struct strnode { - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第 4 章  字符串和数组

第 4 章 字符串和数组北京师范大学 教育技术学院 杨开城北京师范大学 教育技术学院 杨开城

Page 2: 第 4 章  字符串和数组

一、字符串——存储方式

• 堆存储# define MAXSIZE 100char str1[MAXSIZE+1]; /* 静态定义的字符数组,可容纳最大字

符串长度是 MAXSIZE*/char *pstr=NULL; /* 字符指针,将指向存放字符串的内存 */int len;scanf(“%d”,&len); /* 获得字符串的长度 */pstr=(char *)malloc(len+1); /* 根据字符串的长度分配一块内存 */

• 块链存储typedef struct strnode{ char *block; /* 根据需要动态分配的内存,存放一段字符串 */ int size; /*block 所指内存的大小 */ struct strnode *next; /* 指向下一段字符串 */ }STRNODE,*STRNODEPTR,*BLOCKLINKSTR;"My name is Ya"

"ng Kaicheng"

^

block next

Page 3: 第 4 章  字符串和数组

一、字符串——简单模式匹配 (1)

• 【任务描述】已知一字符串 S 和字符串 T ,请确定字符串 T 在 S 中的位置,即字符串 T 的首字母在字符串 S 中的下标值。这里字符串 S 被称为主串, T 被称为子串,又称为模式串。通常情况下, S 串的长度比 T 大。

• 【算法思想】以 pos 作为 S 串的下标。设 T 串的长度是lent。 pos从 0 开始从左向右扫描字符串 S ,检查以S[pos] 为起点的长度为 lent 的子串是否与 T 串完全相同。– 如果完全相同,则意味着匹配成功,返回 pos值;– 如果不同,说明匹配失败,需要将 pos向后移动一个单元,继续

检查以 S[pos]为起点的长度为 lent的子串是否与 T 串完全相同。

这样循环反复,直到匹配成功或者以 S[pos] 为起点的子串长度不够为止。

Page 4: 第 4 章  字符串和数组

一、字符串——简单模式匹配 (2)

int StrIndex(char *s,char *t) /* 返回 t在 s 中的位置,找不到 t ,则返回 -1*/{ int i,j; int pos=0; /* 匹配的起点 */ while(1) {

i=pos;j=0; while(s[i]&&t[j]&&s[i]==t[j])/* 匹配循环 */

{ i++; j++;}

if(t[j]==0)return pos; /* 匹配成功 */ else if(s[i]==0) return -1; /* 匹配到了主串的末尾还没有成功 */ else pos++;/* 匹配的起点向后移动一个单元,重新匹配 */ }//while(1

}时间复杂度: )( mnO )( mnO

Page 5: 第 4 章  字符串和数组

一、字符串——模式匹配的 KMP 算法 (1)

•简单匹配算法的最大问题是:每次匹配失败时, S 串要回到 S[pos+1] 处,而 T 串要回到 T[0] 处,从

头开始比较。•下面的例子蕴含着改进的空间

已知主串是: "aabcdabcdabceab"

模式串是: "abcdabce“

•KMP 算法的基本思想:只要发现 S[i]≠T[j]时, i 保持不动, j 跳到 k 处 ( 滑动 T 串 ) ,直

到 k 是 -1时, i 才向右移动 1 位。 k 被称为 j 的 next值,即k=next(j)。

j 跳到 k 处的条件是: k是 T 串在 j 处的自身最大重复子串的长度。

S a a b c d a b c d a b c e a b

a b c d a b c eT

pos i

jk

jkjk

i

大的重复子串 大的重复子串

Page 6: 第 4 章  字符串和数组

一、字符串——模式匹配的 KMP 算法 (2)

•关键是 next(j) 如何求解–next(j) 表达的是 T 串自身的特征,与 S 串无关–next 函数的数学定义

–next(j) 函数值的求解算法已知 k=next(j) ,下面的循环可以求出 next(j+1) 的值:①比较 T[j] 和 T[k],如果 T[j]=T[k],那么 T[0..j]之间最大

重复子串的长度就是 k+1,也就是说 next(j+1)=k+1,求值完毕;②如果 T[j] ≠T[k],我们只能在 T[0..k-1]内寻找最大重复子串。

T[0..k-1]最大重复子串长度是 next(k),这时设定 k=next(k),也就是说, k 回退到自己的 next值处。我们再次比较 T[j] 和 T[k],即转到步骤①。

其他情况当此集合不空时且

0

]}1..[]1..0[1|{

01

)( jkjTkTjkkMax

j

jnext

Page 7: 第 4 章  字符串和数组

一、字符串——模式匹配的 KMP 算法 (3)

void NextVal(char *T,int *next)/* 求模式串 T 各单元的 next 值 */

{ int j,k,len;

next[0]=-1;

j=0;

len=strlen(T);

while(j<len-1)

{/* 已知 next[j] ,推算 next[j+1]*/

k=next[j];

while(k>=0&&T[j]!=T[k])

k=next[k]; /*k 回退到自己的 next 值处,重复子串的长度变小 */

next[j+1]=k+1; /* 无论是 k==-1, 还是 T[j]==T[k],next[j+1] 的值都是 k+1*/

j++; /* 准备推算下一个 next 值 */

}

}

Page 8: 第 4 章  字符串和数组

一、字符串——模式匹配的 KMP 算法 (4)

#define MAXSIZE 50int next[MAXSIZE]; int StrIndexKMP(char *S, char *T)/* 返回 T在 S 中的位置,查找失败,返回 -1*/{ int i=0, j=0; NextVal(T,next); /* 计算 T 串的 next 值 */ while(S[i]!=0&&T[j]!=0) /* 如果没到字符串末尾,就循环继续匹配 */ { if(S[i]==T[j]) /*i和 j 都向后移动一个单元 */ { i++; j++;} else { j=next[j]; /* 匹配失败, j 滑动到自己的 next 值处 */ if(j==-1) /* 说明滑动之前 j 已经是 0 ,需要将 i 向后移动,同时置 j为0*/

{ i++; j=0; }}//else

}/* 属于 while 语句 */ if(T[j]==0)return i-j; /* 匹配成功,返回位置 */ else return -1; /* 查找失败 */}

Page 9: 第 4 章  字符串和数组

二、数组与矩阵——数组的定义#define N 10#define M 20#define P 10

elemtype a[N];/* 定义了一个 N 个单元的一维数组 */

假设 a[0] 的地址是 Loc, elemtype 数据类型的大小是 S( 字节 ) ,那么数组单元 a[i] 的地址就是 : Loc+i×S 。

elemtype b[M][N];/* 定义了一个 M×N 个单元的二维数组 */

假设 b[0][0] 的地址是 Loc ,那么数组单元 b[i][j] 的前面一共有 i 行( i×N个)单元,外加 j 个单元,它的地址就是: Loc+(i×N+j)×S

elemtype c[P][M][N];/* 定义了一个 P×M×N 个单元的三维数组 */

假设 c[0][0][0] 的地址是 Loc ,那么 c[i][j][k] 地址是: Loc+(i×M×N+j×N+k)×S

Page 10: 第 4 章  字符串和数组

二、数组与矩阵——矩阵的压缩存储 (1)

1 .特殊矩阵之三角矩阵的压缩存储上三角矩阵: k= i×(2×N-i+1)/2+j-i下三角矩阵: k=(1+2+3+…+i)+j=i×(i+1)/2+j

11

1222

111211

10020100

0

nn

n

n

n

A

AA

AAA

AAAA

11121110

222120

1110

00

0

nnnnn AAAA

AAA

AA

A

n(n+1)/2-1

An-1n-1A11 A12A02 ……A00 A01 A0n-1 ……A13

0 1 2上三角矩阵的压缩存储

下三角矩阵的压缩存储

An-1n-1A30A22A11A00 A10 A20 ……A31

0 1 2 3 n(n+1)/2-1

A21

B

B

Page 11: 第 4 章  字符串和数组

二、数组与矩阵——矩阵的压缩存储 (2)

2 .特殊矩阵之对称矩阵的压缩存储,按照三角矩阵的方式3 .特殊矩阵之带状矩阵的压缩存储

k=(i×3-1)+(j-(i-1))=2×i+j

1121

343332

232221

121110

0100

0

0

nnnn AA

AAA

AAA

AAA

AA

带状矩阵的压缩存储

An-1n-1A22A21A10A00 A01 A11 ……A23

0 1 2 3 3n-1

A12B

Page 12: 第 4 章  字符串和数组

二、数组与矩阵——矩阵的压缩存储 (3)

4 .特殊矩阵之稀疏矩阵的压缩存储在一个 M×N 的矩阵中,非零元素的个数是 T ,我们将 称为稀疏因子。通常认为,当稀疏因子小于等于 0.05 时,这个矩阵就称为稀疏矩阵。

⑴ 稀疏矩阵的顺序存储#define MAXSIZE 500typedef struct{ int i,j; /* 在矩阵中的位置下标 */ elemtype v; /* 非零元素 */ }TRIPLE;/* 三元组 */typedef struct{ TRIPLE data[MAXSIZE]; /* 三元组数组 */ int rnum,cnum,sum; /* 行数、列数、非零元素的个数 */ }TRIPLEMATRIX;

Page 13: 第 4 章  字符串和数组

二、数组与矩阵——矩阵的压缩存储 (4)

下面的算法创建一个顺序存储结构的稀疏矩阵:void InputData(TRIPLEMATRIX *m){ int count; TRIPLE t; /* 开始输入稀疏矩阵的行数、列数和非零元素的个数 */ scanf("%d,%d,%d",&m->rnum,&m->cnum,&m->sum); count=0; while(1) {scanf("%d,%d,%d",&t.i,&t.j,&t.v);/* 获取三元组数据,要求按行序输入 */

if(t.i>=0&&t.i<m->rnum&&t.j>=0&&t.j<m->cnum) /* 三元组数据合法 */ { m->data[count]=t; count++; /* 计数器增 1*/ if(count==m->sum) break; /* 所有数据都输入完毕 */

} else break; /* 遇到非法输入 */ }}

Page 14: 第 4 章  字符串和数组

二、数组与矩阵——矩阵的压缩存储 (5)

4 .特殊矩阵之稀疏矩阵的压缩存储⑵ 稀疏矩阵的十字链表存储

typedef struct crsnode_tag{ int i,j; elemtype v; /* 三元组数据 */ struct crsnode_tag * right, * down; /* 行链指针和列链指针, right 指向同一行下一列非零元素, down 指向同一列下一行非零元素 */ }CROSSNODE,* CROSSNODEPTR; /* 定义十字链表结点及其指针 */typedef struct{ CROSSNODEPTR rhead,chead; /* 指向行链数组和列链数组 */ int rnum,cnum,sum; /* 行数、列数、非零元素的个数 */ }CROSSLINKLIST;

Page 15: 第 4 章  字符串和数组

二、数组与矩阵——矩阵的压缩存储 (6)

1 .初始化十字链表int InitCrossLinklist(CROSSLINKLIST *m,int row,int col,int sum){/* 初始化一个十字链表,成功则返回 1 ,否则返回 0*/ int i; /* 根据行数和列数分配相应数量的行链和列链的表头结点 */ m->rhead=(CROSSNODEPTR)malloc(row*sizeof(CROSSNODE)); m->chead=(CROSSNODEPTR)malloc(col*sizeof(CROSSNODE)); if(m->rhead==NULL||m->chead==NULL) return 0; /* 分配失败 */ for(i=0;i<row;i++)

m->rhead[i].right=NULL; /* 将所有行链设为空链 */ for(i=0;i<col;i++)

m->chead[i].down=NULL; /* 将所有列链设为空链 */ m->rnum=row; m->cnum=col; m->sum=sum; /* 设置行数、列数和非零元素的个数 */ return 1; }

Page 16: 第 4 章  字符串和数组

二、数组与矩阵——矩阵的压缩存储 (7)

2. 建立一个十字链表的稀疏矩阵void InputData(CROSSLINKLIST *m) /* 建立一个存储数据的十字链表 */{ int len,count; CROSSNODEPTR p,q; int i,j; elemtype v; int row,col,sum; scanf("%d,%d,%d",&row,&col,&sum); /* 获取行数、列数和非零元素个数 */

if(!InitCrossLinklist(m,row,col,sum))/* 初始化十字链表 */ { printf("no enough memory\n"); exit(0); } count=0;/* 计数器清零 */ while(1) { scanf("%d,%d,%d",&i,&j,&v);/* 输入三元组数据 */ if(i>=0&&i<m->rnum&&j>=0&&j<m->cnum) /* 三元组数据合法 */

{ p=(CROSSNODEPTR)malloc(sizeof(CROSSNODE)); if(!p) /* 分配失败 */ {printf("no enough memory\n"); return; } p->i=i; p->j=j; p->v=v; /* 设置三元组结点 p 的数据域 */

Page 17: 第 4 章  字符串和数组

二、数组与矩阵——矩阵的压缩存储 (8)

/* 开始按列序插入行链,读者应该很熟悉操作细节了 */ q=&m->rhead[i]; while(q->right!=NULL&&q->right->j<j) q=q->right; p->right=q->right; q->right=p; /* 开始按行序插入列链 */ q=&m->chead[j]; while(q->down!=NULL&&q->down->i<i) q=q->down; p->down=q->down; q->down=p; count++; if(count==m->sum)break;/* 数据输入完毕 */

} else/* 三元组数据非法 */ { printf("illegal input data\n"); break; } }//while}

Page 18: 第 4 章  字符串和数组

二、数组与矩阵——矩阵的压缩存储 (9)

3 .销毁十字链表void DestroyCrossLinklist(CROSSLINKLIST *m) /* 销毁一个十字链表 */{ CROSSNODEPTR p; int i; for(i=0;i<m->rnum;i++) {/* 沿着行链,释放链上所有的三元组结点 */ while(m->rhead[i].right!=NULL)

{ p=m->rhead[i].right; m->rhead[i].right=p->right; free(p);

} }

free(m->rhead); free(m->chead); /* 释放行链和列链表头结点数组 */ m->rhead=m->chead=NULL; m->rnum=0; m->cnum=0; m->sum=0; /* 其他成员设置成安全值 */ }

Page 19: 第 4 章  字符串和数组

二、数组与矩阵——稀疏矩阵的转置 (1)

•转置的含义:

•三元组数组的转置算法–遍历三元组数组,记下这个稀疏矩阵中每列的非零元素个数,存储数组 count–推算出转置后矩阵的各三元组数据的正确的存储位置 ,存储位置存放在数组 rpos中–再次遍历三元组数组,对每个数据单元进行转置,按照 rpos中的数据存放到新的三元组数组中

20060

0903

05120

2000

095

6012

030(0,1,12)

(0,2,5)

(1,0,3)

(1,2,9)

(2,1,6)

(2,3,20)

(0,1,3)

(1,0,12)

(1,2,6)

(2,0,5)

(2,1,9)

(3,2,20)

i 0 1 2 3

count[i] 1 2 2 1

rpos[i] 0 1 3 5

Page 20: 第 4 章  字符串和数组

二、数组与矩阵——稀疏矩阵的转置 (2)

TRIPLEMATRIX TransposeMatrix(TRIPLEMATRIX m){/* 返回矩阵 m 的转置矩阵 */ int *count,*rpos; TRIPLEMATRIX T; int k,i,r; count=(int *)malloc(sizeof(int)*m.cnum);/*count[i] 将存放矩阵 m第 i 列非零元素的个数 */ rpos=(int *)malloc(sizeof(int)*m.cnum);/*rpos[i] 将存放转置后的矩阵行非零元素的存储起点 */ if(count==NULL||rpos==NULL) { printf("no enough memory\n"); return; } for(i=0;i<m.cnum;i++) count[i]=0;/* 计数器清零 */ for(i=0;i<m.sum;i++) /* 遍历三元组数组,记录各列非零元素的个数 */ { k=m.data[i].j; count[k]++; } rpos[0]=0;/*第 0 行的存储起点是 0*/ for(i=1;i<m.cnum;i++)/* 计算各行非零元素的存储起点 */ rpos[i]=rpos[i-1]+count[i-1];

Page 21: 第 4 章  字符串和数组

二、数组与矩阵——稀疏矩阵的转置 (3)

T.sum=m.sum; T.rnum=m.cnum; T.cnum=m.rnum; /* 行、列转置,非零元素总量不变 */ for(i=0;i<m.sum;i++) {k=m.data[i].j; r=rpos[k];/*r 是转置后三元组数据的正确的存储位置 */ T.data[r].i=m.data[i].j; T.data[r].j=m.data[i].i; T.data[r].v=m.data[i].v;/* 三元组转置 */ rpos[k]++;/* 存储起点增 1 ,是下一个同一行三元组数据的存储位置 */

} free(count); free(rpos); return T; }

Page 22: 第 4 章  字符串和数组

二、数组与矩阵——稀疏矩阵的乘法 (1)

•矩阵相乘的含义已知一个 M×N 的矩阵 A和 N×P 的矩阵 B ,令 C=A×B ,则 C 是一个M×P 的矩阵,并且:

•二维数组的矩阵乘法void MatrixMulti(int A[M][N],int B[N][P],int C[M][P]){ int i,j,k; for(i=0;i<M;i++)

for(j=0;j<P;j++) { C[i][j]=0;/* 矩阵 C 的数据单元清零 */ for(k=0;k<N;k++) C[i][j]+=A[i][k]*B[k][j]; /* 见矩阵乘法公式 */

} }

Pj

MijkBkiAjiC

N

k

0

0]][[]][[]][[

1

0

Page 23: 第 4 章  字符串和数组

二、数组与矩阵——稀疏矩阵的乘法 (2)

稀疏矩阵十字链表的矩阵乘法void MatrixMulti(CROSSLINKLIST *A, CROSSLINKLIST *B,CROSSLINKLIST* C){/* 矩阵 C 将是矩阵 A 乘以矩阵 B 的结果 */ int i,j,k; elemtype v; CROSSNODEPTR p,q,pC,qC; InitCrossLinklist(C,A->rnum,B->cnum,0); /* 初始化矩阵 C 的十字链表 */ for(i=0;i<A->rnum;i++) /* 遍历矩阵 A 的所有的行链 */ { p=A->rhead[i].right; while(p!=NULL) /* 找到非零元素结点 p ,某个 (i,j,v)*/ { k=p->j; /*k是 A[i][j] 的列号 j, 也就是矩阵 B 的行号 */

q=B->rhead[k].right;while(q!=NULL) /* 遍历矩阵 B 的第 k 行行链,找到所有非零元素结点 q*/ { j=q->j; v=p->v*q->v; /* 计算 A[i][k]×B[k][j]*/

i=0

i<矩阵A行数?

k=p->j

p指向A第i行第一个非零元素结点

p是NULL?

q指向B第k行第一个非零元素结点

q是NULL?

j=q->j

计算A[i][k]*B[k][j]v=p->v*q->v

C[i][j]是零?

生成(i,j,v)结点,插入到矩阵C的十字链表中

将v累加到C[i][j]上

q指向B第k行下一个非零元素结点

p指向A第i行下一个非零元素结点

i++

清除C中的零元素结点

Page 24: 第 4 章  字符串和数组

二、数组与矩阵——稀疏矩阵的乘法 (3)

/* 试图将 (i,j,v) 插入到矩阵 C 中 */ pC=&C->rhead[i]; while(pC->right!=NULL&&pC->right->j<j) /* 寻找插入的位置 */ pC=pC->right; if(pC->right!=NULL&&pC->right->j==j) pC->right->v+=v; /* 原行链中有 (i,j,v’) 结点,执行累加 */ else/* 否则新生成结点 (i,j,v) 结点 qC ,插入到 pC 的后面 */ { qC=(CROSSNODEPTR)malloc(sizeof(CROSSNODE)); if(qC==NULL) { printf("no enough memory\n"); return; } qC->i=i; qC->j=j; qC->v=v; qC->right=pC->right; pC->right=qC;

/* 再将结点 qC 插入到列链中 */ pC=&C->chead[j]; while(pC->down!=NULL&&pC->down->i<i) pC=pC->down; qC->down=pC->down; pC->down=qC; C->sum++; /* 矩阵非零元素个数增 1*/

}/* 属于 else 语句 */

Page 25: 第 4 章  字符串和数组

二、数组与矩阵——稀疏矩阵的乘法 (4)

/* 寻找矩阵 B第 k 行的下一个非零元素 */ q=q->right;

}//while(q) /* 寻找矩阵 A第 i 行的下一个非零元素 */ p=p->right; }//while(p) }//for /* 下面清除十字链表中的零元素结点 */ for(i=0;i<C->rnum;i++) /* 检查所有的行链 */ { p=&C->rhead[i]; while(p->right!=NULL) /* 遍历行链,摘除所有的零元素结点 */ { if(p->right->v==0)/* 发现零元素结点,从行链中摘除 */

p->right=p->right->right;else p=p->right; /* 否则 p 向后移动 */ }

}

Page 26: 第 4 章  字符串和数组

二、数组与矩阵——稀疏矩阵的乘法 (5)

for(i=0;i<C->cnum;i++) {/* 检查所有的列链 */ p=&C->chead[i]; while(p->down!=NULL) {/* 遍历列链,摘除零元素结点 */

if(p->down->v==0) {/* 发现零元素结点,这时它已经从行链中被摘除了 */ q=p->down; p->down=q->down; free(q);/* 摘除结点并释放内存 */ C->sum--;/* 这时矩阵非零元素个数减 1*/

}else p=p->down; }

} }

Page 27: 第 4 章  字符串和数组

导航图 (1)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

Page 28: 第 4 章  字符串和数组

导航图 (2)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

Page 29: 第 4 章  字符串和数组

导航图 (3)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

Page 30: 第 4 章  字符串和数组

导航图 (4)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

Page 31: 第 4 章  字符串和数组

导航图 (5)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

Page 32: 第 4 章  字符串和数组

导航图 (6)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

Page 33: 第 4 章  字符串和数组

导航图 (7)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

Page 34: 第 4 章  字符串和数组

导航图 (8)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储