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這題
具體的解答在這裏
今天的文章就暫時到這了,下一期會重點介紹一個深度優先算法的回朔算法的講解。