1. 程式人生 > >B-Tree的簡單實現(PHP)

B-Tree的簡單實現(PHP)

<?php class BTNode { public $id; public $parent = 0; public $indexNum = 0; public $childNum = 0; public $indexMap = []; public $children = []; public function __construct(BTree $btree) { $this->id = uniqid(); $btree->nodeMap[$this->id] = $this
; } public function add($index = 0, $child = null) { if ($index !== 0) { $this->indexMap[$this->indexNum++] = $index; sort($this->indexMap); } $this->children[$index] = $child; ksort($this->children); $this->childNum++; } public
function isFull($order) { return $this->indexNum >= $order; } } class BTree { /** * @var BTNode */ public $root = null; public $order; public $keyNumMin; public $keyNumMax; public $nodeMap = []; public function __construct($order = 3)
{
$this->order = $order; $this->keyNumMin = ceil($order / 2) - 1; $this->keyNumMax = $order - 1; } public function insert($index) { if ($this->isEmpty()) { $node = new BTNode($this); $node->add(0, 0); $node->add($index, 0); $this->root = $node; } else { $nextNode = $this->root; $prevNode = $this->root; //只有葉節點的子節點才會為null,所以插入都從葉節點開始[3] while ($nextNode != null) { $indexMap = $nextNode->indexMap; $indexNum = $nextNode->indexNum; $pos = $indexMap[$indexNum - 1]; $prevNode = $nextNode; for ($i = 0; $i < $indexNum; $i++) { if ($index < $indexMap[$i]) { if ($i !== 0) { $pos = $indexMap[$i - 1]; } else { $pos = 0; } break; } else if ($index == $indexMap[$i]) { return false; } } $nextNode = $this->getNode($nextNode->children[$pos]); } $prevNode->add($index, 0); if ($prevNode->isFull($this->order)) { $this->split($prevNode); } } } public function find($index) { if (!$this->isEmpty()) { $nextNode = $this->root; while ($nextNode != null) { $indexMap = $nextNode->indexMap; $indexNum = $nextNode->indexNum; $pos = $indexMap[$indexNum - 1]; for ($i = 0; $i < $indexNum; $i++) { if ($index < $indexMap[$i]) { if ($i !== 0) { $pos = $indexMap[$i - 1]; } else { $pos = 0; } break; } else if ($index == $indexMap[$i]) { return true; } } $nextNode = $this->getNode($nextNode->children[$pos]); } } return false; } public function isEmpty() { return is_null($this->root); } public function split(BTNode $node) { $indexMap = $node->indexMap; $children = $node->children; $indexNum = $node->indexNum; $middle = intval(floor($indexNum / 2)); $middleIndex = $indexMap[$middle]; $middleChild = $children[$middleIndex]; //重新建立一個節點[4-2] $newNode = new BTNode($this); $newNode->add(0, $middleChild); for ($i = 0; $i < $indexNum; $i++) { if ($i >= $middle) { if ($i != $middle) { //把原節點中間索引之後的索引加入新節點[4-2] $child = $children[$indexMap[$i]]; $newNode->add($indexMap[$i], $child); if ($child != 0) { $tmp = $this->getNode($child); //這裡需要注意的是需要把這些指向的子節點的parent改為新建立的節點。[4-2] $tmp->parent = $newNode->id; } } //原節點刪除中間索引及之後的索引[4-3] unset($node->indexMap[$i]); unset($node->children[$indexMap[$i]]); $node->indexNum--; $node->childNum--; } } $pid = $node->parent; if ($pid === 0) { //根節點分裂,樹高度增加 $parent = new BTNode($this); $parent->add(0, $node->id); $this->root = $parent; $pid = $parent->id; } else { $parent = $this->getNode($pid); } $node->parent = $pid; $newNode->parent = $pid; //處於中間位置的索引,上升到父節點[4-1] $parent->add($middleIndex, $newNode->id); //父節點容量超出,進行分裂[4-4] if ($parent->isFull($this->order)) { $this->split($parent); } } /** * @param $id * @return BTNode | null */ public function getNode($id) { return isset($this->nodeMap[$id]) ? $this->nodeMap[$id] : null; } } $testList = [3, 4, 5, 10, 22, 32, 7, 6, 2, 1, 999, 101]; $btree = new BTree(3); echo '<pre>'; foreach ($testList as $value) { $btree->insert($value); } /************************************ 插入B樹的所有元素 **********************************/ echo '插入B樹的所有元素: <br>'; foreach ($testList as $value) { echo $value.','; } echo '<hr>'; /******************************* 測試find方法 *****************************************/ $tt = [3, 4, 5, 10, 22, 32, 7, 6, 2, 1, 999, 22, 33, 55, 1, 9, 17, 101]; echo '測試find方法:<br>'; foreach ($tt as $v) { if ($btree->find($v)) { $msg = in_array($v, $testList) ? 'true' : 'false'; echo $v.' 存在! 是否正確:['.$msg.']'; } else { $msg = !in_array($v, $testList) ? 'true' : 'false'; echo $v. ' 不存在! 是否正確:['.$msg.']'; } echo '<br>'; } echo '<hr>'; /****************************************** 除錯程式碼: 列印節點資訊 ************************************************************/ echo '除錯: 列印節點資訊:<br>'; function dump(BTree $btree) { $stack = [$btree->root->id, '關聯索引:0']; while (!empty($stack)) { $tmpNode = array_shift($stack); if (is_string($tmpNode) && strpos($tmpNode, '關聯索引:') === 0) { echo '('.$tmpNode.')'; echo '<br>'; continue; } $tmpNode = $btree->getNode($tmpNode); if (!is_null($tmpNode) && is_array($tmpNode->children) && !empty($tmpNode->children)) { echo '節點索引數:['.$tmpNode->indexNum.'] '; echo '節點列表: '; foreach ($tmpNode->indexMap as $index) { echo $index.','; } echo ' | '; foreach ($tmpNode->children as $key => $value) { if ($value != 0) { array_push($stack, $value); array_push($stack, '關聯索引:'.$key); } } } } } dump($btree); echo '<hr>'; /*************************************** 除錯程式碼: 列印所有樹節點 *****************************************/ echo '除錯: 列印所有樹節點:<br>'; foreach ($btree->nodeMap as $each) { var_dump($each); } echo '<hr>';