久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

深度優(yōu)先搜索和廣度優(yōu)先搜索   當(dāng)我們在學(xué)習(xí)和臨摹垃圾回收(Garbage Collection, 縮寫為 GC)相關(guān)算法和源碼時候, 內(nèi)在細(xì)節(jié)離不開這兩大類搜索算法支撐. 這就是構(gòu)建的背景?, 文章定位是科普掃盲?.0. 引述與

 路人甲Java 2022-10-07 發(fā)布于北京

  當(dāng)我們在學(xué)習(xí)和臨摹垃圾回收(Garbage Collection, 縮寫為 GC)相關(guān)算法和源碼時候, 內(nèi)在細(xì)節(jié)離不開

這兩大類搜索算法支撐. 這就是構(gòu)建的背景?, 文章定位是科普掃盲?.

0. 引述與目錄

  [0] 知乎 - 節(jié)點 or 結(jié)點 ? [走進(jìn)科學(xué)]

  [1] 維基百科 - 廣度優(yōu)先搜索 [概念參照]

  [2] 維基百科 - 深度優(yōu)先搜索 [概念參照]

  [3] 維基百科 - 樹的遍歷 [概念參照]

  [4] CSDN - 二叉樹的深度優(yōu)先遍歷與廣度優(yōu)先遍歷 [盜圖]

  [5] 博客園 - 數(shù)據(jù)結(jié)構(gòu)之圖的基本概念 [遞歸抄襲]

  [6] 博客園 - 圖的遍歷之 深度優(yōu)先搜索和廣度優(yōu)先搜索 [深度抄襲]

  [7] github - 深度搜索和廣度搜索 [演示素材]

目錄
 1. 概念介紹
 2. 樹的例子
  2.1 前序遍歷
  2.2 中序遍歷
  2.3 后序遍歷
  2.4 層次遍歷
  2.5 搜索算法源碼
 3. 圖的例子
  3.0 圖的基本概念
    3.0.1 圖的定義
    3.0.2 圖的基本概念
    3.0.3 圖最基礎(chǔ)的兩類存儲結(jié)構(gòu)
      3.0.3.1 鄰接矩陣表示法
      3.0.3.2 鄰接表表示法
  3.1 深度優(yōu)先搜索圖文介紹
    3.1.1 深度優(yōu)先搜索介紹
    3.1.2 深度優(yōu)先搜索圖解
      3.1.2.1 無向圖的深度優(yōu)先搜索
      3.1.2.2 有向圖的深度優(yōu)先搜索
  3.2 廣度優(yōu)先搜索圖文介紹
    3.2.1 廣度優(yōu)先搜索介紹
    3.2.2 廣度優(yōu)先搜索圖解
      3.2.2.1 無向圖的廣度優(yōu)先搜索
      3.2.2.2 有向圖的廣度優(yōu)先搜索
  3.3 搜索算法源碼
    3.3.1 鄰接矩陣圖表示的無向圖 (Matrix Undirected Graph)
    3.3.2 鄰接矩陣圖表示的有向圖 (Matrix Directed Graph)
    3.3.3 鄰接鏈表圖表示的無向圖 (List Undirected Graph)
    3.3.4 鄰接鏈表圖表示的有向圖 (List Directed Graph)

1. 概念介紹

  關(guān)于遍歷算法種類有很多很多, 這里選了最通用, 深度優(yōu)先遍歷和廣度優(yōu)先遍歷兩類搜索算法進(jìn)行淺顯

講解(作者能力有限). 基礎(chǔ)有時候很有趣, 真的! ok, 那我們 go on. 

  深度優(yōu)先搜索算法(Depth-First-Search, 縮寫為 DFS), 是一種用于遍歷或搜索樹或圖的算法. 這個算法

會盡可能深的搜索樹的分支. 當(dāng)結(jié)點 v 的所在邊都己被探尋過, 搜索將回溯到發(fā)現(xiàn)結(jié)點 v 的那條邊的起始結(jié)

點. 這一過程一直進(jìn)行到已發(fā)現(xiàn)從源結(jié)點可達(dá)的所有結(jié)點為止. 如果還存在未被發(fā)現(xiàn)的結(jié)點, 則選擇其中一

