1. 程式人生 > >【資料結構與演算法】 有向圖的最短路徑實現

【資料結構與演算法】 有向圖的最短路徑實現

Goal: Practice the algorithms of shortest pathproblem.

Task:用一個有向圖表示給定的n個(要求至少10個)城市(或校園中的一些地點)及其之間的道路、距離情況,道路是有方向的。要求完成功能:根據使用者輸入的任意兩個城市,給出這兩個城市之間的最短距離及其路徑。

要求使用真實地點及其位置,可以使用百度地圖獲得各點座標,或者獲取可達的兩點間距離。

採用Dijkstra演算法

Dijkstra演算法是由荷蘭電腦科學家艾茲格·迪科斯徹發現的。演算法解決的是有向圖中最短路徑問題。

舉例來說,如果圖中的頂點表示城市,而邊上的權重表示著城市間開車行經的距離。 Dijkstra演算法可以用來找到兩個城市之間的最短路徑。

Dijkstra演算法的輸入包含了一個有權重的有向圖G,以及G中的一個來源頂點S。 我們以V表示G中所有頂點的集合。圖中的每一個邊,都是兩個頂點所形成的有序元素對。(u,v)表示從頂點uv有路徑相連。 假設E為所有邊的集合,而邊的權重則由權重函式wE → [0, ∞]定義。 因此,w(u,v)就是從頂點u到頂點v的非負花費值(cost)。 邊的花費可以想像成兩個頂點之間的距離。任兩點間路徑的花費值,就是該路徑上所有邊的花費值總和。 已知有V中有頂點st,Dijkstra演算法可以找到st的最低花費路徑(i.e. 最短路徑)。 這個演算法也可以在一個圖中,找到從一個頂點s到任何其他頂點的最短路徑。

採用不同顏色標註配圖路線防止混淆

想到了離散數學的哈密頓圖


#include <iostream>
#include <windows.h>
#include <stdlib.h>
#include <conio.h>
using namespace std;


int arrow(int up,int down);
void title();
void gotoxy(int xpos, int ypos);
void frontPage();
int main();
void search_path();
void quit();
void find_the_path(const int start, const int dest, int& shortest, int * path);
int minVertex(int* D);
void print_path(int * path, int dest, int start);

class Graph
{
public:
virtual int verNum() = 0;
virtual int edgNum() = 0;
virtual int firstNghbr(int v) = 0;
virtual int nextNghbr(int v1, int v2) = 0;
virtual void setEdge(int v1, int v2, int wgt) = 0;
virtual void delEdge(int v1, int v2) = 0;
virtual int weight(int v1, int v2) = 0;
virtual int getMark(int v) = 0;
virtual void setMark(int v, int val) = 0;
};




class Graphm : public Graph
{
private:
int numVertex, numEdge;
int **matrix;
int *mark;


public:
Graphm()
{}
Graphm(int numVert)   //宣告一張圖(用鄰接矩陣方法實現)
{
int i,j;
numVertex = numVert;
numEdge = 0;
mark = new int[numVert];
for(i=0; i<numVertex; i++)
mark[i] = 0;   //初始化各點訪問情況為0,即未訪問
matrix = (int**) new int*[numVertex];
for(i=0; i<numVert; i++)
matrix[i] = new int[numVertex];
for(i=0; i<numVertex; i++)
for(j=0; j<numVertex; j++)
matrix[i][j] = 0;      //權為0表示兩點間沒有邊連線
}


~Graphm()
{
delete [] mark;
int i;
for(i=0; i<numVertex; i++)
delete [] matrix[i];
delete [] matrix;
}


int verNum()
{
return numVertex;
}


int edgNum()
{
return numEdge;
}


int firstNghbr(int v)
{
int i;
for(i=0; i<numVertex; i++)
if(matrix[v][i] != 0)
return i;
return i;           //若無第一個鄰接結點返回節點數
}


int nextNghbr(int v1, int v2)
{
int i;
for(i=v2+1; i<numVertex; i++)
if(matrix[v1][i] != 0)
return i;
return i;
}


void setEdge(int v1, int v2, int wgt)
{
if(matrix[v1][v2] == 0)
numEdge++;
matrix[v1][v2] = wgt;
}


void delEdge(int v1, int v2)
{
if(matrix[v1][v2] != 0)
numEdge--;
matrix[v1][v2] = 0;
}


int weight(int v1, int v2)
{
return matrix[v1][v2];
}


int getMark(int v)
{
return mark[v];
}


void setMark(int v, int val)
{
mark[v] = val;
}
};






