性质:
根节点左边的节点关键字都比根节点的关键字小;根节点右边的节点关键字都比根节点的关键字大;
结构体:
typedef struct BinarySearchTreeNode {
int value;
BinarySearchTreeNode* parent;
BinarySearchTreeNode* left;
BinarySearchTreeNode* right;
}BinarySearchTreeNode;
按序输出:
中序遍历(复杂度为 O(n) ):
void InOrder(BinarySearchTreeNode* root) {
if (root != NULL) {
InOrder(root->left);
cout << root->value << " ";
InOrder(root->right);
}
}
查找元素:
(递归实现)
BinarySearchTreeNode* Search(BinarySearchTreeNode* root, int x) {
if (root == NULL || x == root->value) { //感觉这句好机智
return root;
} else {
if (x > root->value) {
return Search(root->right, x);
} else {
return Search(root->left, x);
}
}
}
(非递归实现)
BinarySearchTreeNode* Search_(BinarySearchTreeNode* root, int x) {
while( root!= NULL && x != root->value) {
if (x > root->value) {
root = root->right;
} else {
root = root->left;
}
}
return root;
}
最小元素和最大元素:
//最小
BinarySearchTreeNode* Min(BinarySearchTreeNode* root) {
BinarySearchTreeNode* ans = NULL;
while (root != NULL ) {
ans = root;
root = root->left;
}
return ans;
}
//最大
BinarySearchTreeNode* Max(BinarySearchTreeNode* root) {
BinarySearchTreeNode* ans = NULL;
while (root != NULL ) {
ans = root;
root = root->right;
}
return ans;
}
寻找前驱和后继:
《算法导论》给出了一个找后继的例子:
如果该节点有右子树,直接返回右子树的最小值;
否则沿着该节点向上找双亲,找到一个节点,这个节点是它的双亲的左子树,就返回它的双亲;
但是我觉得更直观的理解是不断向上找双亲,然后直接比较关键字,遇到第一个关键字比寻找的元素大,就终止返回;
他那样写一定有他的理由,因为是直观的去按照中序遍历的思路去寻找,肯定是很完备的;只是目前我找不出我的思路的错误
//算法导论上的实现
BinarySearchTreeNode* FindSuccessor(BinarySearchTreeNode* root) {
if (root->right != NULL) {
return Min(root->right);
} else {
BinarySearchTreeNode* ans = root.parent;
while(ans != NULL && root == ans->right) {
root = ans;
ans = ans->parent;
}
return ans;
}
}
寻找前驱应该是对称的;
插入和删除:
插入:
首先传入的树根一定是引用,其次,先建立一个临时节点便于之后插入,向下索引时(插入的位置一定是NULL),所以要同时记录插入位置的双亲节点,然后再根据大小关系判断插入。
void Insert(BinarySearchTreeNode* &root, int z) {
BinarySearchTreeNode* temp = new BinarySearchTreeNode;
temp->value = z;
temp->parent = temp->left = temp->right = NULL;
BinarySearchTreeNode* index = root;
BinarySearchTreeNode* index_parent = NULL;
while(index != NULL) {
index_parent = index;
if (temp->value > index->value) {
index = index->right;
} else {
index = index->left;
}
}
temp->parent = index_parent;
if (index_parent == NULL) {
root = temp;
} else if (temp->value > index_parent->value) {
index_parent->right = temp;
} else {
index_parent->left = temp;
}
}
删除:
被删除节点为z
分三种情况:
- z没有孩子,直接删除,并修改双亲结点指向z的指针为NULL
- z只有一个孩子,将孩子提到z的位置,并修改双亲结点指向z的指针为z的孩子
- z有两个孩子,先找z的后继,如果后继不是z的右子树的根节点,先把后继换到右子树的根节点;接着,把后继提上来,再改变后继的左子树为z的左子树
替换的子过程:
//以v为根的子树替换一棵以u为根的子树,此时u作为一个单独的节点保留自己的信息,但是独立出这棵树了
void TransPlant(BinarySearchTreeNode* & root, BinarySearchTreeNode* u, BinarySearchTreeNode* & v) {
if(u->parent == NULL) {
root = v;
} else {
BinarySearchTreeNode* & Parent = u->parent;
if (u == Parent->left) {
Parent->left = v;
} else {
Parent->right = v;
}
}
if (v != NULL) {
v->parent = u->parent;
}
}
删除节点:
void Delete(BinarySearchTreeNode* &root, BinarySearchTreeNode* z) { //如果传入的参数是int,那就调用一次Search
if (root == NULL) {
cout << "Already empty!" << endl;
return;
}
if (z == NULL) {
cout << "Not exist!" << endl;
return;
}
if (z->left == NULL) {
TransPlant(root, z, z->right);
} else if (z->right == NULL) {
TransPlant(root, z, z->left);
} else { //上两种情况是只有一个子树,或者没有子树,下面的情况是有两个子树
BinarySearchTreeNode* y = Min(z->right);
if (y->parent != z) { //如果y不是z的右子树, 把y换到z的右子树
TransPlant(root, y, y->right);
y->right = z->right;
y->right->parent = y;
}
TransPlant(root, z, y);
y->left = z->left;
y->left->parent = y;
}
}
再次考虑到,什么时候该用引用呢?当需要在函数中改变参数的时候,传参就加上引用;但是遇到很多遍历的情况,会经常不知所措,所以目前采取的策略是,在确保不会错的情况下,只要函数里对这个变量有赋值的操作,我都会加上引用;
构建随机二叉搜索树:
将n个不同的关键字按照随机次序单纯使用插入操作建树;
一个有n个不同关键字的随机构建二叉搜索树的期望高度为
log(n)
双亲的意义:
用来寻找前驱和后继;
和堆&优先队列的区别
堆是左右子树的根节点都会比双亲节点大,并且优先队列插入和删除的操作是不断交换的过程