作為源結(jié)點并重復(fù)以上過程, 整個進(jìn)程反復(fù)進(jìn)行直到所有結(jié)點都被訪問為止.

  廣度優(yōu)先搜索算法(Breadth-First Search, 縮寫為 BFS), 又譯作寬度優(yōu)先搜索, 橫向優(yōu)先搜索, 是一種

形搜索算法. 簡單的說, BFS 是從根結(jié)點開始, 沿著樹的寬度遍歷樹的結(jié)點. 如果所有結(jié)點均被訪問, 則算法

止. 

  在計算機(jī)科學(xué)里, 樹的遍歷(也稱為樹的搜索)是圖的遍歷的一種, 指的是按照某種規(guī)則, 不重復(fù)地訪問某

種樹的所有結(jié)點的過程. 具體的訪問操作可能是檢查結(jié)點的值, 更新結(jié)點的值等. 不同的遍歷方式, 其訪問結(jié)

點的順序是不一樣的. 以下我們將二叉樹的廣度優(yōu)先和深度優(yōu)先遍歷算法歸類,但它們也適用于其它樹形結(jié)

構(gòu).

深度優(yōu)先遍歷(搜索)

    前序遍歷(Pre-Order Traversal)

 

樹-前序遍歷-演示

深度優(yōu)先搜索 - 前序遍歷: F -> B -> A -> D -> C -> E -> G -> I ->H

    中序遍歷(In-Order Traversal)

樹-中序遍歷-演示

深度優(yōu)先搜索 - 中序遍歷: A -> B -> C -> D -> E -> F -> G -> H -> I

    后序遍歷(Post-Order Traversal)

樹-后序遍歷-演示

深度優(yōu)先搜索 - 后序遍歷: A -> C -> E -> D -> B -> H -> I -> G -> F

廣度優(yōu)先遍歷(搜索)

    層次遍歷(Level Order Traversal)

樹-層次遍歷-演示

廣度優(yōu)先搜索 - 層次遍歷:F -> B -> G -> A -> D -> I -> C -> E -> H

  通過閱讀和思考以上維基百科中提供的摘錄, 我們大致對兩類遍歷算法有了點直觀的認(rèn)知. 不知道有沒

有人有這樣的疑惑, 為什么寬度優(yōu)先搜索叫法沒有廣度優(yōu)先搜索這個叫法流傳廣, 明明在樹結(jié)構(gòu)中要好理解

多了呢? 乍看確實這樣, 但不要忘了樹結(jié)構(gòu)是一種特殊圖結(jié)構(gòu), 而復(fù)雜圖結(jié)構(gòu)可能沒有直觀從上到下, 從左到

右這種直觀的觀感了. 因而寬度優(yōu)先搜索名詞在一般化中顯得不夠準(zhǔn)確了. 哈哈哈. 另外一個認(rèn)知是關(guān)于必要

條件的. 例如深度和廣度優(yōu)先搜索包含很多不同遍歷相關(guān)相關(guān)算法, 不僅僅只有文章中介紹那幾個, 這點大家

要充分認(rèn)識.   

2. 樹的例子

樹-遍歷-演示 

  觀察圖片, 提示中可以發(fā)現(xiàn)前中后序遍歷定義是圍繞根結(jié)點訪問時機(jī)定義的(還存在潛規(guī)則, 默認(rèn)先左子

樹后右子樹).  下面我們通過具體的代碼來表達(dá)遍歷思路. 首先我們構(gòu)造樹結(jié)構(gòu)

/**
 * 方便測試和描述算法, 用 int node 構(gòu)建 
 */
typedef int node_t;

static void node_printf(node_t value) {
    printf(" %2d", value);
}


struct tree {
    struct tree * left;
    struct tree * right;

    node_t node;
};

static inline struct tree * tree_new(node_t value) {
    struct tree * node = malloc(sizeof (struct tree));
    node->left = node->right = NULL;
    node->node = value;
    return node;
}

static void tree_delete_partial(struct tree * node) {
    if (node->left) tree_delete_partial(node->left);
    if (node->right) tree_delete_partial(node->right);
    free(node);
}

void 
tree_delete(struct tree * root) {
    if (root) tree_delete_partial(root);
}

2.1 前序遍歷

