我要好offer之 搜索算法大總結

1. 二分搜索

詳見筆者博文:二分搜索的那些事兒,很是全面html

 

2. 矩陣二分搜索

(1) 矩陣每行遞增,且下一行第一個元素大於上一個最後一個元素node

(2) 矩陣每行遞增,且每列也遞增c++

 

3. DFS 深度優先搜索

適用場景:數組

(1) 輸入數據:若是是 遞歸數據結構(如單鏈表、二叉樹),則必定可使用DFS緩存

(2) 求解目標:必須走到最深處(例如二叉樹,必須走到葉子節點)才能獲得一個解,這種狀況通常適合用DFS數據結構

 

思考步驟:函數

(1) DFS最多見的3個問題:求可行解的總數、求任一個可行解、求全部可行解spa

(a) 若是是 求可行解總數,則不須要 數組path[] 來存儲 搜索路徑指針

(b) 若是是 求可行解自己,則須要一個 數組path[] 來存儲 搜索路徑序列c++11

DFS在搜索過程當中 始終只有一條搜索路徑,一直搜索到絕境再回溯繼續搜索,所以只須要一個數組就能夠了

BFS須要存儲 擴展過程當中的搜索路徑,在沒有找到答案以前 全部路徑都不能放棄

 

(2) 只要求任一可行解? 要求全部可行解?

若是隻須要一個可行解,找到一個便可返回

若是要求全部可行解,找到一個可行解以後,必須繼續擴展,直到遍歷完

 

BFS通常只要求一個解,若是用BFS要求全部解,就須要擴展到全部葉子節點,至關於在內存中有指數級的存儲空間

 

(3) 如何表示狀態?

一個狀態須要存儲哪些必要的信息,纔可以正確的擴展到下一步狀態.

DFS通常使用函數參數的方法,由於DFS通常有遞歸操做,擴展下一狀態只須要修改 遞歸函數的函數參數便可

BFS通常使用struct結構體存儲全部信息,struct裏的字段與DFS中的函數參數字段一一對應

 

(4) 如何擴展狀態?

對於二叉樹:擴展左子樹、右子樹

對於圖、矩陣:題目告知,好比 只能向右或向下走, 好比 上下左右四個方向都可擴展

 

(5) 如何判重?

(a) 是否須要判重? 

若是 狀態轉換圖是 一棵樹,則不須要判重,樹的全部子樹均分離,不存在重疊子問題,所以二叉樹的全部DFS都不須要判重

若是 狀態轉換圖是 DAG(有向無環圖),則須要判重,所以 全部的BFS都須要判重

(b) 怎樣判重? 

 

(6) 搜索的終止條件是什麼?

終止條件是 不能繼續擴展的末端結點

對於樹:葉子節點

對於圖:出度爲0的節點

 

(7) 收斂條件是什麼?

爲了判斷是否到達收斂條件,DFS通常須要在遞歸函數接口裏 用一個參數記錄當前狀態(cur變量) 或者 距離目標還有多遠(gap變量)

若是是 求一個解,直接返回這個解,即path路徑數組

若是是 求全部解,則把 這個解path數組複製到 解集合中 (通常利用 c++ vector中的push_back函數,push時採用的是copy構造函數)

 

(8) 如何加速?

(a) 剪枝:圖的DFS中須要挖掘各類信息,包括搜索邊界、值大小關係等

(b) 緩存:狀態轉換圖是DAG ==> 存在重疊子問題 ==> 字問題的解會被重複利用

若是輸入結構是 二叉樹,不存在重疊子問題,不須要緩存

通常使用c++11的 std::unordered_map來緩存,或者使用一個二維數組 std::vector<std::vector<int>>

 

DFS模板:

數據結構爲樹(二叉樹)的DFS模板(不須要判重和緩存):

/** DFS模板
 * @param[in] input :對於二叉樹通常爲root指針,對於圖通常是輸入矩陣即二維數組
 * @param[in] path:當前搜索路徑,也是中間結果,通常爲一維vector
 * @param[in] cur or gap:標記當前位置或距離目標的距離
 * @param[out] result:存放最終結果,通常是二維vector,每一維爲path數組
 */
 
void dfs(type input, std::vector<int>& path, int cur or gap, std::vector<std::vector<int>>& result) {
    if (數據非法) return;  // 終止條件,對於二叉樹即input爲空,對於圖即 搜索邊界越界
    if (cur == input.size() or gap == 0) {  // 收斂條件
        result.push_back(path);
    }
    // 執行全部擴展路徑
    path.push_back();   // 執行動做,修改path
    // 擴展動做通常有多個,對於二叉樹就是 input->left和input->right,對於圖可能就是 y-1,y+1,x-1,x+1(上下左右)
    dfs(input, path, cur + 1 or gap - 1, result);
    path.pop_back();   // 恢復path
}

例題: 二叉樹路徑和問題

 

數據結構爲圖的DFS模板(須要判重):

/** DFS模板
 * @param[in] input :對於二叉樹通常爲root指針,對於圖通常是輸入矩陣即二維數組
 * @param[in] path:當前搜索路徑,也是中間結果,通常爲一維vector
 * @param[in] cur or gap:標記當前位置或距離目標的距離
 * @param[in] visited:用於判重的二維數組
 * @param[out] result:存放最終結果,通常是二維vector,每一維爲path數組
 */
 
