1. 程式人生 > >前端演算法之與資料結構-廣度遍歷和深度遍歷與二叉樹遍歷

前端演算法之與資料結構-廣度遍歷和深度遍歷與二叉樹遍歷

一、(圖的遍歷)深度優先和廣度優先

 

廣度優先搜尋(BFS)佇列實現

-類似二叉樹的先序遍歷

越是接近根結點的結點將越早地遍歷。

找到從起始結點到目標結點的路徑,特別是最短路徑。

 

廣度優先遍歷 BFS 從圖中某頂點v出發,在訪問了v之後依次訪問v的各個未曾訪問過的鄰接點,然後分別從這些鄰接點出發依次訪問它們的鄰接點,並使得“先被訪問的頂點的鄰接點先於後被訪問的頂點的鄰接點被訪問,直至圖中所有已被訪問的頂點的鄰接點都被訪問到。 如果此時圖中尚有頂點未被訪問,則需要另選一個未曾被訪問過的頂點作為新的起始點,重複上述過程,直至圖中所有頂點都被訪問到為止。

/* 廣度遍歷dom */
//非遞迴版本

//新建佇列和nodes列表
//佇列不為空,取出第一個元素,push進nodes,迴圈新增children進佇列
//返回nodes
let breadthTraversal = (node) =>{
                let nodes = []
                let queue = []
                if(node){
                    queue.push(node)
                    while(queue.length){
                        let item = queue.shift()
                        let children = item.children
                        nodes.push(item)
                        // 佇列,先進先出
                        // nodes = [] queue= [parent]
                        // nodes = [parent] queue= [child1,child2,child3]
                        // nodes = [parent, child1] queue= [child2,child3,child1-1,child1-2]
                        for(let i = 0;i<children.length;i++){
                            queue.push(children[i])
                        }
                    }
                }
                return nodes
            }

深度優先搜尋(DFS)棧實現

-類似二叉樹的層次遍歷

更早訪問的結點可能不是更靠近根結點的結點。

因此,你在DFS 中找到的第一條路徑可能不是最短路徑。

 

假設初始狀態是圖中所有頂點均未被訪問,則從某個頂點v出發,首先訪問該頂點然後依次從它的各個未被訪問的鄰接點出發深度優先搜尋遍歷圖,直至圖中所有和v有路徑相通的頂點都被訪問到。若此時尚有其他頂點未被訪問到,則另選一個未被訪問的頂點作起始點,重複上述過程,直至圖中所有頂點都被訪問到為止。

/* 深度遍歷dom */
//非遞迴版本
let deepTraversal = (node) => {
  let stack = []
  let nodes = []
  if (node) {
    // 推入當前處理的node
    stack.push(node)
    while (stack.length) {
      let item = stack.pop()
      let children = item.children
      nodes.push(item)
      // node = [] stack = [parent]
      // node = [parent] stack = [child3,child2,child1]
      // node = [parent, child1] stack = [child3,child2,child1-2,child1-1]
      // node = [parent, child1-1] stack = [child3,child2,child1-2]
      for (let i = children.length - 1; i >= 0; i--) {
        stack.push(children[i])
      }
    }
  }
  return nodes
}
//遞迴版本
let deepTraversal1 = (node) => {
                let nodes =[]
                if(node!== null) {
                    nodes.push(node)
                    let children = node.children;
                    for(let i = 0;i<children.length;i++){
                        nodes = nodes.concat(deepTraversal(children[i]))
                    }
                }
                return nodes;
            }
let parent = document.querySelector('.parent');
let joe = deepTraversal(parent);
console.log(joe);

應用:搜尋某個結點的路徑,新增path屬性(bfs使用shift(佇列),dfs使用pop(棧))

 

·         bfs利用佇列實現,迴圈中做的是push => shift => push => shift

 

·         dfs利用棧實現,迴圈中做的是push => pop => push => pop

/* 查詢對應name路徑 廣度遍歷 佇列實現 bfs */
function bfs(target, name) {
                const queue = [...target]
                do {
                    const current = queue.shift()
                    if (current.children) {
                    queue.push(...current.children.map(x => ({ ...x, path: (current.path || current.name) + '-' + x.name })))
                    }
                    if (current.name === name) {
                    return current//返回整個目標物件
                    }
                } while(queue.length)
                return undefined
            } 
/* 查詢對應name路徑 深度遍歷 棧實現  dfs */
function dfs(target,name){
                const stack = [...target]
                do{
                    const current = stack.pop()
                    if(current.children){
                        stack.push(...current.children.map(x => ({...x,path:(current.path || current.name) + '-' + x.name})))
                    }
                    if(current.name === name){
                        return current.path//返回整個目標物件的path屬性
                    }
                    
                }while(stack.length)
                return undefined
            }
