1. 程式人生 > >演算法 寬度遍歷(面試題詳解)

演算法 寬度遍歷(面試題詳解)

問題來源

https://segmentfault.com/q/1010000013091395?_ea=3284779

問題描述:

存在一個0,1值的二維陣列,給定一個座標[x,y],如果該座標所代表的元素值為1,則返回該座標所代表的元素相鄰的所有值為1的元素座標。

解題思路

對於這種查詢元素這類題目,腦袋裡的第一個想法就是應該使用遍歷。然後選擇使用何種遍歷,由於這個查詢元素是跟位置有關的,所以使用寬度遍歷(寬度遍歷的定義)最合適。

寬度遍歷:寬度優先遍歷,是以離初態距離為序進行遍歷。

解題方案

初始化定義:

  • queue: 一個臨時的快取佇列,儲存臨時匹配的結果
  • result: 一個結果陣列,儲存所有的匹配結果
  • memo:一個原陣列的元素的記憶陣列,如果存在記憶為true,初始值全為false
// 定義一個遍歷陣列
var arr =[
    [0,0,0,0,0,0,0,0,0,0,0], 
    [0,0,0,0,0,0,0,0,0,0,0], 
    [0,0,0,0,0,0,0,0,0,0,0], 
    [0,0,0,0,1,0,0,0,1,0,0], 
    [0,0,0,0,1,0,0,0,1,0,0],
    [0,0,0,0,1,0,0,0,1,0,0],
    [0,0,0,0,1,0,0,0,0,0,0],
    [0,0,0,0,1,0,0,0,0,0,0],
    [0,0,0,0,1,1,1,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0],
    [0
,0,0,0,0,0,0,0,0,0,0], ] // 宣告一個遍歷方法 function fn ([x, y]) { // 定義一個快取佇列queue,儲存臨時匹配的結果 const queue = [] // 定義一個result,儲存所有的匹配結果 const result = [] // 定義一個原陣列的元素的記憶陣列,初始值為false,如果原陣列的元素元素存在則記憶值變為true, const memo = arr.map(row => new Array(row.length).fill(false)) // 定義一個方向陣列,它的元素值分別表示左、右、上、下
const direction = [[-1, 0], [1, 0], [0, -1], [0, 1]] // 如果指定位置元素值不為1,直接返回false,跳出查詢函式; // 如果存在,則將位置結果推入臨時陣列queue,和結果陣列result if (arr[x][y] !== 1) { return false } else { queue.push([x, y]) result.push([x, y]) } // 臨時儲存結果陣列中是否有元素,如果有,則進行迴圈;如果沒有,則跳出while迴圈,執行其它語句 while(queue.length > 0) { // 從快取佇列中取出存在元素的座標 const [x, y] = queue.pop() // 查詢該座標位置左右上下位置值為1的元素,如果存在且記憶陣列沒有記憶過該元素,那麼就將用memo記憶該元素, // 然後推入臨時陣列queue和結果陣列,然後結束本次迴圈,接著返回迴圈條件判斷,看是否接著執行迴圈,如果執行條件滿足,重複迴圈體內的執行語句 direction.forEach(([h, v]) => { const newX = x + h const newY = y + v if (arr[newX][newY] === 1 && !memo[newX][newY]) { memo[newX][newY] = true queue.push([newX, newY]) result.push([newX, newY]) } }) } return result } fn([3, 4])

根據JavaScript的特性,可以對演算法進行優化,對於上例的記憶陣列,我們可以使用物件來處理,這樣可以減小初始化的開銷。程式碼如下:

function fn([x, y]) {
    var memo = {}, // 將記憶陣列改為記憶物件
        queue = [],
        result = [],
        direction = [[-1, 0],[1, 0],[0, -1],[0, 1]]

    if (arr[x][y] !== 1) {
        return false
    } else {
        queue.push([x, y])
        result.push(memo[x + "," + y] = [x, y])
    }

    while(queue.length > 0) {
        const [x, y] = queue.pop()
        direction.forEach(([h, v]) => {
            const newX = x + h
            const newY = y + v
            if (arr[newX][newY] === 1 && !memo[newX + "," + newY]) {
                queue.push([newX, newY]);
                result.push(memo[x + "," + y] = [newX, newY]);
            }
        })
    }
    return result;
}

當然,對於遍歷如果我們使用遞迴方法的程式碼的書寫量將會減少不少。可以將程式碼修改如下:

function fn(point) {
    var memo = {},
        result = [],
        direction = [[-1, 0],[1, 0],[0, -1],[0, 1]]
    function dg([x, y]) {
        result.push(memo[x + "," + y] = [x, y]);
        direction.forEach(([h, v]) => {
            const newX = x + h
            const newY = y + v
            if (arr[newX][newY] === 1 && !memo[newX + "," + newY]) {
                dg([newX, newY]);
            }
        })
    }
    dg(point);
    return result;
}

好了,今天寬度遍歷演算法的就先說到這裡,後續可能還會繼續修改,也歡迎各位批評指正。有問題或者有其他想法的可以在我的GitHub上pr。