--- tags: 提升程式設計師的面試力 --- # 樹和圖 ### 二元堆積 (最大堆積和最小堆積) 二元堆積(英語:binary heap)是一種特殊的堆積,二元堆積是完全二元樹或者是近似完全二元樹。二元堆積滿足堆積特性:父節點的鍵值總是保持固定的序關係於任何一個子節點的鍵值,且每個節點的左子樹和右子樹都是一個二元堆積。 當父節點的鍵值總是大於或等於任何一個子節點的鍵值時為「最大堆積」。當父節點的鍵值總是小於或等於任何一個子節點的鍵值時為「最小堆積」。 (然而, 同一層的子節點則無須理會其大小關係) 所以大概長得像這樣(Min heap): ``` 4 - s / \ / \ 50 7 / \ / 55 90 87 Step 1 4 / \ / \ 50 7 / \ / \ 55 90 87 2 Step 2 4 / \ / \ 50 2 / \ / \ 55 90 87 7 Step 3 2 / \ / \ 50 4 / \ / \ 55 90 87 7 ``` https://clu.gitbook.io/data-structure-note/heap-tree ### 線索樹 (字首樹) 線索二元樹(引線二元樹) 的定義如下: 「一個二元樹通過如下的方法「穿起來」:所有原本為空的右(孩子)指針改為指向該節點在中序序列中的後繼,所有原本為空的左(孩子)指針改為指向該節點的中序序列的前驅。」 線索二元樹能線性地遍歷二元樹,從而比遞歸的 中序遍歷更快。使用線索二元樹也能夠方便的找到一個節點的父節點,這比顯式地使用父親節點指針或者棧效率更高。這在棧空間有限,或者無法使用存儲父節點的棧時很有作用(對於通過深度優先搜索來查找父節點而言)。 () / \ \ / \ \ / \ \ / \ \ a r n / \ \ \ h x o o / \ \ \ null i l null \ \ null null https://zh.wikipedia.org/wiki/%E7%BA%BF%E7%B4%A2%E4%BA%8C%E5%8F%89%E6%A0%91 https://www.youtube.com/watch?v=jpCJgD0kusM ### 圖 Graph 中文翻做「圖」。此處談及的「圖」並不是指圖片或者圖形。「圖」是一種用來記錄關聯、關係的東西。 一張圖由數個點( vertex )以及數條邊( edge )所構成。點與點之間,得以邊相連接,表示這兩點有關聯、關係。 Graph 資料結構 : Adjacency Matrix | | 0 | 1 | 2 | | --| --| --|-- | | 0 | 0 | 0 | 1 | | 1 | 0 | 0 | 1 | | 2 | 1 | 1 | 0 | Graph 資料結構 : Adjacency Lists 0:2 1:2 2:0,1 http://web.ntnu.edu.tw/~algo/Graph.html 範例使用圖 https://docs.google.com/presentation/d/13Omtj7zGZkR8j485XO3pow1CGNUxbtF-9P1aL_iu0qw/edit#slide=id.g117efa2c41c_0_41 4.7 建構順序: 你將得到一個專案列表和一個依賴專案列表。在一個專案開始建構之前,必須建構好該專案所有的依賴的專案。請找到一個能讓專案順利建構的順序。如果找不到有效的建構順序,則回傳錯誤。 專案: a,b c,d,e,f 依賴專案順序:(a,b),(f,b),(b,d),(f,a),(d,c) ![](https://i.imgur.com/VKNlddt.jpg) ``` Graph.prototype.findNodeWithNoChildren = function() { for (var node in this.nodes) { if (Object.keys(this.nodes[node]).length === 0) { return node; } } return undefined; }; var buildOrder = function(projects, dependencies) { var graph = new Graph(); projects.forEach(project => { graph.addNode(project); }); dependencies.forEach(dependency => { graph.addEdge(dependency[1], dependency[0]); }); var answer = []; var currNode = graph.findNodeWithNoChildren(); while (currNode !== undefined) { answer.push(currNode); graph.removeNode(currNode); currNode = graph.findNodeWithNoChildren(); } if (answer.length === projects.length) { return answer; } else { throw Error; } }; /* TEST */ var projects = ['a', 'b', 'c', 'd', 'e', 'f']; var dependencies = [['a', 'd'], ['f', 'b'], ['b', 'd'], ['f', 'a'], ['d', 'c']]; console.log(buildOrder(projects, dependencies)); ``` 4.8 第一個共同祖先: 請設計一個演算法並撰寫程式碼,搜尋二元樹中兩個節點的第一個共同祖先。請避免在資料結構中儲存額外的節點。 ``` var BinaryTree = function(value) { this.value = value; this.left = null; this.right = null; this.parent = null; }; BinaryTree.prototype.isAncestor = function(node2) { if (this === node2) { return true; } else { var answer1 = false; var answer2 = false; if (this.left !== null) { answer1 = this.left.isAncestor(node2); } if (this.right !== null) { answer2 = this.right.isAncestor(node2); } return false || answer1 || answer2; } }; var firstCommonAncestor = function(node1, node2) { var currNode = node1; while (!currNode.isAncestor(node2)) { if (currNode === null) { throw Error; } else { currNode = currNode.parent; } } return currNode.value; }; /* TEST */ var a = new BinaryTree('a'); var b = new BinaryTree('b'); var c = new BinaryTree('c'); var d = new BinaryTree('d'); var e = new BinaryTree('e'); var f = new BinaryTree('f'); var g = new BinaryTree('g'); var h = new BinaryTree('h'); var i = new BinaryTree('i'); var j = new BinaryTree('j'); var k = new BinaryTree('k'); var l = new BinaryTree('l'); a.left = b; b.parent = a; a.right = c; c.parent = a; b.left = d; d.parent = b; d.left = g; g.parent = d; d.right = h; h.parent = d; h.right = k; k.parent = h; k.left = l; l.parent = k; c.left = e; e.parent = c; c.right = f; f.parent = c; f.left = i; i.parent = f; f.right = j; j.parent = f; console.log(firstCommonAncestor(g, k), 'd'); console.log(firstCommonAncestor(b, i), 'a'); ``` 4.9 ``` var BST = require('./../util/BST'); var bstSequences = function(bst) { var sequences = []; var recurse = function(nodes, travelled) { var noChild = true; nodes.forEach((node) => { if (node.left !== null && travelled[node.left.value] === undefined) { noChild = false; travelled[node.left.value] = true; recurse(nodes.concat([node.left]), travelled); delete travelled[node.left.value]; } if (node.right !== null && travelled[node.right.value] === undefined) { noChild = false; travelled[node.right.value] = true; recurse(nodes.concat([node.right]), travelled); delete travelled[node.right.value]; } }); if (noChild) { sequences.push(nodes.map(node => node.value)); } }; var startTravelled = {}; startTravelled[bst.value] = true; recurse([bst], startTravelled); return sequences; }; /* TEST */ /* 1, 2, 3, 4, 5, 6, 7 */ var b = new BST(4); b.insert(2); b.insert(6); b.insert(1); b.insert(3); b.insert(5); b.insert(7); console.log(bstSequences(b)); ```