<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                &emsp;&emsp;樹是一種非線性表數據結構,樹的基本概念如下所列。 &emsp;&emsp;(1)結點高度:結點到葉子結點的最長路徑(即邊數)。例題:[112\. 路徑總和](https://leetcode-cn.com/problems/path-sum/)。 &emsp;&emsp;(2)結點深度:根結點到這個結點所經歷的邊的個數。例題:[104\. 二叉樹的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/)。 &emsp;&emsp;(3)結點層數:結點深度加 1。 &emsp;&emsp;(4)樹的高度:根結點的高度。例題:[面試題 04.02. 最小高度樹](https://leetcode-cn.com/problems/minimum-height-tree-lcci/)。 &emsp;&emsp;后面幾張這種類型的圖都來源于《[數據結構與算法之美](https://time.geekbang.org/column/126)》。 :-: ![](https://img.kancloud.cn/50/f8/50f89510ad1f7570791dd12f4e9adeb4_1142x570.jpg =600x) 圖 2 &emsp;&emsp;(5)二叉樹:只包含左右兩個子結點的樹(編號1)。 &emsp;&emsp;(6)滿二叉樹:所有分支結點都存在左右子樹,并且所有葉子結點都在同一層上(編號2)。例題:[894\. 所有可能的滿二叉樹](https://leetcode-cn.com/problems/all-possible-full-binary-trees/)。 &emsp;&emsp;(7)完全二叉樹:葉子結點都在最底下兩層,最后一層的葉子結點都靠左排列,并且除了最后一層,其余結點個數都要達到最大(編號3)。例題:[222\. 完全二叉樹的結點個數](https://leetcode-cn.com/problems/count-complete-tree-nodes/)。 :-: ![](https://img.kancloud.cn/09/c2/09c2972d56eb0cf67e727deda0e9412b_1142x635.jpg =600x) 圖 3 ## 一、二叉樹 **1)實現** &emsp;&emsp;有兩種方法存儲一棵二叉樹,第一種是基于指針的鏈式存儲法,[如下所示](https://codepen.io/strick/pen/LYGMrVO)。 ~~~ class Node { constructor(data) { this.data = data; this.left = null; this.right = null; } } class TreeList { constructor(datas) { this.root = null; datas.forEach((value) => { const node = new Node(value); if (this.root == null) { this.root = node; return; } this.insert(this.root, node); }); } insert(parent, child) { if (parent.data > child.data) { parent.left === null ? (parent.left = child) : this.insert(parent.left, child); return; } parent.right === null ? (parent.right = child) : this.insert(parent.right, child); } } ~~~ &emsp;&emsp;第二種是基于數組的順序存儲法。 ~~~ left = 2 * index + 1; //左結點下標 right = 2 * index + 2; //右結點下標 ~~~ &emsp;&emsp;例題:LeetCode的[236\. 二叉樹的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/),遞歸的在左右子樹中查找兩個指定的結點,最后判斷公共祖先所在的位置。在當前結點的左子樹,或在其右子樹,又或者它就是兩種的公共祖先。 **2)遍歷** &emsp;&emsp;二叉樹的遍歷有四種([示例如下](https://codepen.io/strick/pen/mdVaKVz)): &emsp;&emsp;(1)前序:先訪問當前結點,然后訪問左子樹,再訪問右子樹。 ~~~ preOrder(root = this.root) { //前序 if (!root) { return; } console.log(root.data); this.preOrder(root.left); this.preOrder(root.right); } ~~~ :-: ![](https://img.kancloud.cn/7d/99/7d995a99dadab9fa481a5e17cd4cc8d0_555x419.png =400x) 圖 4 &emsp;&emsp;面試題28[對稱二叉樹](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/)。前序遍歷的變種是先訪問右結點,再訪問左結點,如果其遍歷結果與前序遍歷結果相同,就說明是對稱的。 &emsp;&emsp;面試題34[二叉樹中和為某一值的路徑](https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/)。前序遍歷首先訪問根結點,在用前序遍歷訪問結點時,將其壓入棧中,遇到葉結點,就求和判斷結果是否符合要求。然后將葉結點出棧,回到父節點,繼續遍歷右子樹,遞歸執行該過程。 &emsp;&emsp;(2)中序:先訪問左子樹,然后訪問當前結點,再訪問右子樹。 ~~~ inOrder(root = this.root) { //中序 if (!root) { return; } this.midOrder(root.left); console.log(root.data); this.midOrder(root.right); } ~~~ :-: ![](https://img.kancloud.cn/f7/d5/f7d50d862a6b1902fd7b20b48c6562b7_650x410.png =400x) 圖 5 &emsp;&emsp;面試題7[重建二叉樹](https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/)。前序遍歷第一個數是根結點,中序遍歷以根結點為界其兩邊分別是左右子樹,遞歸構建左右子樹。 &emsp;&emsp;面試題54[BST中第 k 大的結點](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/)。中序遍歷BST,得到的序列是遞增的。 &emsp;&emsp;(3)后序:先訪問左子樹,然后訪問右子樹,再訪問當前結點。 ~~~ postOrder(root = this.root) { //后序 if (!root) { return; } this.backOrder(root.left); this.backOrder(root.right); console.log(root.data); } ~~~ :-: ![](https://img.kancloud.cn/62/5d/625d7bbb11620dc27b10276f46e49ff3_546x346.png =400x) 圖 6 &emsp;&emsp;面試題33[BST的后序遍歷序列](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/)。序列的最后一個數字是根結點,左子樹的結點都比根結點小,右子樹的結點都比根結點大,遞歸執行該過程。 &emsp;&emsp;(4)層序:自上而下,自左至右逐層訪問樹的結點。利用一個輔助隊列來完成層序遍歷。 ~~~ levelOrder(node = this.root) { //層序 let queue = []; queue.push(node); // 根結點入隊 while (queue.length) { node = queue.shift(); // 出隊 console.log(node.data); // 訪問該結點 if (node.left) { // 如果它的左子樹不為空 queue.push(node.left); // 將左子樹的根結點入隊 } if (node.right) { // 如果它的右子樹不為空 queue.push(node.right); // 將右子樹的根結點入隊 } } } ~~~ &emsp;&emsp;除了層序遍歷之外,其余三種都采用遞歸的方式來遍歷二叉樹。 &emsp;&emsp;有兩種圖的搜索算法,也適用于樹。 &emsp;&emsp;(1)廣度優先搜索算法(Breadth-First Search,BFS)會從根結點開始遍歷,先訪問其所有的相鄰點,就像一次訪問樹的一層,也就是先寬后深地訪問結點,之前的層序遍歷就是BFS,如下圖左半部分。 &emsp;&emsp;(2)深度優先搜索算法(Depth-First-Search,DFS)會從根結點開始遍歷,沿著路徑直到這條路徑最后一個葉結點被訪問,接著原路回退并探索下一條路徑,也就是先深度后廣度地訪問結點,如下圖右半部分。 :-: ![](https://img.kancloud.cn/0f/84/0f8439a00e30ab283ccb34d90aa329e9_962x330.png =700x) &emsp;&emsp;在《[算法小抄](https://labuladong.gitbook.io/algo/di-ling-zhang-bi-du-xi-lie/xue-xi-shu-ju-jie-gou-he-suan-fa-de-gao-xiao-fang-fa#san-suan-fa-shua-ti-zhi-nan)》一文中曾強調先刷二叉樹的LeetCode題目,因為很多難題本質上都是基于二叉樹的遍歷,例如LeetCode的[124 題](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/)(二叉樹中的最大路徑和)、[105 題](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)(從前序與中序遍歷序列構造二叉樹)和[99 題](https://leetcode-cn.com/problems/recover-binary-search-tree/)(恢復二叉搜索樹)。 **3)遞歸** &emsp;&emsp;遞歸是一種應用廣泛的編程技巧,如果要使用遞歸,需要滿足三個條件。 &emsp;&emsp;(1)一個問題的解可以分解為幾個子問題的解。 &emsp;&emsp;(2)這個問題與分解之后的子問題,除了數據規模不同,求解思路完全一樣。 &emsp;&emsp;(3)存在遞歸終止條件,即基線條件(Base Case)。 &emsp;&emsp;注意,遞歸的關鍵就是找到將大問題分解為小問題的規律(推薦畫出遞歸樹),基于此寫出遞推公式,然后再推敲終止條件,并且需要警惕重復計算。下面是一個遞歸的大致模板。 ~~~ function recursion(level, param1, param2, ...) { //遞歸的終止條件 if(level > MAX_LEVEL) { console.log("result"); return; } //數據處理 processData(level, data1,...); //繼續遞歸 recursion(level + 1, p1, ...); //收尾工作 reverseState(level); } ~~~ &emsp;&emsp;遞歸的數學模型就是[歸納法](https://baike.baidu.com/item/%E6%95%B0%E5%AD%A6%E5%BD%92%E7%BA%B3%E6%B3%95),其過程如下。 &emsp;&emsp;(1)基礎情況:證明 P(b)語句成立,該步驟只需帶入數字即可。 &emsp;&emsp;(2)聲明假設:假設 P(n)語句成立。 &emsp;&emsp;(3)歸納步驟:證明如果 P(n)語句成立,那么 P(n+1) 語句也一定成立。 &emsp;&emsp;例如設計一程序,求自然數 N 的階乘 N!。 &emsp;&emsp;(1)當 N=1 時,N!=1。 &emsp;&emsp;(2)假設 P(N)=N!,P(N+1)=(N+1)!。 &emsp;&emsp;(3)證明 P(N) 和 P(N+1) 的關系: ~~~ P(N+1) = (N+1)! = (N+1)×(N)×…×2×1 = (N+1)×N! = (N+1)×P(N) ~~~ &emsp;&emsp;根據這個公式可構造一個遞歸函數: ~~~ function factorial(N) { return N * factorial(N - 1); //遞歸部分 } ~~~ &emsp;&emsp;在采用數學歸納法設計遞歸程序后,就能擺脫每一步的遞推,直接根據分析就能轉化為代碼。 &emsp;&emsp;試圖想清楚整個遞和歸過程的做法,實際上是一種思維誤區,不符合人腦平鋪直敘的思維方式。 ## 二、二叉查找樹 &emsp;&emsp;在二叉查找樹(Binary Search Tree,BST)中,每個結點的值都大于左子結點,小于右子結點。當中序遍歷BST時,就可在 O(n) 的時間復雜度內輸出有序的結點。 &emsp;&emsp;BST的時間復雜度和樹的高度成正比,即 O(height),經過推導后,完全二叉樹的高度(height)小于等于 log2^n。 &emsp;&emsp;平衡二叉查找樹的高度接近 logn,所以插入、刪除、查找等操作的時間復雜度也比較穩定,都是 O(logn)。 **1)操作** &emsp;&emsp;在BST中查找一個結點的遞歸算法是([代碼如下所示](https://codepen.io/strick/pen/JjGwZeZ)): &emsp;&emsp;(1)如果被查找的結點和根結點的值相等,則查找命中,否則就遞歸地的在適當的子樹中繼續查找。 &emsp;&emsp;(2)如果被查找的結點值較小就選擇左子樹,否則就選擇右子樹。 ~~~ find(data) { //查找 let node = this.root; while (node != null) { if (data == node.data) { return node; } data < node.data ? (node = node.left) : (node = node.right); } return null; } ~~~ &emsp;&emsp;BST插入結點的過程和查找差不多,依次比較結點值和左右子樹的大小。 ~~~ insert(parent, child) { //插入 if (parent.data > child.data) { parent.left === null ? (parent.left = child) : this.insert(parent.left, child); return; } parent.right === null ? (parent.right = child) : this.insert(parent.right, child); } ~~~ &emsp;&emsp;在BST中查找最大和最小的結點,以最小值為例,如果根結點的左鏈接為空,那么一棵BST中的最小值就是根結點;如果左鏈接非空,那么最小值就是左子樹中的最小值。 ~~~ min(node = this.root) { //最小值 if (node.left == null) return node; return this.min(node.left); } ~~~ **2)刪除** &emsp;&emsp;針對刪除結點的子結點個數的不同,需要分類討論([代碼如下所示](https://codepen.io/strick/pen/oNbJMoK)): &emsp;&emsp;(1)如果沒有子結點,那么只需將父結點中,鏈接刪除結點的指針置為 null。 &emsp;&emsp;(2)如果只有一個子結點,那么只需更新父結點中,鏈接刪除結點的指針指向其子結點即可。 &emsp;&emsp;(3)如果包含兩個子結點,那么需要先找到該結點右子樹中的最小結點,替換要刪除的結點;然后再刪除該最小結點,由于最小結點肯定沒有左子結點,因此可以使用上面兩條規則刪除它。 :-: ![](https://img.kancloud.cn/29/9c/299c615bc2e00dc32225f4d9e3490e2c_1142x620.jpg =600x) 圖 7 ~~~ del(data) { //刪除 let p = this.root, //p指向要刪除的結點,初始化指向根結點 parent = null; //父結點 while (p != null && p.data != data) { parent = p; data > p.data ? (p = p.right) : (p = p.left); } if (p == null) return; //沒有找到 // 要刪除的結點有兩個子結點 if (p.left != null && p.right != null) { //查找右子樹中最小結點 let minP = p.right, minParent = p; //minParent表示minP的父結點 while (minP.left != null) { minParent = minP; minP = minP.left; } p.data = minP.data; //將minP的數據替換到p中 p = minP; //下面就變成了刪除minP了 parent = minParent; } // 刪除結點是葉子結點或者僅有一個子結點 let child; //p的子結點 if (p.left != null) child = p.left; else if (p.right != null) child = p.right; else child = null; if (parent == null) this.root = child; // 刪除的是根結點 else if (parent.left == p) parent.left = child; else parent.right = child; } ~~~ **3)數據重復** &emsp;&emsp;要讓BST支持重復數據,可以有兩種處理方式。 &emsp;&emsp;(1)在每個結點中增加一個鏈表,把相同的值存儲到鏈表中。 &emsp;&emsp;(2)將相同的值插入到結點的右子樹中,作為大于這個結點來處理。 **4)平衡二叉查找樹** &emsp;&emsp;平衡二叉樹是指任意一個結點的左右子樹的高度相差不能大于 1,讓整棵樹左右看起來比較對稱和平衡,不要出現左子樹很高、右子樹很矮的情況。 &emsp;&emsp;在下面的[示例](https://codepen.io/strick/pen/eYJbLmo)中,height()函數會自頂向下遞歸的計算結點的高度,isBalanced()函數會判斷左右子樹的高度差。 ~~~ function isBalanced(root) { if (root == null) return true; if (Math.abs(height(root.left) - height(root.right)) > 1) { return false; } return isBalanced(root.left) && isBalanced(root.right); } function height(root) { if (root == null) return 0; return Math.max(height(root.left) + 1, height(root.right) + 1); } ~~~ ## 三、堆 &emsp;&emsp;堆(heap)是一種特殊的樹形數據結構,它有兩個特性: &emsp;&emsp;(1)必須是一棵完全二叉樹。 &emsp;&emsp;(2)結點的值要大于等于或小于等于兩個子結點的值。 &emsp;&emsp;當結點的值小于等于兩個子結點的值,稱之為小頂堆;當結點的值大于等于兩個子結點的值,稱之為大頂堆。 **1)實現** &emsp;&emsp;往堆中插入一個元素后,需要繼續滿足堆的兩個特性,這個過程叫做堆化(heapify),下面的[示例](https://codepen.io/strick/pen/bGEOxLm)在構建一個大頂堆。 ~~~ function heapify(arr, x, len) { let l = 2 * x + 1, //左結點 r = 2 * x + 2, //右結點 largest = x, temp; if (l < len && arr[l] > arr[largest]) { largest = l; } if (r < len && arr[r] > arr[largest]) { largest = r; } if (largest != x) { //交換位置 temp = arr[x]; arr[x] = arr[largest]; arr[largest] = temp; heapify(arr, largest, len); } } const tree = [4, 5, 1, 2, 3, 6], heapSize = tree.length; for (let i = Math.floor(heapSize / 2) - 1; i >= 0; i--) { heapify(tree, i, heapSize); } ~~~ **2)堆排序** &emsp;&emsp;堆排序是一種原地的、時間復雜度為 O(nlogn) 的不穩定排序算法。排序主要分為兩個過程:一是構建堆;二是交換堆頂元素與最后一個元素的位置,[如下所示](https://codepen.io/strick/pen/pogqOMo)。 ~~~ function heapSort(arr) { let heapSize = arr.length, temp; //建堆 for (let i = Math.floor(heapSize / 2) - 1; i >= 0; i--) { heapify(arr, i, heapSize); } //堆排序 for (let j = heapSize - 1; j >= 1; j--) { temp = arr[0]; arr[0] = arr[j]; arr[j] = temp; heapify(arr, 0, --heapSize); } return arr; } ~~~ **3)應用** &emsp;&emsp;堆有幾個非常重要的應用,例如優先級隊列、求Top K和求中位數。例題:[703\. 數據流中的第K大元素](https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/),[劍指 Offer 41. 數據流中的中位數](https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/)。 &emsp;&emsp;其中求中位數的方法很巧妙,會維護兩個堆:大頂堆和小頂堆。大頂堆中存儲前半部分數據,小頂堆中存儲后半部分數據,且小頂堆中的數據都大于大頂堆中的數據。如果有 n 個數據: &emsp;&emsp;(1)當 n 是偶數時,前 2/n? 個數據存儲在大頂堆中,后 2/n? 個數據存儲在小頂堆中。 &emsp;&emsp;(2)當 n 是奇數時,大頂堆就存儲 2/n?+1 個數據,小頂堆中就存儲 2n? 個數據。 &emsp;&emsp;這樣,大頂堆中的堆頂元素就是要找的中位數。 ***** > 原文出處: [博客園-數據結構和算法躬行記](https://www.cnblogs.com/strick/category/1809992.html) 已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎閱讀。 ![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200) 推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看