廣度優(yōu)先搜索算法(BFS)

廣度優(yōu)先搜索算法(BFS)

標(biāo)簽(空格分隔): algorithm


1.廣度優(yōu)先搜索算法(Breadth First Search:BFS)

  • 廣度優(yōu)先搜索顧名思義,就是要廣闊 ,不斷通過搜索自己旁邊的節(jié)點(diǎn),旁邊的節(jié)點(diǎn)構(gòu)成一個(gè)隊(duì)列,只有把自己旁邊的節(jié)點(diǎn)遍歷完之后铛只,才會(huì)遍歷旁邊旁邊的節(jié)點(diǎn)
  • 對(duì)于這種有先后順序的特性的算法過程,會(huì)考慮使用隊(duì)列這種FIFO的數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)算法
  • 樹的層次遍歷摩泪,可以使用廣度優(yōu)先算法
  • 畫一個(gè)簡單的圖捷绑,帶入一下下面的代碼韩脑,就會(huì)很明白

  • 偽代碼
    void BFS(){
        1.初始化:遍歷需要的隊(duì)列Q、標(biāo)志節(jié)點(diǎn)是否訪問過的粹污,hashmap
        2.起始節(jié)點(diǎn)入隊(duì)列:Q.push(startNode)
        3.標(biāo)志起始節(jié)點(diǎn)已經(jīng)在隊(duì)列當(dāng)中了: hashmap[startNode] = true
        while(隊(duì)列非空:!Q.empty()){
            需要遍歷的節(jié)點(diǎn)出隊(duì)列:node = Q.dequeue()
            訪問節(jié)點(diǎn):visited(node)
            獲取node節(jié)點(diǎn)的旁邊段多,鄰接節(jié)點(diǎn)集合:adjlist = getadjList(node)
            for adjNode : adjlist: 對(duì)每一個(gè)鄰接點(diǎn),執(zhí)行
                如果鄰接點(diǎn)沒有被訪問:hashmap[adjNode] != true
                    把鄰接點(diǎn)加入隊(duì)列壮吩,等待訪問 Q.add(adjlist)
                    標(biāo)志鄰接點(diǎn)已經(jīng)在隊(duì)列當(dāng)中了:hashmap[adjlist] = true
        }
    }

  • 從遍歷的過程來看进苍,只要節(jié)點(diǎn)在隊(duì)列當(dāng)中,那么肯定標(biāo)志hashmap當(dāng)中也一定有標(biāo)記過
  • 所以可以看出來鸭叙,進(jìn)隊(duì)列的時(shí)候說明這個(gè)節(jié)點(diǎn)即將被訪
  • 出隊(duì)列的時(shí)候觉啊,才是節(jié)點(diǎn)真正被訪問的時(shí)候。
  • 只要抓住上述兩點(diǎn)的狀態(tài)變化沈贝,就能夠明白BFS的執(zhí)行過程
  • 有個(gè)問題時(shí)杠人,如果一個(gè)圖當(dāng)中,有兩個(gè)節(jié)點(diǎn)宋下,是沒有任何鄰接節(jié)點(diǎn)的嗡善,那怎么遍歷呢?

  • 上述的問題很好学歧,值得思考罩引,哈哈:(:(,:):)
  • 上述的BFS描述了,從某個(gè)節(jié)點(diǎn)出發(fā)去訪問這個(gè)節(jié)點(diǎn)能夠到達(dá)的所有節(jié)點(diǎn)枝笨,即訪問從這個(gè)節(jié)點(diǎn)出發(fā)蜒程,到任何一個(gè)節(jié)點(diǎn)之間有路徑的圖
  • 如果一個(gè)圖有兩個(gè)節(jié)點(diǎn)直接沒有路徑,那么從單次調(diào)用BFS是無法訪問到所有的節(jié)點(diǎn)的伺帘,所以就需要多次調(diào)用BFS
  • 需要注意的是昭躺,標(biāo)志節(jié)點(diǎn)是否被訪問過的hashmap需要變成對(duì)于整個(gè)圖的
void GraphTraversal(){
    一個(gè)圖 Graph G
    初始化標(biāo)志是否訪問過的hashmap
    對(duì)每一個(gè)在圖G中的節(jié)點(diǎn)調(diào)用BFS: for node in G:
            如果節(jié)點(diǎn)沒有被訪問過則調(diào)用BFS:if hashmap[node] != true :
                                                BFS(node,hashmap)
}
  • 通過上述的偽代碼能看出,一次調(diào)用BFS可能已經(jīng)把很多節(jié)點(diǎn)都訪問了伪嫁,這個(gè)時(shí)候下次以這個(gè)節(jié)點(diǎn)來做循環(huán)的時(shí)候领炫,就不需要在調(diào)用了,因?yàn)閳D的一個(gè)連通圖的從任何一個(gè)節(jié)點(diǎn)開始都能夠訪問到所有的連通圖中所有的節(jié)點(diǎn)
  • 例如A---B相連张咳,那么通過A開始訪問帝洪,就可以把B也訪問了。如果從B節(jié)點(diǎn)開始脚猾,那么這個(gè)時(shí)候也能訪問到A葱峡,但是這重復(fù)訪問,遍歷只需要訪問一次就可以龙助。