static void tree_preorder_partial(struct stack * s) {
    struct tree * top;

    // 2.1 訪問棧頂結(jié)點, 并將其出棧
    while ((top = stack_pop_top(s))) {
        // 2.2 做業(yè)務(wù)處理
        node_printf(top->node);

        // 3. 如果根結(jié)點存在右孩子, 則將右孩子入棧
        if (top->right)
            stack_push(s, top->right);
        // 4. 如果根結(jié)點存在左孩子, 則將左孩子入棧
        if (top->left)
            stack_push(s, top->left);
    }
}

/*
 * 前序遍歷:
 *      根結(jié)點 -> 左子樹 -> 右子樹
 * 
 * 遍歷算法:
 *      1. 先將根結(jié)點入棧
        2. 訪問棧頂結(jié)點, 做業(yè)務(wù)處理, 并將其出棧
        3. 如果根結(jié)點存在右孩子, 則將右孩子入棧
        4. 如果根結(jié)點存在左孩子, 則將左孩子入棧
        重復(fù) 2 - 4 直到??? */
void 
tree_preorder(struct tree * root) {
    if (!root) return;

    struct stack s[1]; 
    stack_init(s);

    // 1. 先將根結(jié)點入棧
    stack_push(s, root);
    // 重復(fù) 2 - 4 直到???/span>
    tree_preorder_partial(s);

    stack_free(s);
}

2.2 中序遍歷

static void tree_inorder_partial(struct tree * node, struct stack * s) {
    // 4. 重復(fù)第 2 - 3步, 直到??詹⑶也淮嬖诖L問結(jié)點
    while (!stack_empty(s) || node) {
        // 2. 將當(dāng)前結(jié)點的所有左孩子入棧, 直到左孩子為空
        while (node) {
            // 1. 先將根結(jié)點入棧
            stack_push(s, node);
            node = node->left;
        }

        // 3.1 彈出并訪問棧頂元素,
        node = stack_pop_top(s);
        // 3.2 做業(yè)務(wù)處理.
        node_printf(node->node);
        // 4.1 如果棧頂元素存在右孩子
        node = node->right;
    }
}

/*
 * 中序遍歷:
 *      左子樹 -> 根結(jié)點 -> 右子樹
 * 
 * 遍歷算法:
 *      1. 先將根結(jié)點入棧
        2. 將當(dāng)前結(jié)點的所有左孩子入棧, 直到左孩子為空
        3. 彈出并訪問棧頂元素, 做業(yè)務(wù)處理.
        4. 如果棧頂元素存在右孩子, 重復(fù)第 2 - 3步, 直到??詹⑶也淮嬖诖L問結(jié)點
 */
void
tree_inorder(struct tree * root) {
    if (!root) return;

    struct stack s[1]; 
    stack_init(s);

    tree_inorder_partial(root, s);

    stack_free(s);
}

2.3 后序遍歷

static void tree_postorder_partial(struct stack * s) { 
    struct tree * top;
    // 記錄前一次出棧結(jié)點
    struct tree * last = NULL;

    // 重復(fù) 1-2 直到???/span>
    while ((top = stack_top(s))) {
        // 2.2 如果我們發(fā)現(xiàn)它左右子結(jié)點都為空, 則可以做業(yè)務(wù)處理;
        // 2.3 或者,如果發(fā)現(xiàn)前一個出棧的結(jié)點是它的左結(jié)點或者右子結(jié)點,,則可以做業(yè)務(wù)處理;
        if ((!top->left && !top->right) || (last && (last == top->left || last == top->right))) {
            // 做業(yè)務(wù)處理
            node_printf(top->node);
            // 出棧
            stack_pop(s);
            // 記錄此次出棧結(jié)點
            last = top;
         } else {
            // 2.4. 否則,表示這個結(jié)點是一個新的結(jié)點,,需要嘗試將其右左子結(jié)點依次入棧.
            if (top->right)
                stack_push(s, top->right);
            if (top->left)
                stack_push(s, top->left);
         }
    }
}

