一個最短路徑問題的解決思路與Dijkstra算法的應用和優化

仍是繼續解決賽碼網上的百度2017/2016秋招題目,選擇了一些4星題目中比較有意思或者對知識有補充的題目寫了解題分析,其餘的題目我準備所有寫完後,來個合集,作一個比較簡單的解題報告。雖然是被標註爲4星(百度的題目中沒有5星),可是題目相對難度仍是通常,或許由於這個題目是針對應屆生,應該是大多數並無從事算法競賽的同窗。可是不管難度如何,最好能夠本身上手練習並提交測試。html


第一天咱們從一個簡單的圖論問題入手,其實是討論了一個最短路徑相關問題,構圖分析之後,使用了SPFA+heap優化的方式來完成目標
https://segmentfault.com/a/11...
這裏咱們仍然討論一個最短路徑的問題,由於不含有負權,咱們使用dijkstra+heap優化的算法來最終解決問題。固然,也徹底可使用 SPFA,由於咱們並無修改算法自己。python

趕火車

<題目來源:百度2017秋招,http://exercise.acmcoder.com/... >算法

問題描述

小A到達了一個陌生的城市,通過幾天的功課,他已經知道了整個城市的公共交通狀況。全部的公交設計都是徹底對稱的,公共汽車都是對向開行,且線路相同,對向開行的道路是同一條道路。小A所住的賓館附近有a個公交車站,你知道火車站附近有b個公交車站。小A想知道從賓館選定附近某個公交車站出發到火車站附近某一公交車站的最近的路程是多少。你能幫他嗎segmentfault

題目給定的目標仍是比較容易達到的,咱們只須要選擇公交車站或者火車站頂點集合裏面的每個頂點求一次單源點最短路徑,最後掃描另一個集合裏面的頂點,獲取最小的單源點最短路徑便可。app

觀察題目數據規模
*輸入
輸入數據有多組,第一行爲一個正整數T(T<=20),表示測試數據組數,接下來包含T組測試數據。
每組測試數據第一行包含四個整數n(1<=n<=1000),m(0<=m< =n*(n-1)/2),a(1<=a<=n), b(1<=b<=n),分別表示城市中公交車站數、道路數目、賓館附近公交車站數目和火車站附近的公交車站數目。
接下來一行包含a個整數si,si(1<=si<=n)表示賓館附近的a個公交車站。
接下來一行包含b個整數ei,ei(1<=ei<=n)表示火車站附近的b個公交車站。
接下來m行每行包含三個整數u, v, w, (1<= u<= n, 1<= v<= n, u不等於v, 0<= w<10000)表示結點u和結點v之間存在一條長度爲w的路徑。(保證沒有重邊)*學習

根據a(1<=a<=n), b(1<=b<=n)這個條件,最壞的狀況下咱們可能須要計算n/2 + 1單源點最短路徑(咱們選擇較小的一個集合裏的點做爲源點來計算最短路徑,a,b兩個集合不相交,也就是同一個頂點不會既是火車站公交車站,又是賓館附近的公交車站,一旦相交那麼結果爲0,就不須要再計算),那麼最壞狀況下咱們計算的時間複雜度O((|n|+|m|)*logn),而且dijkstra算法還須要使用heap優化。對於題目的數據規模而言,不能接受。測試

咱們在稍微深刻的分析下,在計算某一個源點的最短路徑的時候所發生的狀況,咱們假設U={a, b, c}是酒店附近的公交車站,咱們首先計算a的單源點最短路徑,一種可能的狀況是,a出發到x的最短路徑多是
a->b->...->c->...->x
咱們發現一個有趣的現象,若是b, c在a->x的最短路徑上,顯然咱們從c出發能夠得到更短的路徑,咱們也並不關心從集合U中的哪一個點出發,顯然集合之間的點之間的邊對咱們最終的結果不產生影響,由於咱們始終會選擇一個更近的點來做爲源點。優化

所以,咱們能夠考慮將U中的全部的點看作一個點,去掉集合內部頂點之間的邊,保留集合U中任何頂點與不在U中的頂點的邊,這些全部邊都轉爲從一個點發出。這樣的方法咱們稱之爲縮點。spa

下面對縮點的先後的圖進行了描述,其中紅色表示須要去掉的邊,綠色是保留下來的邊。
圖片描述
圖片描述
圖片描述設計

顯然縮點之後咱們須要進行一次單源點最短路徑算法便可而後比較另一個集合中的每一個點的最短路徑值便可。進一步,對於另一個集合中的點,咱們也能夠進行一樣的處理方法,這樣能夠減小一部分運算量,若是我把集合U中的點縮爲1個點標號0,將另一個集合T中的點縮爲1個點,而且標號1,那麼實際上咱們是求解以0號點爲源點,到達1號點的最短路徑,考慮到這個是一個無向圖而且不含有負權,咱們採用了heap優化的dijkstra算法來實現咱們最終的目標。

至此,在假設你已經掌握了dijkstra算法(不要求使用heap優化),那麼最後一個須要解決的問題就是如何將集合中的點進行壓縮。

1.對圖中的點進行映射從新編號,例如集合U中本來爲U={1,2,3,4},咱們令map_v[1] = map_v[2] = map_v[3] = 1,也就是把原圖上標號爲1,2,3的頂點在新圖上同一標爲1,把另一個集合T中本來的點標記爲2,其他不在兩個集合中的點,以此標爲3,4,5...便可。

