第 4 章 字符串和数组

Post on 11-Jan-2016

83 views 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 of 第 4 章 字符串和数组

第 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

一、字符串——简单模式匹配 (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] 为起点的子串长度不够为止。

一、字符串——简单模式匹配 (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

一、字符串——模式匹配的 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

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

一、字符串——模式匹配的 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

一、字符串——模式匹配的 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 值 */

}

}

一、字符串——模式匹配的 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; /* 查找失败 */}

二、数组与矩阵——数组的定义#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

二、数组与矩阵——矩阵的压缩存储 (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

二、数组与矩阵——矩阵的压缩存储 (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

二、数组与矩阵——矩阵的压缩存储 (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;

二、数组与矩阵——矩阵的压缩存储 (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; /* 遇到非法输入 */ }}

二、数组与矩阵——矩阵的压缩存储 (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;

二、数组与矩阵——矩阵的压缩存储 (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; }

二、数组与矩阵——矩阵的压缩存储 (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 的数据域 */

二、数组与矩阵——矩阵的压缩存储 (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}

二、数组与矩阵——矩阵的压缩存储 (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; /* 其他成员设置成安全值 */ }

二、数组与矩阵——稀疏矩阵的转置 (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

二、数组与矩阵——稀疏矩阵的转置 (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];

二、数组与矩阵——稀疏矩阵的转置 (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; }

二、数组与矩阵——稀疏矩阵的乘法 (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

二、数组与矩阵——稀疏矩阵的乘法 (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中的零元素结点

二、数组与矩阵——稀疏矩阵的乘法 (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 语句 */

二、数组与矩阵——稀疏矩阵的乘法 (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 向后移动 */ }

}

二、数组与矩阵——稀疏矩阵的乘法 (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; }

} }

导航图 (1)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

导航图 (2)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

导航图 (3)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

导航图 (4)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

导航图 (5)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

导航图 (6)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

导航图 (7)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储

导航图 (8)

字符串和数组

字符串

数组与矩阵

简单模式匹配 KMP 算法

矩阵的压缩存储

矩阵的转置和乘法

三元组数组的转置

十字链表的矩阵乘法

特殊矩阵的压缩存储

稀疏矩阵的压缩存储