漫谈二叉搜索树的基本算法(三种思路实现查询操作)

  前面我们说了二叉树前序中序后序遍历的递归非递归算法的实现,下面我们再来说说二叉搜索树~  

  二叉排序树分为静态查找(find)和动态查找(insert、delete)
  二叉搜索树:一棵二叉树,可以为空;如果不为空,满足下列性质:
    1.非空左子树的所有键值小于其根结点的键值。
    2.非空右子树的所有键值大于其根结点的键值
    3.左右子树都是二叉搜索树!!
漫谈二叉搜索树的基本算法(三种思路实现查询操作)_第1张图片
2.以上是二叉搜索树(也叫二叉排序树)的一些基本操作,此处我们先说一下二叉树的结点定义··
代码中判断当前结点位置情况的辅助方法以及简单的 get 方法都在常数时间内可以完成,实现也相应非常简单。下面主要讨论 updateHeight ()、 updateSize ()、 sever()setLChild(lc)、 getRChild(rc)的实现与时间复杂度
    1)
    // 更新当前结点及其祖先的高度
public void updateHeight() {
    int newH = 0;// 新高度初始化为 0,高度等于左右子树高度加 1 中的大者
    if (hasLChild())
        newH = Math.max(newH, 1 + getLChild().getHeight());//返回两者中较大的一个
    if (hasRChild())
        newH = Math.max(newH, 1 + getRChild().getHeight());
    if (newH == height)
        return; // 高度没有发生变化则直接返回
    height = newH; // 否则更新高度
    if (hasParent())
        getParent().updateHeight(); // 递归更新祖先的高度
}
updateHeight ():若当前结点 的孩子发生变化,就需要使用 updateHeight ()方法更新当前结点及其祖先结点的高度。请注意,由于一个结点的高度发生变化,会影响到其祖先结点的高度,在这里我们允许直接对任何结点执行这一操作。因为在二叉树中任何一个结点的高度,都等于其左右子树的高度中大者加 1,而左右子树的高度只需要获取该结点左右孩子的高度,只需要Θ (1)时间。继而从 出发沿parent 引用逆行向上,依次更新各祖先结点的高度即可。如果在上述过程中,发现某个结点的高度没有发生变化,算法可以直接终止。综上所述,当对一个结点 调用 updateHeight ()方法时,若 的层数为 level(v),则最多只需要更新 level(v)+1 个结点的高度,因此算法的时间复杂度 T(n) = Ο (level(v))
    2) 
    // 更新当前结点及其祖先的子孙数
public void updateSize() {
    size = 1; // 初始化为 1,结点本身
    if (hasLChild())
        size += getLChild().getSize(); // 加上左子树规模
    if (hasRChild())
        size += getRChild().getSize(); // 加上右子树规模
    if (hasParent())
        getParent().updateSize(); // 递归更新祖先的规模
}
updateSize ():同样如果结点 的孩子发生变化,应该更新当前结点以及其祖先的规模。因为在二叉树中任何一个结点的规模,都等于其左右子树的规模之和加上结点自身,而左右子树的规模只需要获取该结点左右孩子的规模即可获得,只需要Θ (1)时间。因此算法的时间复杂度 T(n) = Ο (level(v))
    3)
    // 断开与父亲的关系
public void sever() {
    if (!hasParent())
        return;
    if (isLChild())
        parent.lChild = null;
    else
        parent.rChild = null;
    parent.updateHeight(); // 更新父结点及其祖先高度
    parent.updateSize(); // 更新父结点及其祖先规模 
    parent = null;
 } 
sever():切断结点 与父结点 之间的关系。该算法需要修改 与 的指针域,需要常数时间。除此之外由于 结点的孩子发生了变化,因此需要调用 updateHeight ()updateSize ()来更新父结点 及其祖先的高度与规模。其时间复杂度 T(n) = Ο (level(v))
    4)
    // 设置当前结点的左孩子,返回原左孩子
public BinTreeNode setLChild(BinTreeNode lc) {
    BinTreeNode oldLC = this.lChild;
    if (hasLChild()) {
        lChild.sever();
    } // 断开当前左孩子与结点的关系
    if (lc != null) {
        lc.sever(); // 断开 lc 与其父结点的关系
        this.lChild = lc; // 确定父子关系
        lc.parent = this;
        this.updateHeight(); // 更新当前结点及其祖先高度
        this.updateSize(); // 更新当前结点及其祖先规模
    }
    return oldLC; // 返回原左孩子
}

    // 取右孩子
