二叉樹遍歷

前言

本篇文章是在二叉排序樹的基礎上進行遍歷、查找、與刪除結點。javascript

那麼首先來看一下什麼是二叉排序樹?java

二叉排序樹

定義

二叉排序樹,又稱二叉查找樹、二叉搜索樹。node

  • 若左子樹不爲空,左子樹上全部結點均小於它的根結點的值;
  • 若右子樹不爲空,右子樹上全部結點均大於它的根結點的值;
  • 左右子樹也分別爲二叉排序樹。
插入算法

咱們知道了什麼是二叉排序樹,如今來看下它的具體算法實現。算法

// 構建二叉樹
function BinaryTree() {
    // 定義結點
    let Node = function(key) {
        this.key = key
        this.left = left
        this.right = right
    }
    
    // 定義根結點
    let root = null
    
    // 得到整棵樹
    this.getRoot = function() {
        return this.root
    }
    // 定義插入結點算法
    let insertNode = function(node, newNode) {
        // 比較要插入結點與當前結點值的大小,若大於當前結點,插入左子樹,反之,插入右子樹
        if(newNode.key < node.key) {
            if(node.left === null) {
                node.left = newNode
            } else {
                insertNode(node.left, newNode)
            }
        } else {
            if(node.right === null) {
                node.right = newNode
            } else {
                insertNode(node.right, newNode)
            }
        }
    }
    
    // 定義二叉排序樹插入算法
    this.insert = function(key) {
        let newNode = new Node(key)
        if(root === null) {
            root = newNode
        } else {
            insertNode(root, newNode)
        }
    }
}

let nodes = [8,3,30,1,6,14,4,7,13]
// 建立樹實例
let tree = new BinaryTree()
nodes.forEach(function(key) {
    tree.insert(key)
})
console.log("建立的二叉樹是:", tree.getRoot())

至此,一棵二叉排序樹就構造完啦。接下來咱們根據構造的這顆二叉樹進行相應遍歷、查找與刪除操做。數組

遍歷二叉樹

二叉樹的遍歷分爲深度優先遍歷廣度優先遍歷ide

深度優先遍歷

深度優先遍歷(Depth First Search)是指沿着樹的深度進行遍歷樹的結點。其中深度優先遍歷又分爲三種:前序遍歷、中序遍歷、後序遍歷。函數

這裏前序、中序、後序是根據根結點的順序命名的。

一、前序遍歷

定義

前序遍歷也叫作先根遍歷、先序遍歷、前序周遊,記作 根左右post

  • 先訪問根結點;
  • 前序遍歷左子樹;
  • 前序遍歷右子樹。

前序遍歷的做用是能夠複製已有的二叉樹,且比從新構造的二叉樹的效率高。this

下面咱們來看它的算法實現。分爲遞歸與非遞歸兩種。code

方法一 遞歸實現
function BinaryTree() {
    // 這裏省略了二叉排序樹的構建方法
    
    // 定義前序遍歷算法
    let preOrderTraverseNode = function(node, callback) {
        if(node !== null) {
            callback(node.key) // 先訪問當前根結點
            preOrderTraverseNode(node.left, callback) // 訪問左子樹
            preOrderTraverseNode(node.right, callback) // 訪問右子樹
        }
    }
    
    // 定義前序遍歷方法
    this.preOrderTraverse = function(callback) {
       preOrderTraverseNode(root, callback) 
    }
}

let nodes = [8,3,10,1,6,14,4,7,13]
let tree = new BinaryTree()
nodes.forEach(function(key) {
    tree.insert(key) // 構造二叉樹
})

// 定義回調函數
let callback = function(key) {
    console.log(key)
}
tree.preOrderTraverse(callback) // 8 3 1 6  4 7 10 14 13
方法二 非遞歸實現
function BinaryTree() {
    // ...
    
    // 定義前序遍歷算法
    let preOrderTraverseNode = function(node, callback) {
        let stack = []
        if(node !== null) {
            stack.push(node)
        }
        while(stack.length) {
            let temp = stack.pop()
            callback(temp.key)
            // 這裏先放右邊再放左邊是由於取出來的順序相反
            if(temp.right !== null) {
                stack.push(temp.right)
            }
            if(temp.left !== null) {
                stack.push(temp.left)
            }
        }
    }
    
    // 定義前序遍歷方法
    this.preOrderTraverse = function(callback) {
        preOrderTraverseNode(root, callback)
    }
}

