一、实验题目及要求
题目:三种算法的同步演示
需求:
1、基于下图构造图;
2、分别使用深度优先遍历(DFS)、Prim、Dijkstra算法从任意用户输入的节点开始对图进行遍历、求MST及最短路径;
3、三个算法同时动态显示构造过程(非节点动态打印);
4、每一步都要求显示/打印所有试探的路径。
二、概要设计
1、数据结构设计
本程序主要采用邻接矩阵作为图的存储结构。算法中的顶点集合使用数组存储。
2、算法设计
(1)深度优先遍历(DFS)
深度优先遍历是一个不断探查和回溯的过程。结点类node的数据成员sign用于记录结点是否被访问过,若访问过,sign的值置为1;否则置为0。待用户输入第一个要访问的结点m后,对该节点进行访问,并将该结点的访问标志sign置1,接着在m所有的邻接结点中找出尚未访问的结点,将其作为下一步探查的当前结点,在n[temp].sign == 0条件下递归地调用DFS函数,直至所有结点都被访问。具体算法为:
(1)首先访问顶点i,并将其访问标志位置1;
(2)然后搜索与顶点i有边相连的下一个顶点j,若j未被访问过,则访问它,并将j的访问标记置1,然后从j开始重复此过程,若j已访问,再看与i有边相连的其他顶点;
(3)若与i有边相连的顶点都被访问过,则退回到前一个访问顶点并重复刚才的过程,直到图中所有顶点都被访问完为止。
代码如下:
void DFS(graph& gra, node* n, int m)//深度优先遍历算法,并画出生成过程
{
cout << n[m].data << endl;
n[m].sign = 1;
int temp = gra.getfirstnei(m);
setlinewidth(5);
setcolor(RED);
while (temp != -1) {
if (n[temp].sign == 0) { Sleep(500); line(n[m].x, n[m].y, n[temp].x, n[temp].y); DFS(gra, n, temp); }//递归
temp = gra.getnextnei(m, temp);
}
}
(2)Prim算法
Prim算法就是每次都选取最短路径,并把访问过的结点纳入集合中,直至所有结点遍历完成。需要一个最小堆,每次选出一个端点在生成树中,另一个端点不在生成树的权值最小的边,它正好在最小堆的堆顶,将其从堆中退出,加入生成树中。然后将新出现的所有一个端点在生成树中,另一个端点不在生成树中的边都插入最小堆中。下一轮迭代中,下一条满足要求的权值最小的又上升到最小堆的堆顶,如此重复,最后建立起该图的最小生成树。其具体实现如下:
选定构造最小生成树的出发顶点u0;
Vmst = {u0},Emst = Æ;
while (Vmst包含的顶点少于n && E不空) {
1) 从E中选一条边(u, v), uÎVmst∩vÎV-Vmst, 且具有最小代价(cost);
2) 令Vmst = Vmst∪{v}, Emst = Emst∪{(u, v)};
3) 将新选出的边从E中剔除:E = E-{(u, v)};
}
if (Vmst包含的顶点少于n)
cout << "不是最小生成树" << endl;
代码如下:
int prim(graph& gra, node* n, int m, int* d)
{
for (int p = 0; p < gra.numver; p++)//将所有的距离初始化,设置为int类型最大值
{
d[p] = INT_MAX;
}
d[m] = 0;//到自己的距离为0
int sum = 0;
for (int i = 0; i < gra.numver; i++)
{
int u = -1;//u点到某一结点最小
int min = INT_MAX;
for (int j = 0; j < gra.numver; j++)
{
if (n[j].sign == 0 && d[j] < min) {
min = d[j];
u = j;
}
}
if (u == -1) { return -1; }//找不到d[j]说明不连通
n[u].sign = 1;
setlinewidth(5);
setcolor(RED);
sum += d[u];
for (int l = 0; l < gra.numver; l++)//画边
{
for (int k = l + 1; k < gra.numver; k++)
{
if (gra.edge[l][k] == d[u]) {
Sleep(500);
line(n[l].x, n[l].y, n[k].x, n[k].y);
}
}
}
for (int v = 0; v < gra.numver; v++) //寻找该结点相邻的最小边
{
if (n[v].sign == 0 && gra.edge[u][v] < d[v]) {
d[v] = gra.edge[u][v];
}
}
}
cout << sum << endl;//输出最小生成树的总权值
return sum;
}
(3)Dijkstra算法
Dijkstra算法用于寻找源点到带权图D中其它各顶点的最短路径。其主要思想是按路径长度的递增次序,逐步产生最短路径,首先求出长度最短的一条最短路径,然后参照它求出长度次短的一条最短路径,以此类推,直到从顶点v到其它各顶点的最短路径全部求出为止。具体描述如下:
(1)一开始第一组S只包括顶点v0,第二组包括其它所有顶点;然后,v0对应的距离值为0,而第二组的顶点对应的距离值这样确定:若图中有边<v0,vk>或者(v0,vk),则vk的距离值为此边所带的权,否则vk的距离值为∞。
(2)每次从第二组的顶点中选一个其距离值为最小的顶点vu加入到第一组中;
(3)每往第一组加入一个顶点vu,就要对第二组的各顶点的距离值进行一次修正:若加进vu做中间顶点,使从v0到vk的最短路径比不加vu的为短,则需要修改vi的距离值。
(4)修改后再选距离值最小的顶点加入到第一组中,如此进行下去,直到图的所有顶点都包括在第一组中或者再也没有可加入到第一组的顶点存在。
代码如下:
void dijkstra(graph& gra, node* n, int* d, int ch, int path[])//ch是终点,d为存储最短距离的数组,path是存储最短路径所经结点的数组
{
int min;
for (int i = 0; i < gra.numver; i++)
{
d[i] = gra.getweight(ch, i);
if (i != ch && d[i] < INT_MAX)path[i] = ch;
else path[i] = -1;
}
n[ch].sign = 1; d[ch] = 0;//将标志置为1,到自己距离为0
for (int i = 0; i < gra.numver - 1; i++)
{
min = INT_MAX;
int u = ch;
for (int j = 0; j < gra.numver; j++)
{
if (n[j].sign == 0 && d[j] < min) {
u = j; min = d[j];
}
n[u].sign = 1;
for (int k = 0; k < gra.numver; k++)
{
int w = gra.getweight(u, k);
if (n[k].sign == 0 && w < INT_MAX && d[u] + w < d[k])
{
d[k] = d[u] + w;//最短路径权值的累加
path[k] = u;
}
}
}
}
}
三、详细设计
图类graph中定义了表示边数、结点数、最大可容纳结点数、结点数组和邻接矩阵的的数据成员,函数成员支持实现图的基本操作,包括获取相邻结点、获取下一个相邻结点和获取连接两结点的边的权值。
class graph
{
public:
graph(int sz);
~graph() { delete[]edge; }
int getweight(int v1, int v2) { if (v1 != -1 && v2 != -1) return edge[v1][v2]; else return 0; }//取得两节点之间边的权值
int getfirstnei(int v);//得到第一个相邻结点
int getnextnei(int v, int w);//得到下一个相邻节点
int numedg;//边总数
int numver;//结点总数
int maxver;//最大结点数
string verlist;//结点数组
int** edge;//边的邻接矩阵
};
结点类中设置了结点的横纵坐标参数和访问标志sign,用data存储结点的数据。
class node//结点类
{
public:
friend graph;
node() {};
~node() {};
int x;//结点的X坐标
int y;//结点的Y坐标
string data;//结点的数据
int sign = 0;//判断是否被访问过的标志
};
图类的构造函数实现图的初始化操作,邻接矩阵主对角线元素为0,其它元素为最大距离maxweight。
graph::graph(int sz)
{
maxver = sz; numedg = 0; numver = 0;
int i, j;
edge = (int**)new int* [maxver];
for (i = 0; i < maxver; i++)
edge[i] = new int[maxver];
for (i = 0; i < maxver; i++)
for (j = 0; j < maxver; j++)
edge[i][j] = (i == j) ? 0 : maxweight;
numver = 10;
numedg = 17;
verlist = "ABCDEFGHIJ";
edge[0][1] = edge[1][0] = 750;
edge[0][2] = edge[2][0] = 680;
edge[1][2] = edge[2][1] = 800;
edge[1][4] = edge[4][1] = 970;
edge[1][3] = edge[3][1] = 650;
edge[3][2] = edge[2][3] = 820;
edge[3][4] = edge[4][3] = 570;
edge[3][6] = edge[6][3] = 530;
edge[3][5] = edge[5][3] = 975;
edge[4][8] = edge[8][4] = 840;
edge[2][5] = edge[5][2] = 960;
edge[6][5] = edge[5][6] = 680;
edge[9][5] = edge[5][9] = 990;
edge[6][7] = edge[7][6] = 900;
edge[6][9] = edge[9][6] = 980;
edge[7][8] = edge[8][7] = 840;
edge[7][9] = edge[9][7] = 985;
}
获取结点的相邻结点操作:
int graph::getfirstnei(int v)
{
if (v != -1)
{
for (int num = 0; num < numver; num++)
if (edge[v][num] > 0 && edge[v][num] < maxweight)return num;
return -1;
}
}
获取结点的下一个相邻结点:
int graph::getnextnei(int v, int w)
{
if (v != -1 && w != -1)
{
for (int num = w + 1; num < numver; num++)
if (edge[v][num] > 0 && edge[v][num] < maxweight)return num;
}
return -1;
}
在main()函数中,首先初始化窗口,输出结点总数和边总数,并输出每两个结点及连接这两个结点的边的权值,接着创建结点,显示图形,之后提示用户选择算法并输入初始结点,待用户输入后逐步显示构造过程。
int main()
{
initgraph(1000, 1000, INIT_RENDERMANUAL); //初始化窗口
setfillcolor(YELLOW);
setbkcolor(WHITE);
setfontbkcolor(WHITE);
cout << "结点:0-A;1-B;2-C;3-D;4-E;5-F;6-G;7-H;8-I;9-J" << endl;
int num = 0;
graph gra(20);
cout << "结点总数:" << gra.numver << " " << "边总数:" << gra.numedg << endl;//输出结点总数,边总数
for (int i = 0; i < gra.numver; i++)
for (int j = i + 1; j < gra.numver; j++)
{
int w = gra.getweight(i, j);
if (w > 0 && w < maxweight) {
char e1 = gra.verlist[i]; char e2 = gra.verlist[j];
cout << "(" << e1 << "," << e2 << "," << w << ")" << endl;
}
}
system("pause");
node* no = new node[10];//创建十个结点
for (int p = 0; p < gra.numver; p++)
{
no[p].data = gra.verlist[p];
}
show(gra, num, no);
cout << "请选择算法:1.DFS算法 2.Prim算法 3.Dijkstra算法" << endl;
int choice;
cin >> choice;
if (choice == 1)
{
int start;
cout << "输入初始节点(0-9)" << endl;
cin >> start;
DFS(gra, no, start);
}
if (choice == 2)
{
int* vec = new int[10];//用于prim算法
int start;
cout << "输入初始节点(0-9)" << endl;
cin >> start;
prim(gra, no, start, vec);
}
if (choice == 3)
{
int start, end;
cout << "请输入:" << endl;
cin >> start >> end;
int* arr = new int[10];
int path[10];
dijkstra(gra, no, arr, start, path);
printpath(gra, no, start, arr, path, end);
}
getch();
closegraph();
}
四、测试结果
1、主界面与图形初始化显示
2、各算法及其演示
五、源代码
#include<iostream>
#define SHOW_CONSOLE
#include <graphics.h>
#include<ege/sys_edit.h>
const int maxweight = 65535;
using namespace std;
class graph
{
public:
graph(int sz);
~graph() { delete[]edge; }
int getweight(int v1, int v2) { if (v1 != -1 && v2 != -1) return edge[v1][v2]; else return 0; }//取得两节点之间边的权值
int getfirstnei(int v);//得到第一个相邻结点
int getnextnei(int v, int w);//得到下一个相邻节点
int numedg;//边总数
int numver;//结点总数
int maxver;//最大结点数
string verlist;//结点数组
int** edge;//边的邻接矩阵
};
class node//结点类
{
public:
friend graph;
node() {};
~node() {};
int x;//结点的X坐标
int y;//结点的Y坐标
string data;//结点的数据
int sign = 0;//判断是否被访问过的标志
};
graph::graph(int sz)
{
maxver = sz; numedg = 0; numver = 0;
int i, j;
edge = (int**)new int* [maxver];
for (i = 0; i < maxver; i++)
edge[i] = new int[maxver];
for (i = 0; i < maxver; i++)
for (j = 0; j < maxver; j++)
edge[i][j] = (i == j) ? 0 : maxweight;
numver = 10;
numedg = 17;
verlist = "ABCDEFGHIJ";
edge[0][1] = edge[1][0] = 750;
edge[0][2] = edge[2][0] = 680;
edge[1][2] = edge[2][1] = 800;
edge[1][4] = edge[4][1] = 970;
edge[1][3] = edge[3][1] = 650;
edge[3][2] = edge[2][3] = 820;
edge[3][4] = edge[4][3] = 570;
edge[3][6] = edge[6][3] = 530;
edge[3][5] = edge[5][3] = 975;
edge[4][8] = edge[8][4] = 840;
edge[2][5] = edge[5][2] = 960;
edge[6][5] = edge[5][6] = 680;
edge[9][5] = edge[5][9] = 990;
edge[6][7] = edge[7][6] = 900;
edge[6][9] = edge[9][6] = 980;
edge[7][8] = edge[8][7] = 840;
edge[7][9] = edge[9][7] = 985;
}
int graph::getfirstnei(int v)
{
if (v != -1)
{
for (int num = 0; num < numver; num++)
if (edge[v][num] > 0 && edge[v][num] < maxweight)return num;
return -1;
}
}
int graph::getnextnei(int v, int w)
{
if (v != -1 && w != -1)
{
for (int num = w + 1; num < numver; num++)
if (edge[v][num] > 0 && edge[v][num] < maxweight)return num;
}
return -1;
}
#define SHOW_CONSOLE
#include"graph.h"
#include<iostream>
#include <graphics.h>
#include<ege/sys_edit.h>
#include<string>
using namespace std;
void show(graph& gra, int& num, node* n);
void DFS(graph& gra, node* n, int m)//深度优先遍历算法
{
cout << n[m].data << endl;
n[m].sign = 1;
int temp = gra.getfirstnei(m);
setlinewidth(5);
setcolor(RED);
while (temp != -1) {
if (n[temp].sign == 0) { Sleep(500); line(n[m].x, n[m].y, n[temp].x, n[temp].y); DFS(gra, n, temp); }//递归
temp = gra.getnextnei(m, temp);
}
}
int prim(graph& gra, node* n, int m, int* d)//prim算法
{
for (int p = 0; p < gra.numver; p++)//将所有的距离初始化,设置为int类型最大值
{
d[p] = INT_MAX;
}
d[m] = 0;//到自己的距离为0
int sum = 0;
for (int i = 0; i < gra.numver; i++)
{
int u = -1;//u点到某一结点最小
int min = INT_MAX;
for (int j = 0; j < gra.numver; j++)
{
if (n[j].sign == 0 && d[j] < min) {
min = d[j];
u = j;
}
}
if (u == -1) { return -1; }//找不到d[j]说明不连通
n[u].sign = 1;
setlinewidth(5);
setcolor(RED);
sum += d[u];
for (int l = 0; l < gra.numver; l++)//画边
{
for (int k = l + 1; k < gra.numver; k++)
{
if (gra.edge[l][k] == d[u]) {
Sleep(500);
line(n[l].x, n[l].y, n[k].x, n[k].y);
}
}
}
for (int v = 0; v < gra.numver; v++) //寻找该结点相邻的最小边
{
if (n[v].sign == 0 && gra.edge[u][v] < d[v]) {
d[v] = gra.edge[u][v];
}
}
}
cout << sum << endl;//输出最小生成树的总权值
return sum;
}
void dijkstra(graph& gra, node* n, int* d, int ch, int path[])
{
int min;
for (int i = 0; i < gra.numver; i++)
{
d[i] = gra.getweight(ch, i);
if (i != ch && d[i] < INT_MAX)path[i] = ch;
else path[i] = -1;
}
n[ch].sign = 1; d[ch] = 0;//将标志置为1,到自己距离为0
for (int i = 0; i < gra.numver - 1; i++)
{
min = INT_MAX;
int u = ch;
for (int j = 0; j < gra.numver; j++)
{
if (n[j].sign == 0 && d[j] < min) {
u = j; min = d[j];
}
n[u].sign = 1;
for (int k = 0; k < gra.numver; k++)
{
int w = gra.getweight(u, k);
if (n[k].sign == 0 && w < INT_MAX && d[u] + w < d[k])
{
d[k] = d[u] + w;//最短路径权值的累加
path[k] = u;
}
}
}
}
setlinewidth(5);
setcolor(RED);
}
void printpath(graph& gra, node* n, int v, int* dist, int path[], int zhongdian)//输出最短路径所经过的结点
{
int i, j, k, num = gra.numver;
int* d = new int[num];
for (i = 0; i < num; i++)
{
if (i != v && i == zhongdian) {
j = i; k = 0;
while (j != v) { d[k++] = j; j = path[j]; }
cout << n[v].data << " ";
while (k > 0) {
cout << n[d[--k]].data << " ";
}
cout << "最短路径长度:" << dist[i] << endl;
}
}
}
int main()
{
initgraph(1000, 1000, INIT_RENDERMANUAL); //初始化窗口
setfillcolor(YELLOW);
setbkcolor(WHITE);
setfontbkcolor(WHITE);
cout << "结点:0-A;1-B;2-C;3-D;4-E;5-F;6-G;7-H;8-I;9-J" << endl;
int num = 0;
graph gra(20);
cout << "结点总数:" << gra.numver << " " << "边总数:" << gra.numedg << endl;//输出结点总数,边总数
for (int i = 0; i < gra.numver; i++)//输出有权值的边
for (int j = i + 1; j < gra.numver; j++)
{
int w = gra.getweight(i, j);
if (w > 0 && w < maxweight) {
char e1 = gra.verlist[i]; char e2 = gra.verlist[j];
cout << "(" << e1 << "," << e2 << "," << w << ")" << endl;
}
}
system("pause");
node* no = new node[10];//创建十个结点
for (int p = 0; p < gra.numver; p++)
{
no[p].data = gra.verlist[p];
}
show(gra, num, no);
cout << "请选择算法:1.DFS算法 2.Prim算法 3.Dijkstra算法" << endl;
int choice;
cin >> choice;
if (choice == 1)
{
int start;
cout << "输入初始节点(0-9)" << endl;
cin >> start;
DFS(gra, no, start);
}
if (choice == 2)
{
int* vec = new int[10];//用于prim算法
int start;
cout << "输入初始节点(0-9)" << endl;
cin >> start;
prim(gra, no, start, vec);
}
if (choice == 3)
{
int start, end;
cout << "请输入:" << endl;
cin >> start >> end;
int* arr = new int[10];
int path[10];
dijkstra(gra, no, arr, start, path);
printpath(gra, no, start, arr, path, end);
}
getch();
closegraph();
}
void show(graph& gra, int& num, node* n)//构建图
{
if (gra.numver == 0)return;
fillellipse(800, 100, 23, 23);//A
n[0].x = 800; n[0].y = 100;
char ch[100] = { NULL };
xyprintf(800, 100, A);
Sleep(500);
fillellipse(700, 160, 23, 23);//B
n[1].x = 700; n[1].y = 160;
xyprintf(700, 160,B);
num++;
Sleep(500);
fillellipse(780, 220, 23, 23);//C
n[2].x = 780; n[2].y = 220;
xyprintf(780, 220,C);
Sleep(500);
fillellipse(700, 250,23,23);//D
n[3].x = 700; n[3].y = 250;
xyprintf(700, 250,D);
Sleep(500);
fillellipse(580, 260, 23, 23);//E
n[4].x = 580; n[4].y = 260;
xyprintf(580, 260, E);
Sleep(500);
fillellipse(800, 400,23,23);//F
n[5].x = 800; n[5].y = 400;
xyprintf(800, 400,F);
Sleep(500);
fillellipse(700, 420,23,23);//G
n[6].x = 700; n[6].y = 420;
xyprintf(700, 420, G);
Sleep(500);
fillellipse(520, 440, 23,23);//H
n[7].x = 520; n[7].y = 440;
xyprintf(520, 440,H);
Sleep(500);
fillellipse(480, 380,23,23);//I
n[8].x = 480; n[8].y = 380;
xyprintf(480, 380, I);
Sleep(500);
fillellipse(680, 600,23, 23);//J
n[9].x = 680; n[9].y = 600;
xyprintf(680, 600, J);
Sleep(500);