【数据结构】浅析B树

来源:互联网 发布:淘宝注销账号怎么注销 编辑:程序博客网 时间:2024/05/01 23:37

一、B树的概念

B树,概括来说是一个节点可以拥有多于2个子节点的平衡多叉树。

特点:

1> 根节点至少有两个子节点

2>每个非跟节点节点有(M/2)-1至M-1个key

3>每个非根节点有[M/2 ,M]个孩子

4>key[i]和key[i+1]之间的孩子节点的值介于key[i]、key[i+1]之间

5> 所有的叶子节点都在同一层

二、B树的应用

由于增删查改的效率十分高(时间复杂度log M-1为底N的对数的样子),B树普遍应用于数据库和文件系统。一般会将M定义的非常大,这样B树的高度就很低,每一个节点中可以使用二分查找,所以B树效率高。缺点就是比较耗内存。

三、实现一个B树

1.节点内容

    根据B树的特点,一个节点应该包含一个大小为M-1的key数组,但是考虑到B树在插入的时候需要先插入节点然后在进行分裂,所以key数组给M大的空间。其次,应该包含一个节点指针类型的数组,存储指向孩子的指针,大小应为M+1。还有,一个指向父节点的指针,一个表示当前结点key值实际数量的size。

2.节点插入

1>空树时,直接调用节点的构造函数,new一个root。

2>非空时,先查找树,若已经存在要插入的关键码,则插入失败。若不存在,让Find()函数返回插入位置的指针。那么,Find不仅要返回key值是否在树内(bool),还要返回要插入节点的位置(Node*)。所以,我们使用库里存在的一个类型pair,它可以带回两个返回值。

3>接下来,把key值插入Find返回的位置。

4>检测插入后key值的个数是否超过M-1,若没有,则插入成功,若超过,则需要分裂。根据B树的特点,可以知道,插入的位置只能是叶子节点。

    分裂是怎么一回事呢?看下图



分裂是插入的难点,理解了分裂,插入就没什么问题了。

3.代码实现

#include<iostream>using namespace std;template<class K , int M>struct BTreeNode{typedef BTreeNode<K, M> Node;K _keys[M];   //多给出一个位置是为了方便分裂。Node* _sub[M + 1];Node* _parent;size_t _size;  //记录实际关键字的个数BTreeNode():_parent(NULL), _size(0){for (size_t i = 0; i < M; i++){_keys[i] = K();_sub[i] = 0;}_sub[M] = 0;}};template<class K,int M>class BTree{public:typedef BTreeNode<K, M> Node;BTree():_root(NULL){}void InOrder(){_InOrder(_root);}~BTree(){_Destory(_root);}bool Insert(const K& key){if (NULL == _root){_root = new Node();_root->_keys[0] = key;_root->_size++;return true;}pair<Node*, size_t> tmp = _Find(key);if (tmp.second != -1)  //已经存在关键值为key,则不能插入{return false;}Node* cur = tmp.first;Node* sub = NULL;K newkey = key;while (1){_Insertkey(cur,newkey,sub);  //先将key放进要插的节点//判断结点的关键字数目是否符合标准。if (cur->_size < M)  //该节点上插入后关键字数目正常return true;//数目超过规定值需要进行分裂while (cur->_size >= M){size_t mid = cur->_size / 2;//1.分裂出新的结点Node* NewNode = new Node;for (size_t i = mid+1; i < cur->_size; i++)//mid之后的key值给新结点{int j = 0;NewNode->_keys[j] = cur->_keys[i];NewNode->_size++;//cur->_size--;  //注意此处先不要哦改动cur的size,否则会影响下一个循环cur->_keys[i] = K();  //赋给新结点后cur对应的key应置成初始值j++;}int j = 0;for (size_t i = mid+1 ; i < cur->_size+1; i++){NewNode->_sub[j] = cur->_sub[i];if (NewNode->_sub[j])NewNode->_sub[j]->_parent = NewNode;j++;cur->_sub[i] = NULL;}if (cur == _root)  //创建出新的根节点{Node* tmp = new Node();tmp->_keys[0] = cur->_keys[mid];cur->_keys[mid] = K();cur->_size=mid;tmp->_size++;tmp->_sub[0] = cur;cur->_parent = tmp;tmp->_sub[1] = NewNode;NewNode->_parent = tmp;_root = tmp;return true;}newkey = cur->_keys[mid];cur->_keys[mid] = K();cur->_size = mid;   sub = NewNode;}cur = cur->_parent;}}protected:void _Destory(Node* root){if (NULL == root)return;size_t i = 0;for (; i < root->_size; i++){_Destory(root->_sub[i]);delete root->_sub[i];}_Destory(root->_sub[i]);delete root->_sub[i];}void _InOrder(Node* root){if (NULL == root)return;size_t i = 0;for (; i < root->_size; i++){_InOrder(root->_sub[i]);cout << root->_keys[i]<<" ";}_InOrder(root->_sub[i]);}pair<Node*,size_t> _Find(const K& key){Node* cur = _root;Node* parent = NULL;while (cur){size_t i = 0;while (i< cur->_size)  //找当前结点的key{if (cur->_keys[i] < key)i++;else if (cur->_keys[i]>key)//cur = cur->_sub[i];break;else   //找到了和传入key相等的关键字return pair<Node* ,size_t>(cur,i);}parent = cur;cur = cur->_sub[i];}return pair<Node* , size_t>(parent, -1);  //没找到}void _Insertkey(Node* cur,const K& key,Node*sub){int i = cur->_size-1;while (i >=0){if (cur->_keys[i] > key){//移动关键字的位置cur->_keys[i + 1] = cur->_keys[i];//移动子树的位置cur->_sub[i + 2] = cur->_sub[i + 1];i--;}elsebreak;}//i记录着要插入位置的前一个cur->_keys[i + 1] = key;cur->_sub[i + 2] = sub;if (sub)sub->_parent = cur;cur->_size++;}private:Node* _root;};void TestBTree(){BTree<int, 3> bt;int a[] = { 53, 75, 139, 49, 145, 36, 101 };for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++){bt.Insert(a[i]);}bt.InOrder();}


0 0
原创粉丝点击