日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深度搜索和广度搜索领接表实现_数据结构与算法--图的搜索(深度优先和广度优先)...

發布時間:2024/9/27 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深度搜索和广度搜索领接表实现_数据结构与算法--图的搜索(深度优先和广度优先)... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

數據結構與算法--圖的搜索(深度優先和廣度優先)

有時候我們需要系統地檢查每一個頂點或者每一條邊來獲取圖的各種性質,為此需要從圖的某個頂點出發,訪遍圖中其余頂點,且使得每一個頂點只被訪問一次,這個過程就稱為圖的搜索或者圖的遍歷。如果限制某個頂點只被訪問一次?我們可以建立一個布爾數組,在某個頂點第一次被訪問時,將該頂點在數組中對應的下標設置為true。圖的搜索通常由兩種方案——深度優先搜索和廣度優先搜索。

深度優先搜索

深度優先搜索(Depth First Search),簡稱DFS,該方法主要思想是:

從某一個頂點開始,選擇一條沒有到達過的頂點(布爾數組中對應的值為false)

標記剛選擇的頂點為“訪問過”(布爾數組中對應的值設置為true)

來到某個頂點,如果該頂點周圍的頂點都訪問過了,返回到上個頂點

當回退后的頂點依然是上述情況,繼續返回

這聽起來像是遞歸。沒錯,代碼確實是遞歸實現的,并且實現起來特別簡單。

package Chap7;

import java.util.Arrays;

import java.util.List;

public class DepthFirstSearch {

// 用來標記已經訪問過的頂點,保證每個頂點值訪問一次

private boolean[] marked;

// s為搜索的起點

public DepthFirstSearch(UndiGraph> graph, int s) {

marked = new boolean[graph.vertexNum()];

dfs(graph, s);

}

private void dfs(UndiGraph> graph, int v) {

// 將剛訪問到的頂點設置標志

marked[v] = true;

// 打印剛訪問的頂點,可換成其他操作

System.out.println(v);

// 從v的所有鄰接點中選擇一個沒有被訪問過的頂點

for (int w : graph.adj(v)) {

if (!marked[w]) {

dfs(graph, w);

}

}

}

public static void main(String[] args) {

List vertexInfo = Arrays.asList("v0", "v2", "v3", "v4", "v5");

int[][] edges = {{0, 1}, {0, 2}, {0, 3},

{1, 3}, {1, 4},

{2, 4}};

UndiGraph graph = new UndiGraph<>(vertexInfo, edges);

DepthFirstSearch search = new DepthFirstSearch(graph, 0);

}

}

從代碼中看出,深度優先搜索其實就兩步:

標記訪問過的頂點

遞歸地訪問當前頂點所有沒有被標記過的鄰居頂點。

在上面的實現中,我們對訪問的每個頂點執行了打印操作。打印只是告訴我們搜索的順序。不過我們很想知道從某個起點開始到另一個頂點的路徑。

為此我們用到了一個edgeTo[]的整型數組,這個數組可以記住每個頂點到起點的路徑,而不是記錄當前頂點到起點的路徑。為了做到這一點在由邊v-w第一次訪問任意w時,將edgeTo[w]設為v,表示v-w是起點s到w的路徑上的最后一條已知的邊。比如0-2-3-5,表示從0到5的路徑,那么edgeTo[5] = 3。同理如果只是到3的路徑,那么edgeTo[3] =2, 到2的路徑是edgeTo[2] = 0。這樣,我們得到的edgeTo[]其實是一棵根結點為起點的樹,而且數組里存的是下標的父結點。就像下圖一樣。

image

edgeTo[1]= 2,而結點1的父結點就是結點2;edgeTo[2] = 0,而頂點2的父結點就是結點0,這和樹的雙親表示法有點類似。不存在給edge[0](根結點)賦值的情況,因為此例中我們的起點是頂點0,所以edgeTo[0]保持默認值0。從樹中可以看出,起點到頂點5的路徑是0-2-3-5。如果我們寫一個方法pathTo(),若傳入5,只能先獲取到edgetTo[5],得到父結點為3,然后根據edgeTo[3]得到父結點2...一直到獲取到根結點,可以看到獲取的順序是從葉子結點到根結點,但是真正輸出路徑的時候是從根結點到葉子結點,所以利用棧(Stack)可以實現這一過程。稍微想一下,先入棧的5被排在了底下,最后入棧的0排在了最頂上,確實是這樣的。

現在來實現。

package Chap7;

import java.util.Arrays;

import java.util.LinkedList;

import java.util.List;

