最短路径问题(Dijkstra算法)

题外话

关于最短路径问题:是一类基本问题。说起最短路径问题,可以牵扯出很多知名问题,不得不想起去年这个时候,人工智能课上曾经讲过一个TSP问题(旅行商问题),也很有意思。
TSP问题如下:
你是一个旅行商,给你一个地图,上面有很多座城市,已知它们之间的距离,旅行商要求从某一城市出发,走遍所有城市后回到该城市,称为一次游历。要求该旅行商进行一次游历所需行走的最短距离。
该问题其实就是一个状态空间树搜索利用启发式函数进行剪枝的问题。去年老师曾要求我们用A*算法设计简单的启发式函数。我最终想了一个基于最小生成树的简易启发式函数,该启发式函数满足一致性,由于该题是树搜索,启发函数满足一致性的树搜索具有最优解。但苦于编程能力实在太差,没有办法进行代码实现。
我设计的启发式函数是这样的:
假设一个图中包含结点{A,B,C,D,E,F,G}。假设某状态n下旅行商从C出发,已经走过的路径是C->A->B,那么我这个启发式函数就是:先找到当前状态已走过的结点集为S:{C,A,B},再找到未走过的结点集T:{D,E,F,G}。得到未走过结点的最小生成树ATT(T),再找到当前已走路径的两个边缘结点,C->A->B为已走路径,两个端点就是C和B,然后分别寻找C到T集中的结点的最短距离和B到T集中的结点的最短距离,记为minDis1和minDis2。该状态下的启发式函数h(n) = ATT(T) + minDis1 + minDis2。进行状态转换时,通过动作a,选出C和B到T集中的最短距离,也就是选择两端中包含到T集中任一结点最短距离的那一端进行拓展。拓展后状态转移到为n’。这样应该是可以证明 h(n) ≤ c(n,a,n`) + h(n’)的,也就满足了一致性。
在做拓展的时候,只拓展满足h(n)≤c(n,a,n’) + h(n’)的结点,也就完成了剪枝操作。
其实,如果不考虑时间复杂度的问题,那么这个题用迪杰斯特拉就可以解决了,求出从起点到剩余各点的距离,再加上这最后一个城市到起点的距离,选出最大值就行了。接下来就是关于Dijkstra算法。

正文

Dijkstra算法是用于求最短路径问题的一种算法,另一种是Floyd算法,又叫for-for-for算法。
将顶点集合V分成两个集合S和T。
其中
(1)S:已确定的顶点集合,初始只含源点s。
(2)T = V-S:尚未确定的顶点集合。
Dijkstra反复地从集合T中选出当前到源点s最近的顶点u,将u加入到集合S,然后对所有从u发出的边进行松弛操作。
其实Dijkstra是一种具备无后效性的贪心策略,故其能保证最优效果。
设结点数为V,边数为E。在不进行优化的情况下,其算法时间复杂度的计算如下:
1.每次遍历T中的所有结点,时间复杂度为O(V)。
2.对每次选出的顶点进行V次松弛操作,时间复杂度为O(V).
3.在松弛操作中会获得顶点距离,时间复杂度为O(1),最多会更新E次最短距离。
所以时间复杂度为O(V^2 + E) = O(V^2)
值得注意的是,我们处理的绝大部分图是稀疏图,也就是边少节点多。可利用优先队列对算法进行优化。算法复杂度是O(E * logV)。大部分稀疏图中E和V是一个数量级,也就是时间复杂度近似于O(V * logV).
由于在求解最短路径的时候,经常要用到与某一结点相关的边的权值,所以采用邻接表法表示这个图。
关于邻接表法如何表示,一个思路是:
建立Edge结构体,包含终点to和长度length。用于表示邻接表中与某个结点相关的一条边。
邻接表可定义为一个向量类型的数组graph,这个向量用于表示图中和某结点相关的边。即:

struct Edge{    //邻接表中与某个结点相关的一条边
    int to;
    int length;
    Edge(int t,int l):to(t),length(l){};
};
vector<Edge> graph[MAXN];   //图的邻接表表示法

由于每次入优先队列的应该是松弛后的结点,因此需要定义用于表示结点的结构体Point如下:

struct Point{
    int number;    //结点编号
    int distance;     //源点到该点的距离
    Point(int n,int d):number(n),distance(d){};
    bool operator< (const Point& p) const{
        return distance > p.distance;     //距离小的优先
    }
};
vector<Edge> graph[MAXN];    //邻接表
int dis[MAXN];        //各结点到s结点的最短距离
bool visit[MAXN];      //用于记录各节点是否已经求出了最短距离


void Dijkstra(int s){   //求s结点到各结点的最短距离
    priority_queue<Point> myPriorityQueue;   //起始结点s入优先队列
    dis[s] = 0;
    myPriorityQueue.push(Point(s,dis[s]));

    while(!myPriorityQueue.empty()){
        int n = myPriorityQueue.top().number;   //队头元素编号
        myPriorityQueue.pop();
        if(visit[n] == true){ //如果该结点已经求出了最短距离,不必再判断这个点。
            continue;
        }else{
            visit[n] = true;
            for(int i=0;i<graph[n].size();i++){
                int v = graph[n][i].to;
                if(visit[v] == false){ //只需更新到还没求出最短距离的结点的dis
                    if(dis[v] > dis[n] + graph[n][i].length){
                        dis[v] = dis[n] + graph[n][i].length;   //更新dis数组
                        myPriorityQueue.push(Point(v,dis[v]));
                    }
                }
            }
        }
    }
}
int main() {
    int n,m;   //n个结点,m条边
    int node1,node2,weight;
    int from,to;
    while(scanf("%d%d",&n,&m)!=EOF){
        memset(graph,0,sizeof(graph));   //初始化邻接矩阵
        fill(dis,dis+MAXN,INF);              //初始化dis数组
        fill(visit,visit+MAXN,false);      //初始化visit数组
        for(int i=0;i<m;i++){
            scanf("%d%d%d",&node1,&node2,&weight);
            graph[node1].push_back(Edge(node2,weight));
            graph[node2].push_back(Edge(node1,weight));
        }
        scanf("%d%d",&from,&to);
        Dijkstra(from);
        if(dis[to] == INF){
            cout<<-1<<endl;   //不连通
        }else{
            cout<<dis[to]<<endl;  //连通,输出s结点到to结点的最短距离
        }
    }
    return 0;
}

关于Dijkstra相关的需求及题目,见下次。

你可能感兴趣的:(数据结构,c++)