1. 程式人生 > >貪心演算法之用優先佇列解決最短路徑問題(Dijkstra演算法)

貪心演算法之用優先佇列解決最短路徑問題(Dijkstra演算法)

#include <iostream>
#include <cstdio>
#include <stack>
#include <cstring>
#include <queue>
#include <cstdlib>

using namespace std;

//城市的節點數目的最大值
const int MAX_CITY_NUM = 100;
//節點權值的最大值
const int MAX_POLICY = 1e7;

/*
一定要記得如果初始化矩陣的話,肯定需要一個變數儲存長和寬的最大值,
如果看到權重的話,肯定是需要有個變數儲存最大值的權重
*/
struct Node
{
	//value是節點值,然後min_dist是源點到這個節點的最短路徑
	int value, min_dist;
	//注意這裡前面不要加public 
	Node(int value, int min_dist)
	{
		this->value = value;
		this->min_dist = min_dist;
	}
	//過載operator < 
	bool operator < (const Node &node) const
	{
		return this->value > node.value;	
	}
};



class Dijkstra 
{
public:
    //初始化工作
	void init();
	//dijkstra演算法
	void dijkstra();
	//顯示源點到其它頂點的經過的頂點
	void showProcess();
	//顯示源點到各個頂點的最小權重
	void showMinPolicy();
private:
    //城市的節點數目和線段的個數和起始位置
	int n, m, start;
	//初始化權重矩陣
	int map[MAX_CITY_NUM][MAX_CITY_NUM];
	//源點到各個頂點的最短具體陣列
	int dist[MAX_CITY_NUM];
	//下標表示當前節點值,然後值儲存為上個節點值
	int p[MAX_CITY_NUM];
	//是否加入集合S,如果在集合S裡面的話,值為true,否則在集合S-V裡面,值為false;
	bool flag[MAX_CITY_NUM];
};

//Dijkstra演算法
void Dijkstra::dijkstra()
{
	priority_queue<Node> queue;
	Node node(start, 0);
	queue.push(node);
	//還是要記得初始化p[i], dist[i], flag[i]
	memset(flag, false, sizeof(flag));
	for (int i = 1; i <= n; ++i)
	{
		dist[i] = MAX_POLICY;
		p[i] = -1;
	}	
	dist[start] = 0;
//	flag[start] = true;
	while (!queue.empty())
	{
		//取出佇列最小元素
		Node node = queue.top();
		queue.pop();
		int value = node.value;
		//這裡防止重複
		if (flag[value])
			continue;
		//為了防止重複必須設定為true,然後上面判斷如果是true,就繼續,
		//相當於這裡為打開了這把鎖作標記,然後下次遇到這把鎖的時候看是否做了標記
		//如果做了標記,就continue;
		flag[value] = true;
		for (int i = 1; i <= n; ++i)
		{
            if (!flag[i] && map[value][i] < MAX_POLICY)			
				if (dist[i] > dist[value] + map[value][i])
				{
					dist[i] = dist[value] + map[value][i];
				    queue.push(Node(i, dist[i]));
					p[i] = value;
				}
		}
	}
}

//打印出每個頂點的路徑,這裡值儲存了前一個節點的key
//所以我們需要用到棧的特點,先進後出
void Dijkstra::showProcess()
{
	int value;
	stack<int> stack;
	for (int i = 1; i <= n; ++i)
	{
		value = p[i];
		std::cout << "源點"<< start << "到"<< i << "的路徑是"; 
		while (value != -1) 
		{
			stack.push(value);
			value = p[value];
		}
		while (!stack.empty())
		{
			//pop函式是出來棧,沒有返回值,先取出棧頂值,然後出棧
			int node = stack.top();
			stack.pop();
			std::cout << node << "-";
		}
		std::cout  << i << "最短距離為" << dist[i] << std::endl;
	}
}

void Dijkstra::init()
{
	//定點u到定點v的權重是w, 然後輸入的起始地點是start;
	int u, v, w;
	std::cout << "請輸入城市的節點個數" << std::endl;
	std::cin >> n;
	if (n <= 0)
	{
		std::cout << "輸入的城市節點個數因該大於0" << std::endl;
		return;
	}
	std::cout << "請輸入城市之間線路的個數" << std::endl;
	std::cin >> m;
	if (m <= 0) 
	{
		std::cout << "輸入的城市之前的線路個數不能小於0" << std::endl;
		return;
	}
	//鄰接舉證的初始化,預設都為最大值,注意這裡下標都是從1開始
	for (int i = 1; i <= n; ++i) 
	{
		for (int j = 1; j <= n; ++j)
		{
			map[i][j] = MAX_POLICY;	
		}
	}
    std::cout << "請輸入城市頂點到城市頂點之前的權重" << std::endl;
	//這裡也可以使用while(--m),因為不涉及到用i
	for (int i = 0; i < m; ++i) 
	{
		std::cin >> u >> v >> w;
		if (u > n || v > n) 
			std::cout << "您輸入的定點有誤" << std::endl;
		//如果2次輸入一樣頂點,那麼取最小的
		map[u][v] = min(map[u][v], w);
	}
	std::cout << "請輸入小明的位置" << std::endl;
	//請輸入起始的頂點
	std::cin >> start;
	if (start < 0 || start > n)
	{
		std::cout << "輸入的起始城市定點有誤" << std::endl;
		return;
	}
}

void Dijkstra::showMinPolicy()
{
	std::cout << "小明所在的位置 " << start << std::endl;
    for (int i = 1; i <= n; ++i)
	{
		std::cout << "小明(" << start << ")要去的位置是" << i;
		if (dist[i] == MAX_POLICY)
		    std::cout << "無路可到" << std::endl;
	    else
	        std::cout << "最短距離為" << dist[i] << std::endl;
	}
}

int main()
{
	Dijkstra dij;
	dij.init();
	dij.dijkstra();
	dij.showMinPolicy();
	dij.showProcess();
	return 0;	
}