今天,我们来分享一下B树的插入。
B树是一种适合外查找且平衡的多叉树,下面简单介绍一下N阶B树(N叉树)的性质。
a. 根结点至少有两个孩子。(根结点至少有一个关键字,一个关键字有两个孩子)
b. 每个非根结点有[N/2, N]个孩子。(最多N个孩子,保证该树是N叉树)
c. 每个非根结点有[N/2-1, N-1]个关键字,且以升序排列。(孩子比关键字多1)
d. 关键字_key[i]和_key[i+1]之间的孩子节点_sub[i+1]的值介于_key[i]、_key[i+1]之间.( _key[i]<_sub[i+1]<_key[i+1])
注:我们习惯吧_sub[i+1]看做_key[i]的右孩子,并且_sub[i+1]看做是_key[i+1]的左孩子。
每一个节点不能有N个关键字,到达N个关键字,节点会分裂为3个节点:中间下标的关键字和孩子构成一个节点(父亲),中间下标以左的关键字和孩子为一个节点(左孩子),中间下标以右的关键字和孩子为一个节点(左孩子)。当N为奇数,左右节点个数相同((N-1)/2 = N/2);当N为偶数, 左右节点个数不相同,必定会一个个数为N/2-1,另一个为N/2,所以定义每个非根结点有[N/2-1, N-1]个关键字。
由上面的性质定义B树的结构体如下:
template<class K, size_t N = 3>
struct BTreeNode
{
K _key[N];//关键字个数
BTreeNode<K, N> * _sub[N+1];//孩子个数
BTreeNode<K, N> * _parent;//父亲节点
int _size;//节点关键字个数
};
B树插入思路:
1》 判断带插入的数key是否存在。
a.已存在,返回key值出现的结点以及key值出现的位置,不在继续插入。
b.不存在,返回满足插入的叶子结点,继续2》。
2》 得到满足插入的(叶子)结点,找到准确的插入位置,插入key和右孩子(一开始没有右孩子默认为NULL),并且增加节点关键字个数,即_size++.
3》 判断此时节点的关键字个数是否达到N,需要分裂。
a.关键字个数未达到N,不用分裂,不用再继续。
b.关键字个数达到N,需要分裂,继续4》。
4》 找到当前节点的关键字中间下标,把中间下标以右的关键字包括孩子复制到新节点中。
5》 判断当前节点是否为根结点(根结点的父亲节点为空)。
a.是根结点,生成新根节点,把中间下标的关键字和右孩子放入新根节点中。新节点的左孩子为当前节点,右孩子为新节点,完成分裂,不再继续。
b.不是根节点,把中间下标的关键字和右孩子插入当前节点的的父亲节点中。新节点的左孩子为当前节点,右孩子为新节点。继续2》。
注:关于B树,其实没有AVL树麻烦,只是细节繁琐,易出错,写的时候必须注意。
代码如下:
BTree.h #pragma once template<class K, size_t M = 3> struct BTreeNode { K _key[M]; BTreeNode<K, M> * _sub[M+1]; BTreeNode<K, M> * _parent; int _size; BTreeNode() :_parent(NULL) { memset(_sub, 0, sizeof(_sub)); _size = 0; } }; template<class K, size_t M = 3> class BTree { public: typedef BTreeNode<K, M> Node; BTree() :_root(NULL) { } bool Insert(const K& key) { if(_root == NULL) { _root =new Node; _root->_key[0] = key; _root->_size = 1; return true; }
pair<Node* , int> s = _find(key);if(s.second >= 0) return false;Node* cur = s.first;Node* sub = NULL;_InsertKey(cur, key, sub);//find,找插入位置
if(cur->_size < M) return true;//没破坏规则,不需要分裂while(cur->_size >= M){//分裂size_t mid = cur->_size/2;//split();Node* newpoint = new Node;int i=mid+1;for(; i<cur->_size; i++){newpoint->_key[i-mid-1] = cur->_key[i];newpoint->_sub[i-mid-1] = cur->_sub[i];cur->_key[i] = -1;//清楚痕迹,避免引起误会cur->_sub[i] = NULL;//清楚痕迹,避免引起误会if(newpoint->_sub[i-mid-1]){newpoint->_sub[i-mid-1]->_parent = newpoint;}newpoint->_size += 1;}newpoint->_sub[i-mid-1] = cur->_sub[i];cur->_sub[i] = NULL;//清楚痕迹,避免引起误会cur->_size = cur->_size - newpoint->_size -1;if(cur->_parent == NULL){_root = new Node;_root->_key[0] = cur->_key[mid];cur->_key[mid] = -1;//清楚痕迹,避免引起误会_root->_sub[0] = cur;cur->_parent = _root;_root->_sub[1] = newpoint;newpoint->_parent = _root;_root->_size = 1;return true;}else{K newkey = cur->_key[mid];cur->_key[mid] = -1;//清楚痕迹,避免引起误会cur = cur->_parent;sub = newpoint;_InsertKey(cur ,newkey, sub);}}return true;}void Ordprintf(){Node* cur = _root;_Ordp(cur);cout<<endl;}protected:pair<Node* , int> _find(const K& key){Node* cur = _root;Node* parent = NULL;while(cur){int i=0;for(; i<cur->_size; i++){if(cur->_key[i] == key){return make_pair(cur, i);}else if(cur->_key[i] > key){break;}}parent = cur;cur = cur->_sub[i];}return make_pair(parent , -1);//找到插入的父亲节点}void _InsertKey(Node* cur, const K& key, Node* sub = NULL){assert(cur);for(int i=cur->_size; i>=0; i--){if(cur->_key[i-1] < key){cur->_key[i] = key;cur->_sub[i+1] = sub;if(sub)cur->_sub[i+1]->_parent = cur;break;}else{cur->_key[i] = cur->_key[i-1];cur->_sub[i+1] = cur->_sub[i];if(cur->_sub[i+1])cur->_sub[i+1]->_parent = cur;}}cur->_size ++;return;}void _Ordp(Node* cur){if(cur == NULL) return;int i=0;for(; i<cur->_size; i++){_Ordp(cur->_sub[i]);cout<<cur->_key[i]<<" ";}_Ordp(cur->_sub[i]);}protected:Node* _root;};void TestBTree(){BTree<int> k;int a[] = {53,79,139,49,145,36,101};cout<<"依次插入: ";for(int i=0; i<sizeof(a)/sizeof(a[0]); i++){k.Insert(a[i]);cout<<a[i]<<" ";}cout<<endl<<"中序打印该树: ";k.Ordprintf();}run.cpp#include<iostream>#include<assert.h>using namespace std;#include"BTree.h" int main(){cout<<"B树插入:"<<endl;TestBTree();system("pause");return 0;} 运行界面//判断是否需要分裂
分享如上,望共同努力进步!