B-Tree的簡單實現(PHP)
阿新 • • 發佈:2018-12-25
<?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>';