public BinTreeNode getRChild() {
    return rChild;
}

    // 设置当前结点的右孩子,返回原右孩子
public BinTreeNode setRChild(BinTreeNode rc) {
    BinTreeNode oldRC = this.rChild;
    if (hasRChild()) {
        rChild.sever();
    } // 断开当前右孩子与结点的关系
    if (rc != null) {
        rc.sever(); // 断开 lc 与其父结点的关系
        this.rChild = rc; // 确定父子关系
        rc.parent = this;
        this.updateHeight(); // 更新当前结点及其祖先高度
        this.updateSize(); // 更新当前结点及其祖先规模
    }
    return oldRC; // 返回原右孩子
}
setLChild(lc) getRChild(rc):两个算法的功能相对,一个是设置结点 v 的左孩子,一个是设置结点 v 的右孩子。两个算法的实现是类似的,以 setLChild()为例说明。首先,如 v 有左孩子 oldLC,则应当调用 oldLC. sever()断开 v 与其左孩子的关系。 其次, 调用 lc. sever()断开其与父结点的关系。最后,建立 v  lc 之间的父子关系,并调用 v. updateSize () v.updateHeight ()更新 v 及其祖先的规模与高度。
下面是用代码实现的树高度和查询树叶子结点的操作。
public class BinSearchTreeTest01 {
	public Point root;
	//访问结点
	public static void visitKey(Point p){
		System.out.println(p.getKey() + "  ");
	}
	//构造树
	public static Point Tree(){
		Point a = new Point('A', null, null);
		Point b = new Point('B', null, a);
		Point c = new Point('C', null, null);
		Point d = new Point('D', b, c);
		Point e = new Point('E', null, null);
		Point f = new Point('F', e, null);
		Point g = new Point('G', null, f);
		Point h = new Point('H', d, g);
		return h;// root
	}
	
	//求二叉树的高度 height = max(HL , HR) + 1
	public static int height(Point p){
		int HL , HR , MaxH;
		if(p != null){
			HL = height(p.getLeftChild());
			HR = height(p.getRightChild());
			MaxH = Math.max(HL, HR);
			return MaxH + 1;
		}
		return 0;
	}
	//求二叉树的叶子结点
	public static void OrderLeaves(Point p){
		if(p != null){
			if(p.getLeftChild() == null && p.getRightChild() == null)
				visitKey(p);
			OrderLeaves(p.getLeftChild());
			OrderLeaves(p.getRightChild());
		}
	}
	public static void main(String[] args) {
		System.out.println("二叉树的高度为:" + height(Tree()));
		System.out.print("二叉树的叶子结点为:");
		OrderLeaves(Tree());
	}
}

class Point{
	private char key;
	private Point LeftChild , RightChild;
	public Point(char key , Point LeftChild , Point RightChild){
		this.key = key;
		this.LeftChild = LeftChild;
		this.RightChild = RightChild;
	}
	public Point(char key){
		this(key , null ,null);
	}
	public char getKey() {
		return key;
	}
	public void setKey(char key) {
		this.key = key;
	}
	public Point getLeftChild() {
		return LeftChild;
	}
	public void setLeftChild(Point leftChild) {
		LeftChild = leftChild;
	}
	public Point getRightChild() {
		return RightChild;
	}
	public void setRightChild(Point rightChild) {
		RightChild = rightChild;
	}	
}
3.下面开始今天的正题,二叉搜索树的查询,插入和删除操作
  1)Find
    ·查找从根结点开始,如果树为空,返回NULL
    ·若搜索树非空,则根结点关键字和X进行比较,并进行不同处理:
      1)若X小于根结点键值,只需要在左子树进行搜索;
      2)若X大于根结点键值,在右子树进行搜索;
      3)若两者比较结果相同,搜索完成,返回此结点。
import java.util.Scanner;

/**
 * 二叉搜索树的操作集锦
 * 
 * @author Administrator
 *
 */
class BinSearchTreeTest02 {
	public Point1 root;

	// 访问结点
	public static void visitKey(Point1 p) {
		System.out.println(p.getKey() + "  ");
	}

	// 构造树
	public static Point1 Tree() {
		Point1 m = new Point1(4, null, null);
		Point1 a = new Point1(6, m, null);
		Point1 b = new Point1(5, null, a);
		Point1 c = new Point1(14, null, null);
		Point1 d = new Point1(10, b, c);
		Point1 e = new Point1(24, null, null);
		Point1 f = new Point1(25, e, null);
		Point1 g = new Point1(20, null, f);
		Point1 h = new Point1(15, d, g);
		return h;// root
	}

