前面学习过二叉树的基本遍历与创建,今天学习二叉树中的二叉查找树,也叫二叉搜索树。
一、二叉搜索树的特点
1、若左子树不为空,则左子树上所有结点的值均小于它的根节点的值;多右子树不为空,则右子树上所有结点的值均大于它的根节点的值;
2、二叉查找树的左右子树也是一颗二叉查找树。
3、二叉查找树的中序遍历是一个升序序列。
4、二叉查找树没有键值相等的节点。
二、二叉查找树的创建。
一、思路:
当我们有一个无序数组,且数组的元素不重复,根据此数组创建一颗二叉查找树。
(1)在数组中任意选取一个元素作为根节点(为了方便我们取第一个元素)
(2)创建一个节点P,并根据该节点的数据值(data),在已有的二叉查找树中寻找插入位置。
1、对于每一个结点都有OLDP,P->data大于OLDP-data则该节点向右结点搜寻,即OLDP = OLDP->right;
若P->data小于 OLDP-data则该节点向左结点搜寻OLDP = OLDP->left。
2、循环实现1 直到OLDP == NULL ,说明P结点已经寻找到需要插入的叶子结点。
3、因为OLDP已经为NULL,不能插入,我们需要寻找的是叶子结点,因此需要使用一个节点LAST,
LAST结点满足 LAST->right/left = OLDP
(3)对于每一个数组元素都需要进行第2步,直到数组元素全部存储。
二、代码:
Node *CreatTree(DataType *a, int N)
{
Node *root = (Node*)malloc(sizeof(Node));
Node *P = NULL,*Last = NULL,*Path = root;
root -> data = a[0];
root->left = NULL;
root->right = NULL;
if (root&&a&&N>0)
{
for (int i = 1; i < N; i++)
{
P = (Node*)malloc(sizeof(Node));
P->data = a[i];
P->left = NULL;
P->right = NULL;
Path = root;
while (Path) //用来寻找适合P结点插入的叶子结点Last
{
Last = Path;
if (P->data > Path->data)
{
Path = Path->right;
}
else
{
Path = Path->left;
}
}
if (P->data>Last->data)
{
Last->right = P;
}
else
{
Last->left = P;
}
}
return root;
}
else
{
return root = NULL;
}
}
三、二叉查找树的查找。
(一)查找指定的值
查找为data结点,找到后并返回结点的指针。
思路:因为对于二叉查找树的任意一个结点都有,该节点的左结点的值<根节点的值<右结点的值,因此我们可如此:如果data>P->data那么P = P->right;如果data < P->data 则,P=P->left。直到P == NULL 或 P ->data == data。
代码:
/***************************************************
函数功能:查找二叉树中值为data的结点
函数参数:root:二叉查找树的根节点
data:需要查找的值
函数返回值 如果找到返回结点指针,没有找到到返回NULL
****************************************************/
Node *FindData(Node *root, DataType data)
{
if (!root)
return NULL;
if (data == root->data)
{
return root;
}
if (data<root->data)
{
FindData(root->left, data);
}
else
{
FindData(root->right, data);
}
}
(二)查找最大值/最小值
思路:二叉排序树的最大值一定是最右侧的结点的值,最小值一定是最左侧的结点的值。因此我们用递归遍历二叉树P = P->left/right;当且仅当P->left/right == NULL 时,递归结束,此时P为二叉树的最小 / 最大值。
代码:
最小值:
/************************************
函数功能:查找二叉查找树的最小值结点
函数参数:root:二叉树的根节点
函数返回值:最小值结点的指针
*************************************/
Node *FindMin(Node *root)
{
if (!root)
return NULL;
if (!root->left)
{
return root;
}
else
{
FindMin(root->left);
}
}
最大值:
/************************************
函数功能:查找二叉查找树的最大值结点
函数参数:root:二叉树的根节点
函数返回值:最大值结点的指针
*************************************/
Node *FindMax(Node *root)
{
if (!root)
return NULL;
if (!root->right)
{
return root;
}
else
{
FindMax(root->right);
}
}
(二)查找指定值的结点的上一个结点
/****************************************************************
函数功能 : 查找指定值结点的根结点。
函数参数 : root :二叉树的根节点
data :指定的数据
函数返回值 : 找到返回上一个结点的指针,没有找到返回NULL
***********************************************************/
Node *FindLastNode(Node *root, DataType data)
{
if (!root)
return NULL;
Node *Plast = NULL,*Pnow = root;
while (Pnow)
{
if (data == Pnow->data && Pnow != root) //根节点没有上一个结点
{
return Plast;
}
else if (data>Pnow->data)
{
Plast = Pnow;
Pnow = Pnow->right;
}
else if (data < Pnow->data)
{
Plast = Pnow;
Pnow = Pnow->left;
}
//一次while循环结束时若Pnow还和root相等则
//说明找到了根节点,根节点没有上一个结点
if (Pnow == root)
{
return NULL;
}
}
}
四、二叉查找树的插入。
二叉树的插入一定是在结点的左子树或右子树为空的地方进行插入,因此我们如下思路:
1、对插入的数据进行结点生成,分配内存。
2、寻找待插入的位置。
(1) 如果data< P->data 则使 P = P->left;
(2) 如果data > P ->data 则使P = P->right;
(3) 循环上面步骤,直到P = NULL;
3、记住P(此时P为NULL)的上一个结点进行插入。
/*******************************************
函数功能 :将指定的值插入到二叉树的合适位置
函数参数 :root : 二叉树的值
data :需要插入的值
函数返回值:true : 成功
false :错误
********************************************/
bool InseartNode(Node *root, DataType data)
{
if (!root)
return false;
Node *Plast = root,*Pnow = (Node*)malloc(sizeof(Node));
Pnow->left = NULL;
Pnow->right = NULL;
Pnow->data = data;
//当root=NULL时,已经找到了要插入的位置。
while (root)
{
Plast = root;
if (data <root->data)
{
root = root->left;
}
else if (data>root->data)
{
root = root->right;
}
}
if (Pnow ->data < Plast->data)
{
Plast->left = Pnow;
return true;
}
else if (Pnow->data > Plast->data)
{
Plast->right = Pnow;
return true;
}
else
return false;
}
五、二叉查找树的结点删除。
1、第一种情况当被查找到的节点是树叶。
2、第二种情况查找到的树有一个节点。
3、第三种情况查找到的树有两个节点。
4、如果什么都没有查到应该返回false。
思路:我们这里删除某个节点Pnow,的本质,是在Pnow的子树里面选取一个结的值,并将该节点的值赋给Pnow,再将选取的结点进行删除的思路。下面我们所有有的删除都会围绕这个思想去进行。
bool DeleteNode(Node *root, DataType data)
{
if (!root)
return true;
Node *Pnow = FindData(root, data); //找到要删除的结点。
Node *Pmin = NULL;
Node *PminLast = NULL;
Node *Ptmp = NULL;
if (Pnow) //如果没有找到则,删除失败
{
//要删除的结点至少存在一个子结点
if (Pnow->left || Pnow->right)
{
//被删除的结点左右结点都存在。
if(Pnow->left&&Pnow->right)
{
//Pmin该结点的左子树一定为NULL(因为他是右子树中的最小值)
Pmin = FindMin(Pnow->right);
//找到最小值结点的上一个结点
PminLast = FindLastNode(Pnow, Pmin->data);
//将最小值结点的值赋给被删除的结点的值,类似于删除了要删除的结点。
Pnow->data = Pmin->data;
//当最小值结点的上一个结点就是要删除的结点时,
//那么最小值结点一定是删除结点Pnow的右子结点。
//当上一个结点不是要删除的结点时,Pmin一定是PminLast的左子结点
//Pmin次时左结点一定为NULL,但是右结点不一定为NULL。
if (PminLast == Pnow)
{
//将最小值结点的右子结点与要被删除Pnow结点的右子结点挂。
Pnow->right = Pmin->right;
}
else
{
//将最小值结点的右子结点与上一个结点的左子挂上。
PminLast->left = Pmin->right;
}
//断开最小值结点的右子结点
Pmin->right = NULL;
free(Pmin);
Pmin = NULL;
return true;
}
else
{
if (Pnow->left&&!Pnow->right) //被删除的结点只有左子结点
{
Ptmp = Pnow->left;
}
else if (!Pnow->left&&Pnow->right) //被删除的结点只有右子结点
{
Ptmp = Pnow->right;
}
Pnow->data = Ptmp->data;
Pnow->data = Ptmp->data;
Pnow->left = Ptmp->left;
Pnow->right = Ptmp->right;
Ptmp->left = NULL;
Ptmp->left = NULL;
free(Ptmp);
Ptmp = NULL;
return true;
}
}
else //要删除的点没有子结点
{
Node *Plast = FindLastNode(root, data);
if (Plast)
{
if (Plast->left == Pnow)
Plast->left = NULL;
else
Plast->right = NULL;
return true;
}
else //该树只有根节点
{
free(Pnow);
Pnow = NULL;
return true;
}
}
}
else
{
cout << "没有找到值为" << data << "的结点" << endl;
return false;
}
}
六、感言
二叉查找树的增删改查,仅仅只是学习二叉树的开始,只有回了基础,才能有更高的进步,后面我会继续学习关于二叉平衡树、以及他的几种实现方法,比如:红黑树,VAL树、替罪羊树、Treap、伸展树等等。加油!!!