演算法 寬度遍歷(面試題詳解)
阿新 • • 發佈:2018-12-16
問題來源
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。