B树
原理参考《算法导论》,这里只给出代码。针对删除算法,要注意:若x->key[i]下移到y结点(即使x->c[i]),那么是在y结点的尾部添加。若是下移到z结点(即x->c[i+1]),那么一定是在z结点的首部添加。这是因为大小顺序问题,理解了这一点,就很好理解《算法导论》给出的算法描述了。
定义一个B树类
template <int t> class BTree { public: typedef struct _BNode{ _BNode(bool _leaf) :n(0), leaf(_leaf) {} int key[2*t - 1];//2*t-1个关键字 _BNode *c[2*t];//2*t个子节点 bool leaf;//true代表叶子(没有子结点,本身就足以容纳关键数据),false代表结点 int n;//代表关键字个数 }BNode, *pBNode; BTree() { root = new BNode(true); } void Insert(BNode *x, int k); std::pair<BNode *, int> Seach(BNode *x, int k); std::pair<BNode *, int> Prev(int k); std::pair<BNode *, int> Next(int k); void Delete(BNode *x, int k); void Empty(); void Print(BNode *x);//顺序输出 void Print_tree(BNode *x);//层次输出 BNode *Root() { return root; } ~BTree() { Empty(); delete root;//删除空的root } private: void Split_child(BNode *x, int i, BNode *y); void Insert_directly(BNode *x, int k); BNode *root; };
实例声明
template class BTree<2>;
对应的成员函数实现
Insert函数,插入关键字到B树中,并实现B树平衡
template <int t> void BTree<t>::Insert(typename BTree<t>::BNode *x, int k) { BNode *r = root, *s; if (r->n == 2 * t - 1) { s = new BNode(false); root = s; s->c[0] = r; Split_child(s, 0, r); Insert_directly(s, k); } else Insert_directly(r, k); }
对应的两个辅助函数Split_child, Insert_directly
template<int t> void BTree<t>::Split_child(typename BTree<t>::BNode *x, int i, typename BTree<t>::BNode *y) {//分裂y,并在x->c[i]处插入y结点 int j; BNode *z = new BNode(y->leaf); //z结点,是x结点的右子结点 z->n = t - 1; for (j = 0; j < t - 1; j++) z->key[j] = y->key[j + t];//复制关键字 if (!y->leaf) //y是结点 for (j = 0; j < t; j++) z->c[j] = y->c[j + t];//复制子结点 //y结点,是x结点的左子结点 y->n = t - 1; //在x结点中增加一个子结点z,保证x->n<=2*t-1,并且x结点的i处是y for (j = x->n; j >= i + 1; j--) x->c[j + 1] = x->c[j]; x->c[i + 1] = z; for (j = x->n - 1; j >= i; j--) x->key[j + 1] = x->key[j]; x->key[i] = y->key[t - 1]; x->n++; }
void BTree<t>::Insert_directly(typename BTree<t>::BNode *x, int k) { int i = x->n - 1; if (x->leaf) {//同一高度插入关键字k while (i >= 0 && k < x->key[i]) {//调用此函数前,保证了x->n<2*t-1 x->key[i + 1] = x->key[i]; i--; } x->key[i + 1] = k; x->n++; } else {//结点满了 while (i >= 0 && k < x->key[i]) i--; i = i + 1; if (x->c[i]->n == 2 * t - 1) { Split_child(x, i, x->c[i]); if (k > x->key[i]) i++; } Insert_directly(x->c[i], k); } }
Delete删除函数,本质就是通过结点的合并实现删除关键字,原理参考《算法导论》的描述,可对照我的代码理解。
template <int t> void BTree<t>::Delete(typename BTree<t>::BNode *x, int k) { int i, j; BNode *y, *z; typename std::pair<BNode *, int> p;//<BNode *, index> //检查k是否在x结点中,找到一个至少比关键字k大的位置 for (i = 0; i < x->n; i++) if (x->key[i] >= k) break; y = x->c[i];//y是x->key[i]的左子结点 z = x->c[i + 1];//z是x->key[i]的右子结点 if (x->key[i] == k && i != x->n) {//k在结点x内 if (x->leaf) {//1, x是叶节点,直接删除关键字 for (j = i; j < x->n - 1; j++) x->key[j] = x->key[j + 1]; x->n--; return; } else {//x是结点,分3种情况 //2-a,左子结点y至少包含t个关键字 if (y->n >= t) { p = this->Prev(k);//前驱 x->key[i] = p.first->key[p.second];//前驱关键字替换k关键字 this->Delete(y, x->key[i]); } else if (z->n >= t) {//2-b,右结点z至少包含t个关键字 p = this->Next(k);//后继 x->key[i] = p.first->key[p.second];//后继关键字替换k关键字 this->Delete(z, x->key[i]); } else {//2-c, 左右子结点都只有t-1个关键字 y->key[y->n] = k;//合并关键字k for (j = 0; j < z->n; j++) y->key[y->n + 1 + j] = z->key[j];//合并结点z的所有关键字 if (!y->leaf) {//合并对应的子结点 for (j = 0; j < z->n + 1; j++) y->c[y->n + 1 + j] = z->c[j]; } y->n = y->n + 1 + z->n;//一个关键字k和来自结点z的n个关键字 for (j = i; j < x->n - 1; j++)//剔除关键字k x->key[j] = x->key[j + 1]; for (j = i + 1; j < x->n; j++)//剔除右子结点z x->c[j] = x->c[j + 1]; x->n--; delete z; //如果x是根结点且x经过上述操作后为空 if (x->n == 0 && root == x) { delete x; root = y; } this->Delete(y, k); } } } else {//k不在结点x内,调整B树 if (x->leaf) { printf("关键字%c不存在, 未删除任何结点,但会让B树结构变化,不影响平衡!\n", k); return; } if (y->n == t - 1) {//左子结点只有t-1个关键字 //3-a,邻左或右结点至少有t个关键字(相对于y结点) if (i < x->n && z->n >= t) {//邻右,即z结点 y->key[y->n] = x->key[i];//i处关键字下降至y结点 x->key[i] = z->key[0];//z的某关键字上升到x,并覆盖x->key[i] for (j = 0; j < z->n - 1; j++) z->key[j] = z->key[j + 1];//剔除关键字z->key[0] if (!y->leaf) { y->c[y->n + 1] = z->c[0];//将z的子结点放入y for (j = 0; j < z->n; j++) z->c[j] = z->c[j + 1];//剔除z->c[0] } y->n++; z->n--; } else if (i > 0 && x->c[i - 1]->n >= t) {//邻左,即y结点的左边相邻兄弟i-1 for (j = y->n - 1; j >= 0; j--) y->key[j + 1] = y->key[j];//预留一个位置 y->key[0] = x->key[i - 1];//x结点中的某一个关键字下降到y x->key[i - 1] = x->c[i - 1]->key[x->c[i - 1]->n - 1];//将左邻兄弟i-1的某关键字上升到x,并覆盖x->key[i-1] //由于是将左邻兄弟i-1的最后一个关键字上升,x->c[i-1]->n--控制,所以不需要在剔除关键字和子结点 if (!y->leaf) { for (j = y->n; j >= 0; j--) y->c[j + 1] = y->c[j];//预留一个位置 y->c[0] = x->c[i - 1]->c[x->c[i - 1]->n];//左邻兄弟i-1的子结点加入到y } y->n++; x->c[i - 1]->n--; } else {//3-b,所有相邻兄弟都只有t-1个关键字,则与其中一个兄弟合并(相对于y结点) //当i < x->n,z合并于y结点 if (i >= x->n) {//y合并于x->c[i-1]结点,此操作防止越界 y = x->c[i - 1];//相对于i-1 z = x->c[i]; i--; } y->key[y->n] = x->key[i];//放入y for (j = 0; j < z->n; j++) y->key[y->n + 1 + j] = z->key[j];//合并z if (!y->leaf) {//合并对应的子结点 for (j = 0; j < z->n + 1; j++) y->c[y->n + 1 + j] = z->c[j]; } y->n = y->n + 1 + z->n; for (j = i; j < x->n - 1; j++)//剔除i处的关键字 x->key[j] = x->key[j + 1]; for (j = i + 1; j < x->n; j++)//剔除i+1处的子结点 x->c[j] = x->c[j + 1]; x->n--; delete z; //如果x是根结点且x经过上述操作后为空 if (x->n == 0 && root == x) { delete x; root = y; } } } this->Delete(y, k); } }
输出函数,Print函数是按顺序输出,Print_tree是层次输出,方便大家考察函数结果是否正确。
template <int t> void BTree<t>::Print(typename BTree<t>::BNode *x) { for (int i = 0; i < x->n; i++){ if (!x->leaf) Print(x->c[i]); printf("%-02c ", x->key[i]); } if (!x->leaf) Print(x->c[x->n]); } template <int t> void BTree<t>::Print_tree(typename BTree<t>::BNode *x) { for (int i = 0; i < x->n; i++) printf("%-02c ", x->key[i]); printf("\n"); if (!x->leaf) //结点 for (int i = 0; i <= x->n; i++) Print_tree(x->c[i]); }
Empty函数,所有结点的释放。原理:通过反复删除根节点的第一个关键字,从而达到所有关键字的删除。
template <int t> void BTree<t>::Empty(){ while (root->n != 0) { this->Delete(root, root->key[0]); Print(root); printf("\n"); } }
主函数
int main() { //while (true) {//用于测试是否存在内存泄露 int key[] = { 'F','S','Q','K','C','L','H','T','V','W','M','R','N','P','A','B','X','Y','D','Z','E' }, k = 'O'; BTree<2> btree; for (int i = 0; i < sizeof(key) / sizeof(key[0]); i++) btree.Insert(btree.Root(), key[i]); btree.Print_tree(btree.Root()); std::pair<BTree<2>::BNode *, int> p = btree.Seach(btree.Root(), 'D'); printf("Seaching at %d, %c found\n", p.second, p.first->key[p.second]); btree.Print(btree.Root()); printf("\n"); btree.Delete(btree.Root(), k); printf("After deleting %c...\n", k); btree.Print_tree(btree.Root()); printf("\n"); btree.Print(btree.Root()); printf("\n清理中....\n");
//调用析构函数 //} return 0; }
数据测试
数据录入
int key[] = { 'F','S','Q','K','C','L','H','T','V','W','M','R','N','P','A','B','X','Y','D','Z','E' }
正确的结果图如下:
程序输出的结果
代码均经过测试,结果正确!!!