Java breadth-first solve the shortest path_algorithm: breadth-first search (BFS) (shortest path)

Algorithm: Breadth First Search (BFS) (shortest path) Algorithm: Breadth First Search (BFS) (shortest path)

Let's look at a case first:

cc58cb6b4f2469f1b3d83bd769d6edf8.png

Traverse a tree structure and output the node content of the tree hierarchically, namely: Desire ABCDEF.

The implementation method is to traverse down from the root node (A), first obtain A, then A's child nodes B and C, followed by B's child node D...

This method of traversing a tree structure or a graph structure is called breadth-first search (BFS), and the corresponding traversal to the lowest child node is depth-first.

Getting Started: BFS Basic Solutions

The BFS core adopts the data structure of the queue. For example, in the above tree structure, the solution is:

A into the queue -> A out of the queue B, C into the queue -> B out of the queue D into the queue -> C out of the queue E, F into the queue -> D, E, F out of the queue

If you want to distinguish between hierarchical edges, use the count parameter.

Solution steps (the blue part is the node that has been processed):

059e3a8727dd02bd5b7a727200eff317.png

a977350be8655f33ed149de65d8c434e.png

f6f49dff575d8c21805a01ebd22cb344.png

af67c152cbf81fa25c4a594d0cdc3266.png

The detailed solution code is:

//Define tree node

public class TreeNode {

int val;

TreeNode left;

TreeNode right;

TreeNode(int x) { val = x; }

}

public List> levelOrder(TreeNode root) {

List> result = new ArrayList<>();

if (root == null) {

return result;

}

Queue queue = new LinkedList<>();

queue.add(root);

while (!queue.isEmpty()) {

List oneLevel = new ArrayList<>();

// Take out all the data of one layer every time

int count = queue.size();

for (int i = 0; i 

TreeNode node = queue.poll();

oneLevel.add(node.val);

if (node.left != null){

queue.add(node.left);

}

if (node.right != null){

queue.add(node.right);

}

}

result.add(oneLevel);

}

return result;

}

进阶:BFS基于图的示例-最短路径

例题

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

每次转换只能改变一个字母。

转换过程中的中间单词必须是字典中的单词。

说明:

如果不存在这样的转换序列,返回 0。

所有单词具有相同的长度。

所有单词只由小写字母组成。

字典中不存在重复的单词。

你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

示例:

输入:beginWord = "hit",endWord = "cog",

wordList = ["hot","dot","dog","lot","log","cog"]

输出: 5

解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",

返回它的长度 5。

基本解法——普通的BFS

不难看出此题是一个很经典的图结构:

f4b2ac88c6cbf29866be6de7d82e406c.png

很容易想到这种题可以用广度优先解法,从start处的hit遍历每一种可能,直到匹配到cog。解法如下(其中提到了剪枝//TODO,因为此题实际不是要走全部节点的广度优先):

class Solution {

public int ladderLength(String beginWord, String endWord, List wordList) {

Queue queue = new LinkedList();

//初始值入队列

queue.offer(beginWord);

int result=1;

//观察题目,其实要求结果在词典中

if(!wordList.contains(endWord)){

return 0 ;

}

while(true){

int length=queue.size();

//全部单词遍历也没结果

if(length==0){

return 0;

}

for(int i=0;i

String a=queue.poll();

//成功转换到了结果值

if(canChange(a,endWord)){

return result+1;

}

for(int z=0;z

if(canChange(a,wordList.get(z))){

queue.offer(wordList.get(z));

//不要重复使用词典中的词,这只会徒增时间复杂度,这是一种 剪枝手段

wordList.set(z,null);

}

}

}

result++;

}

}

/***

*  校验是否符合转换的标准

**/

private boolean canChange(String a,String b){

if(b==null){

return false;

}

int i=0;

for(int q=0;q

if(a.charAt(q)!=b.charAt(q)){

i++;

}if(i>1){

return false;

}

}

return true;

}

}

更快的解法——双向BFS、通用状态

对于上面的单向搜索,当图结构越发复杂时,随着搜索深度的提高,其搜索每一层锁付出的代价也越大,为此我们可以考虑从两端开始分别进行广度优先遍历,这样在多数情况下能节省很多时间,免去不必要的搜索(事实上,这也是一种剪枝手段)。

9089a2fd2d42b85d24d826823ae7659f.png

在上面的图例中,分别使用了从起点(S)和终点(E)进行广度优先搜索的结果,便利了很多多余的节点,但是如果我们使用双向BFS,两边以同时进行搜索(可以每次选择子节点最少的端搜索),效率一般会大大提升:

0007ad239ae719d92eb2a4455911e5a5.png

同时,我们可以根据相应的测试用例改变单词匹配的方式,因为字母数量是已知的(26个),但是字典数据的长度却是未知的,我们使用上面的比较方法即下图的方法1,在首次出队列进行单词匹配时,其时间复杂度为O(M*N),M代表词典长度,N代表单词的字母数。如果我们使用方法2,对于每一个节点不是去遍历整个词典,而是去遍历符合其转换条件的所有单词,并且查看单词是否在词典中,其时间复杂度固定为)O(26*N),虽然每一个节点的遍历复杂度都是一样的,但是对于词典长度较长的用例,显然后者的效率更高且稳定。再加上双端便利,效率提升很大。

89a4f9f78c4f8a4e2aa33020212edf11.png

最终的解法:

class Solution {

public int ladderLength(String beginWord, String endWord, List wordList) {

if (wordList == null || !wordList.contains(endWord)) {

return 0;

}

//转为Set提高查询速度

Set allWord = new HashSet<>(wordList);

//将beginWord加入list

wordList.add(beginWord);

//begin的队列 初始化

Queue queue1 = new LinkedList<>();

//因为用这种方式不知道什么时候才会匹配到,所以需要一个Set记录遍历过的元素

Set visited1 = new HashSet<>();

queue1.add(beginWord);

visited1.add(beginWord);

//end的队列 初始化

Queue queue2 = new LinkedList<>();

Set visited2 = new HashSet<>();

queue2.add(endWord);

visited2.add(endWord);

int depth = 0;

while (!queue1.isEmpty() && !queue2.isEmpty()) {

//将节点更少的作为1队列 交换二者

if (queue1.size() > queue2.size()) {

Queue temp = queue1;

queue1 = queue2;

queue2 = temp;

Set set = visited1;

visited1 = visited2;

visited2 = set;

}

//遍历上面拿到的节点最少的层

depth++;

int size = queue1.size();

while (size-- > 0) {

String poll = queue1.poll();

char[] chars = poll.toCharArray();

//遍历poll的每个字符

for (int i = 0; i 

//保存第i个字符,判断结束后需要还原

char temp = chars[i];

//将poll的每个字符逐个替换为其他字符

for (char c = 'a'; c <= 'z'; c++) {

chars[i] = c;

//判断替换后的单词

String newString = new String(chars);

if (visited1.contains(newString)) {

continue;

}

if (visited2.contains(newString)) {

return depth + 1;

}

//如果替换后的单词,存在字典中,则入队并标记访问

if (allWord.contains(newString)) {

queue1.add(newString);

visited1.add(newString);

}

}

//restore the i-th character

chars[i] = temp;

}

}

}

return 0;

}

}

63f00b5bd745d07b2d8216bdb3279c6b.png2020-06-05 Yuyu

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324163617&siteId=291194637