您的位置:首页 > 理论基础 > 数据结构算法

重写数据结构--二叉搜索树

2016-08-19 18:34 204 查看

性质:

根节点左边的节点关键字都比根节点的关键字小;根节点右边的节点关键字都比根节点的关键字大;

结构体:

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)

双亲的意义:

用来寻找前驱和后继;

和堆&优先队列的区别

堆是左右子树的根节点都会比双亲节点大,并且优先队列插入和删除的操作是不断交换的过程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  数据结构