/*
 * 后序遍歷:
 *      左子樹 -> 右子樹 -> 根結(jié)點
 * 
 * 遍歷算法:
 *      1. 拿到一個結(jié)點, 先將其入棧, 則它一定在比較靠棧底的地方, 比較晚出棧
        2. 在出棧時候判斷到底是只有左結(jié)點還是只有右結(jié)點或者是兩者都有或者是都沒有.
           如果我們發(fā)現(xiàn)它左右子結(jié)點都為空, 則可以做業(yè)務(wù)處理;
           或者,,如果發(fā)現(xiàn)前一個出棧的結(jié)點是它的左結(jié)點或者右子結(jié)點,則可以做業(yè)務(wù)處理;
           否則,,表示這個結(jié)點是一個新的結(jié)點,,需要嘗試將其右左子結(jié)點依次入棧.
        重復(fù) 1-2 直到棧空
 */
void 
tree_postorder(struct tree * root) {
    if (!root) return;

    struct stack s[1]; 
    stack_init(s);

    // 1. 先將根結(jié)點入棧
    stack_push(s, root);
    // 重復(fù) 1 - 4 直到???/span>
    tree_postorder_partial(s);

    stack_free(s);
}

思考上面深度優(yōu)先搜索三種方式, 大致會感覺, 占用??臻g大小映象是 前序遍歷 < 中序遍歷 < 后序遍歷 (存在

特例). 可能, 這也是前序遍歷是樹深度優(yōu)先搜索算法中流傳最廣的原因. 對于樹的后序遍歷代碼實現(xiàn), 版本也有

好幾個. 有的很容易手寫, 很花哨偷巧, 這里推薦的是正規(guī)工程版本. 

2.4 層次遍歷

/*
 * 層次遍歷:
 *      根結(jié)點 -> 下一層 | 左結(jié)點 -> 右結(jié)點
 *
 * 遍歷算法:
 *      1. 對于不為空的結(jié)點, 先把該結(jié)點加入到隊列中
 *      2. 從隊中彈出結(jié)點, 并做業(yè)務(wù)處理, 嘗試將左結(jié)點右結(jié)點依次壓入隊列中
 *      重復(fù) 1 - 2 指導(dǎo)隊列為空 
 */
void
tree_level(struct tree * root) {
    if (!root) return;

    q_t q;
    q_init(q);

    // 1. 對于不為空的結(jié)點, 先把該結(jié)點加入到隊列中
    q_push(q, root);
    do {
        // 2.1 從隊中彈出結(jié)點, 并做業(yè)務(wù)處理
        struct tree * node = q_pop(q);

        node_printf(node->node);

        // 2.2 嘗試將左結(jié)點右結(jié)點依次壓入隊列中
        if (node->left) 
            q_push(q, node->left);
        if (node->right)
            q_push(q, node->right);

    // 重復(fù) 1 - 2 指導(dǎo)隊列為空
    } while (q_exist(q));

    q_free(q);
}

上面思路非常正規(guī)直白, 也可以做語言配合算法進(jìn)行簡單優(yōu)化(推薦).

/*
 * 層次遍歷:
 *      根結(jié)點 -> 下一層 | 左結(jié)點 -> 右結(jié)點
 *
 * 遍歷算法:
 *      1. 開始結(jié)點判空, 決定是否繼續(xù)
 *      2. 構(gòu)建輔助隊列結(jié)構(gòu)
 *      3. 直接對結(jié)點做業(yè)務(wù)處理
 *      4. 嘗試將不為空左結(jié)點右結(jié)點依次壓入隊列中
 *      5. 隊列出隊賦值保存
 *      重復(fù) 3 - 5 直到變量為空
 */
void 
tree_level_optimize(struct tree * root) {
    // 1. 開始結(jié)點判空, 決定是否繼續(xù)
    if (!root) return;

    // 2. 構(gòu)建輔助隊列結(jié)構(gòu)
    q_t q;
    q_init(q);

    do {
        // 3. 直接對結(jié)點做業(yè)務(wù)處理
        node_printf(root->node);

        // 4. 嘗試將不為空左結(jié)點右結(jié)點依次壓入隊列中
        if (root->left) 
            q_push(q, root->left);
        if (root->right)
            q_push(q, root->right);

        // 5. 隊列出隊賦值保存
        root = q_pop(q);

    // 重復(fù) 3 - 5 直到變量為空   
    } while (root);

    q_free(q);
} 

2.5 搜索算法源碼