public static void main(String[] args){
    Graph G = new Graph();
    DFS(G.get(0),G);// 表示從0 節(jié)點(diǎn)開始遍歷
}
public static void BFS(Node S,Graph G) {
        /* 初始化數(shù)據(jù)結(jié)構(gòu)
         * 隊(duì)列Q
         * 是否遍歷的標(biāo)志hashmap
         */
        
        Queue<Node> Q = new LinkedList<Node>();
        HashMap<Node,Boolean> visitedMap = new HashMap<Node,Boolean>();

        /** 初始化標(biāo)志
         */
        Q.add(S);
        visitedMap.put(S, true); // S 已經(jīng)認(rèn)為是在隊(duì)列當(dāng)中砰奕,說明已經(jīng)要被訪問了,所以這么標(biāo)志
        
        while( !Q.isEmpty()) { // (0).隊(duì)列非空
            Node q = Q.poll(); // (1).出隊(duì)列
            visited(q); // (2).這個(gè)就是遍歷函數(shù),或者要通過遍歷來實(shí)現(xiàn)的目標(biāo)的函數(shù),,也可以是其他的過程處理军援,比如計(jì)數(shù)仅淑,求和等
            
            /* (3).把它的相鄰節(jié)點(diǎn),有個(gè)求相鄰節(jié)點(diǎn)的函數(shù)
             * 且未遍歷到的標(biāo)志為待遍歷并放入隊(duì)列Q
             */
            
            ArrayList<Node> adjNodeList = G.getAdjectList(q); // 獲取相鄰的節(jié)點(diǎn)
            for(int i = 0; i < adjNodeList.size() ; ++i) {
                /**沒有被訪問過胸哥,放到隊(duì)列當(dāng)中*/
                if (visitedMap.containsKey(adjNodeList.get(i)) == false) { // 沒有被訪問過
                    Q.add(adjNodeList.get(i)); //加入到隊(duì)列中
                    visitedMap.put(adjNodeList.get(i),true);// 表示即將被訪問
                }
            }//end for
        }// end while
    }
    
    public static void visited(Node s) {
        System.out.println(s); // 這里只是簡單的打印一下涯竟,可進(jìn)行性統(tǒng)計(jì)、計(jì)數(shù)等操作
    }
    

2 圖聯(lián)通量的遍歷

2.1 問題描述

  • 島的個(gè)數(shù)
  • 假設(shè)有一塊地空厌,地中有兩種地方庐船,一種是島:人可以到達(dá),另一種是有水的地方嘲更,人不可以到達(dá)筐钟。每個(gè)島只能從上下左右到達(dá)。
  • 抽象成數(shù)學(xué)問題就是哮内,一個(gè)二維矩陣表示一塊地盗棵,島用'1'表示壮韭,水用'0' 表示北发,島和島之間如果在垂直或者水平方向相鄰,則認(rèn)為這兩個(gè)島是可以組成一個(gè)大島喷屋,現(xiàn)在是要問琳拨,這塊地上有多少個(gè)被水圍起來的孤立大島。
  • 抽象數(shù)據(jù)結(jié)構(gòu)問題:問這個(gè)圖中有多少個(gè)連通分量屯曹,連通指的是狱庇,節(jié)點(diǎn)之間可達(dá)。
11110
11010
11000
00000
answer:1- 1 之間都是可達(dá)的連通的
 
11000
11000
00100
00011
answer:3 左上角的四個(gè)1是連通的恶耽,右下角的11是連通的密任,中間的1和自己是連通的

  • 解答的思路是:通過BFS或者DFS遍歷整個(gè)圖,計(jì)算出需要調(diào)用BFS或者DFS多少次偷俭。
  • 按照上面的BFS模板浪讳,容易寫出代碼,需要注意的是涌萤,這個(gè)題的重點(diǎn)是淹遵,調(diào)用多少次BFS,其實(shí)是標(biāo)志哪些節(jié)點(diǎn)可以再一次BFS調(diào)用中被訪問
  • 調(diào)用一次BFS中被訪問的節(jié)點(diǎn)负溪,是連通的