public class DepthFirstSearch {

// 用來標記已經訪問過的頂點,保證每個頂點值訪問一次

private boolean[] marked;

// 起點

private final int s;

// 到該頂點的路徑上的最后一條邊

private int[] edgeTo;

public DepthFirstSearch(UndiGraph> graph, int s) {

this.s = s;

marked = new boolean[graph.vertexNum()];

edgeTo = new int[graph.vertexNum()];

dfs(graph, s);

}

private void dfs(UndiGraph> graph, int v) {

// 將剛訪問到的頂點設置標志

marked[v] = true;

// System.out.println(v);

// 從v的所有鄰接點中選擇一個沒有被訪問過的頂點

for (int w : graph.adj(v)) {

if (!marked[w]) {

edgeTo[w] = v;

dfs(graph, w);

}

}

}

// 連通圖的任意一個頂點都有某條路徑能到達任意一個頂點,如果v在這個連通圖中,必然存在起點到v的路徑

// 現在marked數組中的值都是true,所以數組中若有這個v(在這個連通圖中), 返回true就表示路徑存在

public boolean hasPathTo(int v) {

return marked[v];

}

public Iterable pathTo(int v) {

if (hasPathTo(v)) {

LinkedList path = new LinkedList<>();

for (int i = v; i != s; i = edgeTo[i]) {

path.push(i);

}

// 最后將根結點壓入

path.push(s);

return path;

}

// 到v不存在路徑,就返回null

return null;

}

public void printPathTo(int v) {

System.out.print(s+" to "+ v+": ");

if (hasPathTo(v)) {

for (int i : pathTo(v)) {

if (i == s) {

System.out.print(i);

} else {

System.out.print("-" + i);

}

}

System.out.println();

} else {

System.out.println("不存在路徑!");

}

}

public static void main(String[] args) {

List vertexInfo = Arrays.asList("v0", "v1", "v2", "v3", "v4", "v5");

int[][] edges = {{3, 5},{0, 2}, {0, 1}, {0, 5},

{1, 2}, {3, 4}, {2, 3}, {2, 4}};

UndiGraph graph = new UndiGraph<>(vertexInfo, edges);

DepthFirstSearch search = new DepthFirstSearch(graph, 0);

search.printPathTo(4);

}

}

/* Output

0 to 5: 0-2-3-5

*/

只是在深度優先搜索的實現中新加了一些東西,最重要的是在dfs遞歸方法中插入了一行edgeTo[w] = v;,我們知道w是v的一個鄰接點,那么這行的字面意思就是到w的頂點是v,即路徑v-w。上面提到過這是起點s到w的最后一條邊。

擴展的方法hasPathTo用來判斷是否有到某頂點的路徑,由于這是連通圖,任意一個頂點(包括起點s)都有某條路徑能到達任意一個頂點,在初始化該類時,已經調用過深度優先搜索,所以marked數組里都是true,這意味著只要某個頂點能在marked數組中找到對應的下標,那么返回true,表示肯定存在到它的路徑。

我們的pathTo用來確定一條從起點到指定頂點的路徑,注意這條路徑不一定是最短的,也可能并非是唯一路徑。必須先判斷是否存在到該指定頂點的路徑,如果不存在則返回null;若存在,則從查找的頂點開始入棧,i = edgeTo[i]表示樹向上一層,更新當前值為結點i的父結點,直到根結點停止,由條件i != s可知,根結點并沒有入棧,所以在循環之后要將根結點入棧。

printPathTo就是將pathTo返回的內容格式化輸出,就像這樣。表示頂點0到頂點5的路徑是0-2-3-5。

0 to 5: 0-2-3-5

可見這路徑并不是最短路徑,0-5直接可達才是最短的。

現在我們來看下深度優先搜索的詳細軌跡,注意對照著上圖的鄰接表:先是從起點0開始

因為2是0的鄰接表第一個元素且沒有被標記訪問,則遞歸調用它并標記。edgeTo[2] = 0表示0-2這條路徑。

現在頂點0是頂點2的鄰接表第一個元素,但是0已經被標記了,所以跳過它,看下一個。1沒有被標記,所以遞歸調用它,并標記。edgeTo[1] = 2,表示0-2-1這條路徑。

頂點1的鄰接表元素都被標記過了,所以不再遞歸,方法從dfs(1)中返回到上一個頂點2,現在檢查2的下一個鄰接頂點,3沒被標記所以遞歸它并標記,edgeTo[3] = 2表示0-2-3這條路徑。

頂點5是3的鄰接表第一個元素沒被標記,遞歸調用它并標記,edgeTo[5] = 3表示0-2-3-5這條路徑。

頂點5的鄰接表元素都被標記過了,方法從dfs (5)中返回到帶上一個頂點3,檢查3的鄰接表下一個元素,4沒有被標記,所以遞歸它并標記,edgeTo[4] = 3表示0-2-3-4。至此,所有頂點都被標記過。搜索算是完成了。