3. 圖的例子

  本章會先對圖的深度優(yōu)先搜索和廣度優(yōu)先搜索進(jìn)行簡單介紹, 然后再給出 C 的源碼實現(xiàn).

3.0 圖的基本概念

3.0.1 圖的定義

定義: 圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成, 通常表示為: G(V, E), 其中, 表示個圖

                V 圖 G 中頂點的集合, 圖 G邊的集合.

在圖中需要注意的是: 

(1) 線性表中我們把數(shù)據(jù)元素叫元素, 樹中將數(shù)據(jù)元素叫結(jié)點, 在圖中數(shù)據(jù)元素, 我們則稱之為頂點(Vertex).

(2) 線性表可以沒有元素, 稱為空表; 樹中可以沒有結(jié)點, 稱為空樹; 但是, 在圖中不允許沒有頂點(有窮非空性).

(3) 線性表中的各元素是線性關(guān)系, 樹中的各元素是層次關(guān)系, 而, 圖中各頂點的關(guān)系是用邊來表示(邊集可以為

      空).

3.0.2 圖的基本概念

(1) 無向圖

 無向圖

如果圖中任意兩個頂點之間的邊都是無向邊(簡而言之就是沒有方向的邊), 則稱該圖為無向圖(Undirected

graphs).

(2) 有向圖

有向圖

如果圖中任意兩個頂點之間的邊都是有向邊(簡而言之就是有方向的邊), 則稱該圖為有向圖(Directed graphs).

(3) 完全圖

① 無向完全圖: 在無向圖中, 如果任意兩個頂點之間都存在邊, 則稱該圖為無向完全圖. (含有 n 個頂點的無向完

    全圖有 (n×(n-1))/2 條邊) 如下圖所示:

無向完全圖

② 有向完全圖: 在有向圖中, 如果任意兩個頂點之間都存在方向互為相反的兩條弧, 則稱該圖為有向完全圖. (含

     有 n 個頂點的有向完全圖有 n×(n-1) 條邊) 如下圖所示:

有向完全圖

PS: 當(dāng)一個圖接近完全圖時, 則稱它為稠密圖(Dense Graph), 而當(dāng)一個圖含有較少的邊時, 則稱它為稀疏圖(

         Spare Graph).

(4) 頂點的度

頂點 V 的度(Degree) 是指在圖中與 V 相關(guān)聯(lián)的邊的條數(shù). 對于有向圖來說, 有入度(In-degree)和出度

(Out-degree) 之分, 有向圖頂點的度等于該頂點的入度和出度之和. 例如 A -> B 標(biāo)識 A 出度為 1, B 入度為 1.

(5) 鄰接

① 若無向圖中的兩個頂點 V 和 W 存在一條邊(V, W), 則稱頂點 V 和 W 鄰接(Adjacent);

② 若有向圖中存在一條邊<V, W>,則稱 頂點 V 鄰接到 頂點 W ,,活 頂點 W 鄰接自 頂點 V; 

      以 (2) 有向圖 舉例, 有 <A, D> <B, A> <B, C> <C, A> 鄰接關(guān)系.

(6) 弧頭和弧尾

有向圖中, 無箭頭一端的頂點通常被稱為"初始點"或"弧尾", 箭頭直線的頂點被稱為"終端點"或"弧頭". <A, D>

鄰接關(guān)系中, 頂點 A 就是弧尾, 頂點 D 就是弧頭. 

PS: 無向圖中的邊使用小括號"()"表示, 而有向圖中的邊使用尖括號"<>" 表示.

(7) 路徑

在無向圖中, 若從頂點 V 出發(fā)有一組邊可到達(dá)頂點 W, 則稱頂點 V 到頂點 W 的頂點序列, 為從頂點 V 到頂點 W的

路徑(Path).

(8) 連通

若從 V 到 W 有路徑可通, 則稱頂點 V 和頂點 W 是連通(Connected)的.

(9) 權(quán)

網(wǎng)-權(quán)

有些圖的邊或弧具有與它相關(guān)的數(shù)字, 這種與圖的邊或弧相關(guān)的數(shù)叫做權(quán)(Weight).

有些圖的邊或弧具有與它相關(guān)的數(shù)字, 這種與圖的邊或弧相關(guān)的數(shù)叫做權(quán)(Weight).

