二叉搜索树
一、二叉搜索树的概念
(1)若它的左子树不为空,则左子树上所有节点的值都小于根节点值
(2)若它的右子树不为空,则右子树上所有节点的值都大于根节点值
(3)它的左右子树都是二叉搜索树
从概念可以看出二叉搜索树的中序遍历是有序排列的,而且没有重复元素。在实际应用当中也是根据这一特点是否采用二叉搜索树。
二、二叉搜索树的操作
- 查找操作
如果根节点不为空:(1)如果根节点值等于key,返回true。(2)如果根节点值小于key,到右子树查找。(3)如果根节点值大于key,到左子树查找。
int BSTreeFind(BSTree *root,int data)
{
if(root == nullptr)
return -1;
if(root->data == data)
return 0;
else if(root->data<data)
return BSTreeFind(root->pRight,data);
else
return BSTreeFind(root->pLeft,data);
}
int BSTreeFindNor(BSTree *root,int data)
{
BSTree *cur = root;
while(cur != nullptr)
{
if(cur->data == data)
return 0;
else if(cur->data > data)
cur = cur->pLeft;
else
cur = cur->pRight;
}
return -1;
}
- 插入操作
在二叉搜索树中插入新元素之前必须要检查该节点值是否已经存在于二叉搜索树。如果存在,不进行插入,否则将新元素插入到搜索停止位置。所以我们在搜索的过程中要记录父节点。
//1、空树直接插入
//2、先查找该元素是否存在,如果存在插入失败
//3、当查找到最后为nullptr进行插入,当然需要记录插入节点的父节点
int BSTreeInsert(BSTree**root,int data)
{
BSTree *newNode = new BSTree;
newNode->data = data;
if(*root == nullptr)
{
*root = newNode;
return 0;
}
BSTree *cur = *root;
if(cur->data == data)
return -1;
else if(data>cur->data)
return BSTreeInsert(&((*root)->pRight),data);
else
return BSTreeInsert(&((*root)->pLeft),data);
}
int BSTreeInsertNor(BSTree**root,int data)
{
assert(root);
BSTree *cur = *root;
BSTree *parent = nullptr;
//查找,如果有重复插入失败
while(cur != nullptr)
{
if(cur->data == data)
return -1;
parent = cur;
if(cur->data>data)
cur = cur->pLeft;
else if(cur->data < data)
cur = cur->pRight;
}
BSTree *newNode = new BSTree;
newNode->data = data;
//空树
if(parent == nullptr)
{
*root = newNode;
return 0;
}
//
if(data>parent->data)
parent->pRight = newNode;
else
parent->pLeft = newNode;
return 0;
}
- 删除操作
首先查找元素是否存在于搜索二叉树,如果不存在,返回,否则删除该元素分为以下情况:
(1)该元素左右子树为空
(2)该元素只有左孩子节点
(3)该元素只有右孩子节点
(4)该元素左右孩子节点都有
其中情况1可以归结为情况2/3。
int BSTreeDelete(BSTree **root,int data)
{
assert(root);
BSTree *cur = *root;
BSTree *parent = nullptr;
while(cur != nullptr)
{
if(cur->data == data)
{
//情况1
if(cur->pLeft == nullptr)
{
if(parent == nullptr)
*root = cur->pRight;
else{
if(data > parent->data){
parent->pRight = cur->pRight;
}
else if(data < parent->data){
parent->pLeft = cur->pRight;
}
}
delete cur;
cur = nullptr;
return 0;
}//情况2
else if(cur->pRight == nullptr)
{
if(parent == nullptr)
*root = cur->pLeft;
else{
if(data>parent->data){
parent->pRight = cur->pLeft;
}
else if(data<parent->data){
parent->pLeft = cur->pLeft;
}
}
delete cur;
cur = nullptr;
return 0;
}
else{
//替换删除
parent = cur;
cur = cur->pRight;
while(cur ->pLeft != nullptr)
cur = cur->pLeft;
parent->data = cur->data;
if(cur == parent->pRight)
parent->pRight = cur->pRight;
delete cur;
cur = nullptr;
return 0;
}
}
parent = cur;
if(data < cur->data)
cur = cur->pLeft;
else
cur = cur->pRight;
}
return -1;
}
三、二叉搜索树应用
判断一个单词是否正确(查找一个单词)
判断一个单词是否正确,可以转化为查找问题(key——value)。也就是在一个单词库中查找一个单词,关键是这个单词库怎么设计。如果这个单词库使用顺序表设计,那么查找效率太慢。所以采用二叉搜索树只需要O(lgN)
#include <iostream>
#include <string>
#include <string.h>
typedef struct Node
{
std::string word;
struct Node *pLeft;
struct Node *pRight;
}Node;
bool Search(Node *root,std::string key)
{
if(root == nullptr)
return false;
int ret = strcmp(key.c_str(),root->word.c_str());
if(ret == 0)
return true;
else if(ret<0)
return Search(root->pLeft,key);
else
return Search(root->pRight,key);
}
bool Insert(Node **root,std::string key)
{
if(*root == nullptr)
{
Node *newNode = new Node;
newNode->word = key;
newNode->pLeft = nullptr;
newNode->pRight = nullptr;
*root = newNode;
return true;
}
int ret = strcmp((*root)->word.c_str(),key.c_str());
if(ret == 0)
return false;
else if(ret<0)
return Insert(&((*root)->pRight),key);
else
return Insert(&((*root)->pLeft),key);
}
void Test()
{
Node *root = nullptr;
Insert(&root,"world");
Insert(&root,"the");
Insert(&root,"lixiong");
Insert(&root,"thanks");
Insert(&root,"word");
Insert(&root,"apple");
bool ret = Search(root,"word");
if(ret == true)
std::cout<<"find it"<<std::endl;
else
std::cout<<"not exist"<<std::endl;
}
四、二叉搜索树的性能分析
同一个关键码集合,按照不同的插入顺序,可能得到的二叉搜索树不同。在最优情况下得到的树为完全二叉树,平均查找效率为lgN;在最坏情况下,二叉搜索树退化为单支树,平均查找效率为N/2;所以如果退化为单支树,二叉搜索树的性能就失去了。为了解决这个问题,引出平衡树。
- AVL树
一颗AVL树要么是空树,要么满足以下性质的二叉搜索树:(1)他的左右子树都是AVL树。(2)左右子树的高度只差不超过1(-1、0、1)
实现AVL树需要进行旋转。所以代价比较高,又引出一种红黑树。
2. 红黑树
红黑树其实也是二叉搜索树,每个节点增加了一个存储为表示红黑色,通过对任何一条从根节点到叶子节点进行约束,红黑树保证最长路径不超过最短路径长度的两倍,因而近似平衡。
红黑树性质:
- 根节点是黑色的,叶子节点是黑色的(此处叶子节点是空节点),每条路径的一头一尾都是黑色的。
- 红色不能挨着红色
- 所有路径黑色数量一样多
为什么满足上述颜色约束,就可以使得最长路径不超过最短路径的两倍?