查找与二叉树

查找与二叉树

我家园子有几棵树系列


  • 查找与二叉树

    • 我家园子有几棵树系列
    • Preface
    • 查找
    • 二叉查找树的实现
      • 定义数据结构
      • 中序遍历
      • 查找操作
      • 插入
      • 删除
        • 删除最小值
        • 复制(拷贝)删除
          • 步骤
      • Rank
    • 2-3查找树
    • 总结

Preface

前面我们学习了基于线性表的数据结构,如数组,链表,队列,栈等。现在我们要开始学习一种非线性的数据结构--树(tree),是不是很兴奋呢!让我们开始新的系列吧!

查找

先让我们回忆一下线性表的查找,首先最暴力的方法就是做一个线性扫描,一一对比是不是要找的值。这么做的时间复杂度显而易见的是 O(N),如表格第一行;更机智一点,我们采用二分法,首先将线性表排好顺序,然后每次对比中间的值就好了,这样做的时间复杂度就是 O(logN),如表格第二行。但上面的做法都是利用的线性数据结构,而它有致命的缺点;那就是进行动态的操作时,比如插入,删除;无法同时实现迅速的查找,只能等重新排序以后再查,效率就低了很多,无法满足日常需求(如下表)。这个时候我们的主角就闪亮登场了——二叉查找树

图源

表格

二叉查找树的实现

首先我放几张图说明一下什么是二叉树,树的高度,深度等等,详细的介绍我已经放在这里,有兴趣的话也可以看看别人的博客。

图源 转载学习,如侵权则联系我删除!

深度

废话不多说我们开始实现一颗二叉查找树(BST)吧!
[注] 为了方便理解大部分代码都提供了递归实现!

定义数据结构

public class BST<Key extends Comparable<Key>, Value> {
    private Node root;             // root of BST

    private class Node {
        private Key key;           // sorted by key
        private Value val;         // associated data
        private Node left, right;  // left and right subtrees
        private int size;          // number of nodes in subtree

        public Node(Key key, Value val, int size) {
            this.key = key;
            this.val = val;
            this.size = size;
        }
    }

    /**
     * Initializes an empty symbol table.
     */
    public BST() {}
    /**
     * Returns the number of key-value pairs in this symbol table.
     * @return the number of key-value pairs in this symbol table
     */
    public int size() {
        return size(root);
    }

    // return number of key-value pairs in BST rooted at x
    private int size(Node x) {
        if (x == null) return 0;
        else return x.size;
    }

}

中序遍历

我们知道二叉查找树的任一个节点,他的左子结点比他小,右子节点比他大,哈,那么我们只要进行一波中序遍历就可以完成数据的排序啦!

    /***************************************************************************
     * 中序遍历,非递归版本
     ***************************************************************************/
    public Iterable<Key> keys() {
        Stack<Node> stack = new Stack<Node>();
        Queue<Key> queue = new Queue<Key>();
        Node x = root;
        while (x != null || !stack.isEmpty()) {
            if (x != null) {
                stack.push(x);
                x = x.left;
            } else {
                x = stack.pop();
                queue.enqueue(x.key);
                x = x.right;
            }
        }
        return queue;
    }
    /************************************************************************
    * 中序遍历,递归打印
    ************************************************************************/
    public	void inOrder(Node* root) {
 		if (root == null) return;
  		inOrder(root.left);
  		print root // 此处为伪代码,表示打印 root 节点
  		inOrder(root.right);
}

查找操作

    /************************************************************************
    * 非递归
    ************************************************************************/
    Value get(Key key){
        Node x = root;
        while(x != null){
            int cmp =  key.compareTo(x.key);
            if(cmp<0)
                x = x.left;
            else if(cmp>0)
                x = x.right;
            else return
                x.value;
        }
        return null;
    }
    /************************************************************************
    * 递归
    ************************************************************************/
    public Value get(Key key) {
        return get(root, key);
    }

    private Value get(Node x, Key key) {
        if (x == null) return null;
        int cmp = key.compareTo(x.key);
        if      (cmp < 0) return get(x.left, key);
        else if (cmp > 0) return get(x.right, key);
        else              return x.val;
    }

插入

/************************************************************************
* 非递归
************************************************************************/
public void put(Key key, Value val) {
        Node z = new Node(key, val);
        if (root == null) {
            root = z;
            return;
        }

        Node parent = null, x = root;
        while (x != null) {
            parent = x;
            int cmp = key.compareTo(x.key);
            if (cmp < 0)
                x = x.left;
            else if (cmp > 0)
                x = x.right;
            else {
                x.val = val;
                return;
            }
        }
        int cmp = key.compareTo(parent.key);
        if (cmp < 0)
            parent.left = z;
        else
            parent.right = z;
    }