3.0.3 圖最基礎(chǔ)的兩類存儲結(jié)構(gòu)

  圖的存儲結(jié)構(gòu)除了要存儲圖中的各個頂點本身的信息之外, 還要存儲頂點與頂點之間的關(guān)系, 因此, 圖的結(jié)

構(gòu)也比較復(fù)雜. 常用最基礎(chǔ)的兩類圖的存儲結(jié)構(gòu)有鄰接矩陣和鄰接表.

3.0.3.1 鄰接矩陣表示法

  圖的鄰接矩陣(Adjacency Matrix)存儲方式是用兩個數(shù)組來表示圖. 一個一維數(shù)組存儲圖中頂點信息, 一個

二維數(shù)組(稱為鄰接矩陣)存儲圖中的邊或弧的信息.

(1) 無向圖

我們可以設(shè)置兩個數(shù)組, 頂點數(shù)組為 vertex[4] = {V0, V1, V2, V3}, 邊數(shù)組 edges[4][4] 為上圖右邊這樣的一個矩

陣. 對于矩陣的主對角線的值, 即 edges[0][0], edges[1][1], edges[2][2], edges[3][3], 全為 0,  因為不存在頂點

自己到自己的邊.

(2) 有向圖

我們再來看一個有向圖樣例, 如下圖所示的左邊. 頂點數(shù)組為 vertex[4] = {V0, V1, V2, V3}, 弧數(shù)組 edges[4][4]

為下圖右邊這樣的一個矩陣. 主對角線上數(shù)值依然為 0. 但因為是有向圖, 所以此矩陣并不對稱, 比如由 V1 到 V0

有弧, 得到 edges[1][0] = 1, 而 V0 到 V1 沒有弧, 因此 edges[0][1] = 0.

不足: 由于存在 n 個頂點的圖需要 n*n 個數(shù)組元素進(jìn)行存儲, 當(dāng)圖為稀疏圖時, 使用鄰接矩陣存儲方法將會出

             現(xiàn)大量 0 元素, 這會造成極大的空間浪費. 這時, 可以考慮使用鄰接表表示法來存儲圖中的數(shù)據(jù).

3.0.3.2 鄰接表表示法

  首先, 回憶我們烙印在身體里的線性表存儲結(jié)構(gòu), 順序存儲結(jié)構(gòu)因為存在預(yù)先分配內(nèi)存可能造成存儲空間

浪費的問題, 于是想到了鏈?zhǔn)酱鎯Φ慕Y(jié)構(gòu). 同樣的, 我們也可以嘗試對邊或弧使用鏈?zhǔn)酱鎯Φ姆绞絹肀苊饪臻g浪

費的問題.

  鄰接表由表頭結(jié)點表結(jié)點兩部分組成, 圖中每個頂點均對應(yīng)一個存儲在數(shù)組中的表頭結(jié)點. 如果這個表

頭結(jié)點所對應(yīng)的頂點存在鄰接結(jié)點, 則把鄰接結(jié)點依次存放于表頭結(jié)點所指向的單向鏈表中.

(1) 無向圖: 下圖所示的就是一個無向圖的鄰接表結(jié)構(gòu).

從上圖中我們知道, 頂點表的各個結(jié)點由 data 和 firstedge 兩個域表示, data 是數(shù)據(jù)域, 存儲頂點的信息,

firstedge 是指針域, 指向邊表的第一個結(jié)點, 即此頂點的第一個鄰接點. 邊表結(jié)點由 adjvex 和 next 兩個域組成.

adjvex 是鄰接點域, 存儲某頂點的鄰接點在頂點表中的下標(biāo), next 則存儲指向邊表中下一個結(jié)點的指針. 例如:

V1 頂點與 V0, V2 互為鄰接點, 則在 V1 的邊表中,,adjvex 分別為 V0 的 0 和 V2 的 2.

PS: 對于無向圖來說, 使用鄰接表進(jìn)行存儲也會出現(xiàn)數(shù)據(jù)冗余的現(xiàn)象. 例如上圖中, 頂點 V0 所指向的鏈表中存

         在一個指向頂點 V3 的同時,頂點 V3 所指向的鏈表中也會存在一個指向 V0 的頂點,。

(2) 有向圖: 若是有向圖, 鄰接表結(jié)構(gòu)是類似的,,但要注意的是有向圖由于有方向的. 因此, 有向圖的鄰接表分為