class City
{
private:
int id;
char * cityName;
public:
City()
{}
City(int newId, char* newName)
{
id = newId;
cityName = newName;
}


char* getCityName()
{
return cityName;
}
};


City* cityDB = new City[10];  //宣告十個城市
Graphm cityGraph = Graphm(10); //宣告城市間火車路線圖






void gotoxy(int xpos, int ypos)
{
  COORD scrn;   


  HANDLE hOuput = GetStdHandle(STD_OUTPUT_HANDLE);


  scrn.X = xpos; scrn.Y = ypos;


  SetConsoleCursorPosition(hOuput,scrn);
}






void title()
{
gotoxy(0,0);


cout<<"**********************************************************************"<<endl; 
cout<<endl;
cout<<"                 歡迎使用城市間最短里程路徑查詢程式    "<<endl;
cout<<endl;
cout<<"**********************************************************************"<<endl;

}






int arrow(int up,int down)
{
int i,j,c;


gotoxy(20,up);
j=up;
cout<<"-->";


for(i=0;i<10000;i++)
{
if(j>down)
{
gotoxy(20,j);
cout<<"   ";
j=up;
gotoxy(20,j);
cout<<"-->";
}


if(j<up)
{
gotoxy(20,j);
cout<<"   ";
j=down;
gotoxy(20,j);
cout<<"-->";
}


c=getch();
if(c==10 || c==13)
break;
if(c==224)
{
c=getch();
if(c == 80)
{
gotoxy(20,j+1);
cout<<"-->";
gotoxy(20,j);
cout<<"   ";
j++;
}
if(c == 72)
{
gotoxy(20,j-1);
cout<<"-->";
gotoxy(20,j);
cout<<"   ";
j--;
}
}
}
return j;


}       //游標移動選擇功能 








void frontPage()
{
system("cls");
title();


    cout<<endl;
cout<<endl;
cout<<endl;
cout<<endl;


cout<<"                 請上下鍵選擇操作:\n"<<endl;
cout<<endl;
cout<<endl;
cout<<"                       查詢城市間最短里程路徑 "<<endl;
cout<<"                       退出程式 "<<endl;


int j = arrow(13,14);


system("cls");


switch (j)
{
case 13:search_path();break;
case 14:quit();break;
}

}


void quit()
{
system("cls");
exit(0);
}








void search_path()
{
title();


cout<<endl;


int start=11, dest=11;
char start1,dest1;

cout<<"本程式儲存有以下十個城市的線路資訊,請輸入其中任意兩個城市ID獲取之間最短路徑: "<<endl;
cout<<endl;
cout<<"         0.北京     1.天津     2.上海     3.武漢     4.廣州"<<endl;
cout<<"         5.南京     6.重慶     7.成都     8.西安     9.鄭州"<<endl;
cout<<endl;
cout<<"提示:若輸入的ID不在[0,9]範圍內或兩次輸入同樣的ID,輸入介面會重新整理等待重新輸入";


while(start<0||start>=10||dest<0||dest>=10||start==dest)
{
gotoxy(17,13);
cout<<"輸入出發城市ID:         ";
gotoxy(17,14);
cout<<"輸入目的城市ID:         ";
gotoxy(33,13);
cin>>start1;

start=int(start1);
   if(start1=='0')
start=10;
else if(start1=='1')
start=1;
else if(start1=='2')
start=2;
else if(start1=='3')
start=3;
else if(start1=='4')
start=4;
else if(start1=='5')
start=5;
else if(start1=='6')
start=6;
else if(start1=='7')
start=7;
else if(start1=='8')
start=8;
else if(start1=='9')
start=9;
gotoxy(33,14);
cin>>dest1;
dest=int(dest1);
    if(dest1=='0')
dest=10;
else if(dest1=='1')
dest=1;
else if(dest1=='2')
dest=2;
else if(dest1=='3')
dest=3;
else if(dest1=='4')
dest=4;
else if(dest1=='5')
dest=5;
else if(dest1=='6')
dest=6;
else if(dest1=='7')
dest=7;
else if(dest1=='8')
dest=8;
else if(dest1=='9')
dest=9;
}


int shortest = 100000;  //儲存最短距離
int path[10];  //儲存最短路徑
int i;
for(i=0;i<10;i++)
path[i] = 11;
find_the_path(start,dest,shortest,path);
cout<<endl;
cout<<cityDB[start].getCityName()<<" 到 "<<cityDB[dest].getCityName()<<" 的最短里程為 "<<shortest<<" 公里, 路徑為:"<<endl;
cout<<cityDB[start].getCityName()<<"-->";
print_path(path,dest,start);
cout<<cityDB[dest].getCityName()<<endl;
cout<<endl;
cout<<"按任意鍵返回主介面"<<endl;
system("pause");
for(i=0;i<10;i++)
cityGraph.setMark(i,0);   //重置訪問標記
frontPage();
}