let nodes = [8,3,10,1,6,14,4,7,13]
let tree = new BinaryTree()
nodes.forEach(function(key) {
    tree.insert(key) // 構造二叉樹
})

// 定義回調函數
let callback = function(key) {
    console.log(key)
}
tree.preOrderTraverse(callback) //8 3 1 6  4 7 10 14 13

二、中序遍歷

定義

中序遍歷也叫作中根遍歷、中序周遊,記作 左根右

  • 若左子樹不爲空,則先中序遍歷左子樹;
  • 訪問根結點;
  • 若右子樹不爲空,則中序遍歷右子樹。
中序遍歷二叉排序樹,獲得的數組是有序的且是升序的。

下面咱們來看中序遍歷算法的實現。分爲遞歸和非遞歸兩種。

方法一 遞歸實現
function BinaryTree() {
    // 省略二叉排序樹的建立
    
    // 定義中序遍歷算法
    let inOrderTraverseNode = function(node, callback) {
        if(node !== null) {
            inOrderTraverseNode(node.left, callback) // 先訪問左子樹
            callback(node.key) // 再訪問當前根結點
            inOrderTraverseNode(node.right, callback) // 訪問右子樹
        }
    }
    
    // 定義中序遍歷方法
    this.inOrderTraverse = function(callback) {
       inOrderTraverseNode(root, callback) 
    }
}

let nodes = [8,3,10,1,6,14,4,7,13]
let tree = new BinaryTree()
nodes.forEach(function(key) {
    tree.insert(key) // 構造二叉樹
})

// 定義回調函數
let callback = function(key) {
    console.log(key)
}
tree.inOrderTraverse(callback) // 1 3 4 6 7 8 10 13 14
方法二 非遞歸實現

藉助於棧,先將左子樹所有放進棧中,以後輸出,最後處理右子樹。

function BinaryTree() {
    // 省略二叉排序樹的構建方法
    
     // 定義中序遍歷算法
    let inOrderTraverseNode = function(node, callback) {
        let stack = []
        while(true) {
            // 將當前結點的左子樹推入棧
            while(node !== null) {
                stack.push(node)
                node = node.left
            }

            // 定義終止條件
            if(stack.length === 0) {
                break
            }
            let temp = stack.pop()
            callback(temp.key)
            node = temp.right
        }
    }
    this.inOrderTraverse = function(callback) {
        inOrderTraverseNode(root, callback) 
    }
}

let nodes = [8,3,10,1,6,14,4,7,13]
let tree = new BinaryTree()
nodes.forEach(function(key) {
    tree.insert(key) // 構造二叉樹
})

// 定義回調函數
let callback = function(key) {
    console.log(key)
}
tree.inOrderTraverse(callback) // 1 3 4 6 7 8 10 13 14

三、後序遍歷

定義

後序遍歷也叫作後根遍歷、後序周遊,記作 左右根

  • 若左子樹不爲空,後序遍歷左子樹;
  • 若右子樹不爲空,後序遍歷右子樹;
  • 訪問根結點。
後序遍歷的做用用於文件系統路徑中,或將正常表達式變成逆波蘭表達式。

下面咱們來看後序遍歷算法的實現。分爲遞歸和非遞歸兩種。

方法一 遞歸實現
// 先構造一棵二叉樹
function BinaryTree() {
    // 省略二叉排序樹的構建方法

    // 定義後序遍歷算法
    let postOrderTraverseNode = function(node, callback) {
        if(node !== null) {
            postOrderTraverseNode(node.left, callback) // 遍歷左子樹
            postOrderTraverseNode(node.right, callback) // 再遍歷右子樹
            callback(node.key) // 訪問根結點
        }
    }
    
    // 定義後序遍歷方法
    this.postOrderTraverse = function(callback) {
        postOrderTraverseNode(root, callback)
    }
}
let nodes = [8,3,10,1,6,14,4,7,13]
let tree = new BinaryTree()
nodes.forEach(function(key){
    tree.insert(key)
})

// 定義回調函數
let callback = function(key) {
    console.log(key)
}

