《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)

目录

图简介

图是什么

广度优先搜索

查找最短路径

队列

实现图

实现算法

运行时间

小结

示例代码

C++

Python

C#

Java

JS


 

广度优先搜索让你能够找出两样东西之间的最短距离,不过最短距离的含义有很多!使用广度优先搜索可以:
 编写国际跳棋AI,计算最少走多少步就可获胜;
 编写拼写检查器,计算最少编辑多少个地方就可将错拼的单词改成正确的单词,如将
READED改为READER需要编辑一个地方;
 根据你的人际关系网络找到关系最近的医生。
 

图简介
 

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第1张图片

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第2张图片

 

还有其他前往金门大桥的路线,但它们更远(需要四步)。这个算法发现,前往金门大桥的
最短路径需要三步。这种问题被称为最短路径问题(shorterst-path problem)。你经常要找出最短
路径,这可能是前往朋友家的最短路径,也可能是国际象棋中把对方将死的最少步数。解决最短
路径问题的算法被称为广度优先搜索
 

要确定如何从双子峰前往金门大桥,需要两个步骤。
(1) 使用图来建立问题模型。
(2) 使用广度优先搜索解决问题。
 

图是什么
 

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第3张图片

就这么简单!图由节点和边组成。一个节点可能与众多节点直接相连,这些节点被称为邻居。
在前面的欠钱图中, Rama是Alex的邻居。 Adit不是Alex的邻居,因为他们不直接相连。但Adit既
是Rama的邻居,又是Tom的邻居。
 

广度优先搜索
 

广度优先搜索是一种用于图的查找算法,可帮助回答两类问题。
 第一类问题:从节点A出发,有前往节点B的路径吗?
 第二类问题:从节点A出发,前往节点B的哪条路径最短?
《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第4张图片

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第5张图片

这样一来,你不仅在朋友中查找,还在朋友的朋友中查找。别忘了,你的目标是在你的人际
关系网中找到一位芒果销售商。因此,如果Alice不是芒果销售商,就将其朋友也加入到名单中。
这意味着你将在她的朋友、朋友的朋友等中查找。使用这种算法将搜遍你的整个人际关系网,直
到找到芒果销售商。这就是广度优先搜索算法
 

查找最短路径
 

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第6张图片

在你看来,一度关系胜过二度关系,二度关系胜过三度关系,以此类推。因此,你应先在一
度关系中搜索,确定其中没有芒果销售商后,才在二度关系中搜索。广度优先搜索就是这样做的!
在广度优先搜索的执行过程中,搜索范围从起点开始逐渐向外延伸,即先检查一度关系,再检查
二度关系。顺便问一句:将先检查Claire还是Anuj呢? Claire是一度关系,而Anuj是二度关系,因
此将先检查Claire,后检查Anuj。
 

你按顺序依次检查名单中的每个人,看看他是否是芒果销售商。这将先在一度关系中查找,
再在二度关系中查找,因此找到的是关系最近的芒果销售商。广度优先搜索不仅查找从A到B的
路径,而且找到的是最短的路径。
 

注意,只有按添加顺序查找时,才能实现这样的目的。换句话说,如果Claire先于Anuj加入
名单,就需要先检查Claire,再检查Anuj。如果Claire和Anuj都是芒果销售商,而你先检查Anuj
再检查Claire,结果将如何呢?找到的芒果销售商并非是与你关系最近的,因为Anuj是你朋友的
朋友,而Claire是你的朋友。因此,你需要按添加顺序进行检查。有一个可实现这种目的的数据
结构,那就是队列(queue)
 

队列
 

队列的工作原理与现实生活中的队列完全相同。
假设你与朋友一起在公交车站排队,如果你排在他前
面,你将先上车。队列的工作原理与此相同。队列类
似于栈,你不能随机地访问队列中的元素。队列只支
持两种操作: 入队和出队。
 

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第7张图片

 

实现图
 

首先,需要使用代码来实现图。图由多个节点组成。
每个节点都与邻近节点相连,如果表示类似于“你→Bob”
这样的关系呢?好在你知道的一种结构让你能够表示这种关
系,它就是散列表!
记住,散列表让你能够将键映射到值。在这里,你要将节
点映射到其所有邻居。
 

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第8张图片

Anuj、 Peggy、 Thom和Jonny都没有邻居,这是因为虽然有指向他们的箭头,但没有从他们
出发指向其他人的箭头。这被称为有向图(directed graph) ,其中的关系是单向的。因此, Anuj
是Bob的邻居,但Bob不是Anuj的邻居。 无向图(undirected graph)没有箭头,直接相连的节点互
为邻居。例如,下面两个图是等价的。

 

graph = {}
graph["you"] = ["alice", "bob", "claire"]

print(graph["you"])

for items in graph["you"]:
    print(items)

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第9张图片

实现算法
 

先概述一下这种算法的工作原理。
 

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第10张图片

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第11张图片

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第12张图片

