二叉树有很多类型,其中最常见的类型就是二叉搜索树。二叉搜索(排序)树的定义如下:它或者是一棵空树,或者是具有下列性质的二叉树:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索(排序)树。下面详细讲述一下二叉搜索树。
#include <iostream>
using namespace std;
struct node {//一个结点
intele;//元素值
node* left;//左指针
node* right;//右指针
node(int e):left(0), right(0){
ele = e;
}//结构体的初始化
};
class BinaryTree {
private:
node* root;//初始的根节点
void MemoryDelete(node* p);//删除时递归删除
static void BuildTree(const node* Source_Root, node*&Target_Root);//树的拷贝构造时递归复制
static void preorder(const node* p);//打印时递归打印
public:
BinaryTree();//默认构造函数
BinaryTree(const BinaryTree&);//copy构造函数
BinaryTree(const int* arr, int len);//有形参的构造函数
void ResetTree(const int* arr, int len);//重置一个树
~BinaryTree();//析构函数
void clear();//清空一个树
void insert(int ele);//插入一个元素
void Delete(int ele);//删除一个元素
void print();//打印树
};
0.二叉搜索树的构造方式
二叉搜索树的构造方式有很多很多,对于同一组数,可以构造出很多不同的二叉搜索树。比如对于3,2,1,6,5,4,7,我们可以每次取中间值(中间值若不唯一可以规定始终取小的或者取大的),平均分成左右两组(左组恒小于右组),中间值作为一个结点,一直递归下去得到一个二叉搜索树。我们也可以先将3作为根,然后按照依次插入的思想来构造一个树(插入的方式见3)。下面分别展示了两种方式构造的树。
我们按插入的思想,构造一个树,代码如下:
BinaryTree::BinaryTree()//默认构造函数
{
root=NULL;
}
BinaryTree::BinaryTree(const int* arr, intlen)//有形参的构造函数
{
root=NULL;
for(inti=0;i<len;i++)
{
insert(arr[i]);
}
}
void BinaryTree::insert(int ele)
{
intflag=1;//判断树中是否已有需要插入的元素
if(root==NULL)
root=newstruct node(ele);
else
{
structnode* temproot=root;//记录当前的指针
structnode* preroot=NULL;//记录前一个指针
while(temproot!=NULL)
{
if(ele<temproot->ele)
{
preroot=temproot;
temproot=temproot->left;
}
elseif(ele>temproot->ele)
{
preroot=temproot;
temproot=temproot->right;
}
else
{
flag=0;
break;
}
}
if(flag!=0)
{
if(ele<preroot->ele)
{
preroot->left=newstruct node(ele);
}
elseif(ele>preroot->ele)
{
preroot->right=newstruct node(ele);
}
}
}
}
如果需要copy构造一个树,代码如下:
BinaryTree::BinaryTree(constBinaryTree& a)//copy构造函数
{
if(a.root==NULL)
{
root=NULL;
return;
}
else
{
root=newstruct node(a.root->ele);
BuildTree(a.root->left,root->left);
BuildTree(a.root->right,root->right);
}
}
void BinaryTree::BuildTree(const node*Source_Root, node* &Target_Root)//copy构造函数的递归调用函数
{
if(Source_Root!=NULL)
{
Target_Root=newstruct node(Source_Root->ele);
BuildTree(Source_Root->left,Target_Root->left);
BuildTree(Source_Root->right,Target_Root->right);
}
}
1.二叉搜索树的遍历方式
二叉树的遍历方式主要有三种:前序遍历,中序遍历和后序遍历。
前序遍历按照根节点->左子树->右子树来遍历。(根节点在第一位)
中序遍历按照左子树->根节点->右子树来遍历。(根节点在第二位)
后序遍历按照左子树->右子树->根节点来遍历。(根节点在第三位,左子树始终在右子树前面)
可以用递归来理解这些遍历方式。如下面的二叉树
前序遍历:先选择初始根节点为A,A左边的视作一个整体左树,右边的也视作一个整体右树。那么下一步遍历这个整体左树,在整体左树中又有根节点,根节点下又有左右树,于是一直做下去,到末端时退回到上一步看上一步的右树。如果结点为空,跳过。图中的为ABCDEF。
中序遍历:同理,对于初始根节点A,先遍历相对于A的整体左树,然后递归下去。图中的为CBDAEF。
后序遍历:图中的为CDBFEA。
可以采用递归方式打印,用中序遍历代码如下:
void BinaryTree::print()//打印函数
{
preorder(root);
cout<<endl;
}
void BinaryTree::preorder(const node* p)//打印函数的递归调用函数
{
if(p!=NULL)
{
cout<<p->ele<<"";
preorder(p->left);
preorder(p->right);
}
}
二.二叉搜索树的节点删除方式
二叉树的节点删除方式有很多种,但每一种都必须要求删除后二叉树的排序方式没有发生变化,下面列举一种删除节点的方式:
删除节点p:
若p没有左结点,直接用p的右结点取代它。
若p有左结点,先找到其左结点q。如果q没有右结点,那么用q来代替p;如果q有右结点,那么找q结点最右边的子结点r。用该子结点r来替代p,把r的左结点(r肯定没有右结点,要不然r便不是最右边的子节点了)作为移动r之前r的父亲的右结点。
可以验证,此种删除方式不管如何删除得到的树仍是二叉搜索树。
用上述方式删除一个结点,代码如下:
void BinaryTree::Delete(int ele)
{
structnode* preptr=NULL;
structnode* nowptr=root;
while(nowptr!=NULL)//找到该结点,nowptr指向该节点,preptr指向它的父亲
{
if(ele<nowptr->ele)
{
preptr=nowptr;
nowptr=nowptr->left;
}
elseif(ele>nowptr->ele)
{
preptr=nowptr;
nowptr=nowptr->right;
}
elseif(ele==nowptr->ele)
{
break;
}
}
if(nowptr!=NULL)//确保删除的元素在树内,否则不执行操作
{
if(nowptr->left==NULL)//如果p的左结点为NULL
{
if(preptr!=NULL)//确保删除的元素不是初始的根,否则需要对根再进行赋值操作
{
if(preptr->left==nowptr)
{
preptr->left=nowptr->right;
deletenowptr;
}
else
{
preptr->right=nowptr->right;
deletenowptr;
}
}
else
{
root=nowptr->right;
deletenowptr;
}
}
elseif(nowptr->left->right==NULL) //如果p有左结点,但该左结点无右结点
{
if(preptr!=NULL)//确保删除的元素不是初始的根,否则需要对根再进行赋值操作
{
if(preptr->left==nowptr)
{
preptr->left=nowptr->left;
preptr->left->right=nowptr->right;
deletenowptr;
}
else
{
preptr->right=nowptr->left;
preptr->right->right=nowptr->right;
deletenowptr;
}
}
else
{
nowptr->left->right=root->right;
root=nowptr->left;
deletenowptr;
}
}
else//如果p有左结点,且该左结点有右结点
{
structnode* nowptr2=nowptr->left;
structnode* preptr2=nowptr->left;
while(nowptr2->right!=NULL)
{
preptr2=nowptr2;
nowptr2=nowptr2->right;
}//找到最右端的结点,nowptr2指向该节点,preptr指向该节点的父亲
preptr2->right=nowptr2->left;
if(preptr!=NULL)//确保删除的元素不是初始的根,否则需要对根再进行赋值操作
{
if(preptr->left==nowptr)
{
preptr->left=nowptr2;
nowptr2->left=nowptr->left;
nowptr2->right=nowptr->right;
deletenowptr;
}
else
{
preptr->right=nowptr2;
nowptr2->left=nowptr->left;
nowptr2->right=nowptr->right;
deletenowptr;
}
}
else
{
root=nowptr2;
root->left=nowptr->left;
root->right=nowptr->right;
deletenowptr;
}
}
}
}
三.二叉搜索树的结点插入方式
二叉搜索树结点的插入方式也有很多种,下面列举一种:
可以采用递归的方法来构造一个二叉搜索树:
归纳基:只有一个结点的是一个二叉搜索树。
归纳步:对于任一个需要加入的元素,比较它和根的大小,小的话再和左子树的根比较,大的话和右子树的根比较,一直下去,直到成为一个叶子。
由上面叙述,我们便可以插入一个结点了。
插入的代码见1
四.二叉搜索树的清空方式
为了避免内存泄漏,我们在清空一个二叉树时要保证每一个结点都删除。可以采取递归的方式来删除结点,代码如下:
BinaryTree::~BinaryTree()//析构函数
{
if(root!=NULL)
MemoryDelete(root);
}
void BinaryTree::clear()//清空树
{
if(root!=NULL)
{
MemoryDelete(root);
root=NULL;
}
}
void BinaryTree::MemoryDelete(node* p)//递归调用的删除函数
{
if(p->left!=NULL)
MemoryDelete(p->left);
if(p->right!=NULL)
MemoryDelete(p->right);
deletep;
}
上述方法大多都是采用递归解决,理论上所有能采用递归的都可以用迭代来求解。而且迭代比递归开销更小。关于迭代的实现,以后再涉及。
2768

被折叠的 条评论
为什么被折叠?



