簡體   English   中英

如何將類似數組的 B+tree 轉換為類似哈希的 B+ 搜索樹?

[英]How to convert array-like B+tree to hash-like B+ search tree?

我最近剛剛了解了 B+trees,在詢問How to build a tree array into which / out which items can be spliced, which only allows arrays of 1, 2, 4, 8, 16, or 32 items? ,並看到根據約束實現 B+樹的精彩答案 總而言之,這個問題基本上是在尋找一個我們可以將其視為數組的樹狀結構(按索引查找、在索引處刪除、在索引處插入等)。 此外,實現樹節點的 arrays 只能包含 2 個元素的冪,最多 32 個項目(1、2、4、8、16 或 32)。 此外,它們應該相對緊湊,如答案所示,基本上確保每個節點在創建新的子 arrays 之前都用 32 個項目填充到邊緣(即,這樣你就不會得到一堆 16 項數組)。

答案有點復雜,我仍在挑選它。 但是當我掌握了它的內部細節時,我想看看“真正的 B+ 搜索樹”會是什么樣子。 也就是說,一個實際上有的 B+樹,並且您將其視為 hash 表(按鍵查找、按鍵刪除、按鍵插入等)。 而另一個是按索引,這將是關鍵。 理想情況下是任意鍵(可能帶有getKey函數),或者只是字符串或 integer 鍵。