这个算法将不断执行,直到满足以下条件之一:
 找到一位芒果销售商;
 队列变成空的,这意味着你的人际关系网中没有芒果销售商。
Peggy既是Alice的朋友又是Bob的朋友,因此她将被加入队列两次:一次是在添加Alice的朋
友时,另一次是在添加Bob的朋友时。因此,搜索队列将包含两个Peggy。但你只需检查Peggy一次,看她是不是芒果销售商。如果你检查两次,就做了无用功。因此,检查完一个人后,应将其标记为已检查,且不再检查他。
如果不这样做,就可能会导致无限循环。假设你的人际关系网类似于下面这样。

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第13张图片

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第14张图片

 

运行时间
 

如果你在你的整个人际关系网中搜索芒果销售商,就意味着你将沿每条边前行(记住,边是
从一个人到另一个人的箭头或连接),因此运行时间至少为O(边数)。
你还使用了一个队列,其中包含要检查的每个人。将一个人添加到队列需要的时间是固定的,
即为O(1),因此对每个人都这样做需要的总时间为O(人数)。所以,广度优先搜索的运行时间为
O(人数 + 边数),这通常写作O(V + E),其中V为顶点(vertice)数, E为边数。
 

小结
 

 广度优先搜索指出是否有从A到B的路径。
 如果有,广度优先搜索将找出最短路径。
 面临类似于寻找最短路径的问题时,可尝试使用图来建立模型,再使用广度优先搜索来
解决问题。
 有向图中的边为箭头,箭头的方向指定了关系的方向,例如, rama→adit表示rama欠adit钱。
 无向图中的边不带箭头,其中的关系是双向的,例如, ross - rachel表示“ross与rachel约
会,而rachel也与ross约会”。
 队列是先进先出(FIFO)的。
 栈是后进先出(LIFO)的。
 你需要按加入顺序检查搜索列表中的人,否则找到的就不是最短路径,因此搜索列表必
须是队列。
 对于检查过的人,务必不要再去检查,否则可能导致无限循环
 

示例代码

C++

#include 
#include 
#include 
#include 
using namespace std;
template 
class Graph {
	map > adjList;

public:
	Graph()
	{}

	void addEdge(T u, T v, bool bidir = true)
	{
		adjList[u].push_back(v);
		if (bidir)
			adjList[v].push_back(u);
	}

	void printAdjList()
	{
		for (auto key : adjList)
		{
			cout << key.first << "->";
			for (auto neighbours : key.second)
				cout << neighbours << ",";

			cout << endl;
		}
	}

	void bfs(T src)
	{
		queue q;

		map visited;

		q.push(src);
		visited[src] = true;

		while (!q.empty())
		{
			T node = q.front();
			cout << node << " ,";
			q.pop();

			//push the neighbours

			for (auto neighbours : adjList[node])
			{
				if (!visited[neighbours])
				{
					q.push(neighbours);
					visited[neighbours] = true;
				}
			}
		}
	}
};

int main() {
	Graph g;

	//adding the edges in the Graph
	g.addEdge(0, 1);
	g.addEdge(1, 2);
	g.addEdge(0, 4);
	g.addEdge(2, 4);
	g.addEdge(2, 3);
	g.addEdge(3, 5);
	g.addEdge(3, 4);

	cout << "The Graph is" << endl;
	g.printAdjList();
	cout << endl;

	cout << "The Breadth First Search from Node 0" << endl;

	g.bfs(0);

	system("pause");
}

《图解算法》学习笔记之广度优先搜索(breadth-first search, BFS)_第15张图片

C++11

#include 
#include 
#include 
#include 
#include 
#include 

using std::cout;
using std::endl;

bool is_seller(const std::string& name) {
	return name.back() == 'm';
}

template 
bool search(const T& name, const std::unordered_map>& graph) {
	std::queue search_queue;
	std::unordered_set searched;

	// add all friends to search queue
	for (auto friend_name : graph.find(name)->second) {
		search_queue.push(friend_name);
	}

	while (!search_queue.empty()) {
		T person = search_queue.front();
	 //	T& person = search_queue.front();
		search_queue.pop();

		// only search this person if you haven't already searched them.
		if (searched.find(person) == searched.end()) {
			if (is_seller(person)) {
				cout << person << " is a mango seller!" << endl;
				return true;
			}
			std::vector friend_list = graph.find(person)->second;

			// add all friends of a person to search queue
			for (T friend_name : friend_list) {
				search_queue.push(friend_name);
			}

			// mark this person as searched
			searched.insert(person);
		}
	}

	return false;
}

int main() {
	std::unordered_map> graph;
	graph.insert({ "you",{ "alice", "bob", "claire" } });
	graph.insert({ "bob",{ "anuj", "peggy" } });
	graph.insert({ "alice",{ "peggy" } });
	graph.insert({ "claire",{ "thom", "jonny" } });
	graph.insert({ "anuj",{} });
	graph.insert({ "peggy",{} });
	graph.insert({ "thom",{} });
	graph.insert({ "jonny",{} });

	std::string name = "you";
	bool result = search(name, graph);
	cout << "Found mango seller: " << result << endl;
}

