当前位置:文档之家› 单源最短路径

单源最短路径

单源最短路径问题I 用贪心算法求解贪心算法是一种经典的算法,通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。

一般具有2个重要的性质:贪心选择性质和最优子结构性质。

一、问题描述与分析单源最短路径问题是一个经典问题,给定带权有向图G =(V,E),其中每条边的权是非负实数。

另外,还给定V中的一个顶点,称为源。

现在要计算从源到所有其他各顶点的最短路长度。

这里路的长度是指路上各边权之和。

这个问题通常称为单源最短路径问题。

分析过程:运用Dijkstra算法来解决单源最短路径问题。

具备贪心选择性质具有最优子结构性质计算复杂性二、算法设计(或算法步骤)用贪心算法解单源最短路径问题:1.算法思想:设置顶点集合S并不断地作贪心选择来扩充这个集合。

一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。

初始时,S中仅含有源。

设u是G的某一个顶点,把从源到u且中间只经过S中顶点的路称为从源到u的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。

Dijkstra算法每次从V-S中取出具有最短特殊路长度的顶点u,将u添加到S中,同时对数组dist作必要的修改。

一旦S包含了所有V中顶点,dist就记录了从源到所有其他顶点之间的最短路径长度。

2.算法步骤:(1) 用带权的邻接矩阵c来表示带权有向图, c[i][j]表示弧<vi,vj>上的权值. 若<vi, vj>∉V,则置c[i][j]为∞。

设S为已知最短路径的终点的集合,它的初始状态为空集。

从源点v到图上其余各点vi的当前最短路径长度的初值为:dist[i]=c[v][i] vi∈V。

(2) 选择vj, 使得dist[j]=Min{dist[i] | vi∈V-S},vj就是长度最短的最短路径的终点。

令S=SU{j}。

的当前最短路径长度:(3) 修改从v到集合V-S上任一顶点vk如果dist[j]+c[j][k]< dist[k] 则修改dist[K]= dist[j]+c[j][k](4) 重复操作(2),(3)共n-1次.三、算法实现#include <iostream>#include <stdlib.h>using namespace std;#define MAX 1000000 //充当"无穷大"#define LEN sizeof(struct V_sub_S)#define N 5#define NULL 0int s; //输入的源点int D[N]; //记录最短路径int S[N]; //最短距离已确定的顶点集const int G[N][N] = { {0, 10, MAX, 30, 100},{MAX, 0, 50, MAX, MAX},{MAX, MAX, 0, MAX, 10},{MAX, MAX, 20, 0, 60},{MAX, MAX, MAX, MAX, 0} };typedef struct V_sub_S //V-S链表{int num;struct V_sub_S *next;};struct V_sub_S *create(){struct V_sub_S *head, *p1, *p2;int n = 0;head = NULL;p1 = (V_sub_S *)malloc(LEN);p1->num = s;head = p1;for(int i = 0; i < N+1; i ++){if(i != s){++ n;if(n == 1)head = p1;elsep2->next = p1;p2 = p1;p1 = (V_sub_S *)malloc(LEN);p1->num = i;p1->next = NULL;}}free(p1);return head;}struct V_sub_S *DelMin(V_sub_S *head, int i) //删除链表中值为i 的结点{V_sub_S *p1, *p2;p1 = head;while(i != p1->num && p1->next !=NULL){p2 = p1;p1 = p1->next;}p2->next = p1->next;return head;}void Dijkstra(V_sub_S *head, int s){struct V_sub_S *p;int min;S[0] = s;for(int i = 0; i < N; i ++){D[i] = G[s][i];}for(i = 1; i < N; i ++){p = head->next;min = p->num;while(p->next != NULL){if(D[p->num] > D[(p->next)->num])min = (p->next)->num;p = p->next;}S[i] = min;head = DelMin(head, min);p = head->next;while(p != NULL){if(D[p->num] > D[min] + G[min][p->num]){D[p->num] = D[min] + G[min][p->num];}p = p->next;}}}void Print(struct V_sub_S *head){struct V_sub_S *p;p = head->next;while(p != NULL){if(D[p->num] != MAX){cout << "D[" << p->num << "]: " << D[p->num] << endl;p = p->next;}else{cout << "D[" << p->num << "]: " << "∞" << endl;p = p->next;}}}int main(){struct V_sub_S *head;cout << "输入源点s (0到4之间): ";cin >> s;head = create();Dijkstra(head, s);head = create();Print(head);system("pause");return 0;}运行结果:四、算法分析(与改进)对于具有n个顶点和e条边的带权有向图,如果用带权邻接矩阵表示这个图,那么Dijkstra算法的主循环体需要O(n)时间。

这个循环需要执行n-1次,所以完成循环需要O(n2)时间。

算法的其余部分所需要时间不超过O(n2)。

II 分支限界法求解分支限界法是一种经典的算法,求解目标则是找出满足约束条件的一个解,是在满足约束条件的解中找出在某种意义下的最优解。

一、问题描述与分析分析过程:在分支限界法中,每一个活结点只有一次机会成为扩展结点。

活结点一旦成为扩展结点,就一次性产生其所有儿子结点。

在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。

此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。

这个过程一直持续到找到所需的解或活结点表为空时为止。

二、算法设计(或算法步骤)算法思想:分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。

解单源最短路径问题的优先队列式分支限界法用一极小堆来存储活结点表。

其优先级是结点所对应的当前路长。