/*** 一個(gè)圖
 * A----B    D    E
 * |    |    |    |
 * F----G    H    I
 *           |    |
 * J    K    L    M
 * |         |
 * N    O----P----Q
 * x,y 坐標(biāo)的相鄰坐標(biāo)
 * (x-1,y-1)---(x-1,y)----(x,y-1)
 * (x , y-1)---(x , y)----(x,y+1)
 * (x+1,y-1)---(x+1,y)----(x+1,y+1)
 */

public int numIslands(char[][] grid) {
    // 初始化訪問標(biāo)志
    HashMap<String,Boolean> visitedMap = new HashMap<String,Boolean>();
    
    int count = 0;
    for(int x =0; x < grid.length; ++x) {
        for(int y = 0; y < grid[0].length; ++y) {
            if(grid[x][y] == '0') // 只從某個(gè)島('1')開始遍歷
                continue;
            String xykey = x + ":"  + y;
            
            if(visitedMap.containsKey(xykey) == false) {
                // 把連通的島遍歷一下透揣,并標(biāo)志下
                BFS(grid,visitedMap,x,y);
                count ++;// 表示找到了一個(gè)連通分量
            }
        }
    }
    return count;
}

/**BFS的實(shí)現(xiàn)過程*/
public static void BFS(char[][] grid,HashMap<String,Boolean> visitedMap,int startx,int starty) { 
    
    /*初始化隊(duì)列*/
    Queue<NodeValue> Q = new LinkedList<NodeValue>();
    
    NodeValue e = new NodeValue(startx, starty);
    /**1.添加起始遍歷節(jié)點(diǎn)到隊(duì)列
       2.標(biāo)志這個(gè)節(jié)點(diǎn)即將被訪問
       3.這里使用 x +":" + y 的字符串形式作為這個(gè)節(jié)點(diǎn)是否被訪問的表示
     **/
    Q.add(e);
    visitedMap.put(startx + ":" + starty, true);
    
    while(! Q.isEmpty()) {
        NodeValue q = Q.poll();
        //visited(q) 不需要任何訪問    
        /**獲取節(jié)點(diǎn)的鄰接節(jié)點(diǎn)集合*/
        ArrayList<NodeValue> adjlist = getAdjList(grid,q);
        
        for(int i = 0 ;i < adjlist.size(); ++i) {
            e = adjlist.get(i);
            String ekey = e.x +":" + e.y;
            if(visitedMap.containsKey(ekey) == false) { //如果沒有被訪問
                Q.add(e);
                visitedMap.put(ekey , true); // 標(biāo)志即將被訪問
            }
        }
    }
}

/**獲取某個(gè)節(jié)點(diǎn)的鄰接節(jié)點(diǎn)集合*/
public static ArrayList<NodeValue> getAdjList(char[][] grid,NodeValue e) {
    ArrayList<NodeValue> adjlist = new ArrayList<NodeValue>();
    int xlen = grid.length;
    
    int ylen = grid[0].length;
    if(e.x - 1 >=0 ) { // 上面(x-1,y)
        if (grid[e.x -1][e.y] == '1') {
            adjlist.add(new NodeValue(e.x - 1,e.y));
        }
    }
    
    if(e.y - 1 >=0 ) { //左面(x,y - 1)
        if (grid[e.x][e.y - 1 ] == '1') {
            adjlist.add(new NodeValue(e.x,e.y - 1 ));
        }
    }
    
    if(e.x + 1 < xlen ) { // 下面(x+1,y)
        if (grid[e.x + 1][e.y] == '1') {
            adjlist.add(new NodeValue(e.x + 1,e.y));
        }
    }
    
    if(e.y + 1 < ylen ) { // 右面(x,y+1)
        if (grid[e.x ][e.y + 1] == '1') {
            adjlist.add(new NodeValue(e.x,e.y + 1));
        }
    }
    
    return adjlist;
}

/**存儲(chǔ)下x,y的坐標(biāo)*/
static class NodeValue{
    public int x, y;
    public NodeValue(int x,int y){
        this.x = x;
        this.y = y;
    }
}


  • 上述的Hashmap的使用其實(shí)很別扭,標(biāo)志的hashmap其實(shí)川抡,需要起到標(biāo)記作用的數(shù)據(jù)結(jié)構(gòu)都是可以的辐真,所以可以對(duì)于二維表來表示某個(gè)坐標(biāo)表示的島是否被訪問過,
  • 使用數(shù)組標(biāo)記的代碼如下