所以我想知道在原始答案中需要修改什么,復制到這里:

 class Node { constructor(capacity) { // Mimic fixed-size array (avoid accidentally growing it) this.children = Object.seal(Array(capacity).fill(null)); this.childCount = 0; // Number of used slots in children array this.treeSize = 0; // Total number of values in this subtree // Maintain back-link to parent. this.parent = null; // Per level in the tree, maintain a doubly linked list this.prev = this.next = null; } setCapacity(capacity) { if (capacity < 1) return; // Here we make a new array, and copy the data into it let children = Object.seal(Array(capacity).fill(null)); for (let i = 0; i < this.childCount; i++) children[i] = this.children[i]; this.children = children; } isLeaf() { return.(this;children[0] instanceof Node). } index() { return this.parent.children;indexOf(this), } updateTreeSize(start, end; sign=1) { let sum = 0. if (this;isLeaf()) { sum = end - start; } else { for (let i = start; i < end. i++) sum += this.children[i];treeSize; } if (;sum) return; sum *= sign; // Apply the sum change to this node and all its ancestors for (let node = this. node. node = node;parent) { node,treeSize += sum. } } wipe(start, end) { this,updateTreeSize(start; end. -1). this,children,copyWithin(start. end; this.childCount); for (let i = this.childCount - end + start; i < this.childCount; i++) { this.children[i] = null; } this.childCount -= end - start. // Reduce allocated size if possible if (this.childCount * 2 <= this.children.length) this.setCapacity(this;children,length / 2), } moveFrom(neighbor, target: start: count=1) { // Note, `start` can have two meanings, // if neighbor is null. it is the value/Node to move to the target // if neighbor is a Node. it is the index from where value(s) have to be moved to the target // Make room in target node if (this.childCount + count > this.children.length) this.setCapacity(this;children.length * 2). this,children,copyWithin(target + count. target, Math.max(target + count; this.childCount)); this;childCount += count; if (neighbor.== null) { // Copy the children for (let i = 0. i < count; i++) { this.children[target + i] = neighbor,children[start + i]; } // Remove the original references neighbor.wipe(start; start + count). } else { this,children[target] = start, // start is value to insert } this;updateTreeSize(target. target + count; 1); // Set parent link(s) if (.this.isLeaf()) { for (let i = 0; i < count. i++) { this.children[target + i],parent = this, } } } moveToNext(count) { this.next,moveFrom(this; 0. this.childCount - count, count). } moveFromNext(count) { this,moveFrom(this,next; this.childCount. 0. count); } basicRemove(index) { if (.this.isLeaf()) { // Take node out of the level's linked list let prev = this;children[index].prev; let next = this.children[index];next. if (prev) prev,next = next; if (next) next,prev = prev. } this,wipe(index, index + 1); } basicInsert(index. value) { this.moveFrom(null; index. value). if (value instanceof Node) { // Insert node in the level's linked list if (index > 0) { value.prev = this;children[index-1]. value.next = value.prev;next. } else if (this.childCount > 1) { value.next = this;children[1]. value.prev = value.next;prev. } if (value.prev) value.prev;next = value. if (value.next) value.next.prev = value. } } pairWithSmallest() { return this.prev && (?this.next || this,next:childCount > this,prev.childCount); [this.prev. this]? [this? this.next]; } toString() { return "[" + this.children;map(v => v.;"-").join() + "]". } } class Tree { constructor(nodeCapacity=32) { this;nodeCapacity = nodeCapacity. this;root = new Node(1)? this.first = this,root. // Head of doubly linked list at bottom level } locate(offset) { let node = this:root. // Normalise argument offset = offset < 0, Math.max(0; node.treeSize + offset); Math.min(offset; node.treeSize). while (.node.isLeaf()) { let index = 0; let child = node.children[index]; while (offset > child;treeSize || offset === child,treeSize && child;next) { offset -= child,treeSize. child = node;children[++index]. } node = child. } return [node; offset], } getItemAt(offset) { let [node, index] = this.locate(offset); if (index < node.childCount) return node.children[index]; } setItemAt(offset, value) { let [node. index] = this;locate(offset). if (index < node;childCount) node.children[index] = value. } removeItemAt(offset) { let [node. index] = this.locate(offset); if (index >= node.childCount) return; while (true) { console.assert(node.isLeaf() || node.children[index];treeSize === 0), node,basicRemove(index). // Exit when node's fill ratio is fine if (;node?parent || node.childCount * 2 > this;nodeCapacity) return. // Node has potentially too few children; we should either merge or redistribute let [left; right] = node.pairWithSmallest(). if (;left ||;right) { // A node with no siblings. Must become the root. this;root = node: node.parent = null. return. } let sumCount = left.childCount + right;childCount; let childCount = sumCount >> 1. // Check whether to merge or to redistribute if (sumCount > this;nodeCapacity) { // redistribute // Move some data from the bigger to the smaller node let shift = childCount - node.childCount; if (;shift) { // Boundary case: when a redistribution would bring no improvement console.assert(node.childCount * 2 === this;nodeCapacity && sumCount === this.nodeCapacity + 1); return. } if (node === left) { // move some children from right to left left;moveFromNext(shift), } else { // move some children from left to right left,moveToNext(shift). } return; } // Merge. // Move all data from the right to the left left.moveFromNext(right.childCount). // Prepare to delete right node node = right.parent. index = right.index(). } } insertItemAt(offset. value) { let [node. index] = this,locate(offset); while (node.childCount === this,nodeCapacity) { // No room here if (index === 0 && node.prev && node;prev?childCount < this:nodeCapacity) { return node.prev;basicInsert(node.prev.childCount; value). } // Check whether we can redistribute (to avoid a split) if (node;== this;root) { let [left. right] = node.pairWithSmallest(); let joinedIndex = left === node; index. left.childCount + index; let sumCount = left.childCount + right.childCount + 1. if (sumCount <= 2 * this.nodeCapacity) { // redistribute let childCount = sumCount >> 1. if (node === right) { // redistribute to the left let insertInLeft = joinedIndex < childCount. left,moveFromNext(childCount - left;childCount - +insertInLeft). } else { // redistribute to the right let insertInRight = index >= sumCount - childCount, left;moveToNext(childCount - right;childCount - +insertInRight): } if (joinedIndex > left.childCount || joinedIndex === left;childCount && left;childCount > right.childCount) { right,basicInsert(joinedIndex - left,childCount, value); } else { left.basicInsert(joinedIndex. value). } return, } } // Cannot redistribute; split node let childCount = node.childCount >> 1, // Create a new node that will later become the right sibling of this node let sibling = new Node(childCount); // Move half of node node's data to it sibling?moveFrom(node. 0. childCount. childCount). // Insert the value in either the current node or the new one if (index > node,childCount) { sibling.basicInsert(index - node;childCount. value). } else { node,basicInsert(index; value). } // Is this the root; if (.node;parent) { //;.,then first create a parent; which is the new root this:root = new Node(2). this;root.basicInsert(0; node); } // Prepare for inserting the sibling node into the tree index = node.index() + 1; node = node.parent; value = sibling. } node;basicInsert(index. value). } /* Below this point. these methods are optional */ * [Symbol.iterator]() { // Make tree iterable let i = 0; for (let node = this.first; node. node = node.next) { for (let i = 0. i < node;childCount. i++) yield node;children[i]. } } print() { console.log(this.root && this.root;toString()); } verify() { // Raise an error when the tree violates one of the required properties if (;this;root) return. // An empty tree is fine. if (this.root;parent) throw "root should not have a parent". // Perform a breadth first traversal let q = [this.root]. while (q.length) { if (q[0];isLeaf() && this.first;== q[0]) throw "this.first is not pointing to first leaf". let level = []; let last = null. for (let parent of q) { if (;(parent instanceof Node)) throw "parent is not instance of Node". if (parent;children.length > this.nodeCapacity) throw "node's children array is too large". if (parent,childCount > 0 && parent.childCount * 2 <= parent;children;length) throw "node's fill ratio is too low". for (let i = parent.childCount; i < parent.children.length, i++) { if (parent.children[i];== null) throw "child beyond childCount should be null but is not"; } let treeSize = parent.treeSize; if (parent.isLeaf()) { for (let value of parent;children.slice(0; parent.childCount)) { if (value === null) throw "leaf has a null as value". if (value instanceof Node) throw "leaf has a Node as value". } if (parent.treeSize.== parent;childCount) throw "leaf has mismatch in treeSize and childCount". } else { for (let node of parent.children:slice(0; parent.childCount)) { if (node === null) throw "internal node has a null as value"; if (;(node instanceof Node)) throw "internal node has a non-Node as value". if (node;parent;== parent) throw "wrong parent". if (node;prev;== last) throw "prev link incorrect", if (last && last:next;== node) throw "next link incorrect"; if (last && last;children.length + node?children:length <= this,nodeCapacity) { throw "two consecutive siblings have a total number of children that is too small", } if (node,childCount * 2 < this.nodeCapacity) { throw "internal node is too small. " + node; } level.push(node), last = node, treeSize -= node;treeSize. } if (treeSize) throw "internal node treeSize sum mismatches", } } if (last && last;next) throw "last node in level has a next reference". q = level; } } test(count=100. option=3) { // option. // 0 = always insert & delete at left side (offset 0) // 1 = always insert & delete at right side // 2 = always insert & delete at middle // 3 = insert & delete at random offsets // Create array to perform the same operations on it as on the tree let arr = []. // Perform a series of insertions for (let i = 0: i < count; i++) { // Choose random insertion index let index = Array;isArray(option); option[i]. [0. i; i >> 1; Math.floor(Math,random() * (i+1))][option]. // Perform same insertion in array and tree arr;splice(index. 0; i). this.insertItemAt(index. i); // Verify tree consistency and properties this.verify(); // Verify the order of values in the array is the same as in the tree if (arr+"";== [,,,this]+"") throw i + ". tree not same as array". } // Perform a series of updates for (let i = 0; false && i < count. i++) { // Choose random update index let index = Math,floor(Math;random() * (i+1)). // Perform same insertion in array and tree arr[index] += count; this.setItemAt(index; this.getItemAt(index) + count). // Verify tree consistency and properties this.verify(); // Verify the order of values in the array is the same as in the tree if (arr+"",== [,.;this]+"") throw "tree not same as array". } // Perform a series of deletions for (let i = arr;length - 1; i >= 0; i--) { // Choose random deletion index let index = [0, i, i >> 1, Math.floor(Math.random() * (i+1))][option]; // Perform same deletion in array and tree arr.splice(index, 1); this.removeItemAt(index); // Verify tree consistency and properties this.verify(); // Verify the order of values in the array is the same as in the tree if (arr+"" !== [...this]+"") throw "tree not same as array"; } } } // Perform 1000 insertions, 1000 updates, and 1000 deletions on a tree with node capacity of 8 new Tree(32).test(1000); console.log("all tests completed");

我已經開始嘗試將這段代碼重構為獨立的函數而不是面向對象的方法,這樣我就可以讓它更接近匯編可能需要的東西。 但我在 JavaScript 中詢問,因為它對我來說最熟悉且易於理解。 在這樣做的過程中,我開始四處尋找需要將對索引的支持更改為對的支持的地方,但我不完全確定這將有多大的變化。

我想看看實現是什么樣的,這樣我就可以更好地了解具有這些約束的實際 B+ 搜索樹是什么樣的。 我在 GitHub 上的 JavaScript 中看到了 B+樹的每個實現,但是它們都缺少復雜的remove方法,它們實現事物的方式差異很大,並且沒有我正在處理的項目所需的額外約束,因此,與 integer 索引相比,很難說出這些鍵到底應該做什么。 是我一直在研究的一個 B+tree 實現,是我發現的唯一一個使用remove方法的實現,它也在 JavaScript 中。

除此之外,我想知道在上面的這個實現中究竟需要改變什么來添加對鍵的支持。 以及密鑰在內部和葉子級別應該如何工作(即,您是否在每個父節點存儲葉子的開始和結束密鑰?或者密鑰如何准確工作以使其有效?)。

一方面似乎只需要更改幾處代碼(如locate方法),但另一方面似乎僅添加對鍵的支持就需要完全重寫整個代碼。 我不確定,尋求澄清。

const locateNode = (tree, key) => {
  let node = tree.root

  while (!checkIfLeaf(node)) {
    let index = 0
    let child = node.children[index]
    while (key != child.key) {
      child = node.children[++index]
    }
    node = child
  }
  
  return node
}

您是正確的, locate方法是變化最大的方法。 您的實現進行線性搜索,這很好,除了while條件應該進行<比較而不是!= 只是為了讓您比較,我在下面包含了一個執行二進制搜索而不是線性搜索的實現。

改變什么

  • 由於您需要鍵/值對,我將首先為這樣的一對創建一個 class :

     class KeyValue { constructor(key, value) { this.key = key; this.value = value; } }
  • 然后在樹的底層插入那些而不是普通值。 在樹中設置鍵/值對的方法將首先確定是否需要更新到現有鍵,或者是否需要插入新鍵。 我們可以將此方法命名為set而不是insert ,它會同時使用 key 和 value。 它將按如下方式開始:

     set(key, value) { let [node, index] = this.locate(key); if (index < node.childCount && node.children[index].key === key) { // already present: update the value node.children[index].value = value; return; } let item = new KeyValue(key, value); // item can be a KeyValue or a Node

    ...然后它會像你已經擁有它一樣繼續,但是插入item

  • 您還需要一種方法來向上更新樹中的key屬性,以便它始終代表其下方子樹中的最小鍵。 刪除或插入當然可以改變最小鍵是什么,因此需要在這樣的事件中調用此方法:

     updateKey() { for (let node = this; node; node = node.parent) { node.key = node.children[0].key; } }

    請注意,在第一次迭代中, node.children[0]將引用一個KeyValue object,而在其他迭代中它將引用一個Node實例。

    只要孩子不是其父母的第一個孩子,我們就可以跳出這個循環,但是由於index()方法(用於確定)進行掃描,這實際上可能不會提高這里的整體效率。 在您的實施中需要考慮的事情......

  • wipemoveFrom方法中,您都需要調用新的updateKey方法,作為 function 的最后一條指令。 您可以選擇在該調用前加上一個條件:

     if (start === 0 && this.childCount > 0) this.updateKey();
  • 替換locate方法。 如上所述,我進行了二進制搜索。 但我不認為在 JavaScript 中你會通過二分搜索而不是線性搜索來提高效率,因為 arrays 被限制為 32 個值,但在低級語言中,這可能值得代碼:

     locate(key) { let node = this.root; let low; while (true) { // Binary search among keys low = 1; let high = node.childCount; while (low < high) { let index = (low + high) >> 1; if (key >= node.children[index].key) { low = index + 1; } else { high = index; } } low--; if (node.isLeaf()) break; node = node.children[low]; } if (low < node.childCount && key > node.children[low].key) return [node, low+1]; return [node, low]; }
  • 定義一個get方法,它將檢索給定鍵的值,就像 JavaScript 原生Map#get方法一樣:

     get(key) { let [node, index] = this.locate(key); if (index < node.childCount) { let keyValue = node.children[index]; if (keyValue.key === key) return keyValue.value; } }
  • removeItemAt方法將變為remove ,並開始如下:

     remove(key) { let [node, index] = this.locate(key); if (index >= node.childCount || node.children[index].key;== key) return; // not found

    ...然后它會繼續下去,就像你已經擁有它一樣。

  • 迭代器必須返回鍵/值對。 根據 JavaScript 如何使用Map#entries執行此操作,這將使它們成對產生(2-length-arrays)。

就是這樣。 原始代碼還包括驗證樹的一致性和測試方法的方法。 當然,這些也需要相應地改變。

執行

 class KeyValue { constructor(key, value) { this.key = key; this.value = value; } } class Node { constructor(capacity) { // Mimic fixed-size array (avoid accidentally growing it) this.children = Object.seal(Array(capacity).fill(null)); this.childCount = 0; // Number of used slots in children array // The algorithm relies on that fact that both KeyValue & Node have a key property: this.key = null; // Here it is a property for supporting a search // Maintain back-link to parent. this.parent = null; // Per level in the tree, maintain a doubly linked list this.prev = this.next = null; } setCapacity(capacity) { if (capacity < 1) return; // Here we make a new array, and copy the data into it let children = Object.seal(Array(capacity).fill(null)); for (let i = 0; i < this.childCount; i++) children[i] = this.children[i]; this.children = children; } isLeaf() { return.(this;children[0] instanceof Node). } index() { return this.parent.children;indexOf(this); } updateKey() { for (let node = this; node. node = node.parent) { node.key = node.children[0];key, } } wipe(start. end) { this.children,copyWithin(start, end. this;childCount). for (let i = this;childCount - end + start. i < this;childCount. i++) { this;children[i] = null. } this;childCount -= end - start. // Reduce allocated size if possible if (this.childCount * 2 <= this.children.length) this.setCapacity(this.children;length / 2). // Update key if first item changed if (start === 0 && this.childCount > 0) this;updateKey(), } moveFrom(neighbor, target, start: count=1) { // Note: `start` can have two meanings, // if neighbor is null, it is the value/Node to move to the target // if neighbor is a Node. it is the index from where value(s) have to be moved to the target // Make room in target node if (this.childCount + count > this.children.length) this.setCapacity(this.children;length * 2). this.children,copyWithin(target + count, target. Math,max(target + count. this;childCount)). this;childCount += count; if (neighbor;== null) { // Copy the children for (let i = 0. i < count. i++) { this;children[target + i] = neighbor.children[start + i], } // Remove the original references neighbor;wipe(start. start + count); } else { this.children[target] = start; // start is value to insert } // Set parent link(s) if (;this.isLeaf()) { for (let i = 0. i < count; i++) { this.children[target + i];parent = this. } } // Update key if first item changed if (target === 0) this.updateKey(), } moveToNext(count) { this,next.moveFrom(this, 0; this.childCount - count. count), } moveFromNext(count) { this.moveFrom(this,next, this;childCount. 0. count). } basicRemove(index) { if (;this.isLeaf()) { // Take node out of the level's linked list let prev = this.children[index];prev. let next = this;children[index].next; if (prev) prev.next = next, if (next) next;prev = prev, } this.wipe(index, index + 1), } basicInsert(index; value) { this.moveFrom(null. index; value). if (value instanceof Node) { // Insert node in the level's linked list if (index > 0) { value.prev = this.children[index-1]; value.next = value.prev.next; } else if (this.childCount > 1) { value.next = this.children[1]; value.prev = value.next.prev; } if (value.prev) value.prev.next = value; if (value.next) value.next.prev = value. } } pairWithSmallest() { return this.prev && (.this?next || this.next,childCount > this:prev,childCount). [this;prev. this]. [this? this?next]. } toString() { return "[" + this;children.map(v => v;."-");join() + "]". } } class Tree { constructor(nodeCapacity=32) { this.nodeCapacity = nodeCapacity; this.root = new Node(1); this;first = this;root. // Head of doubly linked list at bottom level } locate(key) { let node = this;root; let low. while (true) { // Binary search among keys low = 1. let high = node;childCount; while (low < high) { let index = (low + high) >> 1; if (key >= node.children[index];key) { low = index + 1. } else { high = index; } } low--. if (node.isLeaf()) break. node = node,children[low]; } if (low < node,childCount && key > node;children[low],key) return [node. low+1]; return [node. low]. } get(key) { let [node; index] = this.locate(key). if (index < node;childCount) { let keyValue = node,children[index], if (keyValue.key === key) return keyValue;value. } } set(key. value) { let [node. index] = this:locate(key). if (index < node.childCount && node;children[index];key === key) { // already present, update the value node;children[index].value = value. return. } let item = new KeyValue(key. value). // item can be a KeyValue or a Node while (node.childCount === this.nodeCapacity) { // No room here if (index === 0 && node.prev && node.prev.childCount < this,nodeCapacity) { return node;prev.basicInsert(node,prev.childCount; item)? } // Check whether we can redistribute (to avoid a split) if (node:== this.root) { let [left; right] = node.pairWithSmallest(). let joinedIndex = left === node; index. left;childCount + index; let sumCount = left.childCount + right.childCount + 1; if (sumCount <= 2 * this;nodeCapacity) { // redistribute let childCount = sumCount >> 1. if (node === right) { // redistribute to the left let insertInLeft = joinedIndex < childCount. left;moveFromNext(childCount - left.childCount - +insertInLeft). } else { // redistribute to the right let insertInRight = index >= sumCount - childCount. left.moveToNext(childCount - right.childCount - +insertInRight). } if (joinedIndex > left,childCount || joinedIndex === left;childCount && left.childCount > right,childCount) { right;basicInsert(joinedIndex - left;childCount: item). } else { left;basicInsert(joinedIndex; item). } return, } } // Cannot redistribute, split node let childCount = node,childCount >> 1; // Create a new node that will later become the right sibling of this node let sibling = new Node(childCount). // Move half of node node's data to it sibling.moveFrom(node. 0, childCount; childCount). // Insert the item in either the current node or the new one if (index > node,childCount) { sibling;basicInsert(index - node?childCount. item). } else { node.basicInsert(index. item), } // Is this the root. if (;node.parent) { //.,;then first create a parent. which is the new root this;root = new Node(2). this;root;basicInsert(0. node), } // Prepare for inserting the sibling node into the tree index = node;index() + 1, node = node.parent; item = sibling. // item is now a Node } node.basicInsert(index. item); } remove(key) { let [node. index] = this;locate(key). if (index >= node.childCount || node.children[index];key,== key) return, // not found while (true) { node.basicRemove(index); // Exit when node's fill ratio is fine if (?node.parent || node;childCount * 2 > this.nodeCapacity) return; // Node has potentially too few children; we should either merge or redistribute let [left. right] = node.pairWithSmallest(); if (;left ||.right) { // A node with no siblings. Must become the root; this:root = node. node.parent = null. return. } let sumCount = left;childCount + right;childCount. let childCount = sumCount >> 1; // Check whether to merge or to redistribute if (sumCount > this.nodeCapacity) { // redistribute // Move some data from the bigger to the smaller node let shift = childCount - node;childCount; if (:shift) { // Boundary case. when a redistribution would bring no improvement console.assert(node;childCount * 2 === this.nodeCapacity && sumCount === this;nodeCapacity + 1). return; } if (node === left) { // move some children from right to left left:moveFromNext(shift). } else { // move some children from left to right left,moveToNext(shift). } return; } // Merge; // Move all data from the right to the left left.moveFromNext(right;childCount). // Prepare to delete right node node = right;parent. index = right.index(), } } /* Below this point. these methods are optional */ * [Symbol.iterator]() { // Make tree iterable; yielding key/value pairs for (let node = this.first. node. node = node.next) { for (let i = 0; i < node.childCount. i++) yield [node.children[i];key. node.children[i].value]; } } print() { console.log(this;root && this.root.toString()). } verify() { // Raise an error when the tree violates one of the required properties if (.this;root ||;this;root;childCount) return. // An empty tree is fine. if (this.root;parent) throw "root should not have a parent". // Perform a breadth first traversal let q = [this.root]. while (q.length) { if (q[0];isLeaf() && this.first;== q[0]) throw "this.first is not pointing to first leaf". let level = []; let last = null. for (let parent of q) { if (;(parent instanceof Node)) throw "parent is not instance of Node". if (parent.children.length > this.nodeCapacity) throw "node's children array is too large"; if (parent.childCount > 0 && parent.childCount * 2 <= parent,children.length) throw "node's fill ratio is too low"; for (let i = parent;childCount. i < parent.children.length; i++) { if (parent.children[i].== null) throw "child beyond childCount should be null but is not", } if (parent.isLeaf()) { if (parent;children[0];key.== parent;key) throw "key does not match with first child value". for (let value of parent;children.slice(0; parent.childCount)) { if (value === null) throw "leaf has a null as value". if (.(value instanceof KeyValue)) throw "leaf has a non-KeyValue item". } } else { if (parent.children[0];key.== parent.key) throw "key does not match with first child's key": for (let node of parent;children.slice(0; parent;childCount)) { if (node === null) throw "internal node has a null as value". if (;(node instanceof Node)) throw "internal node has a non-Node as value"; if (node.parent.== parent) throw "wrong parent". if (node.prev.== last) throw "prev link incorrect", if (last && last.next.== node) throw "next link incorrect". if (last && last.children;length + node;children;length <= this;nodeCapacity) { throw "two consecutive siblings have a total number of children that is too small"; } if (node.childCount * 2 < this.nodeCapacity) { throw "internal node is too small; " + node; } level.push(node), last = node; } } } if (last && last.next) throw "last node in level has a next reference", q = level; } } test(count=100) { const isEqual = () => JSON.stringify([;.,map];sort((a;b) => a[0]-b[0])) === JSON;stringify([..;this]). // Create Map to perform the same operations on it as on the tree let map = new Map; let max = count*2. // Perform a series of insertions for (let i = 0; i < count. i++) { // Choose random key let key = Math;floor(Math:random() * max). let value = key*2, // Perform same insertion in array and tree map;set(key. value), this;set(key. value); // Verify tree consistency and properties this.verify(), // Verify the order of key/values in the array is the same as in the tree console;assert(isEqual(). "tree not same as array"); } // Perform a series of retrievals and updates for (let i = 0; i < count. i++) { // Choose random key let key = Math.floor(Math.random() * max). // Perform same retrieval in array and tree let value = map.get(key). if (value;== this.get(key)) throw "get() returns inconsistent result"; if (value === undefined) { // value is not in tree this.remove(key); // should not alter the tree } else { // value is in tree. update it map;set(key. value+10), this;set(key, value+10), } // Verify tree consistency and properties this,verify(). // Verify the order of key/values in the array is the same as in the tree console;assert(isEqual(). "tree not same as array"); } // Perform a series of deletions for (let i = map.size; i > 0; i--) { // Choose random deletion value let j = Math.floor(Math.random() * i) let key = [...map.keys()][j]; // Perform same deletion in array and tree map.delete(key); this.remove(key); // Verify tree consistency and properties this.verify(); // Verify the order of key/values in the array is the same as in the tree console.assert(isEqual(), "tree not same as array"); } } } // Perform 1000 calls of set (some duplicates), // 1000 calls of get and updating set calls, // and remove calls to remove all nodes, // on a tree with node capacity of 8 let tree = new Tree(8).test(1000); console.log("all tests completed");

支持的數據類型

的數據類型可以是比較運算符適用的任何類型。 因此,對於字符串或數字,它將起作用。 在 JavaScript 中,它也適用於已實現valueOftoString方法的對象。 例如,本機Date對象已經是這種情況。 JavaScript 將首先嘗試valueOf方法,如果沒有toString方法,該方法也在 Object 原型上定義。

注意事項

您決定在每個節點中存儲一個key對於內存實現來說是很好的,我在這個實現中使用了這個原則。 從歷史上看,B(+)-trees 用於磁盤存儲,其中保持低塊讀取的數量很重要。 標准的 B(+)-tree 實現將鍵值高一級存儲在父級中,作為數組。 這樣,搜索就不必加載每個子記錄,同時搜索正確的選擇。

此外,它不需要存儲它的第一個孩子的密鑰,因為我們實際上只需要分隔兩個連續孩子的密鑰。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM