數(shù)據(jù)結(jié)構(gòu)——無權(quán)圖的路徑問題(C++和java實(shí)現(xiàn))

好像又是接近半個(gè)月沒有更新,這半個(gè)月忙著結(jié)婚的各項(xiàng)事情搜贤,本來預(yù)計(jì)的學(xué)習(xí)任務(wù)也拖拖拉拉谆沃,進(jìn)度緩慢。吐槽一句仪芒,拍婚紗照真的是最非常非常累的一件事情唁影,不想再有下次了耕陷。

好吧,言歸正傳据沈,今天就在這周緩慢的學(xué)習(xí)進(jìn)度中哟沫,抽取出來一個(gè)比較有代表性的知識點(diǎn),記錄一下吧卓舵。

首先南用,首次接觸圖這個(gè)類型的數(shù)據(jù)結(jié)構(gòu),我們先來看一下圖的定義掏湾,了解一下到底什么是圖裹虫。

圖是由頂點(diǎn)的有窮非空集合和頂點(diǎn)之間的邊的集合組成,通常表示為:G(V,E), 其中G表示一個(gè)圖融击,V是圖G中頂點(diǎn)的集合筑公,E是圖G中邊的集合。

接下來我們把圖的定義與線性表定義的進(jìn)行一下對比尊浪,讓我們來更好的體會(huì)一下圖的各種定義與其他數(shù)據(jù)結(jié)構(gòu)的差異:

  • 線性表中匣屡,我們把數(shù)據(jù)元素叫做元素,樹種將數(shù)據(jù)元素叫結(jié)點(diǎn)拇涤,在圖中的數(shù)據(jù)元素捣作,我們則稱之為頂點(diǎn)。
  • 線性表中沒有數(shù)據(jù)元素鹅士,稱為空表券躁。樹種可以沒有結(jié)點(diǎn),叫做空樹掉盅。但是在圖結(jié)構(gòu)中也拜,不允許沒有頂點(diǎn)。在定義中趾痘,若V是頂點(diǎn)的集合慢哈,則強(qiáng)調(diào)了頂點(diǎn)集合V是有窮非空的。
  • 線性表中永票,相鄰的數(shù)據(jù)元素之間具有線性關(guān)系卵贱,樹結(jié)構(gòu)中,相鄰兩層的結(jié)點(diǎn)具有層次關(guān)系瓦侮,而圖中艰赞,任意兩個(gè)頂點(diǎn)之間都可能有關(guān)系,頂點(diǎn)之間的邏輯關(guān)系用邊來表示肚吏,邊集可以是空的方妖。

圖的定義我們就暫時(shí)講到這里,更細(xì)致的定義希望大家自己在網(wǎng)絡(luò)或者書籍中獲取資料罚攀,畢竟我寫的再多党觅,也不如教科書詳盡雌澄,今天我們就來講一個(gè)圖的應(yīng)用,關(guān)于路徑查找的問題杯瞻。在這里我想先說明镐牺,我們的路徑查找是一種針對無向圖的路徑查找,比如給出起始點(diǎn)A魁莉,查詢頂點(diǎn)A至頂點(diǎn)B是否有路徑睬涧,若是有路徑,則打印出A至B的路徑旗唁。而這個(gè)路徑畦浓,我們尋找的不一定是最短路徑。

其實(shí)分析這個(gè)問題就可以知道检疫,這是對圖的深度優(yōu)先遍歷(Depth-First-Search 簡稱DFS)的一個(gè)應(yīng)用讶请,若是我們能實(shí)現(xiàn)了圖的深度優(yōu)先遍歷,那么查找路徑的問題也就迎刃而解屎媳。

接下來就先給出C++的代碼夺溢,來展示解決查詢路徑問題的思路:

#include <iostream>
#include <vector>
#include <stack>
#include <cassert>

using namespace std;

// 路徑查詢
template<typename Graph>
class Path {

private:
    Graph &G; // 圖的引用
    int s;    // 起始點(diǎn)
    bool* visited; // 記錄dfs的過程中節(jié)點(diǎn)是否被訪問
    int* from; // 記錄路徑,from[i]表示查找的路徑上i的上一個(gè)節(jié)點(diǎn)

    // 圖的深度優(yōu)先遍歷
    void dfs( int v ) {
        visited[v] = true;

        typename Graph::adjIterator adj(G, v);
        for (int i = adj.begin(); !adj.end(); i = adj.next()) {
            if (!visited[i]) {
                from[i] = v;
                dfs(i);
            }
        }
    } 

public:
    // 構(gòu)造函數(shù)烛谊、尋路算法风响、尋找圖graph從s點(diǎn)到其他點(diǎn)的路徑
    Path(Graph &graph, int s): G(graph) {

        // 算法初始化
        assert( s >= 0 && s < G.V() );

        visited = new bool[G.V()];
        from = new int[G.V()];

        for (int i = 0; i < G.V(); i++) {
            visited[i] = false;
            from[i] = -1;
        }

        this->s = s;

        // 尋路算法
        dfs(s);
    }

    // 析構(gòu)函數(shù)
    ~Path() {
        delete[] visited;
        delete[] from;
    }

    // 查詢從s點(diǎn)到w點(diǎn)是否有路徑
    bool hasPath( int w ) {
        assert( w >= 0 && w < G.V() );
        return visited[w];
    }

    // 查詢s點(diǎn)到w點(diǎn)的路徑,存放在vec中
    void path( int w, vector<int> &vec ) {
        assert( hasPath(w) );

        stack<int> stack;
        // 通過from數(shù)組逆向查找到從s到w的路徑丹禀,存放在棧中
        int p = w;
        while (p != -1) {
            stack.push(p);
            p = from[p];
        }

        // 從棧中依次取出元素钞诡,獲得順序從s到w的路徑
        vec.clear();
        while ( !stack.empty() ) {
            vec.push_back( stack.top() );
            stack.pop();
        }
    }

    // 打印從s點(diǎn)到w點(diǎn)的路徑
    void showPath( int w ) { 
        assert( hasPath(w) );

        vector<int> vec;
        path(w, vec);
        for (int i = 0; i < vec.size(); i++) {
            cout << vec[i];
            if (i == vec.size() - 1)
                cout << endl;
            else
                cout << " -> ";
        }
    }
};

?
通過上面的代碼可以得知,我們首先在構(gòu)造函數(shù)中傳入我們的圖數(shù)據(jù)結(jié)構(gòu)graph湃崩,以及?我們標(biāo)記的起始點(diǎn)S。而通過showPath()函數(shù)我們能夠展示起始點(diǎn)S至任意點(diǎn)的路徑接箫,測試代碼就如下所示:

int main() {
    string filename = "testG2.txt";
    SparseGraph g = SparseGraph(7, false);
    ReadGraph<SparseGraph> readGraph(g, filename);
    g.show();
    cout << endl;

    // 比較使用深度優(yōu)先遍歷和廣度優(yōu)先遍歷獲得路徑的不同
    // 廣度優(yōu)先遍歷獲得的是無權(quán)圖的最短路徑
    Path<SparseGraph> dfs(g, 0);
    cout << "DFS : " << endl;
    dfs.showPath(6);

    ShortestPath<SparseGraph> bfs(g, 0);
    cout << "BFS : ";
    bfs.showPath(6);    
    
    return 0;
}

而Java版本的代碼也是類似攒读,只是某些函數(shù)的返回值變化了一點(diǎn),代碼如下:

public class Path {

    private Graph G;  // 圖的引用
    private int s;  // 起始點(diǎn)
    private boolean[] visited;  // 記錄dfs的過程中節(jié)點(diǎn)是否被訪問
    private int[] from;  // 記錄路徑辛友,from[i]表示查找的路徑上i的上一個(gè)節(jié)點(diǎn)

    /**
     * 構(gòu)造函數(shù)薄扁,尋路算法,尋找圖graph從點(diǎn)s到其他點(diǎn)的路徑
     * @param graph graph
     * @param s 尋路起始點(diǎn)s
     */
    public Path(Graph graph, int s) {
        assert s >= 0 && s < graph.V();

        this.G = graph;
        this.s = s;

        visited = new boolean[G.V()];
        from = new int[G.V()];

        for (int i = 0; i < G.V(); i++) {
            visited[i] = false;
            from[i] = -1;
        }

        dfs(s);
    }

    /**
     * 深度優(yōu)先遍歷
     * @param v 從v點(diǎn)開始深度優(yōu)先遍歷
     */
    private void dfs(int v) {
        visited[v] = true;

        for (int i: G.adj(v)) {
            if (!visited[i]) {
                from[i] = v;
                dfs(i);
            }
        }
    }

    // 查詢從s點(diǎn)到w點(diǎn)是否存在路徑
    public boolean hasPath(int w) {
        assert w >= 0 && w < G.V();
        return visited[w];
    }

    // 查詢點(diǎn)s到點(diǎn)w的路徑废累,存放在vec中
    public Vector<Integer> path(int w) {
        assert(hasPath(w));

        Stack<Integer> stack = new Stack<Integer>();
        int p = w;
        while (p != -1) {
            stack.push(p);
            p = from[p];
        }

        Vector<Integer> vec = new Vector<Integer>();
        while (!stack.isEmpty()) {
            vec.add(stack.pop());
        }

        return vec;
    }

    // 打印出從點(diǎn)s到點(diǎn)w的路徑
    public void showPath(int w) {
        assert (hasPath(w));

        Vector<Integer> vec = path(w);
        for (int i = 0; i < vec.size(); i++) {
            System.out.print(vec.elementAt(i));
            if (i == vec.size() - 1) {
                System.out.println();
            } else {
                System.out.print(" -> ");
            }
        }
    }
}

今天的無權(quán)圖的路徑問題就講解到這里邓梅,之后的知識點(diǎn)等學(xué)習(xí)整理之后,再行記錄邑滨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末日缨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子掖看,更是在濱河造成了極大的恐慌匣距,老刑警劉巖面哥,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異毅待,居然都是意外死亡尚卫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門尸红,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吱涉,“玉大人,你說我怎么就攤上這事外里≡蹙簦” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵级乐,是天一觀的道長疙咸。 經(jīng)常有香客問我,道長风科,這世上最難降的妖魔是什么撒轮? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮贼穆,結(jié)果婚禮上题山,老公的妹妹穿的比我還像新娘。我一直安慰自己故痊,他們只是感情好顶瞳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著愕秫,像睡著了一般慨菱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上戴甩,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天符喝,我揣著相機(jī)與錄音,去河邊找鬼甜孤。 笑死协饲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缴川。 我是一名探鬼主播茉稠,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼把夸!你這毒婦竟也來了而线?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吞获,沒想到半個(gè)月后况凉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡各拷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年刁绒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烤黍。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡知市,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出速蕊,到底是詐尸還是另有隱情嫂丙,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布规哲,位于F島的核電站跟啤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏唉锌。R本人自食惡果不足惜隅肥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望袄简。 院中可真熱鬧腥放,春花似錦、人聲如沸绿语。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吕粹。三九已至种柑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間匹耕,已是汗流浹背莹规。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泌神,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓舞虱,卻偏偏與公主長得像欢际,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子矾兜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容