二叉搜索树BST的实现
翻博客的时候突然发现,我原来已经有一年多没有写过博客了,懒得不行,最近要开始找工作才发现自己有好多地方欠缺,准备查漏补缺了,多写博客激励自己,不要再懒下去了
很久没有用过指针,有些生疏了
BST
二叉搜索树有下列三个特点:
- 左子树的值都小于根的值
- 右子树的值都大于根的值
- 对于每一个结点,它的左右子树分别又是一个二叉搜索树
根据上述的特点,可以构造出一个包含三个数据对象的结构体:
struct BSTtree {
int val;
BSTtree* leftsubtree;
BSTtree* rightsubtree;
};
每个结点都是一个BSTtree类型
接下来我们考虑怎么建树:(都以动态建树为例)
建树的过程实际上就是往树上挨个加结点
以下列的树为例:
如果要往树上添加一个值为13的结点:
首先将13跟根结点比较
结点往10的右子树走遇到结点18,并与之比较
结点再往18的左子树走,发现18的左子树为空
到这里一个结点就已经插入到了树上
不断的重复这个过程,直到把所有的点都加到树上,建树完毕
如果建树的时候没有初始值,可以先令根的值为0,当有值输入的时候再去修改根的值
对root动态分配空间后,判断是否为空是为了看内存是否已分配完,如果为空的话就不能进行后面的操作了
BSTtree* Build_Tree() {
BSTtree* root = (BSTtree*)malloc(sizeof(BSTtree));
if (root == NULL) {
printf("内存不足!\n");
return root;
}
root -> val = 0;
root -> leftsubtree = NULL;
root -> rightsubtree = NULL;
return root;
}
如果我们已有输入,建一棵有n个数值的BST,首先,可以将输入全放入一个动态分配长度为n的数组a中,以a[0]为树根,再遍历数组建树,如果当前的值大于根就将它归到根的右子树区间去,如果小于根的值将它归到根的左子树区间去,然后再与右子树或左子树的根的值做比较,一直比较到当前树的某个结点node,该结点左子树或右子树为空,如果当前的值>node -> val就插入一个结点到node作为右子树,反之作为左子树插入,即每一次遍历为当前建的BST添加一个叶子结点
BST建树的非递归实现:
//非递归实现
BSTtree* Build_Tree(int n) {
BSTtree* root = (BSTtree*)malloc(sizeof(BSTtree));
if (root == NULL) {
printf("内存不足!\n");
return root;
}
int* a = (int*)malloc(n * sizeof(int));
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
root -> val = a[0];
root -> leftsubtree = NULL;
root -> rightsubtree = NULL;
BSTtree* current;
for (int i = 1; i < n; i++) {
current = root;
BSTtree* node = (BSTtree*)malloc(sizeof(BSTtree));
node -> val = a[i];
node -> leftsubtree = NULL;
node -> rightsubtree = NULL;
while (1) {
if (a[i] > current -> val) {
if (current -> rightsubtree == NULL) {
current -> rightsubtree = node;
break;
}
else current = current -> rightsubtree;
}
else {
if (current -> leftsubtree == NULL) {
current -> leftsubtree = node;
break;
}
current = current -> leftsubtree;
}
}
/*错误写法
// while (current == NULL) {
// if (a[i] > current -> val) {
// current = current -> rightsubtree
// }
// else {
// current = current -> leftsubtree;
// }
// }
// current = node;
*/
}
free(a);
return root;
}
注释的部分是一开始写错了的,由于current是临时变量,当current为NULL时,把node赋值给current没有意义,因为树中的每个结点都是BSTtree型的,而current也是BSTtree型的,当current的值为NULL后,current就相当于一个没有连在树上的孤立结点,把node赋值给current,就是把动态生成的一个有值的孤立结点再赋给一个没有值的孤立结点,node并没有连在树上
BST树的递归实现:
void Build_Tree(BSTtree* &root, int val) {
if (root == NULL) {
root = (BSTtree*)malloc(sizeof(BSTtree));
if (root == NULL) {
printf("内存不足!\n");
return;
}
root -> val = val;
root -> leftsubtree = NULL;
root -> rightsubtree = NULL;
return;
}
if (val > root -> val) {
Build_Tree(root -> rightsubtree, val);
}
else {
Build_Tree(root -> leftsubtree, val);
}
}
由于对于一棵BST的每个结点,它的左右子树分别是一棵BST,它的相同子结构让我们可以用递归来实现建树递归要考虑的问题就是结束条件,以及要怎样递归调用自身,这里主要需要解决的问题就是结束条件,是遍历到结点是空的时候就赋值返回吗?如果是这样的话,就会遇到上面提到的一样的问题。
那要怎么判断呢?
用指针就好了,一个指向结点BSTtree*类型的指针,如果该指针为空,就把结点放上去
如果我们希望能够打印出一个有序的数组,就可以用到BST的中序遍历
中序遍历的顺序:左子树 根 右子树
模拟下面这棵树的打印过程:
从根开始,由于根结点不等于NULL,我们将根结点压入栈中,{10},然后遍历10的左子树,访问5,结点5也不为空,压入栈中{10,5},然后在遍历5的左子树,访问4,结点4也不为空,压入栈中{10,5,4},再访问4的左子树,发现为空了,结点4可以出栈并打印,再访问4的右子树,为空,返回,结点5出栈,并打印,访问结点5的右子树,一直这样下去,直到整棵树访问完毕
由于BST左子树都小于根,右子树都大于根的特性,每次最先打印出来的都是当前的最小值
void print(BSTtree* root) {
//递归打印出树的值
//中序遍历
//左根右
if (root == NULL) {
return; //递归结束条件 叶子结点指向的元素是空, 即遍历到叶子结点下一层
}
print(root -> leftsubtree);
printf("%d ", root -> val);
print(root -> rightsubtree);
}
前序遍历和后序遍历同理,只是根打印的顺序有所不同
在BST中插入某个数,跟建树的过程一样
void insert(BSTtree* &root, int val) {
if (root == NULL) {
root = (BSTtree*)malloc(sizeof(BSTtree));
if (root == NULL) {
printf("内存不足!\n");
return;
}
root -> val = val;
root -> leftsubtree = NULL;
root -> rightsubtree = NULL;
return;
}
if (val > root -> val) {
insert(root -> rightsubtree, val);
}
else {
insert(root -> leftsubtree, val);
}
}
由于我们是动态建树,在不需要这棵树之后就需要考虑怎么释放分配出去的空间了
void Destroy_Tree(BSTtree* root) {
//在delete整棵树的时候,先要把左右子结点delete掉再delete自己;
//在树的遍历中就是后序遍历
BSTtree* current = root;
if (current == NULL) {
return;
}
Destroy_Tree(root -> leftsubtree);
Destroy_Tree(root -> rightsubtree);
free(root);
}
要先free掉左右子树再free掉根,这样的顺序跟后序遍历一样