/************************************************************************
* 递归版本
************************************************************************/
public void put(Key key, Value value) {
        root = put(root, key, value);
    }

    private Node put(Node x, Key key, Value value) {
        if (x == null)
            return new Node(key, value, 1);
        int cmp = key.compareTo(x.key);
        if (cmp < 0)
            x.left = put(x.left, key, value);
        else if (cmp > 0)
            x.right = put(x.right, key, value);
        else
            x.value = value;
        x.size = 1 + size(x.left) + size(x.right);
        return x;
    }

删除

删除有两种方式,一种是合并删除,另一种是复制删除,这里我主要讲第二种,想了解第一种可以点这里

删除最小值

在正式的删除之前让我们先热身一下,看看怎么删除一棵树的最小值(如图)。

步骤

  • 我们先找到最小值,即不断查找节点的左子节点,若无左节点,那他就是最小值。
  • 找到最小的节点后,返回他的右子节点给上一层,最小节点会被GC机制回收
  • 因为用的是递归方法,所以依次更新节点数量

public void deleteMin(){
    root = deleteMin(root);
}

private Node deleteMin(Node x){
    if (x.left == null) return x.right;
    x.left = deleteMin(x.left);
    x.N = size(x.left) + size(x.right) + 1;
    return x;
}

复制(拷贝)删除

在说复制删除之前,我们需要先熟悉二叉查找树的前驱和后继(根据中序遍历衍生出来的概念)。

  • 前驱:A节点的前驱是其左子树中最右侧节点。
  • 后继:A节点的后继是其右子树中最左侧节点。

BSTcopyDelete

上图是复制删除的原理,我们既可以用前驱节点 14 代替,又可以用后继节点 18 代替。

步骤

BSTcDelete

如图所示,我们分为四个步骤

  1. 将指向即将被删除的节点的链接保存为t;
  2. 将 x 指向它的后继节点 min(t.right);
  3. 将 x 的右链接(原本指向一颗所有节点都大于 x.key 的二叉查找树) 指向deleteMin(t.right),也就是在删除后所有节点仍然都大于 x.key 的子二叉查找树。
  4. 将 x 的左链接(本为空) 设为 t.left
public void delete(Key key){
    root = delete(root,key);
}
private Node min(Node x){
    if(x.left == null) return x;
    else return min(x.left);
}
private Node delete(Node x, Key key){
    if(x==null) return null;
    int cmp = key.compareTo(x.key);
    if(cmp < 0) x.left = delete(x.left, key);
    else if(cmp > 0) x.right = delete(x.right, key);
    else{
        if(x.right == null) return x.left;
        if(x.left == null) return x.right;
        Node t = x;
        x = min(t.right);
        x.right = deleteMin(t.right);
        x.left = t.left;
    }
    x.N = size(x.left) + size(x.right) + 1;
    return x;
}

在前面的代码中,我们总是删除node中的后继结点,这样必然会降低右子树的高度,在前面中我们知道,我们也可以使用前驱结点来代替被删除的结点。所以我们可以交替的使用前驱和后继来代替被删除的结点。

J.Culberson从理论证实了使用非对称删除, IPL(内部路径长度)的期望值是 O(n√n), 平均查找时间为 O(√n),而使用对称删除, IPL的期望值为 O(nlgn),平均查找时间为 O(lgn)。

Rank

查找节点 x 的排名

public int rank(Key key){
    return rank(key, root);
}

private int rank(Key key, Node x){
    // 返回以 x 为根节点的子树中小于x.key的数量
    if(x == null) return 0;
    int cmp = key.compareTo(x.key);
    if(cmp<0) return rank(key,x.left);
    else if(cmp>0) return 1 + size(x.left) + rank(key,x.right);
    else return size(x.left);
}

2-3查找树

通过前面的分析我们知道,一般情况下二叉查找树的查找,插入,删除都是 O(lgn)的时间复杂度,但是二叉查找树的时间复杂度是和树的高度是密切相关的,如果我们以升序的元素进行二叉树的插入,我们会发现,此时的二叉树已经退化成链表了,查找的时间复杂度变成了 O(n),这在性能上是不可容忍的退化!那么我们该怎么解决这个问题呢?

答案相信大家都知道了,那就是构建一颗始终平衡的二叉查找树。那么有哪些平衡二叉查找树呢?如何实现?

这些我们留到下节再讲。

总结

