Android程序员面试会遇到的算法系列:html
上次咱们在结束二叉树的题目分析以前,作了一个简单的二叉树层序遍历(广度优先搜索)的模板代码的学习,咱们应该还能记得,广度优先要使用队列
,AKA -> Queue这个数据结构来作。用Java的伪代码咱们再复习一遍分布式
public void levelTraverse(TreeNode root){
if(root == null){
return;
}
//初始化队列
Queue queue = new LinkedList();
//把根节点加入队列
queue.add(root);
//开始遍历队列
while(!queue.isEmpty()){
TreeNode current = queue.poll();
System.out.println(current.toString());
//只要当前节点的左右节点不为空,那么咱们就能够把其加入到队列的尾部,等待下一次遍历,咱们continue这个while循环
if(current.left != null){
queue.add(current.left);
}
if(current.right != null){
queue.add(current.right);
}
}
}
复制代码
从以上模板代码咱们能够看出,对于广度优先搜索,其核心就在于使用队列Queue来作一个while循环,在循环内除了对当前节点的处理以外,还须要将当前节点的孩子节点放入队列的尾部。这样咱们就实现了简单的广度优先搜索。post
就是这么的简单!
那么,问题来了,核心的部分既然这么简单,广度优先搜索的应用又有哪些呢?
今天文章的重点就是,哪些广泛的问题能够用广度优先搜索来解决。
熟练玩耍各类社交网站的朋友都会发现网站常常都会给你推荐可能认识的好友,还会"友情"提示该推荐好友到底经过什么途径推荐。这里咱们重点介绍几度好友这种推荐模式。
顾名思义,几度好友的意思表明的就是该位用户和你中间相隔了有多少度,也就是多少我的。曾经哈佛大学的心理学教授Stanley Milgram 提出了一个叫六度分隔理论每一个人和另外随机的一个陌生人的距离只隔着6我的。也就是说你和特朗普之间可能也就是隔着6我的哦。
好了,交代了这么多背景,咱们能够开始思索一个问题了,假如说咱们已经有了好友相关信息,推荐系统怎么找到对应度数的好友呢?
举个栗子。人人网如今要推荐给一个用户他的三度之内的好友放在推荐栏里面,咱们怎么获取?
先把用户的数据结构贴出来
public class User{
//这个friends是该用户的直接好友,也就是一度好友、
private List<User> friends;
private String name;
//获取好友列表
public List<User> getFriends(){
return Collections.unmodifiableList(friends);
}
public String getUserName(){
return name;
}
}
复制代码
假设咱们已经有了这样的一个好友结构在咱们的内存里面(固然在实际的场景里面,咱们不可能把一个社交网络的全部用户信息存在Ram里面,这不现实,不过找几度好友的原理确定同样,只不过在分布式场景里面获取用户信息的过程要复杂不少),每一个User都有一个叫friends的List,保存他的直接好友。
根据以上的条件咱们能够这么思考,咱们须要去求的几度好友的这个几度,是否是其实就是层序遍历的那个_层_呢?x度难度不就是x层么?有了这个讯息,咱们就知道其实根据上面的广度优先的模板代码稍微修改一下,咱们就能够获得第x层(x度)之内的好友了。
public List<User> getXDegreeFriends(User user, int degree){
List<User> results = new ArrayList<User>();
Queue<User> queue = new LinkedList<User>();
queue.add(user);
//用于记录已经遍历过的User,由于A是B的好友,那么B也必定是A的好友,他们互相存在于对方的friends列表中。
HashSet<User> visited = new HashSet<User>();
//用一个counter记录当前的层数。
int count = degree;
//这里结束while循环的两个条件,一个是层数,一个是queue是否为空,由于万一该当前用户压根就没有那么多层的社交网络,好比他压根就没有朋友。
while(count>=1 && !queue.isEmpty()){
int queueSize = queue.size();
for(int i = 0 ; i < queueSize; i++){
User currentUser = queue.poll();
//假如该用户已经遍历过,那么不作任何处理。
if(!visited.contains(currentUser)){
results.add(currentUser);
queue.addAll(currentUser.getFriends();
visited.add(currentUser);
}
}
count--;
}
return results;
}
复制代码
就是这么简单!经过队列Queue,咱们成功的把一个User的x度好友所有包在一个队列里面而后返回,这样咱们就完成了一个简单的推荐好友方法!!。
同时一个小细节是咱们使用HashSet这个数据结构来去重,为何咱们要多作这么一步呢?不管是广度仍是深度优先搜索,这一步均可以说是重中之重,,由于咱们在遍历节点的时候,会遇到重复已经遍历过的节点。好比:
A用户和B用户互为好友,因此他们的getFriends()
方法会返回对方。
假设咱们在代码中没有使用去重的数据结构的话,第一步放入A的时候,咱们返回B加入队列,第二步咱们调用B的getFriends()
的时候又会返回A。。。。因此程序就会无限制的走下去了,同时层数也不正确了。因此咱们遍历过的节点,必定要经过某种方式保存其相关信息防止重复遍历。
说到最短距离,咱们第一反应确定都是想到迪杰特斯拉算法。
在一个有向图或者无向图中,每个节点与节点之间都有不一样的权值(能够理解为距离),最后算出每一个点与点之间的最短距离与其相应的路径。
此次咱们咱们要学习的是这种算法的一个特例。也就是若是节点与节点之间权值相等,可是可能存在障碍物的状况。
最经典的就是走迷宫问题了。
假设一个二维整型矩阵表明迷宫,0表明路,1表明墙壁(不能走不能经过),左上角是入口,右下角是出口(保证都为0)。求最少须要多少步能够走到出口。
对于这种问题,咱们一样须要用广度优先来处理。为何呢?
由于对于每个节点来讲,往下走一层都是须要一步(你们距离权值相等),那么咱们在走迷宫的过程其实就是像一个决策树同样,每一层都只须要一步来走完,那么终点的步数,其实就是取决于终点这个节点在这个树结构中的第几层。终点在第x层,就表明至少须要x步才能走到。
好比上图,从A出发,到其余节点的分层为 1层: B,C,D 2层: F, E 3层: H,G 4层: I 其相应与A的距离也就是他们的层数。
因此在求迷宫问题的距离时,咱们能够从起点开始作广度优先的遍历,不停的记录当前的层数,当遍历到终点的时候,查看当前已经遍历的层数,该层数也就是步数了。
public int getMinSteps(int[][] matrix) {
int row = matrix.length;
int col = matrix[0].length;
//迷宫能够走四个方向,这个二维数组表明四个方向的x与y的偏移量
int[][] direction = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };
HashSet<Integer> visited = new HashSet<>();
Queue<Integer> queue = new LinkedList<>();
//把起点加入到队列
queue.add(0);
int level = 0;
while (!queue.isEmpty()) {
//把该层的节点所有遍历
int size = queue.size();
for (int i = 0; i < size; i++) {
int current = queue.poll();
if (!visited.contains(current)) {
visited.add(current);
//肯定该节点的x与y坐标
int currentX = current / col;
int currentY = current % col;
//假如该点是重点,那么直接返回level
if (currentX == matrix.length - 1 && currentY == matrix[0].length - 1) {
return level;
}
//若是不是,那么咱们分别把它的四个方向的节点都尝试加入到队列尾端,也就是下一层中
for (int j = 0; j < direction.length; j++) {
int tempX = currentX + direction[j][0];
int tempY = currentY + direction[j][1];
//由于1表明墙壁,咱们不能走,只能加数值为0的点
if (tempX > -1 && tempY > -1 && tempX <row&& tempY < col&& matrix[tempX][tempY] != 1) {
int code = tempX * col + tempY;
queue.add(code);
}
}
}
}
level++;
}
return -1;
}
复制代码
以上代码就是简单的解决了迷宫问题中的最短路径,同时还能够帮助判断该迷宫到底有没有可行的路径到达出口(以上方法假如没有路径的时候会返回-1),由于方法在进行while循环的时候,从起点开始全部能遍历的点都遍历过以后,咱们尚未找到右下角的点,while循环会结束。
Multi-End 广度优先搜索和 以前的迷宫问题有点相似,咱们只须要把上述条件改一改。
假如在这个迷宫里面有不止一个出口,那么咱们从出口(坐标为0,0的点)开始,到达任何一个出口的最短路径该怎么求呢?
有朋友可能会说,假设有K个出口,那么我运行以前走迷宫的方法K次,比较最短路径不就好了。假设咱们的节点有MN个,这样的方式时间复杂度就是O(km*n)了。有没有更快一点的方法呢?
咱们能够尝试着反向去思考这个问题,咱们以前都是以起点为开始点,作广度优先搜索。对于多重点的问题,咱们难道不能够用重点们做为起点,加入队列中,再不停更新道路的权值,直到咱们找到起点不就好了么。
这个算法我就不具体展开了,有兴趣的朋友能够看看leetcode的Gates and Wall这题
具体的解答在这里
今天的文章就暂时到这了,下一期会重点介绍一个深度优先算法的回朔算法的讲解。