出邊表和入邊表(又稱逆鄰接表), 出邊表的表結(jié)點存放的是從表頭結(jié)點出發(fā)的有向邊所指的尾結(jié)點; 入邊表的表

結(jié)點存放的則是指向表頭結(jié)點的某個頂點, 如下圖所示.

(3) 帶權(quán)圖: 對于帶權(quán)值的網(wǎng)圖, 可以在邊表結(jié)點定義中再增加一個 weight 的數(shù)據(jù)域, 存儲權(quán)值信息即可, 如下

圖所示.

3.1 深度優(yōu)先搜索圖文介紹

3.1.1 深度優(yōu)先搜索介紹

  圖的深度優(yōu)先搜索(Depth First Search),我們再重復(fù)重復(fù)加深理解. 我們介紹下它的搜索步驟: 假設(shè)初始

狀態(tài)是圖中所有頂點均未被訪問, 則從某個 頂點(結(jié)點) v 出發(fā), 首先訪問該頂點, 然后依次從它的各個未被訪問

的鄰接點出發(fā)深度優(yōu)先搜索遍歷圖, 直至圖中所有和 頂點 v 有路徑相通的頂點都被訪問到. 若此時, 尚有其他頂

點未被訪問到, 則另選一個未被訪問的頂點作起始點, 重復(fù)上述過程, 直至圖中所有頂點都被訪問到為止.  顯然,,

深度優(yōu)先搜索對于圖這類結(jié)構(gòu)很容易被遞歸過程所構(gòu)造. 

3.1.2 深度優(yōu)先搜索圖解

3.1.2.1 無向圖的深度優(yōu)先搜索

  下面以"無向圖"為例, 來對深度優(yōu)先搜索進(jìn)行演示.

對上面的 圖 G1 進(jìn)行深度優(yōu)先遍歷,,從 頂點 A 開始.

第1步: 訪問 A.

第2步: 訪問(A 的鄰接點) C.

            在 第1步 訪問 A 之后, 接下來應(yīng)該訪問的是 A 的鄰接點, 即 "C, D, F" 中的一個. 但在本文的實現(xiàn)中,

            頂點 ABCDEFG 是按照順序存儲,C在"D和F"的前面,,因此,,先訪問C.

第3步: 訪問(C 的鄰接點) B.

            在 第2步 訪問 C 之后, 接下來應(yīng)該訪問 C 的鄰接點, 即 "B和D" 中一個(A已經(jīng)被訪問過,就不算在內(nèi))

            . 而由于 B 在 D 之前, 先訪問 B.

第4步: 訪問(C 的鄰接點) D.

           在 第3步 訪問了 C 的鄰接點 B 之后, B 沒有未被訪問的鄰接點; 因此, 返回訪問 C 的另一個鄰接點 D.

第5步: 訪問(A 的鄰接點) F.

            前面已經(jīng)訪問了 A, 并且訪問完了 "A 的鄰接點 B 的所有鄰接點(包括遞歸的鄰接點在內(nèi))"; 因此, 此時

            返回到訪問A的另一個鄰接點F,。

第6步: 訪問(F 的鄰接點) G.

第7步: 訪問(G 的鄰接點) E.

因此訪問順序是: A -> C -> B -> D -> F -> G -> E

3.1.2.2 有向圖的深度優(yōu)先搜索

  下面以"有向圖"為例, 來對深度優(yōu)先搜索進(jìn)行演示.

對上面的 圖 G2 進(jìn)行深度優(yōu)先遍歷, 從 頂點 A 開始.

第1步: 訪問 A.

第2步: 訪問 B.

            在訪問了 A 之后, 接下來應(yīng)該訪問的是 A 的出邊的另一個頂點, 即頂點 B.

第3步: 訪問 C.

            在訪問了 B 之后, 接下來應(yīng)該訪問的是 B 的出邊的另一個頂點, 即 頂點 C, E, F. 在本文實現(xiàn)的圖中, 頂

            點 ABCDEFG 按照順序存儲, 因此先訪問 C.

第4步: 訪問 E.

            接下來訪問 C 的出邊的另一個頂點, 即頂點 E.

