参考:《算法导论》
定义
二叉搜索树首先是一棵二叉树(每个节点至多有两个孩子),每个节点的左子树中节点的键值都不大于它,右子树中的节点键值都不小于它。每个节点的属性包括left(左儿子节点)、right(右儿子节点)、parent(父亲节点),以及该节点的键值data。
如下图所示,图(a)、(b)是一棵二叉搜索树,图c不是一棵二叉搜索树
定义二叉搜索树的数据结构如下所示
using Elem_Type = int; struct BitSearchTree { BitSearchTree *left = nullptr, *right = nullptr, *parent = nullptr; //左右儿子和父亲 Elem_Type data; //存储的数据 }*Root; //定义一个根
二叉搜索树的操作主要包括顺序遍历Inorder_Walk、查询节点Find_node、查找最大值节点Maxmum、最小值节点Minimum、查找后继节点Succcessor、查找后缀节点Precursor、插入节点InsertNode、删除节点DeleteNode
操作
顺序遍历
由于二叉搜索树的性质,因此中序遍历(左、根、右)即可按顺序输出二叉搜索树的节点,时间复杂度O(n),n为二叉搜索树的节点数
void Inorder_Walk(BitSearchTree *t) { //从小到大输出二叉树的值(中序遍历) if (t != nullptr) { //非空 Inorder_Walk(t->left); //遍历左儿子 printf("%d", t->data); //输出当前节点的键值 Inorder_Walk(t -> right); //遍历右儿子 } }
查询
查询二叉搜索树中是否存在某个键值为x的节点,根据二叉搜索树的性质,先从根开始查询,当查询的键值比当前节点键值大时,向右走,比当前的键值小时向左儿子走,直到找到该值或者到到空节点(未找到)。函数返回键值所在的节点或者,未查到则返回空节点。如下图所示,查询节点1,从根节点开始,5>1,向左走到3,3>1,向左到1,则返回该节点,查询8时,5<8,向右到6,6<8,向右到9,9>8,向左,到空节点。时间复杂度为O(h),h为二叉搜索树的高度
BitSearchTree *Find_node(BitSearchTree *t, Elem_Type x) { //搜索值为x的节点(也可用while循环) if (t == nullptr) { //空节点,未查到 //cout<<"not find"<<endl; return t; } if (t->data == x) //当前节点的键值与查询的键值相同 return t; if (x > t->data) //查询的键值比当前节点大,向右走 return Find_node(t->right,x); return Find_node(t->left, x); //左走 }
//调用:Find_node(root, x),从根开始查
最大值和最小值
根据二叉搜索树的性质,一直向左走即可找到最小键值的节点,一直向右走即可找到最大键值的节点。复杂度为O(h),h为高度
BitSearchTree *Maxmum(BitSearchTree *t,Elem_Type &a) { //查找最大值节点 while (t->right != nullptr) { a = t->data; t = t->right; //向右走 } return t; } BitSearchTree *Minimum(BitSearchTree *t, Elem_Type &a) { //查找最小值节点 while (t->left != nullptr) { a = t->data; t = t->left; //向左走 } return t; }
前驱和后继
节点x的后继是大于x键值的节点中,键值最小的节点,如果x的键值为最大值,则返回空节点。根据二叉树的性质,如果该节点存在右孩子,则其后继为右子树的最小值。如图所示,节点15的后继为右子树的最小值17。如果该节点不存在右子树,则其后继为其祖先中作为左儿子存在的节点的父节点。如图中找15的后继节点,由于15无右孩子,需要向上找,直到6,是作为左儿子存在,故15的后继为6的父亲16。复杂度为O(h),h为高度。
BitSearchTree *Succcessor(BitSearchTree *t, Elem_Type &a) { //查找节点t的后继 if (t->right != nullptr) //存在右儿子 return Minimum(t->right, a); //右子树最小值 BitSearchTree *p = t->parent; while (p != nullptr&&p->right == t) { //找到作为左儿子存在的节点t a = p->data; t = p; p = p->parent; } return p; //返回t的父亲节点p } BitSearchTree *Precursor(BitSearchTree *t, Elem_Type &a) { //查找节点t的前驱 if (t->left != nullptr) //存在左儿子 return Maxmum(t->left, a); //返回左子树最大值 BitSearchTree *p = t->parent; while (p != nullptr&&p->left == t) { //找到作为右儿子存在的节点t t = p; a = p->data; p = p->parent; } return p; //返回t的父亲p }
插入
二叉搜索树的插入都是将新节点插到叶子节点的子节点。如果树非空,从根节点开始,如果插入节点的键值大于当前节点,则向右走;否则向左走。直到叶子节点,将插入的节点作为叶子节点的子节点,左儿子还是右儿子由值决定。复杂度O(h),h为树的高度。
BitSearchTree *InsertNode(BitSearchTree *t, Elem_Type a) { //插入一个值a,t一般为root BitSearchTree *x =new BitSearchTree(), *z=nullptr; x->data = a; while (t != nullptr) { z = t; if (a > t->data) //向右走 t = t->right; else t = t->left; //向左走 } x->parent = z; if (z == nullptr) { //树为空 Root = x; return Root; } else if (z->data > a) //和叶子节点相连 z->left = x; else z->right = x; return x; }
删除
删除的情况比较复杂,因为如果删除的节点有孩子,还需要找一个节点代替这个被删除的节点,使得新的二叉树满足二叉搜索树的性质,删除的情况主要分为四种如下图所示,设要删除的节点为z:
- 删除的节点无左儿子
此时直接使其右儿子取代z即可,则易知二叉搜搜索树的性质不变,时间复杂度O(1)
- 删除的节点无右儿子
同理,此时直接使其右儿子取代z即可,二叉搜搜索树的性质不变,时间复杂度O(1)
- z节点左右儿子都存在,其后继恰好是其右儿子,此时用什么节点替代z如何才能保持二叉搜索树的性质不变呢?考虑到后继节点的特点,如果用z的后继替代,则左子树键值依然不大于z,右子树键值不小于z,此时直接用y代替z即可
- z的左右儿子都在,且其后继y不是他的孩子,此时依然要用z的后继节点代替z才能保持二叉搜索树的性质不变。用后继y(y一定没有左孩子)代替z,y的右子树代替y的位置
删除操作时间复杂度为O(h),h为树的高度
首先定义一个Transplant函数,表示使用节点v为根的子树代替节点为u的子树(函数没有更新v原先的左右孩子)
void Transplant(BitSearchTree *u, BitSearchTree *v) { //v为根的子树代替u为根的子树,未更新v的孩子 if (u->parent == nullptr) //代替根节点 Root = v; else if (u->parent->left == u) //u为左孩子 u->parent->left = v; else u->parent->right = v; //u为右孩子 if (v != nullptr) v->parent = u->parent; }
下面定义删除节点函数
void DeleteNode(BitSearchTree *t,int &a) { //删除节点t,t存储的数据位a if (t != nullptr) a = t->data; if (t->left == nullptr) //t无左儿子 Transplant(t, t->right); else if (t->right == nullptr) //t无右儿子 Transplant(t, t->left); else if (t->right->left == nullptr) { //z的后继为其右儿子 Transplant(t, t->right); t->left->parent = t->right; t->right->left = t->left; } else { //其他情况 int b=0; BitSearchTree *p = Succcessor(t, b); Transplant(p, p->right); Transplant(t, p); p->left = t->left; p->left->parent = p; p->right = t->right; p->right->parent = p; } delete t; }