三、算法实现#include <iostream>using namespace std;#define MAX 9999 //定义无穷大/***Graph类,用以存放有关图的所有信息*/class Graph{public://---------------------------//param int 初始节点编号//--------------------------void ShorestPaths(int);void ShowDist();Graph();private:int n; //图的节点个数int *prev; //存放顶点的前驱节点int **c; //存放图的邻接矩阵int *dist; //存放源点到各个顶点的距离};/***节点*/class MinHeapNode{friend Graph;public:int getI() {return i;}void setI(int ii){i = ii;}int getLength(){return length;}void setLength(int len){length = len;}private:int i; //顶点编号int length; //当前路长};/***最小堆*/class MinHeap{friend Graph;public:MinHeap();MinHeap(int);void DeleteMin(MinHeapNode &);void Insert(MinHeapNode);bool OutOfBounds();private:int length;MinHeapNode *node;};Graph::Graph(){int wi = 0;int yi = 0;cout<<"请输入图的节点个数:";cin>>n;cout<<"请输入图的邻接矩阵:(无穷大请以9999代替)" << endl;c = new int*[n+1];dist = new int[n+1];prev = new int[n+1];//------------------------------//初始化邻接矩阵//------------------------------for (wi = 0; wi <= n; wi++){c[wi] = new int[n+1];if (wi == 0){for (yi = 0; yi <= n; yi++){c[wi][yi] = 0;}}else{for (yi = 0; yi <= n; yi++){if (yi == 0){c[wi][yi] = 0;}else{cin >> c[wi][yi];}}}}//----------------------------------//初始化数组//----------------------------------for (wi = 0; wi <= n; wi++){dist[wi] = MAX;prev[wi] = 0;}}void Graph::ShowDist(){cout << "从源点到该节点的最短路径:" << endl;int i = 0;int temp = 0;for (i = 1; i <= n; i++){cout << "dist[" << i << "] = " << dist[i] << endl;}cout << "从源点到终点的最短路径长度为:" << dist[n] << endl;cout << "其路径为:";temp = n;while(temp != 0){if (prev[temp] == 0){cout << (temp);}else{cout << (temp) << " <- ";}temp = prev[temp];}cout << endl;}void Graph::ShorestPaths(int v){MinHeap H(n); //最小堆MinHeapNode E; //扩展节点E.i = v;E.length = 0;dist[v] = 0;//搜索问题的解空间树while (true){int j = 0;for (j = 1; j <= n; j++){cout<<"c["<<E.i<<"]["<<j<<"]="<<c[E.i][j]<<endl;if ((c[E.i][j] != MAX) && (c[E.i][j] != 0)){//节点控制关系if (E.length + c[E.i][j] < dist[j]){dist[j] = E.length + c[E.i][j];prev[j] = E.i;//加入活结点优先队列//若节点为叶子节点,则不加入活结点队列if (j != n){MinHeapNode N;N.i = j;N.length = dist[j];H.Insert(N);}}else{H.DeleteMin(E);}}}if (H.OutOfBounds()){break;}cout<<"上一个扩展节点"<<E.i<<" "<<E.length<<endl;H.DeleteMin(E);cout<<"下一个扩展节点"<<E.i<<" "<<E.length<<endl;}}MinHeap::MinHeap(){length = 10;node = new MinHeapNode[length+1];for (int i = 0; i <= length; i++){node[i].setI(0);node[i].setLength(0);}}MinHeap::MinHeap(int n){length = n;node = new MinHeapNode[length+1];for (int i = 0; i <= length; i++){node[i].setI(0);node[i].setLength(0);}/***取下一个扩展结点,并删除此节点**算法实现其实是用下一个节点的信息替代现有节点的数据**首先在现有的节点中,找出length最短的节点**然后将此节点的数据替换原有的数据*/void MinHeap::DeleteMin(MinHeapNode &E){int i = 0;int j = 0;j = E.getI(); //用来删除原来的扩展节点node[j].setI(0); //置零node[j].setLength(0); //置零int temp = MAX;//-------------------------------------//选择可扩展节点中length域最小的可扩展节点//将所选择的扩展节点的数值替换原有的扩展节//点的值,最后在可扩展节点队列中删除原扩展//节点,删除方式为:所有域置零//-------------------------------------for (i = 1; i <= length; i++){if ((node[i].getLength() < temp) && (node[i].getLength() != 0)){E.setI(i);E.setLength(node[i].getLength());temp = node[i].getLength(); //temp中始终为最小值}}}/***加入最小堆**此处添加按节点编号添加,即对应的节点编号添加时**对应队列中相应的编号,即节点5则添加到队列中5号**位置*/void MinHeap::Insert(MinHeapNode N){node[N.getI()].setI(N.getI());node[N.getI()].setLength(N.getLength());}/***判断最小堆是否为空bool MinHeap::OutOfBounds(){int i = 0;bool flag = true;for (i = 1; i <= length; i++){if (node[i].getI() != 0){flag = false;}}return flag;}int main(){Graph graph;graph.ShorestPaths(1);graph.ShowDist();return 0;}/*输入节点数为5输入邻接矩阵为:{ 0 ,2 ,9999,1 ,49999,0 ,5 ,2 ,99999999,9999,2 ,5 ,99999999,9999,9999,0 ,29999,9999,9999,9999,0} */运行结果:四、算法分析(与改进)分支限界法的时间复杂度是O(n!); 空间复杂度:O(n*n);。

相关主题