明天就要实验啦,今晚写这个不知道学不学的完啊,太头秃了。
不过咱死马当活马医,能复习怎样就怎样咯。
声明一下哈,这篇博客里用到的代码不是我原创的。参考了优快云上一位大佬的答案。在此,仅对大佬的代码进行复现,以及加上我自己的理解。希望能对我这个菜狗有所帮助。
DFS与BFS
时间有限本文写不了多长咯,长话短说,直接上DFS的示例,栈结构,和shaffer教材的实现伪代码
DFS示例与该过程递归栈的变化
这里是伪代码实现示例。
这里有什么不懂的呢,最显而易见的是PreVisit和PostVisit了,这两个函数是干嘛的呢?
回想我们在树结构处学过先序遍历、中序遍历、后序遍历和层次遍历。那时候用到了一个visit函数,visit被作为参数传入,根据访问顺序的不同,而存在于递归访问的不同位置。(这么说如果不记得树的实验就不容易看懂,不过没关系,后续复习的时候会把树结构的实验也补上来的。)
下面是广度优先搜索的示例和伪代码。
知识背景复习就到这里~
Dijkstra与Prim
这里我都记得,不过一时找不到课本伪代码了,所以这一部分的背景补充先略过
实验文件与模块介绍
解压实验包并新建项目,我们就得到这样的目录
graph里是图结构的ADT
Graph_test里是test内容,我们要完成的Dijkstra和Prim,BFS DFS都在里面
grlist是图的邻接链表实现
grmat是图的邻接矩阵实现
link和llist list都是链表的实现
需要完成的代码部分
先上开胃菜
完成顶点出度入度的计算
究其思想就是借用ADT里给出的isEdge(int v1,int v2)来判断(v1,v2)是不是图的一条边。
martix和list实现唯一的区别在于出度的计算,list实现可以直接返回vertex[v]->length()就可
martex实现需要遍历一下所有点到v的边是否存在于图就可以
int getInDegree(int v) // 求顶点v的入度
{
int result = 0;
//............... 在此行以下插入补充代码
for(int i=0;i<numVertex;i++) {
if(i!=v) {
if(isEdge(i,v)) result++;
}
}
//............... 在此行以上插入补充代码
return result;
}
int getOutDegree(int v) // 求顶点v的出度
{
int result=0;
//............... 在此行以下插入补充代码
result=vertex[v]->length();
//............... 在此行以上插入补充代码
return result;
}
};
so easy啦是不是,其实我自己做的时候遇到了两个坑。
1.图的链表实现和图的邻接矩阵实现,对应的出度入度计算方法是不同的(毕竟他们给出的ADT就不同)
2.一定仔细看ADT哇,善于借用ADT来帮助完成函数功能。我第一次没有注意看,手撸链表,然后理所当然地出现了segment error
上面是grlist的实现方式,下面展示grmat的实现方式
int getInDegree(int v) // 求顶点v的入度
{
int result = 0;
//............... 在此行以下插入补充代码
for(int i=0;i<numVertex;i++) {
if(isEdge(i,v)) result++;
}
//............... 在此行以上插入补充代码
return result;
}
int getOutDegree(int v) // 求顶点v的出度
{
int result = 0;
//............... 在此行以下插入补充代码
for(int i=0;i<numVertex;i++)
if(isEdge(v,i)) result++;
//............... 在此行以上插入补充代码
return result;
}
DFS与BFS
这两种图的搜索方法上面已经介绍过。因为两种图的实现都已经封装好了,所以这一步两种实现的处理是一样的。
void DFS(int v, void (*PreVisit)(int v), void (*PostVisit)(int v), void (*Visiting)(int v)) // Depth first search
{
PreVisit(v);//如果要先序遍历/访问,可以从previsit这里传入需要做的处理
visiting(v);//中序遍历
G->setMark(v,VISITED);//访问当前结点
for(int i=G->first(v);i<G->n();i=G->next(v,i)) {//这里有点像回溯算法是不是
if(G->getMark(i)==UNVISITED)
DFS(i, PreVisit, PostVisit, Visiting);
}
PostVisit(v);//后序遍历。真的是太像BinTree啦
}
ok下面是BFS
void BFS(int start, void (*PreVisit)(int v), void (*PostVisit)(int v), void (*Visiting)(int v))
{
int v,w;
queue<int> q;
G->setMark(start,VISITED);
q.push(start);//上面可称为准备过程
while(!q.empty()) {
v=q.front();
q,pop();
if(v==start) PreVisit(v);//由于start已经设为访问过了,所以单独处理
visiting(v);//中序遍历,和DFS过程一样(虽然我现在并不是很理解)
for(w=G->first(v),w<G->n();w=G->next(v,w)) //开始处理结点的邻居结点
if(G->getMark(w)==UNVISITED) {
PreVisit(w);
G->setMark(w,VISITED);
q.push(w);
}
PostVisit(v);
}
}
Dijkstra与Prim算法
void Dijkstra1(int* D, int s)
{//来吧,我最爱的dijkstra算法,“一把手就拉住了,我亲爱的小冤家啊~~”
int v,w;
//注意,通常情况下D数据是需要对source进行初始化的,但这里不需要,D的初始化在main函数里做过了,要是再做的话,0会覆盖掉本该出现的INF
for(int i=0;i<G->n();i++) {//这个意思是说要遍历n个结点
v=minVertex(D);//找到D数组中为访问的最小数的下标,别的不用管,minVertex帮你做过了
if(v==INFINITY) return;
G->setMark(v,VISITED);
for(w=G->first(v);w<G->n();w=G->next(v,w)) //我这里还有疑问,为什么不需要判断w结点是否已访问过呢?我还试了下,如果验证是否访问,反而会出现错误
if(D[w]>D[v]+G->weight(v,w)) D[w]=D[v]+G->weight(v,w);
}
}
接下来,Prim算法
int V[G->n()];
int v,w,i;
for(int i=0;i<G->n();i++) {
v=minVertex(D);
if(v==INFINITY) return;
G->setMark(v,VISITED);
if(v!=s) ADDEdgetoMST(V[v],v);
for(w=G->first(v);w<G->n();v=G->next(v,w))
if(D[w]>G->weight(v,w)) {
D[w]=G->weight(v,w);
V[w]=v;
}
}
}
好了,今晚就写到这里了。明天还有早八。
中午实验加油啊QwQ
实验做完了,我靠,你糊的机房该换新了吧。编译器能给我气抽抽,卡到裂开好吗,每次还要手动clean。而且我觉得题目的代码有隐性bug,我的BFS代码在本地机上可以跑,但是机房就不行了,报错在graph(int int) 不能reload。离谱啊,就即便不调用BFS也会出编译问题。这我能怎么办咯。不过还行,20道测试,AC10道就算满分了。
哎~希望下次能抽到一个灵敏的电脑。
作为菜狗,我理所当然地参加了周一中午的补测。体验和上次形成鲜明对比,(按理来说我上次已经满分了),编译器非常利索,但这次也不是一次AC的,记录一下debug过程出现的问题,以及今天完美过掉的BFS。
1.DFS里的递归部分
本该是DFS(w,pervisit,visiting,postvisit)把写成v了,导致无穷递归
2.Dijkstra和Prim里忘记写setMark(w,VISITED)导致每次结果都只返回源点到各边的距离。这也是我觉得用minMartex的不好处,选择标记不是自己写的,就老记不住访问过后加标记。
标记加在确定每轮访问的v之后就可以,顺序无所谓。
今天的BFS:
void BFS(int start,void (PreVisit*)(int v),void (Visiting*)(int v),void (PostVisit*)(int v)) {
int v=start,w=0;
queue<int> q;
Previsit(v);
q.push(v);
G->setMark(v,VISITED);
while(!q.empty()) {
v=q.front();
q.pop();
Visiting(v);
for(w=G->first(v);w<G->n();w=G->next(v,w)) {
if(G->getMark(w)==UNVISITED)
{ PreVisit(w);
G->setMark(w,VISITED);
q.push(w);
}
}
PostVisit(v);
}
}
相信我,这段代码在HNU绝对“吸烟刻肺”级别的了,好吗。
这次实验就先这样吧。以及,如果你有个在CS的朋友,送ta一把漂亮耐用的雨伞和一大盒感冒灵颗粒,ta会感动到哭的。