搜索二叉树,BST, 是二叉树的一种。特点是,
(1).左孩子关键字比根小,右孩子关键字比根大。
(2).根的左孩子和右孩子,如果不为空,也都是搜索二叉树。
( 我一般这么想的,如有错误,敬请指正。)
左孩子比根小,右孩子比根大,好处就是很方便查找,思想就是二分,和折半查找(二分)一样。查找是计算机中十分重要的一个操作,在海量的数据面前,如果一个一个遍历就太麻烦了,但是利用二叉树可以降低查找的复杂度,1000000的数据,储存在完美的二叉树中,也就20层的样子,大大降低了查找的复杂度。
而且搜索二叉树的中序遍历就可以得到一个有序的序列,因为一直保持着 左孩子 < 根 < 右孩子 ,按照这个顺序递归。
下面来看一道搜索二叉树的例子:
1. insert a key k to a empty tree, then the tree become a tree with
only one node;
2. insert a key k to a nonempty tree, if k is less than the root ,insert
it to the left sub-tree;else insert k to the right sub-tree.
We call the order of keys we insert “the order of a tree”,your task is,given a oder of a tree, find the order of a tree with the least lexicographic order that generate the same tree.Two trees are the same if and only if they have the same shape.
4 1 3 4 2
1 3 2 4
这道题目,给出建立一棵搜索二叉树的序列,要求出建立这个二叉树的,按照字典序的最小序列。我们在建立搜索二叉树的时候,左右孩子的插入顺序是不确定的,可以先插左子树,也可以先插右子树,这就导致了序列不一定按照字典序。如果要按照字典序,得到一模一样的二叉树,就需要先插入左子树,再插入右子树,因为左子树一定小于右子树。这就是二叉树的先序遍历,回顾一下二叉树的基础。
#include <iostream>
#include <cstdio>
using namespace std ;
struct Node{
int weight ; // 关键字,很多时候是权重
Node *lchild , *rchild ;
Node ( int _weight ){
weight = _weight ;
lchild = rchild = NULL ;
}
void Destroy( Node *&root ){ // 每次重建一棵二叉树,释放原来的内存。
if( ! root ) return ;
if( root->lchild ) Destroy( root->lchild ) ;
if( root->rchild ) Destroy( root->rchild ) ;
delete root ; root = NULL ;
}
} *root , *stack[100005] ;
void insert ( Node *&root , int weight ){ // 建立搜索二叉树
if( !root ) { root = new Node( weight ) ; return ; }
if( weight < root->weight ) insert( root->lchild , weight ) ;
else insert( root->rchild , weight ) ;
}
void Display( Node *cur ){ // 先序遍历的递归形式
if( !cur ) return ;
printf( cur == root ? "%d" : " %d" , cur->weight ) ; // 这里注意根的前面,没有空格,否则会 Presentation Error
if( cur->lchild ) Display( cur->lchild ) ;
if( cur->rchild ) Display( cur->rchild ) ;
}
int main(){
int n , weight , i ;
root = NULL ;
while( ~scanf( "%d" , &n ) ){
for( i = 1 ; i <= n ; ++i )
scanf( "%d" , &weight ) , insert( root , weight ) ;
Display( root ) ; // 打印先序序列
cout << endl ;
root->Destroy( root ) ;
}
root = NULL ;
return 0 ;
}
在这里也回顾一下,二叉树先序遍历的非递归形式:
利用一个栈,从根节点往左子树一路打印左子树,最左边的访问完了,就检查最左边的节点是否有右孩子,有右孩子就访问右孩子;访问结束之后,pop , 再检查当前栈顶节点(之前被压栈的左子树)的右子树;重复 pop , 直到栈为空 而且 二叉树访问完毕。
void Pre_visit( Node *root ){
Node *ptr = root ;
while( top || ptr ){ // 之前压栈的节点的右子树尚未访问 ;以当前访问的节点的树还没访问完
while( ptr ){
stack[top++] = ptr ; // stack 是一个 Node* 类型的数组,做栈,以下都是
printf( ptr == root ? "%d" : " %d" , ptr->weight ) ;
ptr = ptr->lchild ;
}
if( top ) ptr = stack[--top] , ptr = ptr->rchild ; // 当前栈未空,之前压栈的左子树的右孩子还没访问
}
printf( "\n" ) ;
ptr = NULL ;
}
二叉树中序遍历的非递归形式:
上面的先序遍历是遇到左子树就打印,一路到底,因为先序遍历的访问顺序是 根 -> 左子树 -> 右子树 ,从根节点开始,每一个压栈的节点所 “掌管” 的树都是以这个节点为根,之前已经打印过了,左子树也刚访问完,再检验右子树是否可以访问;
中序遍历的顺序是 左子树 -> 根 -> 右子树,之前压栈的节点都是某些 "树" 的根节点,不能一开始就访问,要先走到最左边的节点,然后逐渐 pop , 再访问原来压栈的众多“根节点” , 然后和先序遍历一样,检验右子树是否访问过了。
void Mid_visit( Node *root ){
Node *ptr = root ;
while( top || ptr ){
while( ptr ) stack[top++] = ptr , ptr = ptr->lchild ;
if( top ){
ptr = stack[--top] ;
printf( ptr == root ? "%d" : " %d" , ptr->weight ) ;
ptr = ptr->rchild ;
}
}
printf( "\n" ) ;
ptr = NULL ;
}
二叉树后序遍历的非递归形式:
后序遍历是 左孩子 -> 右孩子 -> 根。也就是在访问之前压栈的“根节点”之前要检验它的左孩子和右孩子是否都访问过了。和中序遍历一样的是,先访问到最左边,然后逐渐 pop , 就能保证最先访问左子树,然后 pop 的过程中,当前的栈顶元素就是“根” , 要先检验它的右子树是否之前访问过了。这里我用一个 *Pre 指针保存上一次访问的节点。检验当前节点的右子树,有两种情况:
1. 没有右子树,就是右子树为空。
2. 右子树在之前访问过了,就可以访问当前节点了。
void Post_visit( Node *root ){
Node *ptr = root , *Pre = NULL , *cur = NULL ;
while( top || ptr ){
while( ptr ) stack[top++] = ptr , ptr = ptr->lchild ; // 一路保存左边的节点
if( top ){
cur = stack[top-1] ; // 这里要先取栈顶,也就是当前元素 , top-1 是因为栈的节点从 0 开始存储的
if( cur->rchild == NULL || cur->rchild == Pre ){ // 右子树为空 ;右子树刚才访问过
printf( cur == root ? "%d" : "%d " , cur->weight ) ; // 才可以访问当前结点
Pre = cur ; // 更新 Pre
top-- ; // 当前结点访问过了,pop
}
else ptr = cur->rchild ; // 右子树存在而且没访问过 , 存储当前元素的指针 ptr 移向右孩子
}
}
printf( "\n" ) ;
ptr = Pre = cur = NULL ;
}
如有错误,敬请指正。