第5步: 訪問 D.

            接下來訪問 E 的出邊的另一個頂點, 即 頂點 B, D. 頂點 B 已經(jīng)被訪問過, 因此訪問 頂點 D.

第6步: 訪問 F.

            接下應(yīng)該回溯"訪問 A 的出邊的另一個頂點 F".

第7步: 訪問 G.

因此訪問順序是: A -> B -> C -> E -> D -> F -> G

3.2 廣度優(yōu)先搜索圖文介紹

3.2.1 廣度優(yōu)先搜索介紹

  廣度優(yōu)先搜索算法(Breadth First Search), 又稱為"寬度優(yōu)先搜索"或"橫向優(yōu)先搜索", 簡稱BFS. 它的遍歷

常規(guī)思路是: 從圖中某頂點(結(jié)點) v 出發(fā), 在訪問了 v 之后依次訪問 v 的各個未曾訪問過的鄰接點, 然后分別從

這些鄰接點出發(fā)依次訪問它們的鄰接點, 并使得"先被訪問的頂點的鄰接點先于后被訪問的頂點的鄰接點被訪問

, 直至圖中所有已被訪問的頂點的鄰接點都被訪問到. 如果此時圖中尚有頂點未被訪問, 則需要另選一個未曾被

訪問過的頂點作為新的起始點, 重復(fù)上述過程, 直至圖中所有頂點都被訪問到為止. 換句話說, 廣度優(yōu)先搜索遍歷

圖的過程是以 v 為起點, 由近至遠(yuǎn), 依次訪問和 v 有路徑相通且'路徑長度'為 1, 2 ... 的頂點.

3.2.2 廣度優(yōu)先搜索圖解

3.2.2.1 無向圖的廣度優(yōu)先搜索

  下面以"無向圖"為例, 來對廣度優(yōu)先搜索進(jìn)行演示. 還是以上面的 圖 G1 為例進(jìn)行說明.

第1步: 訪問 A.

第2步: 依次訪問 C, D, F.

            在訪問了 A 之后,,接下來訪問 A 的鄰接點. 前面已經(jīng)說過, 在本文實現(xiàn)中, 頂點 ABCDEFG 按照順序存儲

            的, C 在 "D 和 F" 的前面, 因此, 先訪問 C. 再訪問完 C 之后, 再依次訪問 D, F.

第3步: 依次訪問 B, G.

            在 第2步 訪問完 C, D, F 之后, 再依次訪問它們的鄰接點. 首先訪問 C 的鄰接點 B, 再訪問 F 的鄰接點 G.

第4步: 訪問 E.

            在 第3步 訪問完 B, G 之后, 再依次訪問它們的鄰接點. 只有 G 有鄰接點 E, 因此訪問 G 的鄰接點 E.

因此訪問順序是: A -> C -> D -> F -> B -> G -> E

3.2.2.2 有向圖的廣度優(yōu)先搜索

  下面以"有向圖"為例, 來對廣度優(yōu)先搜索進(jìn)行演示. 還是以上面的 圖 G2 為例進(jìn)行說明.

第1步: 訪問 A.

第2步: 訪問 B.

第3步: 依次訪問 C, E, F.

            在訪問了 B 之后, 接下來訪問 B 的出邊的另一個頂點, 即 C, E, F. 前面已經(jīng)說過, 在本文實現(xiàn)中,頂點

            ABCDEFG 按照順序存儲的, 因此會先訪問 C, 再依次訪問 E, F.

第4步: 依次訪問 D, G.

            在訪問完 C, E, F 之后, 再依次訪問它們的出邊的另一個頂點. 還是按照 C, E, F 的順序訪問, C 的已經(jīng)全部

            訪問過了, 那么就只剩下 E, F; 先訪問 E 的鄰接點 D, 再訪問 F 的鄰接點 G.

因此訪問順序是:A -> B -> C -> E -> F -> D -> G

3.3 搜索算法源碼

3.3.1 鄰接矩陣圖表示的無向圖 (Matrix Undirected Graph)

3.3.2 鄰接矩陣圖表示的有向圖 (Matrix Directed Graph)

3.3.3 鄰接鏈表圖表示的無向圖 (List Undirected Graph)

3.3.4 鄰接鏈表圖表示的有向圖 (List Directed Graph)

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多