Python

from collections import deque

def person_is_seller(name):
      return name[-1] == 'm'

graph = {}
graph["you"] = ["alice", "bob", "claire"]
graph["bob"] = ["anuj", "peggy"]
graph["alice"] = ["peggy"]
graph["claire"] = ["thom", "jonny"]
graph["anuj"] = []
graph["peggy"] = []
graph["thom"] = []
graph["jonny"] = []

def search(name):
    search_queue = deque()
    search_queue += graph[name]
    # This array is how you keep track of which people you've searched before.
    searched = []
    while search_queue:
        person = search_queue.popleft()
        # Only search this person if you haven't already searched them.
        if person not in searched:
            if person_is_seller(person):
                print person + " is a mango seller!"
                return True
            else:
                search_queue += graph[person]
                # Marks this person as searched
                searched.append(person)
    return False

search("you")

C#

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication
{
    public class Program
    {
        private static Dictionary _graph = new Dictionary();

        public static void Main(string[] args)
        {
            _graph.Add("you", new[] { "alice", "bob", "claire" });
            _graph.Add("bob", new[] { "anuj", "peggy" });
            _graph.Add("alice", new[] { "peggy" });
            _graph.Add("claire", new[] { "thom", "jonny" });
            _graph.Add("anuj", Array.Empty());
            _graph.Add("peggy", Array.Empty());
            _graph.Add("thom", Array.Empty());
            _graph.Add("jonny", Array.Empty());
            Search("you");
        }

        private static bool Search(string name)
        {
            var searchQueue = new Queue(_graph[name]);
            var searched = new List();
            while (searchQueue.Any())
            {
                var person = searchQueue.Dequeue();
                if (!searched.Contains(person))
                {
                    if (PersonIsSeller(person))
                    {
                        Console.WriteLine($"{person} is a mango seller");
                        return true;
                    }
                    else
                    {
                        searchQueue = new Queue(searchQueue.Concat(_graph[person]));
                        searched.Add(person);
                    }
                }
            }
            return false;
        }

        private static bool PersonIsSeller(string name)
        {
            return name.EndsWith("m");
        }
    }
}

Java

 

import java.util.*;

public class BreadthFirstSearch {
    private static Map> graph = new HashMap<>();

    private static boolean search(String name) {
        Queue searchQueue = new ArrayDeque<>(graph.get(name));
        // This list is how you keep track of which people you've searched before.
        List searched = new ArrayList<>();

        while (!searchQueue.isEmpty()) {
            String person = searchQueue.poll();
            // Only search this person if you haven't already searched them
            if (!searched.contains(person)) {
                if (person_is_seller(person)) {
                    System.out.println(person + " is a mango seller!");
                } else {
                    searchQueue.addAll(graph.get(person));
                    // Marks this person as searched
                    searched.add(person);
                }
            }
        }

        return false;
    }

    private static boolean person_is_seller(String name) {
        return name.endsWith("m");
    }

    public static void main(String[] args) {
        graph.put("you", Arrays.asList("alice", "bob", "claire"));
        graph.put("bob", Arrays.asList("anuj", "peggy"));
        graph.put("alice", Arrays.asList("peggy"));
        graph.put("claire", Arrays.asList("thom", "jonny"));
        graph.put("anuj", Collections.emptyList());
        graph.put("peggy", Collections.emptyList());
        graph.put("thom", Collections.emptyList());
        graph.put("jonny", Collections.emptyList());

        search("you");
    }
}

 

JS

'use strict';

function person_is_seller(name) {
  return name[name.length-1] === 'm';
}

const graph = {};
graph["you"] = ["alice", "bob", "claire"];
graph["bob"] = ["anuj", "peggy"];
graph["alice"] = ["peggy"];
graph["claire"] = ["thom", "jonny"];
graph["anuj"] = [];
graph["peggy"] = [];
graph["thom"] = [];
graph["jonny"] = [];


function search(name) {
  let search_queue = [];
  search_queue = search_queue.concat(graph[name]);
  // This array is how you keep track of which people you've searched before.
  const searched = [];
  while (search_queue.length) {
    let person = search_queue.shift();
    // Only search this person if you haven't already searched them
    if (searched.indexOf(person) === -1) {
      if (person_is_seller(person)) {
        console.log(person + ' is a mango seller!');
        return true;
      } else {
        search_queue = search_queue.concat(graph[person]);
        // Marks this person as searched
        searched.push(person);
      }
    }
  }
  return false;
}


search('you'); // thom is a mango seller!


 

你可能感兴趣的:(Python,C/C++,C#,Java,JavaScript,数学与实验数据处理)