	// 构造空树
	public static Point1 EmptyTree(int t) {
		Point1 a = new Point1(t, null, null);
		return a;
	}

	// *********************************查找操作****************************
	/**
	 * 二叉搜索树查找操作方法1
	 * 
	 * @param t
	 * @param node
	 * @return
	 */
	public static boolean contains(int t, Point1 node) {
		if (node == null)
			return false;// 结点为空,查找失败
		String st = Integer.toString(t);// 把要查找的结点转化为String类型
		int result = compareTo(st, node);
		if (result == 1) {
			return true;
		} else {
			return false;
		}
	}

	public static int compareTo(String a, Point1 p) {
		// String b = Integer.toString(p.getKey());
		// if(a.equals(b))和下面这行是等价的
		if (a.equals(p.getKey() + ""))
			return 1;
		int i = 0, j = 0;
		if (p.getLeftChild() != null) {
			i = compareTo(a, p.getLeftChild());
		}
		if (p.getRightChild() != null) {
			j = compareTo(a, p.getRightChild());
		}
		if (i == 1) {
			return i;
		} else if (j == 1) {
			return j;
		} else {
			return -1;
		}
	}

	/**
	 * 二叉搜索树查找操作方法2(递归算法) 尾递归的方式
	 * 
	 * @param t
	 * @param node
	 * @return
	 */
	public static Point1 contains2(int t, Point1 node) {
		if (node == null)
			return null;// 结点为空,查找失败
		if (t > node.getKey())
			return contains2(t, node.getRightChild());// 查找右子树
		else if (t < node.getKey())
			return contains2(t, node.getLeftChild());// 查找左子树
		else
			return node;
	}

	/**
	 * 二叉搜索树查找的搜索方法2的变形,非递归算法的效率更高 将尾递归改为迭代函数
	 * 
	 * @param args
	 */
	public static Point1 contains3(int t, Point1 node) {
		while (node != null) {
			if (t > node.getKey())
				node = node.getRightChild();
			else if (t < node.getKey())
				node = node.getLeftChild();
			else
				return node;
		}
		return null;
	}

	/**
	 * 二叉搜索树的最大元素 根据其性质可以得出二叉搜索树的最大元一定位于右子树的最右端,最小元则相反
	 * 
	 * @param args
	 */
	public static int findMax(Point1 point) {
		if (point != null) {
			while (point.getRightChild() != null) {
				point = point.getRightChild();
			}
		}
		return point.getKey();
	}

	public static int findMin(Point1 point) {
		if (point == null)
			return 0;// 先判断树是否为空
		else if (point.getLeftChild() == null)
			return point.getKey();// 在判断左子树是否为空
		else
			return findMin(point.getLeftChild());
	}

	public static void main(String[] args, Point1 p) {
		System.out.println("此二叉树的最大结点为:" + findMax(Tree()));
		System.out.println("此二叉树的最小结点为:" + findMin(Tree()));
		@SuppressWarnings("resource")
		Scanner scanner = new Scanner(System.in);
		System.out.println("输入一个结点,将判断是否是此二叉树的结点");
		int a = scanner.nextInt();
		if (contains2(a, Tree()) != null) 
			System.out.println(a + " 是此二叉树的结点"); 
		else 
			System.out.println(a + " 不是此二叉树的结点");	 
	}
}

class Point1 {
	private int key;
	private Point1 LeftChild, RightChild;

	public Point1(int key, Point1 LeftChild, Point1 RightChild) {
		this.key = key;
		this.LeftChild = LeftChild;
		this.RightChild = RightChild;
	}

	public Point1(int key) {
		this(key, null, null);
	}

	public int getKey() {
		return key;
	}

	public void setKey(char key) {
		this.key = key;
	}

	public Point1 getLeftChild() {
		return LeftChild;
	}

	public void setLeftChild(Point1 leftChild) {
		LeftChild = leftChild;
	}

	public Point1 getRightChild() {
		return RightChild;
	}

	public void setRightChild(Point1 rightChild) {
		RightChild = rightChild;
	}
}

(给出了三种不同的查找的思路,不过确实要比另外两种好实现一些,希望可以有所借鉴,插入和删除晚些时候放上)


你可能感兴趣的:(漫谈二叉搜索树的基本算法(三种思路实现查询操作))