void print_path(int * path, int dest, int start)
{
int k;
k = path[dest];
if(k == start)
return;
print_path(path,k,start);
cout<<cityDB[k].getCityName()<<"-->";
}






void find_the_path(const int start, const int dest, int& shortest, int * path)  //Dijkstra演算法應用
{
int D[10];   //儲存設定的源城市到任意城市的估計最小長度
int i,v,w;


for(i=0; i<10; i++)
{
D[i] = 100000;
path[i] = -1;
}
D[start] = 0;
//path[start] = 0;
for(i=0; i<cityGraph.verNum(); i++)
{
v=minVertex(D);
if(D[v] == 100000)
return;
cityGraph.setMark(v,1);
for(w=cityGraph.firstNghbr(v); w<cityGraph.verNum(); w=cityGraph.nextNghbr(v,w))
{
if(D[w]>(D[v]+cityGraph.weight(v,w)))
{
D[w] = D[v] + cityGraph.weight(v,w);
path[w] = v;
}
}
}


shortest = D[dest];
}


int minVertex(int* D)
{
int i,v;
for(i=0; i<cityGraph.verNum(); i++)
{
if(cityGraph.getMark(i) == 0)
{
v = i;
break;
}
}
for(i++; i<cityGraph.verNum(); i++)
if((cityGraph.getMark(i) == 0) && (D[i] < D[v]))
v = i;
return v;
}


int main()
{
system("mode con cols=70 lines=23");
char c[20]="color 7f";     
    system(c);       //對cmd視窗顯示的調整變化(美化)
       
cityDB[0] = City(0,"北京");
cityDB[1] = City(1,"天津");
cityDB[2] = City(2,"上海");
cityDB[3] = City(3,"武漢");
cityDB[4] = City(4,"廣州");
cityDB[5] = City(5,"南京");
cityDB[6] = City(6,"重慶");
cityDB[7] = City(7,"成都");
cityDB[8] = City(8,"西安");
cityDB[9] = City(9,"鄭州");

cityGraph.setEdge(1,0,108);
cityGraph.setEdge(2,1,956);
cityGraph.setEdge(0,9,624);
cityGraph.setEdge(2,9,807);
cityGraph.setEdge(9,8,427);
cityGraph.setEdge(9,3,457);
cityGraph.setEdge(8,3,661);
cityGraph.setEdge(7,8,617);
cityGraph.setEdge(6,7,267);
cityGraph.setEdge(3,6,752);
cityGraph.setEdge(3,2,686);
cityGraph.setEdge(3,5,457);
cityGraph.setEdge(5,6,1199);
cityGraph.setEdge(2,4,1219);
cityGraph.setEdge(4,3,846);
cityGraph.setEdge(4,5,1142);


frontPage();
return 0;
}

能否用同一個呼叫形式,既能呼叫派生類又能呼叫基類的同名函式。

在程式中不是通過不同的物件名去呼叫不同派生層次中的同名函式,而是通過指標呼叫它們。例如,用同一個語句“pt->display( );”可以呼叫不同派生層次中的display函式,只需在呼叫前給指標變數 pt 賦以不同的值(使之指向不同的類物件)即可。

C++中虛擬函式的作用是允許在派生類中重新定義與基類同名的函式,並且可以通過基類指標或引用來訪問基類和派生類中的同名函式。

純虛擬函式在基類中是沒有定義的,必須在子類中加以實現,很像java中的介面函式!

百度解釋:

在某基類中宣告為 virtual 並在一個或多個派生類中被重新定義的成員函式,用法格式為:virtual 函式返回型別 函式名(引數表) {函式體};實現多型性,通過指向派生類的基類指標或引用,訪問派生類中同名覆蓋成員函式
中文名
虛擬函式
外文名
virtual function
定    義
被virtual關鍵字修飾的成員函式
作    用
實現多型性
形象解釋
求同存異
關    鍵
用指向基類指標或引用操作物件
聲    明
 virtual