2.在讀入圖的時候,數據以u, v, w的形式給出,表示從頂點u有一條到頂點v的邊,其權值爲w,那麼咱們須要比較映射後的結果,若是map_v[u]=map_v[v],說明他們壓縮後在同一個點(也就是這兩個點在同一個集合中),咱們不須要進行任何處理,反之,咱們就像圖中添加一條由map_v[u]到map_v[v]的權重爲w的邊,同時添加map_v[v]到map_v[u]的權重爲w的邊(別忘記這是一張無向圖)

3.按映射後的圖執行dijstra算法便可,最後得出結果便可。

至此,問題已經獲得解決,下面會再簡單介紹dijkstra算法正確證實和heap優化方法。可是這裏並不打算詳細的介紹dijkstra算法的原理和實現,若是須要學習推薦參考下面兩篇文章:
http://wiki.mbalib.com/wiki/D...算法
http://www.cnblogs.com/shenbe...

大多數狀況下咱們並不須要關心如何去證實這些算法的正確性,可是dijkstra算法的證實比較簡單,且有助於咱們進一步理解算法。下面我就數學概括法來進行一個簡單的證實。
咱們用集合U表示被dijkstra已經計算出最短路徑dist[]的點:
a.首先考慮源點加入時的狀況,源點加入時U中只有源點s,且dist[s] = 0,顯然源點到源點的距離爲0
b.當加入第1個點時,根據算法,選擇了與s距離最近的一個點u來加入集合S,若是s->u的路徑不是最短路徑,那麼必然還存在點p使得s->p->u距離更近,且edge(s,p) < edge(s, u),所以咱們選擇的時候就應該選擇edge(s, p)而不是edge(s ,u),這與咱們選擇距離最近的點加入矛盾,故dist[u]是s到u的最短路徑
c.當加入第k個點時,集合U中包含前k - 1個已經求出最短路徑的點,考慮第k的加入時的狀況,若是選擇的k不是到源點的最短距離,那麼必然在存在一個不在集合S中的點p使得
edge(i, p) + edge(p, k) < edge(i, k),那麼edge(i, p) < edge(i, k),那麼咱們顯然會選擇edge(i, p),這個與咱們選擇距離最小的邊矛盾
綜上,到算法運行完畢時,U集合中的全部全部點的dist[]都是源點到該點的最短路徑

最後,咱們使用heap來優化

咱們觀察到,在dijkstra算法運行的時候,咱們須要去尋找一個當前最小的dist[i],而且不在集合U的這樣一個i點,並把i加入到集合中去。顯然咱們在不斷更新dist的時候,能夠維護一個小根堆,將更新獲得的dist[i]和i的信息放入堆中,在須要獲取最小dist的時候,咱們從堆頂彈出頂點標號,只要這個頂點不在集合S中,咱們就把這個頂點加入到集合中,而且去擴展計算與之相鄰頂點的dist

提醒一個python的讀入問題
python的raw_input()方式讀入數據再行split(' ')切分會很是的慢,推薦使用map的方式,具體能夠參見下面的代碼部分。

import sys
import heapq

const_idx_vertex = 0
const_idx_wight = 1


def get_map_val(map_v, v, v_seq):
    r = map_v.get(v)
    if r is None:
        v_seq[0] += 1
        map_v[v] = v_seq[0]
        r = v_seq[0]
    return r


def dijkstra_with_heap(dist, src, n, vertexes):
    dist[src] = 0
    heap = []
    S = set()
    heapq.heappush(heap, (0, src))

    for i in range(1, n):
        while len(heap) > 0:
            u = heapq.heappop(heap)[1]
            if u not in S:
                S.add(u)
                break

        for vertex in vertexes[u]:
            v = vertex[const_idx_vertex]
            w = vertex[const_idx_wight]

            if dist[v] == -1 or dist[v] > dist[u] + w:
                dist[v] = dist[u] + w
                heapq.heappush(heap, (dist[v], v))


def main():
    t_cases = int(raw_input())
    for t_case in range(1, t_cases + 1):
        n, m, a, b = map(int, sys.stdin.readline().strip().split())

        map_v = {}
        v_seq = [1]
        line = map(int, sys.stdin.readline().strip().split())
        for l in line:
            map_v[l] = v_seq[0]

        v_seq[0] += 1
        line = map(int, sys.stdin.readline().strip().split())
        for l in line:
            map_v[l] = v_seq[0]

        vertexes = [[]for i in range(n + 1)]
        for i in range(m):

            u, v, w = map(int, sys.stdin.readline().strip().split())

            u = get_map_val(map_v, u, v_seq)
            v = get_map_val(map_v, v, v_seq)
            if u != v:
                vertexes[u].append((v, w))
                vertexes[v].append((u, w))

        new_n = v_seq[0]
        dist = [-1 for i in range(n + 1)]
        dijkstra_with_heap(dist, 1, new_n, vertexes)

        print 'Case #{}: '.format(t_case)
        if dist[2] != -1:
            print dist[2]
        else:
            print 'No answer'


if __name__ == '__main__':
    main()
相關文章
相關標籤/搜索