public int numIslands(char[][] grid) {
   if(grid.length == 0 ){
       return 0;
   }
    /**標(biāo)志數(shù)組*/
    int [][] visitedMap= new int[grid.length][grid[0].length];
    for(int i= 0 ;i< grid.length;++i){
        for(int  j= 0;j < grid[0].length;++j)
            visitedMap[i][j] = 0;// 0:表示為訪問,1:表示訪問到了
    }
    int count = 0;
    for(int x =0; x < grid.length; ++x) {
        for(int y = 0; y < grid[0].length; ++y) {
            if(grid[x][y] == '0')// 忽略是水的地方拆祈,因?yàn)閸u只可能是'1'
                continue;
                            
            if(visitedMap[x][y] == 0 ) {
                BFS(grid,visitedMap,x,y);// 把連通的島遍歷一下恨闪,并標(biāo)志下
                count ++;// 表示找到了一個(gè)連通分量
            }
        }
    }
    return count;
}

public static void BFS(char[][] grid,int [][] visitedMap,int x,int y) { 
    
    Queue<NodeValue> Q = new LinkedList<NodeValue>();
    
    NodeValue e = new NodeValue(x, y);
    Q.add(e);
    visitedMap[x][y] = 1;
    
    while(! Q.isEmpty()) {
        NodeValue q = Q.poll();
        ArrayList<NodeValue> adjlist = getAdjList(grid,q);
        
        for(int i = 0 ;i < adjlist.size(); ++i) {
            e = adjlist.get(i);
            
            if(visitedMap[e.x][e.y] == 0) {
                Q.add(e);
                visitedMap[e.x][e.y] = 1;
            }
        }
    }
    
}

public static ArrayList<NodeValue> getAdjList(char[][] grid,NodeValue e) {
    ArrayList<NodeValue> adjlist = new ArrayList<NodeValue>();
    int xlen = grid.length;
    
    int ylen = grid[0].length;
    if(e.x - 1 >=0 ) { // 上面
        if (grid[e.x -1][e.y] == '1') {
            adjlist.add(new NodeValue(e.x - 1,e.y));
        }
    }
    
    if(e.y - 1 >=0 ) { //左面
        if (grid[e.x][e.y - 1 ] == '1') {
            adjlist.add(new NodeValue(e.x,e.y - 1 ));
        }
    }
    
    if(e.x + 1 < xlen ) { // 下面
        if (grid[e.x + 1][e.y] == '1') {
            adjlist.add(new NodeValue(e.x + 1,e.y));
        }
    }
    
    if(e.y + 1 < ylen ) { // 右面
        if (grid[e.x ][e.y + 1] == '1') {
            adjlist.add(new NodeValue(e.x,e.y + 1));
        }
    }
    
    return adjlist;
}

static class NodeValue{
    public int x, y;
    public NodeValue(int x,int y){
        this.x = x;
        this.y = y;
    }
}
  • 上述題目還有相關(guān)的變形,主要是visited函數(shù)的功能的實(shí)現(xiàn)
  • 相關(guān)題目

3.參考內(nèi)容:

  • 1.算法導(dǎo)論第22章基本圖算法-廣度優(yōu)先搜索
  • 2.leetcode 200題目Number of Islands
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末放坏,一起剝皮案震驚了整個(gè)濱河市咙咽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌淤年,老刑警劉巖钧敞,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異麸粮,居然都是意外死亡溉苛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門弄诲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來愚战,“玉大人,你說我怎么就攤上這事齐遵〖帕幔” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵梗摇,是天一觀的道長拓哟。 經(jīng)常有香客問我,道長伶授,這世上最難降的妖魔是什么断序? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮糜烹,結(jié)果婚禮上违诗,老公的妹妹穿的比我還像新娘。我一直安慰自己疮蹦,他們只是感情好诸迟,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挚币,像睡著了一般亮蒋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妆毕,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天慎玖,我揣著相機(jī)與錄音,去河邊找鬼笛粘。 笑死趁怔,一個(gè)胖子當(dāng)著我的面吹牛湿硝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播润努,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼关斜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了铺浇?” 一聲冷哼從身側(cè)響起痢畜,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鳍侣,沒想到半個(gè)月后丁稀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡倚聚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年线衫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惑折。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡授账,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惨驶,到底是詐尸還是另有隱情白热,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布敞咧,位于F島的核電站棘捣,受9級(jí)特大地震影響辜腺,放射性物質(zhì)發(fā)生泄漏休建。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一评疗、第九天 我趴在偏房一處隱蔽的房頂上張望测砂。 院中可真熱鬧,春花似錦百匆、人聲如沸砌些。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽存璃。三九已至,卻和暖如春雕拼,著一層夾襖步出監(jiān)牢的瞬間纵东,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國打工啥寇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留偎球,地道東北人洒扎。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像衰絮,于是被迫代替她去往敵國和親袍冷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

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