基础知识
性质
二叉搜索树又叫二叉排序树,其或者是一棵空树。
性质:
- 每个结点都有一个作为搜索依据的关键码(key),所有结点的关键码各不相同。
- 左子树(如果存在)上所有节点的关键码都小于根节点的关键码
- 右子树(如果存在)上所有结点的关键码都大于根节点的关键码。
- 左子树和右子树也是二叉搜索树
举例理解
如上三图,如果一颗而擦函数进行中序遍历,可以按从小到大的顺序,将各节点关键码排序起来,所以也称二叉搜索树为二叉排序树。
结构体
一棵二叉搜索出存在三个指针,左右孩子和双亲指针,还有一个关键码。
typedef int KeyType;
typedef struct BstNode {
struct BstNode* leftchild;
struct BstNode* parent;
struct BstNode* rightchild;
KeyType key;
}BstNode;
typedef struct {
BstNode* root;
int cursize;
}BStree;
函数编写
搜索查找
从二叉排序树中查找一个关键码:我们知道左子树的关键码必然小于根节点的关键码,根节点的关键码必然小于右孩子的关键码。所以遍历时仅仅需要判断要查找的关键是否大于根节点的关键码,大于在右边找,继续判断右子树的根节点关键码与要查找的关键码大小,反之在左子树进行查找。
非递归
BstNode* FindValue(BSTree* ptree, KeyType kx) {
BstNode* ptr = ptree->root;
while (ptr!=nullptr&&kx!=ptr->key){
ptr = kx < ptr->key ? ptr->leftchild : ptr->rightchild;
}
return ptr;
}
递归
BstNode* Search(BstNode* ptr, KeyType kx) {
if (ptr == nullptr || ptr->key == kx) return ptr;
else if (kx < ptr->key)return Search(ptr->leftchild, kx);
else return Search(ptr->rightchild,kx);
}
BstNode* Searchvalue(BSTree* ptree, KeyType kx) {
return Search(ptree->root, kx);
}
插入数据
BstNode* Buynode() {//购买新节点
BstNode* s = (BstNode*)calloc(1, sizeof(BstNode));
if (nullptr == s) exit(EXIT_FAILURE);
return s;
}
bool Insert_Item(BSTree* ptree,KeyType kx) {
assert(ptree!=nullptr);
if (ptree->root ==nullptr) {//判断不存在根节点
ptree->root = Buynode();
ptree->root->key = kx;
ptree->cursize = 1;
return true;
}
BstNode* ptr = ptree->root, * pa = nullptr;
while (ptr!=nullptr && ptr->key != kx) {//遍历,遍历结束找出该结点本应该存放的双亲结点
pa = ptr;
ptr = kx > ptr->key ? ptr->rightchild : ptr->leftchild;
}
if (ptr != nullptr && ptr->key == kx) return false;//树中原本就存在这个数据,直接返回错误
//树中不存在该结点进行插入
ptr = Buynode();//购买新节点
ptr->key = kx;//新节点关键码赋值
ptr->parent = pa;//双亲指针赋值
//判断双亲结点的关键码与要插入的值的大小比较
if (ptr->key < pa->key) {
pa->leftchild = ptr;
}
else {
pa->rightchild = ptr;
}
ptree->cursize += 1;//节点个数加一
return true;
}
中序遍历
递归
void InOrder(BstNode* ptr) {
if (ptr != nullptr) {
InOrder(ptr->leftchild);//遍历左子树
printf("%d ", ptr->key);
InOrder(ptr->rightchild);//遍历右子树
}
}
void InOrder(BSTree* ptree) {
assert(ptree != nullptr);
InOrder(ptree->root);
printf("\n");
}
非递归
BstNode* First(BstNode* ptr){//寻找中序遍历的第一个结点
while (ptr!=nullptr&&ptr->leftchild!=nullptr) {//一直寻找最左边的结点即可
ptr = ptr->leftchild;
}
return ptr;
}
BstNode* Next(BstNode* ptr) {//寻找某节点中序遍历的后继节点
if (nullptr == ptr)return nullptr;
if (ptr->rightchild != nullptr) {//右孩子不等于null,说明其后继结点是右子树中序遍历的第一个结点
return First(ptr->rightchild);
}
else {//右子树为null分为两种情况,第一种是该结点是其双亲结点的左子树,那么直接后继便是其双亲结点。
//第二种是该结点是其双亲结点的右孩子,那么就要回溯,直到找到一棵树是其根节点的左孩子,此时根节点便是直接后继
BstNode* pa = ptr->parent;
while (pa!=nullptr&& pa->leftchild != ptr) {//此处pa!=null,为了出现最后一个结点的情况
ptr = pa;
pa = ptr->parent;
}
return pa;
}
}
void NiceInOrder(BSTree* ptree) {
for (BstNode* ptr = First(ptree->root); ptr != nullptr; ptr = Next(ptr)) {//从第一个结点一直遍历直到最后一个
printf("%d ",ptr->key);
}
}
}
逆序打印中序遍历
此处和中序遍历打印相对应,可以通过以上代码理解思考
非递归
BstNode* Last(BstNode* ptr) {//寻找中序遍历最后一个结点
while (ptr != nullptr && ptr->rightchild != nullptr) {
ptr = ptr->rightchild;
}
return ptr;
}
BstNode* Prev(BstNode* ptr) {
if (ptr == nullptr)return nullptr;
if (ptr->leftchild != nullptr) {
return Last(ptr->leftchild);
}
else {
BstNode* pa = ptr->parent;
while (pa != nullptr && pa->rightchild != ptr) {
ptr = pa;
pa = ptr->parent;
}
return pa;
}
}
void ResNiceInOrder(BSTree* ptree) {
assert(ptree != nullptr);
for (BstNode* ptr = Last(ptree->root); ptr != nullptr; ptr = Prev(ptr)) {
printf("%d ", ptr->key);
}
printf("\n");
}
删除数据
void Freenode(BstNode* ptr) {
free(ptr);
}
bool Remove(BSTree* ptree, const KeyType kx) {
assert(ptree!=nullptr);
if (ptree->root == nullptr)return false;
BstNode *ptr = FindValue(ptree,kx);
if (ptr == nullptr) return false;
//双分支 狸猫换太子
if (ptr->leftchild != nullptr && ptr->rightchild != nullptr) {
BstNode* nextnode = Next(ptr);
ptr->key = nextnode->key;
ptr = nextnode;
}
//叶子结点的删除 单分支
BstNode* child = ptr->leftchild != nullptr ? ptr->leftchild : ptr->rightchild;//找出左右孩子中不为null的树
BstNode* pa = ptr->parent;//找出双亲结点
if (child != nullptr) child->parent = pa;//孩子不为null,直接将孩子的双亲指针指向待删除指针的双亲结点。
if (pa != nullptr) {//如果待删除结点的双亲结点不为null,要将待删除结点的左右孩子指向待删除结点的孩子
if (pa->leftchild == ptr) {
pa->leftchild = child;
}
else {
pa->rightchild = child;
}
}
else {//根节点
ptree->root = child;
}
Freenode(ptr);
ptree->cursize -= 1;
return true;
}