tree.postOrderTraverse(callback) // 1 4 7 6 3 13 14 10 8
方法二 非遞歸實現
// 先構造一棵二叉樹
function BinaryTree() {
    // 省略二叉排序樹的構建方法

    // 定義後序遍歷算法
    let postOrderTraverseNode = function(node, callback) {
        let stack = []
        let res = []
        stack.push(node)
        while(stack.length) {
            let temp = stack.pop()
            res.push(temp.key)
            if(temp.left !== null) {
                stack.push(temp.left)
            }
            if(temp.right !== null) {
                stack.push(temp.right)
            }
        }
        callback(res.reverse())
    }
    
    // 定義後序遍歷方法
    this.postOrderTraverse = function(callback) {
        postOrderTraverseNode(root, callback)
    }
}
let nodes = [8,3,10,1,6,14,4,7,13]
let tree = new BinaryTree()
nodes.forEach(function(key){
    tree.insert(key)
})

// 定義回調函數
let callback = function(key) {
    console.log(key)
}

tree.postOrderTraverse(callback) // 1 4 7 6 3 13 14 10 8

廣度優先遍歷

廣度優先遍歷(Breadth First Search),又叫作寬度優先遍歷、層次遍歷,是指從根結點沿着樹的寬度搜索遍歷。

下面來看它的實現原理

方法一 遞歸
function BinaryTree() {
    // 省略二叉排序樹的構建
    
    let wideOrderTraverseNode = function(root) {
        let stack = [root] // 先將要遍歷的樹壓入棧

        return function bfs(callback) {
            let node = stack.shift()
            if(node) {
                callback(node.key)
                if(node.left) stack.push(node.left);
                if(node.right) stack.push(node.right);
                bfs(callback)
            }
        }
    }
    
     this.wideOrderTraverse = function(callback) {
        wideOrderTraverseNode(root)(callback)
    }
}

let nodes = [8,3,10,1,6,14,4,7,13]
let tree = new BinaryTree()
nodes.forEach(function(key) {
    tree.insert(key)
})
// 定義回調函數
let callback = function(key) {
    console.log(key)
}

tree.wideOrderTraverse(callback) // 8,3,10,1,6,14,4,7,13
方法二 非遞歸

使用棧實現,未訪問的元素入棧,訪問後則出棧,並將其leve左右元素入棧,直到葉子元素結束。

function BinaryTree() {
    // 省略二叉排序樹的構建
    
    let wideOrderTraverseNode = function(node, callback) {
        let stack = []
        if(node === null) {
            return []
        }
        stack.push(node)
        while(stack.length) {
            // 每一層的結點數
            let level = stack.length
            // 遍歷每一層元素
            for(let i = 0; i < level; i++) {
                // 當前訪問的結點出棧
                let temp = stack.shift()
                
                // 出棧結點的孩子入棧
                temp.left ? queue.push(temp.left) : ''
                temp.right ? queue.push(temp.right) : ''
                callback(temp.key)
            }
        }
    }
    
     this.wideOrderTraverse = function(callback) {
        wideOrderTraverseNode(root, callback)
    }
}

let nodes = [8,3,10,1,6,14,4,7,13]
let tree = new BinaryTree()
nodes.forEach(function(key) {
    tree.insert(key)
})
// 定義回調函數
let callback = function(key) {
    console.log(key)
}

tree.wideOrderTraverse(callback) // 8,3,10,1,6,14,4,7,13
方法三 非遞歸
function BinaryTree() {
    // 省略二叉排序樹的構建
    
    let wideOrderTraverseNode = function(node, callback) {
        let stack = []
        if(node === null) {
            return []
        }
        stack.push(node)
        while(stack.length) {
            let temp = stack.shift()
            callback(temp.key)
            if(temp.left) {
                stack.push(temp.left)
            }
            if(temp.right) {
                stack.push(temp.right)
            }
        }
    }
    
     this.wideOrderTraverse = function(callback) {
        wideOrderTraverseNode(root, callback)
    }
}

let nodes = [8,3,10,1,6,14,4,7,13]
let tree = new BinaryTree()
nodes.forEach(function(key) {
    tree.insert(key)
})
// 定義回調函數
let callback = function(key) {
    console.log(key)
}

tree.wideOrderTraverse(callback) // 8,3,10,1,6,14,4,7,13

鑑於篇幅過長,二叉樹結點的查找和刪除會在下一篇文章內~

相關文章
相關標籤/搜索