資料結構篇(8) 二叉樹的一些基本操作實現
阿新 • • 發佈:2022-04-01
/** * parent: 雙親節點 * left: 左子節點 * right: 右子節點 */ interface TreeNode { left: TreeNode | null, right: TreeNode | null, data: any, count: number } class TreeNode { constructor(data: any, left: TreeNode | null, right: TreeNode | null) { this.data = data; this.left = left; this.right = right; this.count = 1; } } //二叉排序樹 interface BsTree { root: TreeNode | null //刪除一個節點 _removeNode(node: TreeNode, data: any): void //刪除給定的資料節點 remove(data: any): void //向二叉樹中插入節點 insert(data: any): void //尋找給定資料的節點 find(data: any): TreeNode | null //獲得最小值的節點 getMinNode(node: TreeNode): TreeNode | null //獲得最大值的節點 getMaxNode(node: TreeNode): TreeNode | null display(): void // 獲得二叉樹中節點個數 getNodeNumber(node: TreeNode): number // 獲得二叉樹中葉子節點的個數 getLeafNodeNumber(node: TreeNode): number // 獲取二叉樹的深度 getTreeDepth(node: TreeNode): number // 計算給定層數節點的個數 getLevelNodeNumber(level: any, node: TreeNode): number // 判斷二叉樹是不是完全二叉樹 isCompleteTree(node: TreeNode): boolean // 求二叉樹的映象 invertTree(node: TreeNode): void } class BsTree { root: TreeNode | null = null /** * * @param node 要刪除的節點 * @param data 要刪除值 * 遞迴刪除節點 * 待刪除的節點是葉子節點。 * 待刪除的節點沒有左子節點,或者沒有右子節點。 * 待刪除的節點的左右子節點均存在。 * 當待刪除的節點時葉子節點時,這種情況比較簡單,直接將待刪除的節點置空返回即可。 * 當待刪除的節點沒有左子節點時,返回該節點的右孩子節點,並刪除該節點。待刪除節點沒有右節點時類似處理。 * 比較麻煩的是最後一種情況,待刪除的節點的左右子節點均存在時,可以有兩種做法:要麼查詢待刪除節點左子樹上的最大值,要麼查詢其右子樹上的最小值。 * 這裡使用查詢其右子樹上的最小值的方法。在找到待刪除節點的右子樹上的最小值後,建立一個臨時節點,將臨時節點上的值複製到待刪除節點,然後再刪除臨時節點。 */ _removeNode(node: TreeNode, data: any): TreeNode | null { if (node == null) { return null; } if (data == node.data) { if (node.left == null && node.right == null) { return null; } //沒有左節點的節點 if (node.left == null) return node.right; //沒有右節點的節點 if (node.right == null) return node.left; //有兩個節點的節點 /* 做法: 找到待刪除節點的右子樹上的最小值建立一個臨時節點。 將臨時節點上的值複製到待刪除節點,然後再刪除臨時節點 */ // 尋找右子樹上的最小值 let tmpNode = this.getMinNode(node.right); if (tmpNode) { node.data = tmpNode.data; node.right = this._removeNode(node.right, tmpNode.data); return node; } } else if (data < node.data && node.left) { // 待刪除節點在左子樹上 node.left = this._removeNode(node.left, data); return node; } else { // 待刪除節點在右子樹上 if (node.right) { node.right = this._removeNode(node.right, data); return node; } } return null } /** * * @param data 要刪除的節點值 */ //刪除給定的資料節點 remove(data: any): void { if (this.root) { console.log(this._removeNode(this.root, data)); } } /** * 如果在插入時,root節點為空,則直接將新節點賦給root節點即可。 * 如果新的節點值小於當前節點值,說明待插入的位置應在在當前節點的左子樹上,那麼在大於時,就應該在當前節點的右子樹上。進而更新當前節點所指向的節點,直到當前節點為空時,說明找到了正確的插入位置。 */ insert(data: any): void { let newNode = new TreeNode(data, null, null); let parentNode = null; if (this.root === null) { this.root = newNode; } else { let currNode: any = this.root; while (true) { parentNode = currNode; if (newNode.data > currNode.data) { currNode = currNode.right; if (!currNode) { parentNode.right = newNode; break; } } else if (newNode.data < currNode.data) { currNode = currNode.left; if (!currNode) { parentNode.left = newNode; break; } } else if (currNode.data === newNode.data) { currNode.count++; break; } } } } //尋找給定資料的節點 find(data: any): TreeNode | null { if (!this.root) { return null; } let currNode: TreeNode = this.root; while (currNode) { if (data > currNode.data && currNode.right) { currNode = currNode.right; } else if (data < currNode.data && currNode.left) { currNode = currNode.left; } else { return currNode; } } return null; } //獲得最小值的節點 getMinNode(node: TreeNode | null = this.root): TreeNode | null { let currNode = node; while (currNode?.left) { currNode = currNode.left; } return currNode; } //獲得最大值的節點 getMaxNode(node: TreeNode | null = this.root): TreeNode | null { let currNode = node; while (currNode?.right) { currNode = currNode.right; } return currNode; } preOrderRec(node: any = this.root) { let result = ''; if (!(node == null)) { result += `${node.data} `; result += this.preOrderRec(node.left); result += this.preOrderRec(node.right); } return result; } // 前序遍歷非遞迴方法 preOrderNonRec(node: any = this.root) { let stack: Array<any> = []; let result = ''; while (node || stack.length) { if (node) { result += `${node.data} `; stack.push(node); node = node.left; } else { node = stack.pop(); node = node.right; } } return result; } inOrderRec(node: any = this.root) { let result = ''; if (!(node == null)) { result += this.inOrderRec(node.left); result += `${node.data} `; result += this.inOrderRec(node.right); } return result; } //中序遍歷非遞迴演算法 inOrderNonRec(node: any = this.root) { let result = ''; let stack: Array<any> = []; while (node || stack.length) { if (node) { stack.push(node); node = node.left } else { node = stack.pop(); result += `${node.data} `; node = node.right; } } return result; } //後續遍歷 postOrderRec(node: any = this.root) { let result = ''; if (!(node == null)) { result += this.postOrderRec(node.left); result += this.postOrderRec(node.right); result += `${node.data} ` } return result; } getNodeNumber(node = this.root) { // 統計二叉樹中結點個數的演算法 (先根遍歷) let count = 0; if (node) { count++; count += this.getNodeNumber(node.left); count += this.getNodeNumber(node.right) } return count; } getNorNodeNumber(node: any = this.root) { let queue = []; let count = 0; queue.push(node); while (queue.length) { count++; let node = queue.shift(); if (node.left) { queue.push(node.left); } if (node.right) { queue.push(node.right); } } return count; } //遞迴求葉子節點數量 getLeafNodeNumber(node: TreeNode | null = this.root): number { let count = 0; if (node) { if (node.right == null && node.left == null) { count++; } count += this.getNodeNumber(node.left); count += this.getNodeNumber(node.right); } return count; } //非遞迴求葉子節點數量 getLeafNorNodeNumber(node: any = this.root): number { let count = 0; let queue = []; if (node) { queue.push(node); while (queue.length) { node = queue.shift(); if (node.left) { queue.push(node.left); } if (node.right) { queue.push(node.right); } if (!node.right && !node.left) { count++; } } } return count; } //獲得二叉樹的深度 getTreeDepth(node: any = this.root): number { if (!node) { return 0; } let leftMax = this.getTreeDepth(node.left); let rightMax = this.getTreeDepth(node.right); if (leftMax > rightMax) { return leftMax + 1; } else { return rightMax + 1; } } //獲得規定層數的節點個數(層序遍歷法) getLevelNodeNumber(level: number, node: any = this.root) { if (node) { let queue = []; let depth = 1; queue.push(node); if (level === queue.length) return queue.length; while (true) { let size = queue.length; if(size == 0) { break; } while (size) { node = queue.shift();//當前佇列全部出棧後,儲存的就是下一層節點 if (node.left) { queue.push(node.left); } if (node.right) { queue.push(node.right); } size--; } depth++; if (depth === level) { return queue.length; } } } } //判斷二叉樹是不是完全二叉樹 如果層次遍歷時遇到一個空節點後,再向後的遍歷的時候依然還有節點,則這個二叉樹肯定不是完全二叉樹。 isCompleteTree(node:any = this.root):boolean { let queue = []; let flag = false; queue.push(node); while(queue.length) { node = queue.shift(); if(node) { if(flag) return false; queue.push(node.left); queue.push(node.right); } else { flag = true; } } return true; } //如果一個節點只有右子節點,則不是完全二叉樹。當遍歷到一個不是葉子節點的節點時,如果其前面的節點沒有右孩子節點,則不是完全二叉樹。 isCompleteTree2(node:any = this.root):boolean { let queue = []; let noRight = false; queue.push(node); while(queue.length) { node = queue.shift(); if(!node.left&&node.right) { return false; } if((node.left||node.right) && noRight) { return true; } if(node.left) { queue.push(node.left); } if(node.right) { queue.push(node.right); } else { noRight = true; } } return true; } //映象二叉樹 invertTree(node = this.root) { // 遞迴的方法 if (!node) return; // 交換當前節點的左右子樹 let tmpNode = node.left; node.left = node.right; node.right = tmpNode; this.invertTree(node.left); this.invertTree(node.right); } } /** * @param {Array} preArr 先序序列 * @param {Array} inArr 中序序列 * @param {Number} pBeg 先序序列的第一個下標 * @param {Number} pEnd 先序序列的最後一個下標 * @param {Number} iBeg 中序序列的第一個下標 * @param {Number} iEnd 中序序列的最後一個下標 */ let myTree = new BsTree(); let tree2 = new BsTree(); function preInCreate(preArr: Array<Number>, inArr: Array<number>, pBeg: number, pEnd: number, iBeg: number, iEnd: number): TreeNode { let node = new TreeNode(preArr[pBeg], null, null); let lLen = 0, rLen = 0; let splitIdx = -1; if (tree2.root == null) { tree2.root = node; } for (let i: number = iBeg; i <= iEnd; i++) { if (inArr[i] === node.data) { splitIdx = i; break; } } if (splitIdx > -1) { lLen = splitIdx - iBeg; rLen = iEnd - splitIdx; } if (lLen) { node.left = preInCreate(preArr, inArr, pBeg + 1, pBeg + lLen, iBeg, iBeg + lLen - 1); } else { node.left = null; } if (rLen) { // 遞迴建立右子樹 node.right = preInCreate(preArr, inArr, pEnd - rLen + 1, pEnd, iEnd - rLen + 1, iEnd); } else { node.right = null; } return node; } let preOrder = [20, 13, 7, 9, 15, 14, 42, 22, 21, 24, 57]; let inOrder = [7, 9, 13, 14, 15, 20, 21, 22, 24, 42, 57]; let inLstIdx = inOrder.length - 1; let preLstIdx = preOrder.length - 1; preInCreate(preOrder, inOrder, 0, preLstIdx, 0, inLstIdx); myTree.insert(20); myTree.insert(13); myTree.insert(7); myTree.insert(9); myTree.insert(15); myTree.insert(14); myTree.insert(42); myTree.insert(22); myTree.insert(21); myTree.insert(24); myTree.insert(57); console.log(myTree.getNodeNumber()); console.log(myTree.getTreeDepth()) console.log(myTree.getLevelNodeNumber(3)) // console.log(myTree.postOrderRec())