这一节我们学会了使用非线性的数据结构--二叉查找树来高效的实现查找,插入,删除操作。分析了它的性能,在随机插入的情况下,二叉查找树的高度趋近于 2.99lgN ,平均查找时间复杂度为 1.39lgN(2lnN),而且在升序插入的情况下,树会退化成链表。这些知识为我们后面学习2-3查找树和红黑树打下了基础。

原文地址:https://www.cnblogs.com/jiaweixie/p/11344265.html

时间: 2024-10-12 20:46:07

查找与二叉树的相关文章

在路上---学习篇(一)Python 数据结构和算法 (5)二分查找、二叉树遍历

独白: 利用算法进行查找指定元素,最近学习二分查找和二叉树遍历.二分查找前提是在有序中进行查找,二叉树引入了树的概念.树的概念其中有许多小知识点,也是一种新的数据结构.还是之前的感悟,需了解其本质才会写出更好的算法. 二分查找 二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且插入删除困难.因此,折半查找方法适用于不经常变动而查找频繁的有序列表.首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功:否则利用

深入浅出数据结构C语言版(12)——从二分查找到二叉树

在很多有关数据结构和算法的书籍或文章中,作者往往是介绍完了什么是树后就直入主题的谈什么是二叉树balabala的.但我今天决定不按这个套路来.我个人觉得,一个东西或者说一种技术存在总该有一定的道理,不是能解决某个问题,就是能改善解决某个问题的效率.如果能够先了解到存在的问题以及已存在的解决办法的不足,那么学习新的知识就更容易接受,也更容易理解. 万幸的是,二叉树的讲解是可以按照上述顺序来进行的.那么,今天在我们讨论二叉树之前,我们先来讨论一种情形.一种操作:假设现在有一个数组,数组中的数据按照某

分治法-折半查找和二叉树的相关特性

4.3 折半查找 对于有序数组的查找来说,折半查找是一种性能卓越的算法.它通过比较查找健K和数组中间元素A[m]来完成查找工作.如果它们相等,算法结束.否则,如果K<A[m],就对数组的左半部分执行该操作,如果K>A[m],则对数组的右半部分执行该操作. 折半查找是基于递归思想的,但也可以以迭代方式实现. 代码实现: /** * 折半查找(递归方式实现) * @author xiaofeig * @since 2015.9.16 * @param array 查找的目标数组 * @param

二分查找和二叉树的羁绊!!

最近博主的老师让博主写一个二分查找,并且计算他的时间复杂度. 首先我是这样写的: 1 #include<stdio.h> 2 #include<assert.h> 3 4 void binary_search(int arr[],int len,int num){ 5         assert(arr); 6         int left = 0; 7         int right = len-1; 8         while(left<right){ 9

折半查找/二分查找 以及二叉树的 三种遍历方式

二分查找   线性查找 1.二分查找 public class BinarySearch { /** * 二分查找 * @param data * @return */ public int binarySearch(long[] data,long n) { //左右 端点 int left =0; int right =data.length-1; //中间元素 int mid=-1; while(left<right){ // 有两种情况 1.left = right 2. left>r

查找之二叉树查找

1.      查找树的创建(createTree) 假设有如下数组4,1,45,78,345,23,12,3,6,21 首先选定4为root,然后遍历剩下的数字,如果大于等于4则放到4的右侧,小于4放到4的左侧,最后构建成的树:所有的左孩子都小于父节点,所有的右孩子都大于等于父节点.如下图: 2.      遍历查找树(displayTree) 按照左中右的顺序遍历树,结果为:1,3,4,5,12,21,23,45,78,345,遍历的结果就是已经排好序的数字. 3.      查找树中的节点

数据结构之二叉树查找

数据结构之--二叉树查找 定义:它是一棵树,或者具有以下性质的树.  若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值: 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值:  它的左.右子树也分别为二叉排序树: 图解: ?        ?    ?    ?    ?    ?? #include<stdio.h> #include<stdlib.h> typedef int Status; #define TRUE 1 #define FALSE 0 t

php 二叉树 与赫夫曼树

在学习图之前,中间休息了两天,感觉二叉树需要消化一下.所以中间去温习了下sql,推荐一本工具书<程序员的SQL金典>看名字不像一本好书,但是作为一个不错的SQL工具书还是可以小小备忘一下.涵盖内容不详细但是挺广,覆盖多种主流数据库 言归正传,以前知道折半查找,二叉树的概念也是感觉挺有意思,二叉树的实现有一个案例很不错,代码如下 class BiNode{ public $data; public $lchild; public $rchild; public function __constr

python 实现二分法查找

二分查找图 二叉树: 代码 #!/usr/bin/python #-*-coding:utf-8-*- #---------------------------------------------------------------------------------------- # to_do : binary find # authors: zuoxingyu # time : 2014-06-07 #--------------------------------------------