DFS的非遞歸版本

DFS也可以自己設一個棧模擬系統棧,下面是非遞歸版本。

/**

* 非遞歸實現DFS

*

* @param graph 圖

* @param s 起點

*/

public void dfs(UndiGraph> graph, int s) {

boolean[] marked = new boolean[graph.vertexNum()];

// 模擬系統棧

LinkedList stack = new LinkedList<>();

// 起點先入棧

stack.push(s);

// 標記訪問

marked[s] = true;

System.out.print(s);

while (!stack.isEmpty()) {

// 取出剛訪問的頂點

int v = stack.peek();

for (int w : graph.adj(v)) {

if (!marked[w]) {

marked[w] = true;

System.out.print(w);

stack.push(w);

// 模擬DFS只存入一個就好,一定要break

break;

}

}

// 所有鄰接點都被訪問過了,模擬遞歸的返回

stack.pop();

}

}

廣度優先搜索

深度優先搜索得到的路徑上面已經看到,并不是最短路徑,很自然地我們對下面的問題感到興趣:單點最短路徑,即給定一個圖和一個起點s,是否存在到給定頂點v的一條路徑,如果有找出最短的那條。

解決這個問題方法是廣度優先搜索(Breadth First Search),簡稱BFS。

這個算法的思想大體是:

從起點開始,標記之并加入隊列。

起點出列,其所有未被標記的鄰接點在被標記后,入列。

隊列頭的元素出列,將該元素的所有未被標記的鄰接點標記后,入列。

如此反復,當隊列為空時,所有頂點也都被標記過了。不像DFS的遞歸那樣隱式地使用棧(系統管理的,以支持遞歸),BFS使用了隊列。

package Chap7;

import java.util.Arrays;

import java.util.LinkedList;

import java.util.List;

import java.util.Queue;

public class BreadthFirstSearch {

// 用來標記已經訪問過的頂點,保證每個頂點值訪問一次

private boolean[] marked;

// 起點

private final int s;

// 到該頂點的路徑上的最后一條邊

private int[] edgeTo;

public BreadthFirstSearch(UndiGraph> graph, int s) {

this.s = s;

marked = new boolean[graph.vertexNum()];

edgeTo = new int[graph.vertexNum()];

bfs(graph, s);

}

public void bfs(UndiGraph> graph, int s) {

marked[s] = true;

// offer入列, poll出列

Queue queue = new LinkedList<>();

queue.offer(s);

while (!queue.isEmpty()) {

int v = queue.poll();

// System.out.print(v+" ");

for (int w: graph.adj(v)) {

if (!marked[w]) {

edgeTo[w] = v;

marked[w] = true;

queue.offer(w);

}

}

}

}

public boolean hasPathTo(int v) {

return marked[v];

}

public Iterable pathTo(int v) {

if (hasPathTo(v)) {

LinkedList path = new LinkedList<>();

for (int i = v; i != s; i = edgeTo[i]) {

path.push(i);

}

// 最后將根結點壓入

path.push(s);

return path;

}

// 到v不存在路徑,就返回null

return null;

}

public void printPathTo(int v) {

System.out.print(s+" to "+ v+": ");

if (hasPathTo(v)) {

for (int i : pathTo(v)) {

if (i == s) {

System.out.print(i);

} else {

System.out.print("-" + i);

}

}

System.out.println();

} else {

System.out.println("不存在路徑!");

}

}

public static void main(String[] args) {

List vertexInfo = Arrays.asList("v0", "v1", "v2", "v3", "v4", "v5");

int[][] edges = {{3, 5},{0, 2}, {0, 1}, {0, 5},

{1, 2}, {3, 4}, {2, 3}, {2, 4}};

UndiGraph graph = new UndiGraph<>(vertexInfo, edges);

BreadthFirstSearch search = new BreadthFirstSearch(graph, 0);

search.printPathTo(5);

}

}

/* Output

0 to 5: 0-5

*/

我把打印操作注釋了,實際上它會輸出如下內容

0 2 1 5 3 4

先是打印了起點,然后依次打印了0的所有鄰接點2、1、5,之后按照隊列的出列順序,打印2的所有未被標記的鄰接點,實際上這已經打印完了所有頂點。而且從代碼里也能看出,不像DFS那樣每次只標記一個頂點,BFS每次都標記了若干頂點。

上面的代碼中,除了bfs的實現代碼,其余有關path的方法可以直接使用DFS中的實現。還是來看下詳細的搜索軌跡。

首先頂點0入列

