第12章 二叉搜索树
12.1 什么是二叉搜索树
- 二叉搜索树是以一棵二叉树来组织的,可用链表结构表示,每个结点是一个对象,包含属性key和卫星数据,还有结点指针left、right、parent,分别指向左孩子、右孩子和双亲。
- 对任何结点x,其左子树的关键字最大不超过x.key,其右子树的关键字最小不小于x.key。
- 不同的二叉搜索树可以代表同一组值的集合
- 大部分搜索树的最坏运行时间与树的高度成正比
- 二叉搜索树通过简单的递归算法来按序输出二叉搜索树的关键字。
- 中序遍历 :输出的子树根的关键字位于其左子树的关键字值和右子树的关键字值之间
- 先序遍历 :输出的根的关键字在其左右子树之前
- 后序遍历 :输出的根的关键字在其左右子树的关键字值之后
定理1 如果x是一棵有n个结点子树的根,那么调用中序递归输出需要 Θ(n) 时间。
二叉树的构造和结构:
/* 二叉树的创建和遍历多种方式实现
由于要用到C++的STL的,所以混合交叉使用C和C++
主要使用c进行编程
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<stack>
using namespace std;
//二叉树的链式结构
typedef struct node
{
int value;
struct node* left;
struct node* right;
struct node* parent;
}Node;
Node* Create_tree()
{
Node* p=(Node*)malloc(sizeof(node));
p->left=NULL;
p->right=NULL;
p->parent=NULL;
p->value=0;
if(p!=NULL)
return p;
else
{
printf("fail to create\n");
exit(0);
}
}
```
练习
12.1-1
- 满足左子树小于等于其父节点,右子树大于等于其父节点,进行拼凑集合,注意高度指的是根结点到叶子结点的某条最大路径上的边的个数。
12.1-2
- 二叉查找树的性质是左子树小于等于其父节点,右子树大于等于其父节点。 最小堆是左右子树都大于其父节点
- 不能,最小堆的输出其左右子树并没有序,而二叉查找树左右是有序的,左子树肯定小于等于右子树。
12.1-3
代码如下:
“`
void Inorder_tree_walk_Iterative(Node* T)
{
stack<Node* > s;
Node* p=T;
while(p!=NULL||!s.empty())
{
while(p!=NULL)
{
s.push(p);
p=p->left;
}
if(!s.empty())
{
p=s.top();
cout<<p->value<<" ";
s.pop();
p=p->right;
}
}
}
```
12.1-4
代码如下:
void Preorder_tree_walk_recursive(Node* T)
{
if(T!=NULL)
{
printf("%d",T->value);
Preorder_tree_walk_recursive(T->left);
Preorder_tree_walk_recursive(T->right);
}
}
void Postorder_tree_walk_recursive(Node* T)
{
if(T!=NULL)
{
Postorder_tree_walk_recursive(T->left);
Postorder_tree_walk_recursive(T->right);
printf("%d",T->value);
}
}
//Iterative version of Preorder_tree_walk
void Preorder_tree_walk_Iterative(Node* T)
{
if(T==NULL) return;
stack<Node* > s;
s.push(T);
while(!s.empty())
{
Node* p=s.top();
cout<<p->value<<endl;
s.pop();
if(p->right!=NULL)
s.push(p->right);
if(p->left!=NULL)
s.push(p->left);
}
}
12.1-5
- 暂时没想好
12.2 查询二叉搜索树
查找
- 在一棵二叉搜索树中查找一个具有给定关键字的结点,输入一个指向树根的指针和一个关键字k,如果这个结点存在,返回一个指向关键字为k的结点的指针,否则返回NULL
- 由于查找过程时,二叉树有序,左边的结点一定小于等于有边结点,所以查找过程执行判断便知走哪条路径,相当于二分,所以整个遍历过程的运行时间为 /Theta(h) ,h为这棵树的高度。
二种实现:递归查找和迭代查找。
//the Iterative version of search Node* Iterative_Tree_Search(Node* T,int key) { Node* p=T; while(p!=NULL&&key!=p->value) { if(p->value<key) p=p->right; else p=p->left; } return p; }
最大关键字和最小关键字元素
由于二叉搜索树的性质,其最大关键字和最小关键字只要分别遍历其右子树和左子树,直到其叶子结点。该叶子结点对应最大(最小)关键字。
代码如下:
“`Node* Tree_Minimum(Node* x) { Node *p=x; while(p->left!=NULL) p=p->left; return p; } Node* Tree_Maximum(Node* x) { node* p=x; while(p->right!=NULL) p=p->right; return p; }
后继和前驱
给定一棵二叉搜索树中的一个结点,有时候需要按中序遍历的次序查找它的后继,如果所有的关键字都不相同,则一个结点x的后继时大于x.key的最小关键字的结点。一棵二叉搜索树的结构允许我们通过没有任何关键字的比较来确定一个结点的后继。如果结点x的右子树存在,则寻找该结点子树的最小值即可。如果该结点x的右子树不在,则寻找其父节点祖先中的第一颗左子树存在的结点。
代码如下:Node* Tree_Successor(Node* x) { if(x->right!=NULL) return Tree_Minimum(x->right); Node* y=x->parent; while(y!=NULL&&x==y->right) { x=y; y=y->parent; } return y; } Node* Tree_Predecessor(Node* x) { if(x->left!=NULL) return Tree_Maximum(x->left); node* y=x->parent; while(y!=NULL&& x==y->left) { x=y; y=y->parent; } }
定理2 在一棵高度为h的二叉搜索树上,动态集合上的操作SEARCH、MINIMUM、MAXIMUM、SUCCESSOR、PREDECESSOR可以在 Θ(h) 时间完成。
12.3 插入和删除
插入
- 要将一个新值插入v插入到一棵二叉搜索树T中 ,需要从根节点开始,遍历整个树,找到适合它的大小的位置,将结点的值与树中结点的值遍历,如果小于该结点的值,则从其当前结点的左子树遍历,反之,从右子树遍历。如果任一位置为NULL,则表示无元素,并可以插入。
删除
- 从一棵二叉搜索树中删除一个结点z需要分三种基本情况:
- 如果z没有孩子结点,只是简单地将它删除,并修改他的父节点,用NILL作为孩子来替换z。
- 如果z只有一个孩子,那么将这个孩子提升到树中z的位置上,并修改z的父结点,用z的孩子来替换z。
- 如果z有两个孩子,那么找z的后继y(一定在z的右子树中),并让y占据树中z的位置。z的原来右子树称为y的新的右子树,并且z的左子树成为y的新的左子树。
- 拓展概括的四种情况:
- 如果z没有左孩子,那么用其右孩子来替换z,这个由右孩子可以为NULL,也可以不是,当z的右孩子为NULL时,对应于z没有孩子结点的情形。如果z的有孩子非NULL时,对应与z仅有一i个孩子结点的情形。
- 如果z仅有一个孩子且为其左孩子,那么用其左孩子来替换z
- 否则,z既有一个左孩子又有一个右孩子。需要查找z的后继,这个后继位于z的右子树中,并且没有左孩子。现在需要将y移除原来的位置进行拼接,并替换树中的z。
- 如果y是z的右孩子,那么用y替换z,并仅留下y的右孩子。
- 否则,y位于z的右子树中但并不是z的右孩子,这种情况下,先用y的有孩子替换y,然后在用y替换z。
定理3 在一棵高度为h的二叉搜索树上,实现动态集合操作INSERT和DELETE的运行时间均为 Θ(h)
代码如下:
“`
//insert node z in the tree T
//persume the element is all different in the tree
void Tree_Insert(Node* T,Node* z)
{
node* y=NULL;
node* x=T;
while(x!=NULL)
{
y=x;
if(x->value<z->value)
x=x->right;
else
x=x->left;
}
z->parent=y;
if(y==NULL)
T=z;
else
if(z->value<y->value)
y->left=z;
else
y->right=z;
}
//to transplant the node v to the position of node u int the Tree T
void Transplant(Node* T,Node *u,Node* v)
{
if(u->parent==NULL) //judge whether the tree is empty
T=v;
else //find the right postion to subbitute
if(u==u->parent->left)
u->parent->left=v;
else
u->parent->right=v;
if(v!= NULL)
v->parent=u->parent;
}
//delete the node z in the tree,and keep the order
void Tree_Delete(Node* T,node* z)
{
if(z->left==NULL) //if z doesn't have the left child
Transplant(T,z,z->right);
else if(z->right==NULL) //z has the left child but doesn't have the right child
Transplant(T,z,z->left);
else // z both has the left child and right child
{
Node* y=Tree_Minimum(z->right); //find the next element after z
if(y->parent!=z)
{
Transplant(T,y,y->right);
y->right=z->right;
y->right->parent=y;
}
Transplant(T,z,y);
y->left=z->left;
y->left->parent=y;
}
}
12.4 随机构建二叉搜索树
定理4 一棵有n个不同关键字的随机构建二叉搜索树的期望高度为 Θ(lgn)
测试代码如下:
“`
int main()
{
Node* p,*c;
p=Create_tree();
c=p;
int a[11]={15,6,18,3,7,2,4,13,9,17,20};
for(int i=0;i<11;i++)
{
Node* x=new Node;
x->value=a[i];
x->left=NULL;
x->right=NULL;
x->parent=NULL;
cout<<"ok"<<" "<<i<<endl;
Tree_Insert(c,x);
}
Preorder_tree_walk_Iterative(p);
cout<<endl;
Inorder_tree_walk_Iterative(p);
cout<<endl;
Postorder_tree_walk_recursive(p);
cout<<endl;
Inorder_tree_walk_recursive(p);
cout<<endl;
return 0;
}
```