一、二叉搜索树
所有根节点大于左子树的节点,小于右子树的节点的二叉树。
性质:
-
如果左子树不为空,则左子树上的所有节点都小于根节点
-
如果右子树不为空,则右子树上的所有节点都大于根节点
3.左右子树也为二叉搜索树
二叉搜索树的算法时间复杂度的分析
二叉搜索树与序列元素的插入顺序有关,即元素按照不同的序列建立二叉搜索树会出现不同结构的树。
对于相对平衡的二叉搜索树(接近完全二叉树) ,操作的时间复杂度为 (n为节点个数,logn为树的层数)
在最坏情况下,二叉搜索树呈左单支(或右单支)树,此时层数与节点数相同,时间复杂度为
二叉搜索树主要实现 高效的数据查找和排序
1.查找:根据二叉搜索树的性质,通过比较节点的键值快速定位目标节点 ,对于一棵相对平衡的二叉搜索树,每次的查找时间复杂度通常为 ,n为节点的个数
2.排序:对于一个无序的序列,利用二叉搜索树的性质进行排序,将序列中的元素依次插入到二叉搜索树中,然后进行中序遍历 ,即可按升序输出序列。对于一个相对平衡的二叉搜索树,时间复杂度在 (n个元素,n次二叉树操作)
二、二叉搜索树的操作
首先用结构体定义树
typedef struct tree{
struct tree *left;
struct tree *right;
int data;
}tree;
1. 树的建立(插入元素)
① 若树为空,直接插入
tree *temp=root;
if(temp == NULL)
{
root=(tree *)malloc(sizeof(tree));
root->data=x;
root->left=NULL;
root->right=NULL;
return ;
}
② 树不为空,按照二叉树搜索的性质插入位置,此时插入的该元素一定是叶子节点
有两种插入方法
非递归写法
// 非递归写法
tree *tx=temp;
while(temp!=NULL) // temp为空弹出,tx为temp的上一级 即将新节点插入到tx的下方
{
tx=temp;
if(x<temp->data) temp=temp->left;
else if(x>temp->data) temp=temp->right;
else return ;
}
if(x<tx->data) //插入到左孩子
{
tx->left=(tree*)malloc(sizeof(tree));
tx->left->data=x;
tx->left->left=NULL;
tx->left->right=NULL;
}
else // 插入到右孩子
{
tx->right=(tree*)malloc(sizeof(tree));
tx->right->data=x;
tx->right->left=NULL;
tx->right->right=NULL;
}
递归写法
void insert(tree **troot,int x) // 非递归写法
{
tree *temp=*troot;
if(temp == NULL)
{
(*troot)=(tree*)malloc(sizeof(tree));
(*troot)->data=x;
(*troot)->left=NULL;
(*troot)->right=NULL;
return ;
}
if(x<temp->data) insert(&temp->left,x);
else if(x>temp->data) insert(&temp->right,x);
else return ;
}
插入过程图示:
③ 树不为空,若插入的数据在树中已存在,则不将数据插入到树中。二叉搜索树的数据都是独一无二的。
2.树的查询
树的查询就是运用二叉树的性质通过递归去查找元素。
bool search(int x) // 查找
{
tree *temp=root;
while(temp!=NULL)
{
if(x<temp->data) temp=temp->left;
else if(x>temp->data) temp=temp->right;
else return true;
}
return false;
}
3.树的删除节点操作
删除节点的操作与前两种复杂很多,需要考虑到许多种情况。
首先需要找到删除的节点,与树的查询类似,运用二叉树的性质查找,并且记录其父节点。
tree *temp=root,*temp1=root; // temp为删除的节点 temp1为删除节点的父节点
while(temp!=NULL && temp->data!=x)
{
temp1=temp; // temp1储存为temp的前一个节点
if(x<temp->data) temp=temp->left;
else if(x>temp->data) temp=temp->right;
}
①当删除节点是叶子节点(判断条件:无左右子树)
直接删除即可,再将其父节点到该节点的指针清除。
需要判断删除节点是父节点的左孩子还是右孩子
if(temp->left==NULL && temp->right==NULL) // 删除的为叶子节点
{
if(temp1->left==temp) temp1->left=NULL; // 为左孩子,并将该处指针赋空
else if(temp1->right==temp) temp1->right=NULL; // 为右孩子
}
②当删除节点只有一个孩子(只有左孩子或者右孩子)
以只有右孩子为例,左孩子同理。将删除节点的父节点直接指向其右孩子(这里仍需要判断删除节点时是父节点的左孩子还是右孩子),并释放删除节点, 赋为空(NULL)
if(temp->right==NULL) //只有左孩子,右孩子为空
{
if(temp1->left->data == temp->data) // 删除节点为父节点的左孩子
{
temp1->left=temp->left;
tree *free_temp=temp;
free(free_temp);
free_temp=NULL;
}
else // 删除节点为父节点的右孩子
{
temp1->right=temp->left;
tree *free_temp=temp;
free(free_temp);
free_temp=NULL;
}
}
else if(temp->left==NULL) // 只有右孩子,左孩子为空
{
if(temp1->left->data==temp->data) // 删除节点为父节点的左孩子
{
temp1->left=temp->right;
tree *free_temp=temp;
free(free_temp);
free_temp=NULL;
}
else // 删除节点为父节点的右孩子
{
temp1->right=temp->right;
tree *free_temp=temp;
free(free_temp);
free_temp=NULL;
}
}
③当删除节点既有左孩子又有右孩子
有两种考虑方法:左子树的最大值节 点 或右子树的最小值节点替换删除节点
这种方法实现的原因:二叉搜索树删除节点的后,需要保证再次中序遍历时,仍然保持其原来的顺序,以找左子树最大替换图示。
找左子树最大的方法:就是找以删除节点的左孩子为根的树的最大值节点,同时记录其父节点
tree *maxn=temp->left,*maxn1=temp->left; // maxn maxn1找左子树最大,其中maxn存储左子树最大的节点 maxn1 为其父节点
// 找出的节点一定没有右节点
while(maxn->right!=NULL)
{
maxn1=maxn;
maxn=maxn->right;
}
过程顺序:将 maxn 的 data数据 赋到 删除节点 temp 上
①若删除节点不是 maxn 的父节点,直接将其父节点 maxn1 的右节点 赋为 maxn 的左节点,即 maxn 的左子树全部接到 maxn1 的右子树 上**( maxn只有左孩子 没有右孩子,否则maxn在查找时仍然回往下)**
if(temp!=maxn1) // 如果删除节点不是左子树最大节点的父节点
{
maxn1->right=maxn->left; // 删除节点的父节点 接上 删除节点的左子树
free(maxn);
maxn=NULL;
}
②当删除节点是 maxn 的父节点 (那么,其父节点的左孩子一定是 maxn ),其他与上同理
else
{
maxn1->left=maxn->left;
free(maxn);
maxn=NULL;
}