頂點0出列,將它所有鄰接點2、1、5(參考DFS中的鄰接表圖片),標記他們。且edgeTo[2]、edgeTo[1]、edgeTo[5]的值都設為0

頂點2出列,檢查它的鄰接點,0、1已經被標記,于是將3、4入列,并標記它們。edgeTo[3]、edgeTo[4]的值都設為2

頂點1出列,其鄰接點均已被標記

頂點5出列,其鄰接點均已被標記

頂點3出列,其鄰接點均已被標記

頂點4出列,其鄰接點均已被標記

可以發現,實際上標記工作和edgeTo數組在第三步之后就已經完成,之后的工作只是檢查出列的頂點的鄰接點是否被標記過而已。

我們不妨打印下起點到其余各個頂點的路徑

0 to 5: 0-5

0 to 4: 0-2-4

0 to 3: 0-2-3

0 to 2: 0-2

0 to 1: 0-1

不難發現,這些路徑都是最短路徑。實際上有這么一個命題:對于從s可達的任意頂點v,廣度優先搜索都能找到一條從s到v的最短路徑。

廣度優先搜索是先覆蓋起點附近的頂點,只在鄰近的所有頂點都被訪問后才向前進,其搜索路徑短而直接;而深度優先搜索是尋找離起點更遠的頂點,只有在碰到周圍的鄰接點都被訪問過了才往回退,選一個近處的頂點,繼續深入到更遠的地方,其路徑長而曲折。

以上深度優先和廣度優先的實現對于有向圖也是適用的,把接收的參數的換成有向圖即可。

by @sunhaiyu

2017.9.19

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的深度搜索和广度搜索领接表实现_数据结构与算法--图的搜索(深度优先和广度优先)...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 91午夜影院 | 4438x全国最大成人网 | 岳睡了我中文字幕日本 | av网站入口 | 精品久久免费视频 | 免费成人美女在线观看 | aaaa毛片| 操操操操操操操操操 | 中文在线国产 | 午夜视频在线瓜伦 | 欧美激情在线 | a级黄色网| 亚洲不卡电影 | 最近中文字幕免费视频 | 中文字幕乱妇无码av在线 | 人人做| 国产男女视频在线观看 | 性猛交富婆╳xxx乱大交天津 | 国产做受高潮漫动 | 亚洲人成无码www久久久 | 亚洲av永久无码精品三区在线 | 亚洲清纯唯美 | 成人看片免费 | 国产色在线视频 | 国产性―交―乱―色―情人 | 好色先生视频污 | 天天操天天干天天舔 | 一区二区日韩电影 | 欧美a∨| 成年人在线免费观看网站 | 欧美yyy | 毛片大全免费看 | 中文字幕免费在线看线人 | 黄色aaa毛片 | 九色福利视频 | 波多野在线 | 特黄特色大片免费播放器使用方法 | 亚洲欧美日韩精品在线观看 | 亚洲视频一二区 | 天堂av一区二区三区 | 久久久无码精品亚洲国产 | av日韩在线免费观看 | 欧美人体做爰大胆视频 | av在线看片 | 一本色道久久88 | 国产淫视| 欧美日韩成人精品 | 国产高清视频在线免费观看 | 久久禁 | 97爱视频| 午夜av不卡 | 久久久久亚洲av无码专区桃色 | 人人干视频 | 99精品网 | 一级一级黄色片 | 一区欧美| 欧美成人精品欧美一级乱 | 国产福利91 | 国产精品伦一区二区三区 | 日韩国产一区二区 | 天天操天天干视频 | 内射合集对白在线 | 亚洲网站在线 | xxx一区二区| 人人艹在线 | 日韩六九视频 | 免费大片黄在线观看视频网站 | av在线成人 | 中国超碰 | 在线看黄色网址 | 青青在线播放 | 女人18片毛片60分钟 | 激情片网站 | 不卡av在线播放 | 国产精品久久久久久久无码 | 杨幂毛片午夜性生毛片 | 噜噜噜久久,亚洲精品国产品 | 国产农村妇女精品一区二区 | 干夜夜 | 青青久视频 | 久久精品一区二区国产 | 中文字幕Av日韩精品 | 亚洲片国产一区一级在线观看 | 日本丰满肉感bbwbbwbbw | 午夜亚洲国产 | 国内精品在线观看视频 | 欧美激情videos | 日韩资源网 | 青青草视频在线观看 | 久久噜噜噜精品国产亚洲综合 | 中文字幕亚洲第一 | 亚洲自偷自偷偷色无码中文 | 国产欧美成人 | 国产污视频在线 | 香蕉影院在线观看 | 在线免费黄色网址 | 欧美一级色片 | 国产一区二区视频在线观看 | 久久国产中文字幕 |