zoukankan      html  css  js  c++  java
  • 二叉查找树

    二叉查找树

    定义

    给定一棵二叉树,树上的每个节点带有一个数值,称为节点的关键码。所谓“BST性质”是指,对于树中的任意一个节点:

    • 该节点的关键码不小于它的左子树中任意节点的关键码;
    • 该节点的关键码不大于它的右子树中任意节点的关键码。

    满足上述性质的二叉树就是一棵二叉查找树(BST)。显然,二叉查找树的中序遍历是一个关键码单调递增的节点序列。

    建树

    为了避免越界,减少边界情况的特殊判断,我们一般在BST中额外插入一个关键码为正无穷和一个关键码为负无穷的节点。仅由这两个节点构成的BST就是一棵初始的空BST。

    struct BST {
    	int l,r,val;
    }T[N];
    
    int New(int val) {
    	T[++cnt].val=val;
    	return cnt;
    }
    
    void Build() {
    	New(-INF),New(INF);
    	rt=1;
    	T[rt].r=2;
    	return;
    }
    

    检索

    在BST中检索是否存在关键码为$val$的节点。
    设变量$o$等于根节点$rt$,执行以下过程:

    1. 若$o$的关键码等于$val$,则已经找到。
    2. 若$o$的关键码大于$val$
      (1) 若$o$的左子节点为空,则说明不存在$val$。
      (2) 若$o$的左子节点不为空,在$o$的左子树中递归进行检索。
    3. 若$o$的关键码小于$val$
      (1) 若$o$的右子节点为空,则说明不存在$val$。
      (2) 若$o$的右子节点不为空,在$o$的右子树中递归进行检索。
    int Search(int o,int val) {
    	if(o==0) {
    		return 0;
    	}
    	if(val==T[o].val) {
    		return o;
    	}
    	return val<T[o].val?Search(T[o].l,val):Search(T[o].r,val);
    }
    

    插入

    在BST中插入一个新的值$val$(假设目前BST中不存在关键码为$val$的节点)。
    与BST的检索过程类似。
    在发现要走向的$o$的子节点为空,说明$val$不存在时,直接建立关键码为$val$的新节点作为$o$的子节点。

    void Insert(int &o,int val) {
    	if(o==0) {
    		o=New(val);
    		return;
    	}
    	if(val==T[o].val) {
    		return;
    	}
    	if(val<T[o].val) {
    		Insert(T[o].l,val);
    	}
    	else {
    		Insert(T[o].r,val);
    	}
    }
    

    求前驱&后继

    • 前驱:在BST中关键码小于$val$的前提下,关键码最大的节点。
    • 后继:在BST中关键码大于$val$的前提下,关键码最小的节点。

    后继为例。
    初始化$ans$为具有正无穷关键码的那个节点的编号。然后,在BST中检索$val$。在检索过程中,每经过一个节点,都检查该节点的关键码,判断能否更新所求的后继$ans$。
    检索完成后,有三种可能的结果:

    1. 没有找到$val$。
      此时$val$的后继就在已经经过的节点中,$ans$即为所求。
    2. 找到了关键码为$val$的节点$o$,但$o$没有右子树。
      与上一种情况相同,$ans$即为所求。
    3. 找到了关键码为$val$的节点$o$,且$o$有右子树。
      从$o$的右子节点出发,一直向左走,就找到了$val$的后继。

    前驱同理。

    int Pre(int val) { //求前驱
    	int ans=1,o=rt; //T[1].val==-INF
    	while(o) {
    		if(val==T[o].val) { //检索成功
    			if(T[o].l>0) { //有左子树
    				o=T[o].l;
    				while(T[o].r>0) { //左子树上一直往右走
    					o=T[o].r;
    				}
    				ans=o;
    			}
    			break;
    		}
    		//每经过一个节点,都尝试更新前驱
    		if(T[o].val<val&&T[o].val>T[ans].val) {
    			ans=o;
    		}
    		o=val<T[o].val?T[o].l:T[o].r;
    	}
    	return ans;
    }
    
    int Suc(int val) { //求后继
    	int ans=2,o=rt; //T[2].val==INF
    	while(o) {
    		if(val==T[o].val) { //检索成功
    			if(T[o].r>0) { //有右子树
    				o=T[o].r;
    				while(T[o].l>0) { //右子树上一直往左走
    					o=T[o].l;
    				}
    				ans=o;
    			}
    			break;
    		}
    		//每经过一个节点,都尝试更新后继
    		if(T[o].val>val&&T[o].val<T[ans].val) {
    			ans=o;
    		}
    		o=val<T[o].val?T[o].l:T[o].r;
    	}
    	return ans;
    }
    

    删除

    从BST中删除关键码为$val$的节点。
    首先,在BST中检索$val$,得到节点$o$。
    若$o$的子节点个数小于$2$,则直接删除$o$,并令$o$的子节点代替$o$的位置,与$o$的父节点相连。
    若$o$既有左子树又有右子树,则在BST中求出$val$的后继节点$nxt$。因为$nxt$没有左子树,所以可以直接删除$nxt$,并令$nxt$的右子树代替$nxt$的位置。最后,再让$nxt$节点代替$o$节点,删除$o$即可。
    这里需要注意的是,在找$val$的后继时,不必调用前面的$Suc$函数,因为该节点的性质可以直接在右子树中往左跳,复杂度较$Suc$函数更小。

    void Remove(int &o,int val) {
    	if(o==0) {
    		return;
    	}
    	if(val==T[o].val) { //已经检索到值为val的节点
    		if(T[o].l==0) { //没有左子树
    			o=T[o].r; //右子树代替o的位置,注意o是引用
    		}
    		else if(T[o].r==0) { //没有右子树
    			o=T[o].l; //左子树代替o的位置,注意o是引用
    		}
    		else { //既有左子树又有右子树
    			//求后继节点
    			int suc=T[o].r;
    			while(T[suc].l>0) {
    				suc=T[suc].l;
    			}
    			//节点suc一定没有左子树,直接删除
    			Remove(T[o].r,T[suc].val);
    			//令节点suc代替节点o的位置
    			T[suc].l=T[o].l,T[suc].r=T[o].r;
    			o=suc; //注意o是引用
    		}
    		return;
    	}
    	if(val<T[o].val) {
    		Remove(T[o].l,val);
    	}
    	else {
    		Remove(T[o].r,val);
    	}
    }
    

    板子

    板子我是用$namespace$写的,放在这方便以后用。

    #define N 100010
    #define INF 2147483647
    
    int cnt,rt;
    
    struct BST {
    	int l,r,val;
    }T[N];
    
    namespace Binary_Search_Tree {
    	int New(int val) { //新建节点,这里用int类型是为了方便记录序号
    		T[++cnt].val=val;
    		return cnt;
    	}
    	
    	void Build() {
    		New(-INF),New(INF);
    		rt=1;
    		T[rt].r=2;
    		return;
    	}
    	
    	int Search(int o,int val) { //检索
    		if(o==0) {
    			return 0; //检索失败
    		}
    		if(val==T[o].val) {
    			return o;
    		}
    		return val<T[o].val?Search(T[o].l,val):Search(T[o].r,val);
    	}
    	
    	void Insert(int &o,int val) { //插入
    		if(o==0) { //子节点为空
    			o=New(val);
    			return;
    		}
    		if(val==T[o].val) {
    			return;
    		}
    		if(val<T[o].val) {
    			Insert(T[o].l,val);
    		}
    		else {
    			Insert(T[o].r,val);
    		}
    	}
    	
    	int Pre(int val) { //求前驱
    		int ans=1,o=rt; //T[1].val==-INF
    		while(o) {
    			if(val==T[o].val) { //检索成功
    				if(T[o].l>0) { //有左子树
    					o=T[o].l;
    					while(T[o].r>0) { //左子树上一直往右走
    						o=T[o].r;
    					}
    					ans=o;
    				}
    				break;
    			}
    			//每经过一个节点,都尝试更新前驱
    			if(T[o].val<val&&T[o].val>T[ans].val) {
    				ans=o;
    			}
    			o=val<T[o].val?T[o].l:T[o].r;
    		}
    		return ans;
    	}
    	
    	int Suc(int val) { //求后继
    		int ans=2,o=rt; //T[2].val==INF
    		while(o) {
    			if(val==T[o].val) { //检索成功
    				if(T[o].r>0) { //有右子树
    					o=T[o].r;
    					while(T[o].l>0) { //右子树上一直往左走
    						o=T[o].l;
    					}
    					ans=o;
    				}
    				break;
    			}
    			//每经过一个节点,都尝试更新后继
    			if(T[o].val>val&&T[o].val<T[ans].val) {
    				ans=o;
    			}
    			o=val<T[o].val?T[o].l:T[o].r;
    		}
    		return ans;
    	}
    	
    	void Remove(int &o,int val) { //删除
    		if(o==0) {
    			return;
    		}
    		if(val==T[o].val) { //已经检索到值为val的节点
    			if(T[o].l==0) { //没有左子树
    				o=T[o].r; //右子树代替o的位置,注意o是引用
    			}
    			else if(T[o].r==0) { //没有右子树
    				o=T[o].l; //左子树代替o的位置,注意o是引用
    			}
    			else { //既有左子树又有右子树
    				//求后继节点
    				int suc=T[o].r;
    				while(T[suc].l>0) {
    					suc=T[suc].l;
    				}
    				//节点suc一定没有左子树,直接删除
    				Remove(T[o].r,T[suc].val);
    				//令节点suc代替节点o的位置
    				T[suc].l=T[o].l,T[suc].r=T[o].r;
    				o=suc; //注意o是引用
    			}
    			return;
    		}
    		if(val<T[o].val) {
    			Remove(T[o].l,val);
    		}
    		else {
    			Remove(T[o].r,val);
    		}
    	}
    }
    
  • 相关阅读:
    猫眼电影面试经历
    北京市-钟鼓楼
    vipkid 面试经历
    转转面试经历
    二维数组中的查找
    不用除法来实现两个正整数的除法
    牛客网面试经历
    9. Palindrome Number
    Spring 简介
    mysql8 安装配置教程
  • 原文地址:https://www.cnblogs.com/luoshui-tianyi/p/13474897.html
Copyright © 2011-2022 走看看