二叉排序树 (Binary Sort Tree)
定义: 二叉排序树又称“二叉查找树”、“二叉搜索树”。
二叉排序树:或者是一棵空树,或者是具有下列性质的二叉树:
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树。
数据结构: 二叉树一般采用二叉链表实现。
1. 数据结构
//define data type
typedef int keyType;
//define the data structure
typedef struct BinaryTreeNode
{
keyType key;
BinaryTreeNode * lchild;
BinaryTreeNode * rchild;
} BinaryNode,*BinaryTree;
2. 插入
/** insert a Node into BinaryTree
* @params tree: The BinaryTree
* @params key: the Key of the inserting Node.
* @return 1: succeed, 2: fail
**/
int insert(BinaryTree &tree, keyType key)
{
//当不存在树,直接插入这个新结点
if(tree == NULL) {
tree = (BinaryNode *) malloc(sizeof(BinaryNode));
tree->key = key;
return 1;
}
int found = 0;
BinaryNode* curr = tree;
BinaryNode* prev = NULL;
//从根开始,与key比较,大往右子树,小往左子树,找到的话说明存在结点,不需要插入
while (curr != NULL) {
if (key == curr->key) {
found = 1;
break;
}
prev = curr;
curr = (key > curr->key)? curr->rchild: curr->lchild;
}
//找不到的时候在最后查找停下来的位置插入新结点
if(!found && curr == NULL) {
curr = (BinaryNode*) malloc(sizeof(BinaryNode));
curr->key = key;
((key > prev->key) ? prev->rchild:prev->lchild) = curr;
return 1;
}
return 0;
}
3. 查找
//查找,成功返回node, 失败返回NULL
BinaryNode* search(BinaryTree tree, keyType key)
{
BinaryNode* node = tree;
int found = 0;
while(node != NULL) {
if(key == node->key) {
found = 1;
break;
}
node = (key > node->key) ? node->rchild : node->lchild;
}
return found ? node : NULL;
}
3. 前序,中序,后序遍历
前序,中序,后序可以理解成是将root作为前序输出,中序输出,还是后序输出。
//前序遍历输出, 前序是指 根 最先输出
void pre_print(BinaryTree tree)
{
if(tree!=NULL) {
cout << tree->key << " ";
pre_print(tree->lchild);
pre_print(tree->rchild);
}
}
//后序遍历输出, 后序是指 根 最后输出
void after_print(BinaryTree tree)
{
if(tree!=NULL) {
after_print(tree->lchild);
after_print(tree->rchild);
cout << tree->key << " ";
}
}
//中序遍历输出, 中序是指 根 在中间输出
void inorder_print(BinaryTree tree)
{
if(tree!=NULL) {
inorder_print(tree->lchild);
cout << tree->key << " ";
inorder_print(tree->rchild);
}
}
4. 删除
删除是整个二叉搜索树编码难度最高的地方,需要考虑到各种情况。这里借用算法导论中的图,这张图非常清晰的列出所有可能性。
下面代码中3,4 采用要删除结点的左子树结点
int delete_key(BinaryTree tree, keyType key) {
//设定一个临时的树根,他的key假设为无穷大,防止出现root被删除的情况。
BinaryTree head = (BinaryNode *)malloc(sizeof(BinaryNode));
head->key = INT_MAX;
head->lchild = tree;
//定位要删除的点
BinaryNode *curr = tree, *prev = head;
BinaryNode *t1 = NULL, *t2 = NULL;
int found = 0;
while(curr != NULL) {
if(key == curr->key) {
found = 1;
break;
}
prev = curr;
curr = ((key > curr->key) ? curr->rchild : curr->lchild);
}
if(found) {
//前两种情况只需要把要删除的点的左或者右结点给prev的结点接管就行。
if(curr->lchild == NULL) { //左子树为空,包括左右子树都为空的情况
((curr == prev->lchild) ? prev->lchild : prev->rchild) = curr -> rchild;
free(curr);
} else if (curr->rchild == NULL) //右子树为空
{
((curr == prev->lchild) ? prev->lchild : prev->rchild) = curr -> lchild;
free(curr);
} else { //左右子树都不为空
t1 = curr -> lchild;
while(t1 -> rchild != NULL) { //循环找到左子树的右子树的右子树。。。一直下去,找到那个数来替换curr
t2 = t1;
t1 = t1 -> rchild;
}
curr -> key = t1 -> key;
((t2 == NULL) ? curr -> lchild : t2 -> rchild) = t1 -> lchild;//之前定义t2为NULL,如果t2为NULL,代表curr的左子树的右子树是空的。
free(t1);
}
}
//还原
tree = head->lchild;
free(head);
return found;
}
先序遍历;中序遍历;后续遍历;层次遍历。事实上,知道任意两种方式,并不能唯一地确定树的结构,但是,只要知道中序遍历和另外任意一种遍历方式,就一定可以唯一地确定一棵树。
5. 用非递归的方法实现前序,中序,后序遍历
前序
前序的实现是最容易的,采用一个栈,先压入根,然后进入循环。先输出,再压入右子树,再压人左子树。
//非递归方法 前序
/**
* 按先输出根节点,再压人右子树,再压入左子树
**/
void PreOrder(BinaryTree tree)
{
stack<BinaryTreeNode*> s;
s.push(tree);
BinaryTreeNode *curr = tree;
while(!s.empty()) {
curr = s.top();
s.pop(); //因为c++ 的stack.pop()不返回值,只是删除栈顶元素。所以使用top(先返回值)
cout << curr->key << " ";
if(curr->rchild != NULL) {
s.push(curr->rchild);
}
if(curr->lchild != NULL) {
s.push(curr->lchild);
}
}
}
中序
中序的思路也容易一些,往左子树压到底,然后开始弹出打印,并试图进入右子树,没有就继续弹,有就继续深入右儿子的左子树。
void InOrder(BinaryTree tree) {
stack<BinaryTree> s;
BinaryTreeNode * curr = tree;
while(!s.empty() || curr!=NULL) {
if(curr != NULL) {
s.push(curr);
curr = curr->lchild;
}else{
curr = s.top();
s.pop();
cout << curr->key << " ";
curr = curr->rchild;
}
}
}
后序
后序的实现相对困难,先看两个栈的实现方法
用s2来保存最后的顺序,循环,每次把s1栈顶的元素压入s2,再压入s1不为空的左子树和右子树。
void PosOrder(BinaryTree tree)
{
stack<BinaryTreeNode*> s1;
stack<BinaryTreeNode*> s2;
s1.push(tree);
BinaryTreeNode *curr;
while(!s1.empty()) {
curr = s1.top();
s1.pop();
s2.push(curr);
if(curr->rchild != NULL) {
s1.push(curr->rchild);
}
if(curr->lchild != NULL) {
s1.push(curr->lchild);
}
}
while(!s2.empty()) {
cout << s2.top()->key << " ";
s2.pop();
}
}
用一个栈也可以实现:
用h来表示上一个打印的结点。循环分3种情况
1. 左儿子不为空,并且左儿子不是上一个打印的结点,右儿子也不是上一个打印的结点。这时候就继续压入左儿子。
2. 如果左儿子是上一个被打印的结点,然后右儿子不为空,那么说明左子树已经打印完,那么深入右子树
3. 如果右儿子是上一个被打印的结点,或者该树左右儿子都是空,那么直接打印这个结点,然后赋值h
void PosOrderOneStack(BinaryTree tree)
{
stack<BinaryTreeNode*> s;
s.push(tree);
BinaryTreeNode* h = tree;
BinaryTreeNode* c;
while(!s.empty()) {
c = s.top();
if(c->lchild != NULL && h != c->lchild && h != c->rchild) {
s.push(c->lchild);
}else if(c->rchild != NULL && h!= c->rchild) {
s.push(c->rchild);
}else{
cout << s.top()->key << " ";
s.pop();
h = c;
}
}
}