340 likes | 514 Views
第 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 {
E N D
第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 { char *block; /*根据需要动态分配的内存,存放一段字符串*/ int size; /*block所指内存的大小*/ struct strnode *next; /*指向下一段字符串*/ }STRNODE,*STRNODEPTR,*BLOCKLINKSTR; 一、字符串——存储方式
一、字符串——简单模式匹配(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 } 时间复杂度:
一、字符串——模式匹配的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处的自身最大重复子串的长度。
一、字符串——模式匹配的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],即转到步骤①。
一、字符串——模式匹配的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 50 int 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
二、数组与矩阵——矩阵的压缩存储(2) 2.特殊矩阵之对称矩阵的压缩存储,按照三角矩阵的方式 3.特殊矩阵之带状矩阵的压缩存储 k=(i×3-1)+(j-(i-1))=2×i+j
二、数组与矩阵——矩阵的压缩存储(3) 4.特殊矩阵之稀疏矩阵的压缩存储 在一个M×N的矩阵中,非零元素的个数是T,我们将 称为稀疏因子。通常认为,当稀疏因子小于等于0.05时,这个矩阵就称为稀疏矩阵。 ⑴稀疏矩阵的顺序存储 #define MAXSIZE 500 typedef 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中的数据存放到新的三元组数组中
二、数组与矩阵——稀疏矩阵的转置(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]; /*见矩阵乘法公式*/ } }
二、数组与矩阵——稀疏矩阵的乘法(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]*/
二、数组与矩阵——稀疏矩阵的乘法(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; } } }