void dfs(type input, std::vector<int>& path, int cur or gap, std::vector<std::vector<bool>> visited, std::vector<std::vector<int>>& result) {
    if (數據非法) return;  // 終止條件,對於二叉樹即input爲空,對於圖即 搜索邊界越界
    if (cur == input.size() or gap == 0) {  // 收斂條件
        result.push_back(path);
    }
    if(visited[x][y] == true) return; // 判重 // 執行全部擴展路徑
    visited[x][y] = true;
    path.push_back(); // 執行動做,修改path
    // 擴展動做通常有多個,對於二叉樹就是 input->left和input->right,對於圖就是 y-1,y+1,x-1,x+1(上下左右)
    dfs(input, path, cur + 1 or gap - 1, visited, result);
    path.pop();   // 恢復path
    visited[x][y] = false;
}

例題:在字符矩陣中查找單詞

        矩陣右下走的路徑總數(可能有障礙)

 

4. BFS 廣度優先搜索

適用場景:求最短搜索路徑

/** BFS模板
 * param[in] state_t:狀態,如整數、字符串、數組等
 * param[in] start:起點
 * parma[in] grid:輸入矩陣數據
 * return 從起點到目標狀態的一條最短路徑
 */
 
std::vector<state_t> bfs(state_t start, std::vector<std::vector<int>>& grid) {
    std::queue<state_t> que;  // 隊列
    std::unordered_set<state_t> visited;  // 判重,也能夠直接使用 二維vector
    
    bool found = false;
    que.push(start);
    visited.insert(start);
    while (!que.empty()) {
        state_t state = que.front();
        que.pop();
        if (state爲目標狀態) {
            found = true;
            break;
        }
        // 擴展狀態,對於二叉樹即左右子樹,對於圖可能就是上下左右四個方向
        std::vector<state_t> stateVec = state_extend(state); // 擴展狀態必須考慮 搜索邊界越界時的剪枝visited的判重
        for (auto iter = stateVec.begin(); iter != stateVec.end(); ++iter) {
            state_t curState = *iter;
            if (curState爲目標狀態) {
                found = true;
                break;
            }
            // curState不知足目標狀態,則入隊
            que.push(curState);
            visited.insert(start);
        }
    }
    if (found) {
        return generate vector<state_t>;
    } else {
        return vector<state_t>();
    }
}

例題:二叉樹層次遍歷    單詞接龍

 

5. 綜合

走迷宮問題

迷宮矩陣中元素全爲0或1,0表明通路,1表明障礙,每次能夠上下左右四個方向走,如今要求:

求 出發點到終點的全部可行路徑?

求 出發點到終點的最短路徑?

前面講過,最短問題通常使用BFS,可不可使用DFS呢? 固然能夠

記住:DFS能夠求出出全部的可行解,全部的解都求出來了,最短的路徑固然能夠肯定了,只是這是DFS的效率明顯比BFS低

如今咱們使用 DFS 和 BFS 求第二個問題

(1) DFS求解,每次求得一個可行路徑時,咱們就與前一個可行解作出大小判斷,最後結果即爲最短路徑

int dx[4] = {-1, 1, 0, 0}; // x --> row
int dy[4] = {0, 0, -1, 1}; // y --> col

int minStep = INT_MAX;  // 最終結果

void dfs(std::vector<std::vector<int>>& maze, const std::vector<int>& start, const std::vector<int>& end, int curX, int curY, int step) {
    if (curX == end.at(0) && curY == end.at(1)) { // 求得可行解
        minStep = std::min(minStep, step);  // 更新最小解
        return;
    }
    
    for (int i = 0; i < 4; ++i) {  // 上下左右4個方向進行搜素擴展
        int nextX = curX + dx[i];
        int nextY = curY + dy[i];
        if (nextX < 0 || nextX >= maze.size())       continue;  // 搜索邊界剪枝
        if (nextY < 0 || nextY >= maze.at(0).size()) continue; // 搜索邊界剪枝 
        if (maze.at(nextX).at(nextY) == 0) { // 必須是通路,即判重
            maze.at(nextX).at(nextY) = 1; // 標記已訪問過
            dfs(maze, start, end, nextX, nextY, step + 1); // 執行搜索擴展
            maze.at(nextX).at(nextY) = 0; // 回溯操做
        }
    }
}

 

(2) BFS求解,使用一個結構體保存座標狀態,使用一個隊列輔助BFS操做

int dx[4] = {-1, 1, 0, 0}; // x --> row
int dy[4] = {0, 0, -1, 1}; // y --> col
int minStep = 0;  // 最終結果
// 保存座標狀態
struct node {
    int x;
    int y;
    node(int xPos, int yPos) : x(xPos), y(yPos) { }
};

std::queue<node> que;

int minStep(std::vector<std::vector<int>>& maze, const std::vector<int>& start, const std::vector<int>& end) {
    que.push(node(start.at(0), start.at(1)));
    maze.at(start.at(0)).at(start.at(1)) = 1; //標記已訪問過
    while (!que.empty()) {
        node curNode = que.front(); 
        que.pop();
        if (curNode.x == end.at(0) && curNode.y == end.at(1)) {  // 求得可行解,BFS必定是最短
            return minStep;
        }
        
        for (int i = 0; i < 4; ++i) {  // 上下左右4個方向進行搜索擴展
            int nextX = curNode.x + dx[i];
            int nextY = curNode.y + dy[i];
            if (nextX >= 0 && nextX < maze.size() && nextY >= 0 && nextY < maze.at(0).size() && maze.at(nextX).at(nextY) == 0) {
                que.push(node(nextX, nextY)); 
                maze.at(nextX).at(nextY) = 1; // 標記已訪問過
            }
        }
        ++minStep; // 增大每一層搜索的距離
    }
    return 0;  // 存在沒有可行路徑的可能
}
相關文章
相關標籤/搜索