图的遍历算法
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
1图的遍历问题
在实践中常常遇到这样的问题:给定n个点,从任一点出发对所有的点访问一次并且只访问一次。
如果用图中的顶点表示这些点,图中的边表示可能的连接,那么这个问题就可以表示成图的遍历问题,即从某个顶点出发,沿着某条搜索路径
对图中每个顶点各做一次且仅做一次访问。
图的遍历操作和树的遍历操作功能相似,是图的一种基本操作,图的许多其它操作都是建立在遍历操作的基础上。
由于图结构本身的复杂性,所以图的遍历操作也比较复杂,主要表现在以下几个方面:
(1) 在图结构中,没有一个确定的首结点,图中任意一个顶点都可以作为第一个被访问的结点。
(2) 在非连通图中,从一个顶点出发,只能够访问它所在的连通分量上的所有顶点,因此,还需要考虑如何选取下一个出发点以访问图中其余的连通分量。
(3) 在图结构中,如果有回路存在,那么一个顶点被访问后,有可能沿回路又回到该顶点。
⑷在图结构中,一个顶点可以和其它多个顶点相连,当这样的顶点访问过后,存在如何选取下一个要访问的顶点的问题。
基于以上分析,图的遍历方法目前有深度优先搜索(DFS)和广度优先搜索(BFS)两种算法。
下面将介绍两种算法的实现思路,分析算法效率并编程实现。
1.1深度优先搜索算法
深度优先搜索算法是树的先根遍历的推广,它的实现思想是:从图G的某个顶点V o出发,访问V o,然后选择一个与V o相邻且没被访问过的顶点V i访问,再从V i出发选择一个与V i相邻且未被访问的顶点V j进行访问,依次继续。
如果当前被访问过的顶点的所有邻接顶点都已被访问,贝U退回已被访问的顶点序列中最后一个拥有未被访问的相邻顶点的顶点W,从W出发按同样的方法向前遍历,直到图中所有顶点都被访问。
其递归算法如下:
Boolean visited[MAX_VERTEX_NUM]; // 访问标志数组
Status (*VisitFunc)(int v); //VisitFunc是访问函数,对图的每个顶点调用该函数
void DFSTraverse (Graph G Status(*Visit)(i nt v)){
VisitF unc = Visit;
for(v=0; vvG.vex num; ++v)
visited[v] = FALSE; //访问标志数组初始化
for(v=0; v<G .vex num; ++v)
if(!visited[v])
DFS(G v); //对尚未访问的顶点调用DFS
}
void DFS(Graph G int v){ //从第v个顶点出发递归地深度优先遍历图G
visited[v]=TRUE; VisitFunc(v); // 访问第v 个顶点for(w=FirstAdjVex(G ,v); w>=0;
w=NextAdjVex(G ,v,w))
//FirstAdjVex返回v的第一个邻接顶点,若顶点在G中没有邻接顶点,则返回空(0)。
〃若w是v的邻接顶点,NextAdjVex返回v的(相对于w的)下一个邻接顶点。
〃若w是v的最后一个邻接点,则返回空(0)。
if(!visited[w])
DFS(G w); //对v的尚未访问的邻接顶点w调用DFS
}
由以上叙述可知,深度优先搜索算法的效率取决于图的数据结构的表示方
法。
当访问某顶点V i时,DFS的时间主要消耗在从该顶点出发搜索它的所有邻接点上。
用邻接矩阵表示图时,其搜索时间为0( n);用邻接表表示图时,需搜
索第i个边表上的所有结点。
因此,对所有n个顶点访问,在邻接矩阵上共需检
查n2个矩阵元素,而在邻接表上需将边表中所有e个结点检查一遍。
故由理论
分析可知,DFS的时间复杂度为O(n2)或O(n+e)。
1.2广度优先搜索算法
广度优先搜索算法是树的按层次遍历的推广,它的基本思路是:首先访问初始点V i,并将其标记为已访问点,然后访问V i的所有未被访问过的邻接点V i1,
V i2,…V it,并均标记为已访问,再按照V i1,V i2,…V it的顺序,依次访问每一个顶点
的所有未被访问过的邻接点,并标记为已访问。
依次类推,直到图中所有和初始点V i有路径相通的顶点都被访问过为止。
如果仍有未被访问过的顶点,
该算法必须从图的其它连通分量的任意顶点重新开始。
其非递归算法如下:Boolean visited[MAX_VERTEX_NUM]; // 访问标志数组
Status (*VisitFunc)(int v); //VisitFunc是访问函数,对图的每个顶点调用该函数void BFSTraverse (Graph G Status(*Visit)(i nt v)){
VisitF unc = Visit;
for(v=0; vvG.vex num, ++v)
visited[v] = FALSE;
initQueue(Q); 〃置空辅助队列Q
for(v=0; v<G .vex num; ++v)
if(!visited[v]){ visited[v]=TRUE; VisitFu nc(v);
En Queue(Q, v); //v 入队列
while(!QueueEmpty(Q)){
DeQueue(Q, u); //队头元素出队并置为u
for(w=FirstAdjVex(G ,u); w>=0; w=NextAdjVex(G ,u,w)) if(!Visited[w]){ //w为u的尚未访问的邻接顶点
Visited[w]=TRUE; VisitFu nc(w);
En Queue(Q, w);}}}}
广度优先搜索算法的时间复杂度和深度优先搜索算法的时间复杂度相同。
和深度优先搜索不同的是,广度优先搜索的队列是惟一的,对于具有n个顶点和e 条边的无向图或有向图,每个顶点均入队一次,当图是连通图时,只需要调用一次邻接矩阵或邻接链表即可完成遍历操作。
故邻接矩阵表示法的遍历时间效率为
O(n2),邻接链表表示法的遍历时间效率为0(n+e)。
1.3运行结果及分析
这里选择邻接矩阵作为图的存储结构编写程序,然后将图1的顶点和边输入程序作为测试。
图2运行结果图
由结果知,对图1深度优先遍历的结果为:a b d h e c f g ,广度优先遍历的结果为a b c d e f g h 。
又因为程序的时间效率为0(n2),当测试数值非常小时,程序运行的时间将十分小,忽略不计,故遍历时间为0。
2.字符串匹配问题
字符串匹配(String match)是在实际工作中经常碰到的问题,通常是输入主字符串(String)和字串(又称模式Pattern组成,然后根据一定的算法来得出字串在主字符串中的位置。
通常精确的字符串匹配算法包括暴力搜索(Brute force,又叫蛮力法),KMP(Knuth-Morris-Pratt),BM(Boyer Moore)等等。
假定原字符串长度为n,子字符串长度为m,下面将介绍以上这三种方法并给出其实现。
2.1蛮力法
蛮力法是一种简单的匹配算法,它将字串和主字符串从左方对齐,然后从左
到右将子串和主字符串中每一对相应的字符串进行匹配,如果一旦不匹配,则把
字串向右移动一格,再进行下一轮匹配。
因为这种尝试的最大次数是n-m+1次, 在最坏的情况下,每次尝试需要进行m次比较,所以在最坏的情况下,字符比较的次数为m*(n-m+1)。
故蛮力法的时间效率为0(mn)。
其设计思想为:
①在串S、T中比较的起始下标为i和j;
②循环直到S中剩下的字符个数小于T的长度或T的所有
字符都比较完:
如果S[i]=T[j],则继续比较S和T的下一个字符,否则
将i和j回溯,进行下一趟比较;
③如果T中的字符都比较完,贝U匹配成功,返回匹配的起
始下标,否则匹配失败,返回0。
2.2 KMP算法
KMP算法使用了输入增强的思想,对模式进行预处理以得到一些信息,把
这些信息存储在表中,然后在给定文本中实际查找模式时使用这些信息。
KMP 算法也是将子字符串从左到右和主串进行匹配,和蛮力法不同的是,KMP算法在匹配失败后,并不是简单的从目标串的下一个字符串开始新一轮的检测,而是依据在检测之前得到的有用信息,直接跳过不必要的检测,从主串中找一个和子串字符匹配成功的字符,以这个字符为起点将字串对齐,然后开始新的匹配。
从而达到一个较高的匹配效率。
KMP算法的时间复杂度为0(n+m),当m远小于n 的时候,算法的效率将取决于主字符串的长度,即时间复杂度为0(n)。
其设计
思想为:
①在串S、T中比较的起始下标为i和j;
②循环直到S中剩下的字符个数小于T的长度或T的所有字符都比较完:如果
S[i]=T[j],则继续比较S和T的下一个字符,否则将i向右滑动到next[j]的位置,即j=next[i];如果j=0,则将i和j分别加1,准备进行下一趟比较。
③如果T中的字符都比较完,贝U匹配成功,返回匹配的起始下标,否则匹配失败,返回0。
2.3 BM算法
BM算法是一种精确字符串匹配算法,采用输入增强思想,对模式进行预处
理以得到一些有用信息。
和 KMP 算法不同的是,BM 算法采用从右到左比较的万 法,同时应用到了坏字符规则和好后缀规则, 来决定向右跳跃的距离。
所谓坏字 符规则就是在BM 算法从右到左扫描过程中,若发现某个字符 x 不匹配,则按如 下两种情况考虑:
A. 如果字符x 在模式p 中没有出现,那么从字符x 开始的m 个文本显然不可能与 p 匹配成功,直接跳过该区域即可。
B. 如果x 在模式p 中出现,则以该字符为起点将子串对齐。
好后缀规则为:若发现某个字符不匹配的同时,已有部分字符匹配成功,则按如 下情况考虑:
A.如果在模式p 中位置t 处已匹配部分pl 在p 中某位置t1也出现,且位置t1 前一个字符与位置t 的前一个字符不相同,则将p 右移使t1对于t 方才所在的 位置。
B.如果在p 中任何位置已匹配部分pl 都没有再出现,则找到与pl 的后缀p2相 同的p 的最长前缀X,向右移动p ,使得x 对应方才p2后缀所在位置。
BM 算法的 时间复杂度为0(n +询,当子串长度m 非常小时,算法效率取决与主字符串的长 度n 。
故其时间复杂度为0(n)。
2.运行结果及分析
根据上面的介绍,我们编写程序来实现三种字符串匹配算法。
如图 3所示 0»00000000Prt 盂 S dny kcy to ctiritlnue.
I
: lakdFalKfdadfkdlkf JwoeiucJfsltrkf laf cumrCnsmdlklx, . .du.cuxdalfefkdf fh; 1J; ;la ;fa ;
dElJclakdlkl ;dlFka ;fkdl ;aLE ;da
alas.
请省人主串釘和子
5 rtnB aRB ■ a ij J- Bh- J] ” J ■ - i] J ■
- hta
——鳖四法 曽KM 羸~]Hg 为 ;间 雷叭 i
歸 i »
■ - ■世51 期 为 胪 冋 鬻鼬 鳖
醤配 P 图3匹配算法运行结果图
由图3我们可知当随机输入主字符串和子字符串时,BF算法运行时间为0.016秒,而另外两种算法的时间为0,可忽略不计。
可知明显蛮力法所花费的
时间比较多,这和前面的理论分析中的蛮力法的时间复杂度为O(m* n)相符合。
而通过对结果分析知,KMP算法和BM算法的匹配效率非常高,这和理论分析中二者时间复杂度为O(n)也相符合。
事实上,当我们输入的字符串长度非常大时,这种时间上的优势将更加明显,这里就不再一一测试了。
附录(程序代码)
1.图的遍历
#in elude <iostream>
# in elude "time.h"
#defi ne INFINITY 32767
#defi ne MAX_VEX 20 // 最大顶点个数
#defi ne QUEUE_SIZE (MAX_VEX+1) // 队列长度
using n amespaee std;
bool *visited; // 访问标志数组
//图的邻接矩阵存储结构
typedef struet{
char *vexs; // 顶点向量
int ares[MAX_VEX][MAX_VEX]; // 邻接矩阵
in t vex nu m,arcnum; // 图的当前顶点数和弧数
}Graph;
//队列类
elass Queue{
publie:
void Ini tQueue(){
base=(i nt *)malloc(QUEUE_SIZE*sizeof(i nt));
fron t=rear=0;
}
void En Queue(i nt e){
base[rear]=e;
rear=(rear+1)%QUEUE_SIZE;
}
void DeQueue(i nt &e){
e=base[fr on t];
fron t=(fro nt+1)%QUEUE_SIZE;
}
publie:
int *base;
int front;
int rear;
};
//图G中查找元素c的位置
int Locate(Graph G,char c){
for(i nt i=0;i<G.vex nu m;i++)
if(G.vexs[i]==c) return i;
return -1;
}
//创建无向网
void CreateUDN(Graph & G){
int i,j,w,s1,s2;
char a,b,temp;
printf(”输入顶点数和弧数:”);
scan f("%d%d",&G.vex num,&G.arc nu m);
temp=getchar(); // 接收回车
G.vexs=(char *)malloc(G.vex nu m*sizeof(char)); // 分配顶点数目printf(" 输入%d个顶点.\n",G.vexnum);
for(i=0;i<G.vex num;i++){ // 初始化顶点
printf(" 输入顶点%d:",i);
scan f("%c",&G.vexs[i]);
temp=getchar(); // 接收回车
}
for(i=0;i<G.vex num;i++) // 初始化邻接矩阵
for(j=0;j<G.vex nu m;j++)
G.arcs[i][j]=INFINITY;
printf(" 输入%d条弧.\n",G.arcnum);
for(i=0;i<G.arcnum;i++){ // 初始化弧
printf(" 输入弧%d:",i);
scan f("%c %c %d",&a,&b, &w); // 输入一条边依附的顶点和权值temp=getchar(); // 接收回车
s仁Locate(G,a);
s2=Locate(G,b);
G.arcs[s1][s2]=G.arcs[s2][s1]=w;
}
}
//图G中顶点k的第一个邻接顶点
int FirstVex(Graph G,i nt k){
if(k>=0 && k<G.vex num){ //k 合理
for(i nt i=0;i<G.vex nu m;i++)
if(G.arcs[k][i]!=INFINITY) return i;
}
return -1;
//图G 中顶点i 的第j 个邻接顶点的下一个邻接顶点
int NextVex(Graph G,i nt i,i nt j){
if(i>=0 && i<G.vex num && j>=0 && j<G.vex nu m){ //i,j 合理 for(i nt k=j+1;k<G.vex nu m;k++) if(G.arcs[i][k]!=INFINITY) return k; }
return -1;
}
//深度优先遍历
void DFS(Graph G,i nt k){
int i;
if(k==-1){ // 第一次执行 DFS 时,k 为-1
for(i=0;i<G.vex nu m;i++)
if(!visited[i]) DFS(G,i); //
对尚未访问的顶点调用 DFS }
else{
visited[k]=true;
printf("%c ",G.vexs[k]); //
访问第 k 个顶点 for(i=FirstVex(G,k);i>=0;i=NextVex(G,k,i)) if(!visited[i]) DFS(G,i); //
对k 的尚未访问的邻接顶点 i 递归调用DFS }
}
//广度优先遍历
void BFS(Graph G){ int k;
Queue Q; // 辅助队列 Q
for(i nt w=FirstVex(G,k);w>=O;w=NextVex(G,k,w)) if(!visited[w]){ //w visited[w]=true;
prin tf("%c ",G.vexs[w]);
Q.E nQueue(w);
}
}
}
}
//主函数 int mai n(){
int i;
time_t DFSstart,DFSe nd;
time_t BFSstart,BFSe nd; Q.I ni tQueue();
for(i nt i=0;i<G.vex nu
m;i++)
if(!visited[i]){ //i
visited[i]=true;
prin tf("%c ”,G.vexs[i]);
Q.E nQueue(i); //i
while(Q.fro nt!=Q.rear){
Q.DeQueue(k); // 尚未访问 入列 队头元素出列并置为 k 为k 的尚未访问的邻接顶点
double totaltime1,totaltime2;
Graph G;
CreateUDN(G);
visited=(bool *)malloc(G.vex nu m*sizeof(bool));
printf("\n 深度优先遍历:”);
DFSstart=clock();
for(i=0;i<G.vex nu m;i++)
visited[i]=false;
DFS(G,-1);
DFSe nd=clock();
totaltime1=(double)(DFSe nd - DFSstart)/CLOCKS_PER_SEC;
prin tf("\n 深度优先遍历时间:\n");
cout<<totaltime1<<e ndl;
printf("\n 广度优先遍历:”);
BFSstart=clock();
for(i=0;i<G.vex nu m;i++)
visited[i]=false;
BFS(G);
BFSe nd=clock();
totaltime2=(double)(BFSe nd - BFSstart)/CLOCKS_PER_SEC; prin tf("\n 广度优先遍历时间:\n");
cout<<totaltime2<<e ndl;
printf("\n 程序结束.\n");
return 0;
}
2.字符串匹配
#include <iostream>
#include <string>
#i nclude"time.h"
using namespace std;
class stri ngcom
{
private:
char s[256],t[256];〃分别代表主串与要匹配的串
int num,pos[256];〃分别记录匹配次数与所出现匹配的位置
int next[256],dist[256];//KMP,BM 记录的不匹配时下一位置FILE *fp1,*fp2;
public:
void file ();
void BF ();
void KMP ();
void BM ();
void GetNext ();
void GetDist ();
void In put ()
{cin> >s»t;}
void Output ();
};
void main ()
{
stringcom str;
cout<<"请输入主串S,和子串t: "<<endl;
str.I nput (); //这里选择输入字符串
str.BF ();
str.KMP ();
str.BM ();
}
void stringcom::BF () //蛮力法匹配
{
int i,j=0;
time_t start,end;
double totaltime;
num=0;
start=clock();
for (i=0;i<strlen (s);i++)
if (s[i]==t[j])
{
j++;
if (!t[j])
{
pos [num ]=i-j+2;
nu m++;
i=i-strlen (t)+1;//i从下一个位置开始,避免了重叠的串j=0;
}
}
else
{
i=i-j;
j=0;
}
cout<<endl<<"按BF 算法实现:"<<endl;
Output ();
en d=clock();
totaltime=(double)(e nd-start)/CLOCKS_PER_SEC; printf("BF 算法运行时间为:%.8f",totaltime);
void stringcom::KMP { int i,j; time_t start,end;
double totaltime;
() 〃输入增强
num=0;
i=0;
j=0;
start=clock();
GetNext ();
while (strlen (s)-i>0)
if (j==0||(s[i]==t[j]))
{
i++;j++;
if (!t[j])
{
pos[num]=i-strlen (t)+1;〃位置从1开始记录,下标从
i=pos[num];//i 从下一个位置开始,避免了重叠的串 nu
m++;
j=0;
}
}
else j=n ext[j];
cout<<endl<<"按 KMP 算法实现:"<<endl;
Output ();
en d=clock();
totaltime=(double)(e nd-start)/CLOCKS_PER_SEC; printf("KMP 算法运行时间为:%.8f',totaltime);
} void stringcom::GetNext () {
int j,k;
next[1]=0; j=1;
k=0;
while (j<strlen (t))
if (k==O||(t[j]==t[k]))}
*************KMP ^1算^法
**************** 0开始 ******** *GetNext ****************
j++;
k++;
n ext[j]=k;
}
else k=n ext[k];
}
**********B^^ ^1算^法********************
void stringcom::BM ()
{
int i,j,len;
time_t start,end;
num=0;
len=strlen (t);
i=le n-1;
double totaltime;
start=clock();
GetDist ();
while (i<strlen (s))
{
j=le n-1;
while (j>=0&&s[i]==t[j])
{
i--;
j--;
}
if (j==-1)
{
pos[ nu m++]=i+2;
i+=strlen (t)+1;//i从下一个位置开始,避免了重叠的串} else i+=dist[s[i]];
}
cout<<endl<<"按BM 算法实现:"<<endl;
Output ();
en d=clock();
totaltime=(double)(e nd-start)/CLOCKS_PER_SEC;
printf("BM 算法运行时间为:%.8f",totaltime);
} void stringcom::GetDist () {
********* GetDist* ************
int i,lenth;
lenth=strlen (t);
for (i=0;i<256;i++) dist[i]=le nth;
for (i=0;i<lenth-1;i++) dist[t[i]]=le nth-i-1;
dist[t[le nth-1]]=le nth;
}
************* ^输出***************
void stringcom::Output ()
{
int i;
if (num)
{
cout<<"主串中共有"<<num<<"个与之相匹配的串"<<endl;
cout<<"位置是:";
for (i=0;i<num;i++)
cout<<T<<pos[i]<<".."<<pos[i]+strle n (t)-1<<"]";
cout<<e ndl;
}
else cout<<"无相匹配的串!"<<endl;
}。