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

来源:互联网 发布:侠义精神 知乎 编辑:程序博客网 时间:2024/05/22 00:38

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

  二叉排序树分为静态查找(find)和动态查找(insert、delete)
  二叉搜索树:一棵二叉树,可以为空;如果不为空,满足下列性质:
    1.非空左子树的所有键值小于其根结点的键值。
    2.非空右子树的所有键值大于其根结点的键值
    3.左右子树都是二叉搜索树!!

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) + 1public 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());// 查找左子树elsereturn 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();elsereturn 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();// 在判断左子树是否为空elsereturn 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;}}

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


1 0
原创粉丝点击