二叉搜索树

二叉树有很多类型,其中最常见的类型就是二叉搜索树。二叉搜索(排序)树的定义如下:它或者是一棵空树,或者是具有下列性质的二叉树:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索(排序)树。下面详细讲述一下二叉搜索树。

#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;

}

 

上述方法大多都是采用递归解决,理论上所有能采用递归的都可以用迭代来求解。而且迭代比递归开销更小。关于迭代的实现,以后再涉及。


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值