• 二叉树上的宽搜 BFS in Binary Tree
• 图上的宽搜 BFS in Graph
• 拓扑排序 Topological Sorting
• 棋盘上的宽搜 BFS
若是求最短路径,则很大可能是使用宽度优先搜索,也可能是使用DP
若是求最长路径,则很大可能是使用深度优先搜索,也可能是使用DP
1.二叉树的层次遍历
2.二叉树的锯齿形层次遍历
3.图的节点,边的数据结构表示以及BFS
4.拓扑排序
5.课程表I,II
6.单词接龙
题目一:二叉树的层次遍历
https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
class Solution {
public:
/**
* @param root: A Tree
* @return: Level order a list of lists of integer
*/
vector<vector<int>> levelOrder(TreeNode * root) {
// write your code here
vector<vector<int>> res;
if(root==NULL) return res;
queue<TreeNode*> m_queue;
m_queue.push(root);
while(!m_queue.empty()){
int size = m_queue.size();
vector<int> tmp;
for(int i = 0 ; i < size; i++){
TreeNode* curNode = m_queue.front();
m_queue.pop();
int curValue = curNode->val;
tmp.push_back(curValue);
if(curNode->left!=NULL){
m_queue.push(curNode->left);
}
if(curNode->right!=NULL){
m_queue.push(curNode->right);
}
}
res.push_back(tmp);
}
return res;
}
};
题目2: 二叉树的锯齿形层次遍历
https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
vector<vector<int>> res;
if(root==NULL) return res;
queue<TreeNode*> m_queue;
m_queue.push(root);
int curLevel = 0;
while(!m_queue.empty()){
int size = m_queue.size();
vector<int> tmp;
for(int i = 0 ; i < size; i++){
TreeNode* curNode = m_queue.front();
m_queue.pop();
int curValue = curNode->val;
if(curNode->left!=NULL){
m_queue.push(curNode->left);
}
if(curNode->right!=NULL){
m_queue.push(curNode->right);
}
tmp.push_back(curValue);
}
res.push_back(tmp);
}
//改变的部分
int level = 1;
for(int i = 0 ; i < res.size(); i++){
level++;
if(level%2){
reverse(res[i].begin(),res[i].end());
}
}
return res;
}
};
图的BFS和树的BFS有什么区别呢?图可能回到原来的节点,而树则不会,树是一直往下的。可使用hashmap或者数组来进行标记。这里我们写一个图的模板,以后任何关于图的结构就都使用这个模板。
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include<queue>
using namespace std;
// To execute C++, please define "int main()"
class GraphNode;
class GraphEdge;
class GraphNode{
public:
int value;
int in ; //入度
int out;//出度
vector<GraphNode*> nexts;//相邻的顶点
vector<GraphEdge*> edges;//相邻的边
GraphNode(int value){
this->value = value;
this->in = 0;
this->out = 0;
}
};
class GraphEdge{
public:
int weight; //边的权重
GraphNode* from;
GraphNode* to;
GraphEdge(int weight,GraphNode* from, GraphNode* to){
this->weight = weight;
this->from = from;
this->to = to;
}
};
class Graph{
public:
unordered_map<int,GraphNode*> NodeMap;
unordered_set<GraphEdge*> Edges;
};
Graph* GenerateGraph(vector<vector<int>>& edges){
Graph* G = new Graph();
for(int i = 0 ; i < edges.size();i++){
int weight = edges[i][0];
int from = edges[i][1];
int to = edges[i][2];
if(!G->NodeMap.count(from)){
//如果图中第一次出现这个点
G->NodeMap.insert({from,new GraphNode(from)});
}
if(!G->NodeMap.count(to)){
G->NodeMap.insert({to,new GraphNode(to)});
}
GraphNode* fromNode = G->NodeMap.find(from)->second;//这样的写法实际上有点危险,因为可能find不到东西,这个时候->就可能报错了 取决你的输入能都正确了,比如1 []
GraphNode* toNode = G->NodeMap.find(to)->second;
fromNode->out++;
fromNode->nexts.push_back(toNode);//有向图
toNode->in++;
GraphEdge* newEdge = new GraphEdge(weight,fromNode,toNode);
fromNode->edges.push_back(newEdge);
G->Edges.insert(newEdge);
}
return G;
}
/*
测试代码:使用BFS进行测试是否正确
*/
class BFS{
public:
void bfs(GraphNode* root){
if(root == NULL) return;
queue<GraphNode*> m_queue;
unordered_set<GraphNode*> m_set;
m_queue.push(root);
while(!m_queue.empty()){
GraphNode* curNode = m_queue.front();
/*
这里的输出是为了验证代码是否正确
*/
for(auto edge: curNode->edges){
cout << edge->weight << " "
<< edge->from->value << " "
<< edge->to->value << endl;
}
m_queue.pop();
for(auto next : curNode->nexts){/*图的宽度优先算法会判断该点之前是否进入过*/
if(!m_set.count(next)){
m_set.insert(next);
m_queue.push(next);
}
}
}
}
};
int main() {
//weight from to
vector<vector<int>> edges{
{1,0,1},
{2,0,3},
{3,1,3},
{4,0,5}};
Graph* G = GenerateGraph(edges);
BFS m_bfs;
m_bfs.bfs(G->NodeMap.find(0)->second);
return 0;
}
最终的输出为,与输入一致....
1 0 1
2 0 3
4 0 5
3 1 3
题目3:验证一个图是都是树
https://www.lintcode.com/problem/graph-valid-tree/description
class Solution {
public:
/**
* @param n: An integer
* @param edges: a list of undirected edges
* @return: true if it's a valid tree, or false
*/
bool validTree(int n, vector<vector<int>> &edges) {
// write your code here
if(n == 0 ) return false;
if(n != edges.size()+1) return false;
if(n == 1 && edges.size() == 0) return true;//为了处理 1 []的情况
unordered_map<int,unordered_set<int>> Graph;
for(int i = 0 ; i < edges.size(); i++){
int from = edges[i][0];
int to = edges[i][1];
if(!Graph.count(from)){
Graph.insert({from,unordered_set<int>()});
}
if(!Graph.count(to)){
Graph.insert({to,unordered_set<int>()});
}
Graph.find(from)->second.insert(to);
Graph.find(to)->second.insert(from);
}
queue<int> m_queue;
unordered_set<int> visited;
m_queue.push(0);//因为标号从0~n-1开始
visited.insert(0);
while(!m_queue.empty()){
int curNode = m_queue.front();
m_queue.pop();
// if(Graph.find(curNode) == Graph.end()) brea;//这句非常关键,可能会出现 1 []这种情况,但是如果一开始就截段了就不需要加了
unordered_set<int> adj = Graph.find(curNode)->second;
for(auto item : adj){
if(!visited.count(item)){//因为是无向图,所以需要加一个visit来验证之前是否看过
visited.insert(item);
m_queue.push(item);
}
}
}
return visited.size() == n;
}
};
官方的解答则更为优雅,主要是由于官方的图结构使用vector<unordered_Set<int>>来表示.
// bfs version
class Solution {
public:
bool validTree(int n, vector<pair<int, int>>& edges) {
vector<unordered_set<int>> g(n, unordered_set<int>());
unordered_set<int> v;
queue<int> q;
q.push(0);
v.insert(0);
for (auto a : edges) {
g[a.first].insert(a.second);
g[a.second].insert(a.first);
}
while (!q.empty()) {
int t = q.front(); q.pop();
for (auto a : g[t]) {
if (v.find(a) != v.end()) return false;
v.insert(a);
q.push(a);
g[a].erase(t);
}
}
return v.size() == n;
}
};
也可以使用并查集进行解决
// union find version
class Solution {
public:
/**
* @param n an integer
* @param edges a list of undirected edges
* @return true if it's a valid tree, or false
*/
bool validTree(int n, vector<vector<int>>& edges) {
// Write your code here
vector<int> root(n, -1);
for(int i = 0; i < edges.size(); i++) {
int root1 = find(root, edges[i][0]);
int root2 = find(root, edges[i][1]);
if(root1 == root2)
return false;
root[root1] = root2;
}
return edges.size() == n - 1;
}
int find(vector<int> &root, int e) {
if(root[e] == -1)
return e;
else
return root[e] = find(root, root[e]);
}
};
题目4:克隆图
1.先把节点的对应关系表构造出来
2.然后把节点的边关系表构造出来
class Solution {
public:
Node* cloneGraph(Node* node) {
if(node == nullptr) return nullptr;
//第一次遍历找到相互之前的对应关系
unordered_map<Node*,Node*> TransformRule;
queue<Node*> m_queue;
unordered_set<Node*> m_set;
m_queue.push(node);
m_set.insert(node);
while(!m_queue.empty()){
Node* curNode = m_queue.front();
m_queue.pop();
Node* cloneNode = new Node(curNode->val);
TransformRule.insert({curNode,cloneNode});
for(auto neighbor : curNode->neighbors){
if(!m_set.count(neighbor)){
m_set.insert(neighbor);
m_queue.push(neighbor);
}
}
}
//至此完成了转换关系,接下来负责边的连接
for(auto item : m_set){
for(auto neighbor: item->neighbors){
TransformRule.find(item)->second->neighbors.push_back( TransformRule.find(neighbor)->second );
}
}
return TransformRule.find(node)->second;
}
};
官方解法则更加优雅,在建立关系表的同时边的关系也确定下来了。主要就是编程思路不一样,在压入节点之前就已经把关系建立好了
class Solution {
public:
/*
* @param node: A undirected graph node
* @return: A undirected graph node
*/
UndirectedGraphNode* cloneGraph(UndirectedGraphNode* node) {
if (!node) {
return nullptr;
}
UndirectedGraphNode *p1 = node;
UndirectedGraphNode *p2 = new UndirectedGraphNode(node->label);
unordered_map<UndirectedGraphNode*, UndirectedGraphNode*> map;
queue<UndirectedGraphNode*> q;
q.push(node);
map[node] = p2;
while (!q.empty()) {
p1 = q.front(); p2 = map[p1];
q.pop();
for (int i = 0; i < p1->neighbors.size(); i++) {
UndirectedGraphNode *nb = p1->neighbors[i];
if (map.count(nb)) {
p2->neighbors.push_back(map[nb]);
} else {
UndirectedGraphNode *temp = new UndirectedGraphNode(nb->label);
p2->neighbors.push_back(temp);
map[nb] = temp;
q.push(nb);
}
}
}
return map[node];
}
};
题目4:课程表 https://leetcode-cn.com/problems/course-schedule/
拓扑排序
当然你可以按照图的模板慢慢写出来,但实际上我们写代码的时候只是用基本的结构就能代表了一个图了.
unordered_map<int,int> NodeToNum;//节点的数量
unordered_map<int,unordered_set<int>> neighbors;//节点的邻居
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
unordered_map<int,int> NodeToNum;//节点的数量
unordered_map<int,unordered_set<int>> neighbors;//节点的邻居
for(int i = 0 ;i < prerequisites.size(); i++){
int from = prerequisites[i][0];
int to = prerequisites[i][1];
if(!NodeToNum.count(from)){
NodeToNum.insert({from,0});
}
if(!NodeToNum.count(to)){
NodeToNum.insert({to,0});
}
NodeToNum[to]++;
neighbors[from].insert(to);
}
//至此图节点的对应关系已经
queue<int> m_queue;
for(int i = 0 ; i < NodeToNum.size(); i++){
if(NodeToNum[i] == 0){
m_queue.push(i);
}
}
//至此入度为0的点就进入了队列
while(!m_queue.empty()){
int curNode = m_queue.front();//查看当前节点的邻居
m_queue.pop();
for(int item : neighbors[curNode]){
NodeToNum[item]--;
if(NodeToNum[item]==0){
m_queue.push(item);
}
}
NodeToNum.erase(curNode);
}
return NodeToNum.size() == 0 ;
}
};
题目5: 课程表II,需要考虑一些特殊的处理
输入
3 [[1,0]]
输出
[0,1]
预期结果
[2,0,1]
class Solution {
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
unordered_set<int> visited;
unordered_map<int,int> NodeToNum;//节点的数量
unordered_map<int,unordered_set<int>> neighbors;//节点的邻居
for(int i = 0 ;i < prerequisites.size(); i++){
int to = prerequisites[i][0];
int from = prerequisites[i][1];
if(!NodeToNum.count(from)){
NodeToNum.insert({from,0});
}
if(!NodeToNum.count(to)){
NodeToNum.insert({to,0});
}
NodeToNum[to]++;
neighbors[from].insert(to);
}
//至此图节点的对应关系已经
queue<int> m_queue;
for(int i = 0 ; i < NodeToNum.size(); i++){
if(NodeToNum[i] == 0){
m_queue.push(i);
}
}
//至此入度为0的点就进入了队列
vector<int> res;
while(!m_queue.empty()){
int curNode = m_queue.front();//查看当前节点的邻居
res.push_back(curNode);
visited.insert(curNode);
m_queue.pop();
for(int item : neighbors[curNode]){
NodeToNum[item]--;
if(NodeToNum[item]==0){//因为是有xiang
m_queue.push(item);
}
}
NodeToNum.erase(curNode);
}
//如果存在环
if(NodeToNum.size() != 0){
return vector<int>();
}
for(int i = 0 ; i < numCourses; i++ ){
if(!visited.count(i)){
res.push_back(i);
}
}
return res ;
}
};
官方给出了一个非常优雅的实现
class Solution {
public:
/**
* @param numCourses a total of n courses
* @param prerequisites a list of prerequisite pairs
* @return true if can finish all courses or false
*/
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
// Write your code here
vector<unordered_multiset<int>> edges(numCourses);
vector<int> d(numCourses, 0);
for(int i = 0; i < prerequisites.size(); ++ i) {
edges[prerequisites[i].second].insert(prerequisites[i].first);
d[prerequisites[i].first] ++;
}
queue<int> q;
for (int i = 0; i < numCourses; ++i)
if (d[i] == 0)
q.push(i);
int node = 0;
while (!q.empty()) {
int x = q.front(); q.pop();
node ++;
for(auto it = edges[x].begin(); it != edges[x].end(); ++ it) {
-- d[*it];
if (d[*it] == 0) {
q.push(*it);
}
}
}
return node == numCourses;
}
};
题目6:序列重构https://www.lintcode.com/problem/sequence-reconstruction/description
判断是否只存在一个拓扑排序的序列
只需要保证队列中一直最多只有1个元素即可
在写程序的时候遇到了一个bug,注意以后程序不能这样写
int main() {
vector<int> org{1};
unordered_map<int,unordered_set<int>> m_map;
unordered_map<int,int> indegree;
for(auto i : org){
m_map.insert({i,unordered_set<int>()});
indegree[i] = 0;
}
queue<int> m_queue;
std::cout << "indegree.size : " << indegree.size() << std::endl;
for(int i = 0 ; i < indegree.size(); i++){//这里hellp会输出两次,indegree[]本来是不存在Indegree[0]的
if(indegree[i] == 0) {
m_queue.push(i);
cout<< "hello " <<endl;
}
}
cout << "===============" << endl;
return 0;
}
题目7: 单词接龙 https://leetcode-cn.com/problems/word-ladder/
这真的只是中等题??
1.构建一个预处理数组,然后再进行宽度优先遍历/
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出: 5
解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
返回它的长度 5。
思路:实际上还是进行暴力搜索,然后查看
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
if(beginWord == endWord){
return 1;
}
if(wordList.size() == 0){
return 0;
}
unordered_set<string> m_set;
for(int i = 0 ; i < wordList.size(); i++){
m_set.insert(wordList[i]);
}
queue<string> m_queue;
m_queue.push(beginWord);
int length = 0;
while(!m_queue.empty()){ //开始bfs
int size = m_queue.size();
length++;
for(int i = 0; i < size; i++){
string cur_string = m_queue.front();
m_queue.pop();
if(cur_string == endWord){
return length;
}
for(int i = 0 ; i < cur_string.size(); i++){
char oldchar = cur_string[i];
for(char c = 'a'; c<= 'z'; c++){
if(c == oldchar) continue;
cur_string[i] = c;
if(m_set.count(cur_string)){
m_queue.push(cur_string);
m_set.erase(cur_string);
}
}
cur_string[i] = oldchar;
}
}
}
return 0;
}
};
矩阵类型的BFS,待更新
总之,宽度优先搜索非常适合于求最短路径,以及最少需要多少步,以及一些关于联通域的问题,比如岛屿的数量
还有其他题目,下次再看了.