二叉树的基本概念
1.定义
二叉树(binary tree)是一颗树,其中每个节点都不能有多于两个的儿子。
2.实现
每一个节点用一个结构体表示,包含一个关键字和两个指向左儿子和右儿子的指针。
3.遍历方式
中序遍历:左 节点 右
后序遍历:左 右 节点
先序遍历:节点 左 右
4.二叉查找树的概念
一颗二叉树,对于每一个节点X,它的左子树中所有的关键字值都小于X的关键字值,它的右子树中所有的关键字值都大于X的关键字值。
5.二叉查找树的性质
平均深度O(logN)
二叉查找树各种操作的代码实现
用到了很多的递归操作,且所有操作都需要传入操作的节点(即可以对子树进行操作)
1.清空树
算法实现步骤:
1.递归删除左子树
2.递归删除右子树
3.删除该节点,返回一个NULL指针(释放内存后指针依旧指向该段内存,但是内容不确定了)
/**
*@name Make_empty:递归删除该节点以及该节点下所有的子树
*@param1 T:指向该节点的指针
*@ret:将NULL返回给删除的该节点
**/
Search_Tree Binary_Tree::Make_empty(Search_Tree T )
{
//这里使用了递归删除
if(T!=NULL)
{
Make_empty(T->left);
Make_empty(T->right);
delete T;
T=NULL;
}
return T;
}
2.查找最大(最小值)
算法实现步骤:
1.由二叉查找树的结构决定,找到最左边的叶(最小值),找到最右边的叶(最大值)
2.这里也利用递归去找,为最小值为例,直到找到的节点的左儿子为空时,将该节点返回
注:
这里在递归调用Find_Min时需要加return,否则从堆栈pop出来的时候就只有一个ret,可能会出现问题
/**
*@name Find_Max:找到该节点的子树中最大的那个值
*@param1 T:传入的树节点 如果要找整根树中最大的值则输入root
*@ret:指向存最大值的树节点 为子树中最右边的叶
**/
Search_Tree Binary_Tree::Find_Max(Search_Tree T)
{
if(T==NULL)
return NULL;
if(T->right==NULL)
{
return T;
}
else
{
return Find_Max(T->right);
}
}
/**
*@name Find_Max:找到该节点的子树中最小的那个值
*@param1 T:传入的树节点 如果要找整根树中最小的值则输入root
*@ret:指向存最大值的树节点 为子树中最左边的叶
**/
Search_Tree Binary_Tree::Find_Min(Search_Tree T)
{
if(T==NULL)
{
return NULL;
}
if(T->left==NULL)
{
return T;
}
else
{
return Find_Min(T->left);
}
}
3.查找关键字匹配的节点
算法实现步骤:
1.从某一节点向下,如果查询的关键字大于该节点的关键字,到右子树中递归查找,如果查询的关键字小于该节点的关键字,到左子树中递归查找。
2.如果递归到节点已空,则没有找到;如果找到则返回该节点的地址。
注:
在递归Find_Position前也要加return
/**
*@name Find_Position:查找关键字所在的树的节点
*@param1 data:需要匹配的关键字
*@param2 T:从哪一个节点开始往下找 只会向下查找
*@ret:指向匹配到关键字的节点的指针 没有找到则返回NULL
**/
Search_Tree Binary_Tree::Find_Position(int data,Search_Tree T)
{
if(T==NULL)
{
return NULL;
}
//如果数据比该节点的关键字小的话,查找去左子树中查找
if(T->data>data)
{
return Find_Position(data,T->left);
}
//如果数据比该节点的关键字大的话,查找去右子树中查找
else if(T->data<data)
{
return Find_Position(data,T->right);
}
else
{
return T;
}
}
4.插入节点
由于二叉查找树的结构,插入永远会在叶上,故方便很多
算法实现步骤:
1.根据新插入的节点的关键字大小,在给定节点的左子树递归插入或右子树递归插入
2.直到递归到空节点,new一个节点,然后return该地址
3.最后返回插入完成了的给定节点的地址
/**
*@name Insert:在树的合适位置插入关键字为data的节点
*@param1 data:插入的数据
*@param2 T:在该节点的子树中插入数据
**/
Search_Tree Binary_Tree::Insert(int data,Search_Tree T)
{
//递归到空节点 这时建立一个新节点
if(T==NULL)
{
T=new Tree_node;
T->data=data;
T->left=T->right=NULL;
return T;
}
//插入的数据小于此时节点的数据,往左插(别忘了赋值给子左节点,指针形参改变不了传入的参数,即虽然T->left新建了一个节点,但此时只有形参指向它)
if(T->data>data)
{
T->left=Insert(data,T->left);
}
//插入的数据大于此时节点的数据,往右插
else if(T->data<data)
{
T->right=Insert(data,T->right);
}
//如果插入的数据等于此时节点的数据,直接返回该节点
else
{
cout<<"can't insert the same data"<<endl;
}
return T;
}
5.删除某一关键字对应的节点
算法实现步骤:
1.关键字小于给点节点关键字去左子树删,关键字大于给点节点关键字去右子树删。
2.如果关键字等于当前节点了分三种情况删:
(1)当前节点为叶,直接delete,返回NULL;
(2)当前节点只挂一侧有子树,则先用定义中间指针变量temp指向该节点,然后delete当前节点,然后返回其右子树;
(3)当前节点两侧都有子树,则取出右子树中的min节点,将此关键字赋给当前节点,然后递归删除min节点;
3.如果找到空节点了还没找到就报错。
/**
*@name Delete:删除关节字为data的元素
*@param1 data:关键字
*@param2 T:从该节点以下的树中删除
**/
Search_Tree Binary_Tree::Delete(int data,Search_Tree T)
{
Search_Tree temp;
if(T==NULL)
cout<<"error:without element to delete"<<endl;
else if(T->data<data)
T->right=Delete(data,T->right);
else if(T->data>data)
T->left=Delete(data,T->left);
else
{
//此时data等于T->data
temp=T;
if((T->left==NULL)&&(T->right==NULL)) //此时的节点为叶
{
delete T;
T=NULL;
}
else
{
if(T->left==NULL) //此时的左儿子为空,只有右子树
{
T=T->right; //将T指向右儿子节点
delete temp; //释放该节点的内存
temp=NULL;
}
else if(T->right==NULL) //此时的右儿子为空,只有左子树
{
T=T->left; //将T指向左儿子节点
delete temp; //释放该节点的内存
temp=NULL;
}
else //此时既存在左子树又存在右子树
{
temp=Find_Min(T->right); //找到右子树中最小的节点
T->data=temp->data; //将该节点的元素赋给当前节点
T->right=Delete(temp->data,T->right); //对右子树进行一次删除操作
}
}
}
return T;
}
对递归和指针理解的还不够,搞在一起就开始糊涂了。。。
注:
为什么这里的形参指针不能改变传入的指针的值?
解:
1.改变形参指针指向的内容对传入的指针毫无影响,因为调用完后就消失了
2.这里由于递归带来的混淆,我一直以为形参指针和传入的指针指向的一直是同一块内存,这里举最简单的一个例子,传入的指针指向NULL,形参做出判断,新建了一块内存并指向它,而传入的指针依旧是NULL。