/*  公共的搜尋方法,mode預設bfs */
function commonSearch(target, name, mode) {
    const stackOrQueue = [...target]
    do {
        const current = stackOrQueue[mode === 'dfs' ? 'pop' : 'shift']()
        if (current.children) {
            stackOrQueue.push(...current.children.map(x => ({ ...x, path: (current.path || current.name) + '-' + x.name })))
        }
        if (current.name === name) {
        return current
        }
    } while(stackOrQueue.length)
    return undefined
}

 

/* 搜尋地區測試用例 */
const data = [{
                id: '1',
                name: '廣東省',
                children: [
                    {
                        id: '12',
                        name: '廣州市',
                        children: [
                            {
                                id:'121',
                                name: '天河區'
                            },
                            {
                                id:'122',
                                name: '荔灣區'
                            }
                        ]
                    }
                ]
            }]
            console.log(dfs(data,'天河區'))

·         結果1:返回物件

·         結果2:返回物件的path屬性

 

 

 

二、(二叉樹的遍歷)前序中序後序

重點中的重點,最好同時掌握遞迴和非遞迴版本。

遞迴版本很容易書寫,但是真正考察基本功的是非遞迴版本。

·         二叉樹的前序遍歷(根節點N在左右子樹前)NLR - ABDECF

·         二叉樹的中序遍歷(根節點N在左右子樹中)LNR - DBEAFC

 

·         二叉樹的後序遍歷(根節點N在左右子樹後)LRN - DEBFCA

 

/* 遞迴寫法 */
/* 前序 */
const preorderTraversal = function (root, array=[]) {
    if(root) {
        array.push(root.val);
        preorderTraversal(root.left, array);
        preorderTraversal(root.right, array);
    }
    return array;
}
/* 中序 */
const inorderTraversal = function(root, array= []) {
    if(root) {
        inorderTraversal(root.left, array);
        array.push(root.val);
        inorderTraversal(root.right, array);
    }
    return array;
}
/* 後序 */
const postorderTraversal = function(root, array = []) {
    if(root){
        postorderTraversal(root.left, array);
        postorderTraversal(root.right, array);
        array.push(root.val);
    }
    return array;
} 

/* 非遞迴寫法 */
/* 前序 */
const preorderTraversal1 = function (root) {
    const result = [];
    const stack = [];
    let current = root;
    while(current||stack.length > 0){
        while(current){
            //取根節點值,左節點入棧,迴圈到左節點為空
            result.push(current.val);
            stack.push(current);
            current = current.left;
        }
            //節點出棧,再遍歷右節點
            current = stack.pop();
            current = current.right;
    }
    return result;
}
/* 中序 */
const inorderTraversal1 = function (root) {
    const result = [];
    const stack = [];
    let current = root;
    while(current||stack.length > 0){
        while(current){
            //左節點入棧,迴圈到左節點為空
            stack.push(current);
            current = current.left;
        }
            //節點出棧,取節點值,再遍歷右節點
            current = stack.pop();
            result.push(current.val);
            current = current.right;
    }
    return result;
}
/* 後序 */
const postorderTraversal1 = function (root) {
    const result = [];
    const stack = [];
    let last = null;
    let current = root;
    while(current || stack.length > 0) {
        while(current) {
            //左節點入棧,迴圈到左節點為空
            stack.push(current);
            current = current.left;
        }
            //取棧內最後一個節點,判斷是為空或者已遍歷過,則將節點值傳入
            current = stack[stack.length-1];
            if(!current.right || current.right == last){
                //節點出棧
                current = stack.pop();
                result.push(current.val);
                last = current;
                current = null;
                //繼續迴圈彈棧
            } else {
                //取右節點做遍歷操作
                current = current.right
            }
    }
    return result;
}
/* 測試用例 */
const data = {
    val:1,
    left:{
        val:2,
        left:{
            val:4,
        },
        right:{
            val:5
        }
    },
    right:{
        val:3,
        left:{
            val:6
        },
        right:{
            val:7
        }
    }
}
console.log('遞迴計算結果')
console.log(preorderTraversal(data))
console.log(inorderTraversal(data))
console.log(postorderTraversal(data))
console.log('非遞迴計算結果')
console.log(preorderTraversal1(data))
console.log(inorderTraversal1(data))
console.log(postorderTraversal1(data))

//遞迴計算結果
[ 1, 2, 4, 5, 3, 6, 7 ]
[ 4, 2, 5, 1, 6, 3, 7 ]
[ 4, 5, 2, 6, 7, 3, 1 ]
//非遞迴計算結果
[ 1, 2, 4, 5, 3, 6, 7 ]
[ 4, 2, 5, 1, 6, 3, 7 ]
[ 4, 5, 2, 6, 7, 3, 1 ]

 

 

 

 

 

&n