第四章 串,数组和广义表
4.1串的定义
- 串(s,或者说是字符串)是由零个或者是多个字符组成的有限序列,零个字符的串称为空串
- 子串,主串:子串主串中连续的一段字符串
- 串相等:长度和各个元素相等
- 空格串:由一个或者多个空格组成的串
4.3 串的类型定义,存储结构及其运算
4.3.1 串的抽象类型定义
串是特殊的线性表,串和线性表不同的是,串操作的是多个元素,而线性表操作的是单个元素
抽象数据类型的定义如下
4.3.2 串的存储结构(顺序,和链式存储)
定义1(定长)
typedef struct
{
char ch[MAXLEN+1];//存储串的一维数组
int length;//串的当前长度
}SString;
为了显示方便,这里存储的数据都是从下标1开始的,而不是0
定义2(堆式)
typedef struct
{
char *ch;//类似数组
int length;
}HString;
定义3(链式)
#define CHUNKSIZE 80
typedef struct Chunk//串块
{
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typedef struct
{
Chunk *head,*tail;//串的头尾指针
int length;//串的当前长度
}LString;
顺序串的几个基本算法
//这里的字符串都是从下标0开始的,以后从下标1开始
void StrAssign(SString &S, char chars[])//chars的值插入到S中
{
int i=0;
while(chars[i])//开始复制
{
S.ch[i]=chars[i];
i++;
}
S.length=i;
}
void StrCopy(SString &T,SString S)//S复制到T
{
int i=0;
while(i<S.length)//开始复制
{
T.ch[i]=S.ch[i];
i++;
}
T.ch[++i]='\0';
T.length=S.length;
}
bool StrEmpty(SString S)//是否为空串
{
if(S.length==0)
return true;
else return false;
}
int StrCompar(SString S,SString T)//判断大小
{
int i=0;
int flag=0;
while((i<S.length)&&(i<T.length))
{
if(S.ch[i]>T.ch[i])
{
return 1;
}
else if(S.ch[i]<T.ch[i])
return -1;
else continue;
}
return 0;
}
int StrLength(SString S)//输出字符串长度
{
int i=0;
while(i<S.length)
{
i++;
}
return i;
}
void ClearString(SString &S)//清空字符串
{
S.ch[0]='\0';//这种方法...
}
void Concat(SString &T,SString S1,SString S2)//链接S1,S2
{
int i = 0;
while (i<S1.length)//插入第一个
{
T.ch[i] = S1.ch[i];
i++;
}
int j = 0;
while ((j<S2.length) && (i < MAXLEN))//插入第二个
{
T.ch[i] = S2.ch[j];
i++;
j++;
}
}
void SubString(SString &Sub,SString S,char pos,int len)//用Sub返回pos起的后面len个字符串
{
int i=0;
while(i<S.length)
{
i++;
if(pos==S.ch[i])//找到位置
break;
}
int j=0;
while(len)//开始复制
{
len--;
Sub.ch[j++]=S.ch[i];
}
}
void display(SString S)
{
int i=0;
while(i<S.length)
{
cout << S.ch[i++] << " ";
}
}
4.3.3 串的模式匹配算法
- 串的模式匹配和串匹配:子串的定位运算,比如用在搜索引擎,拼写检查,语言翻译,数据压缩
著名的模式匹配算法有BF算法和KMP算法
BF算法
算法实现
#include<iostream>
using namespace std;
#define OK "OK"
#define ERROR "ERROR"
#define MAXLEN 255
typedef string Status;
typedef struct
{
char ch[MAXLEN+1];//存储串的一维数组
int length;//串的当前长度
}SString;
void StrAssign(SString &S, char chars[])//chars的值插入到S中
{
int i=1;
while(chars[i])//开始复制
{
S.ch[i]=chars[i];
i++;
}
S.length=i-1;
}
int Index_BF(SString S,SString T,int pos)//BF算法
{
int 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;//匹配不成功
}
int main()
{
SString S;
SString T;
StrAssign(S,(char*)"scacscdaef");
StrAssign(T,(char*)"cscdae");
cout << Index_BF(S,T,1);
return 0;
}
算法思路(S是主串,T是模式串,称为模式T)
- 如图,将i指针放在开始匹配的地方,j指针也放在这个地方,两个串开始比较
- 比较一直比较到模式串T走完,若中途匹配不成功,则i返回头部并向下移动一位,继续开始比较
- 直到最后匹配完成,若一直没有匹配成功的,则return 0
- 此种算法最好情况时间复杂度为O(m+n),最坏情况O(m*n)
KMP算法
算法实现
#include<iostream>
using namespace std;
#define OK "OK"
#define ERROR "ERROR"
#define MAXLEN 255
typedef string Status;
typedef struct
{
char ch[MAXLEN+1];//存储串的一维数组
int length;//串的当前长度
}SString;
void StrAssign(SString &S, char chars[])//chars的值插入到S中
{
int i=0;
while(chars[i])//开始复制
{
S.ch[i+1]=chars[i];
i++;
}
S.length=i;
}
int next(SString T,int j)//模式串滑动位数函数
{
if(j==1)//第一种情况
return 0;
int flag1=0;
int k=2;
int Max=2;
while(k<j)
{
int flag=1;//判断当前k是否满足条件
int i=1;
while(i<k)
{
if(T.ch[i]!=T.ch[j-k+i])//不满足条件
{
flag=0;
break;
}
i++;
}
if(flag==1) //满足条件,k更新
{
flag1=1;//有匹配子串
Max = k++;
}
else k++;
}
if(flag1==1)//第二种情况
return Max;
else return 1;//第三种情况
}
int Index_KMP(SString S,SString T,int pos)
{
int 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(T,j);//匹配失败则滑动模式串
}
if(j>T.length) return i-T.length;
else return 0;
}
int main()
{
SString S;
SString T;
int i=1;
StrAssign(S,(char*)"ababcabcacbab");
StrAssign(T,(char*)"abcac");
cout << Index_KMP(S,T,1);
return 0;
}
思路
主串从指定位置开始匹配,匹配失败则i不移动,j移动到next()函数指定的位置处,逐步向后匹配,其余思路和第一个差不多
next函数(书本)
void get_next(SString T,int next[])
{
int i=0;next[1]=0;j=0;
while(i<T[0])
{
if(j==0||T.ch[i]==T.ch[j]) {i++;j++;next.ch[i]=j;}//匹配开始
else j=next[j];
}
}
next函数
int next(SString T,int j)//模式串滑动位数函数
{
if(j==1)//第一种情况
return 0;
int flag1=0;
int k=2;
int Max=2;
while(k<j)
{
int flag=1;//判断当前k是否满足条件
int i=1;
while(i<k)
{
if(T.ch[i]!=T.ch[j-k+i])//不满足条件
{
flag=0;
break;
}
i++;
}
if(flag==1) //满足条件,k更新
{
flag1=1;//有匹配子串
Max = k++;
}
else k++;
}
if(flag1==1)//第二种情况
return Max;
else return 1;//第三种情况
}
上面函数根据以下得来
两种算法的比较
BF算法在很多情况下时间复杂度O(n+m),至今任然被采用,KMP则对比较庞大数据更有效,无需重头回读
4.4数组
4.4.1数组的类型定义
- 数组是由类型相同的数据袁术构成的有序集合
- 数组的元素属于同一个数据类型
- 数组也是线性表
- 数组被定义其维数和维界就不再改变
- 数组被定义后只能进行存储元素和修改元素值的行为
数组的抽象数据类型定义如下
初始化
销毁数组
返回指定下标数值
录入指定元素
4.4.2数组的顺序存储
- 数组的存储结构分为以行为主序(C,Basic,Pascal,Java)和以列为主序(FORTRAN)两种,显而易见的区别就是两种行列标互换
- 二维数组的存储位置:
LOC(i,j)=LOC(0,0)+(n*i+j)L;
,其中L是单个数据元素占得存储单元大小,该数组是m行n列 - 二维数组的起始存储地址称为基地址或者基址
- 数组存取数组中任意元素的时间相等,数组是一种随机存取结构
4.4.3特殊矩阵的压缩存储
数组可用矩阵表示
特殊矩阵:值相同的元素或者零元素在矩阵中的分布有一定的规律
以下是几种特殊矩阵
对称矩阵
定义:满足aij=aji
压缩方法:为每一对称元分配一个存储空间,可将n²个元压缩存储到n(n+1)/2个元的空间中,相当于存储为下三角或者上三角
三角矩阵
定义:上三角矩阵是下三角区域为常数,下三角矩阵类似
压缩方法:与对称矩阵类似,除存三角区域外,加一空间存储常数
- 上三角矩阵
- 下三角矩阵
对角矩阵
定义:元素都分布在以主对角线为中心的带状区域中,其余元素全部都为零
压缩方法:可按某个原则(或以行为主,或以对角线的顺序)将其压缩存储到一维数
组上。
4.5广义表
4.5.1广义表的定义
- 广义表是线性表的推广,也称为列表,广泛用于人工智能等领域的表处理语言
- 记作:LS=(a1,a2,a3,an);其中LS是广义表的名称,n是长度,ai可以是单个元素(原子)也可以是广义表(子列)
- 习惯上用大写字母表示广义表的名称,小写字母表示原子
结论
- 广义表的元素也可以是广义表,是一个多层次的结构,如图圆圈表示广义表,方块表示原子
- 广义表可以为其他广义表共享,上图中,D中不必列出A,B,C的元素,而是通过子表的名称来引用
- 广义表是一个递归的表,其本身也是一个子表
注意:广义表()和广义表(())不同,一个是空表,长度为0,一个不是空表,长度为1,可以分解其表头表尾均为空表()
4.5.2广义表的存储结构
因为广义表中的数据元素可以有不同的结构,一次通常采用链式存储结构,有两种链式存储结构,一个是头尾链表的存储结构和扩展线性链表的存储结构
头尾链表的存储结构
- 表结点:标志域,指示表头的指针域,指示表尾的指针域
- 原子结点:标志域,值域
形式定义说明如下
typedef enum{ATOM,LIST} ElemTag;//ATOM==0原子,LIST==1子表
typedef struct GLNode
{
ElemTag tag;//区分原子结点和表结点,标志域
union//原子结点和表结点联合部分
{
AtomType atom;//原子结点的值域
struct//指针域定义
{
struct GLNode *hp,*tp;//表头指针和表尾指针
}ptr;
};
}*GList;//广义表类型
图4.14存储结构示例
- 上图中矩形的三个框分别是标志域,表头指针,表尾指针,两个框的分别表示标志域和值域
- 除了空表的表头指针为空外,对于任何的非空表结点,其表头指针均指向一个表结点
- 容易分清列表中原子和子表所在的层次
- 最高层的表结点个数即为广义表的长度
扩展线性链表的存储结构
这种结构均由三个域组成
其定义同上
其他知识:
4.6案例分析与实现
病毒感染检测
#include<iostream>
#include <fstream>
using namespace std;
#define MAXLEN 255
typedef struct
{
string ch;//存储串的一维数组
int length=0;//串的当前长度
}SString;
int Index_BF(SString S,SString T,int pos)//BF算法
{
int 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;//匹配不成功
}
void Virus_detection()//BF算法实现病毒检测
{
int flag=0;//匹配标识变量
SString Virus;//病毒
SString Person;//人
string Vir;//临时存入病毒变量
string Per;
int num;//待检测任务数
int m;//字符串长度
SString temp;//临时变量
ifstream inFile("病毒感染检测输入数据.txt");
ofstream outFile("病毒感染检测输出结果.txt");
inFile >> num;
while(num--)//循环每一个任务
{
inFile >> Virus.ch;//输入病毒,可能有问题
inFile >> Person.ch;//输入人DNA,可能有问题
Virus.length=(int)Virus.ch.length();//存入字符串长度
Person.length=(int)Person.ch.length();
Vir=Virus.ch;//病毒DNA存入临时变量
Per=Person.ch;//存入临时变量
Virus.ch='0'+Virus.ch+Virus.ch;//字符串后移一位
Person.ch='0'+Person.ch;
flag=0;
m=Virus.length;
for(int i=0;i<m;i++)//一次取长度为m的病毒DNA
{
temp.ch='0';//重新初始化
temp.length=m;
for(int j=1;j<=m;j++) temp.ch.push_back(Virus.ch[i+j]);//将这m个字符串存入临时变量
flag= Index_BF(Person,temp,1);//BF算法判断
if(flag) break;
}
if(flag) outFile << Vir << " " << Per << " YES" << endl;
else outFile << Vir << " " << Per << " NO" << endl;
}
}
int main()
{
Virus_detection();
cout << "检测完成!" << endl;
system("pause");
return 0;
}
字符统计
#include<iostream>
#include <fstream>
#include<unordered_map>
using namespace std;
int main()
{
ifstream inFile("输入数据.txt");
ofstream outFile("计算输出结果.txt");
string str;
int sum[36];
inFile >> str;//读入字符串数据
unordered_map<char,int>hash_map;
for(int i=65;i<91;i++)//初始化哈希表
hash_map[(char)i]=0;
for(int i=48;i<58;i++)//初始化哈希表
hash_map[(char)i]=0;
for(char it:str)//录入数据
hash_map[it]++;
int j=0;
for(int i=65;i<91;i++)//输出字母数据到文件
outFile << (char)i << ":" << hash_map[(char)i] << endl;
for(int i=48;i<58;i++)//输出数字数据到文件
outFile << (char)i << ":" << hash_map[(char)i] << endl;
cout << "完成" << endl;
system("pause");
return 0;
}