IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> BST 二叉搜索树 -> 正文阅读

[数据结构与算法]BST 二叉搜索树

目录

一、BST概念

二、BST的查找、插入和遍历

1、查找

2、插入

3、遍历

(1)前序

(2)中序(递增显示)

(3)后序

(4)深度优先(DFS)

(5)广度优先(BFS)

4、删除

(1)无左子结点时

(2)有左子结点时

三、定义一个树接口

四、实现BST类?

五、数据压缩:哈夫曼编码


一、BST概念

前几天学习了堆排序,其实堆排序用到的结构就是二叉树。

那么BST是满足如下规则的二叉树:

树中的任意结点,左子树的值都小于结点的,右子树的值都大于结点的左小右大

二、BST的查找、插入和遍历

1、查找

遵循BST“左小右大”的原则,查询时从根结点向下依次查找各个子树,直到 子树为空 或 已查询到 为止。否则设置的对比current结点将一直与目标值比较,小于当前结点时赋给current.left,大于当前结点时赋给current.right。

2、插入

设置一个parent结点(新元素的父结点)和current结点(定位结点)。如果树为空,就使用新元素创造一个新的根结点。否则遍历寻找新元素结点的父结点的位置,接下来的操作就和“查找”操作很像了。定位父结点成功后比较新元素和父元素大小,判断标准依旧是左大右小。

3、遍历

树的遍历方法主要有前序(preorder)、中序(inorder)、后序(postorder)、深度优先和广度优先。以下面这个二叉树为例:

(1)前序

访问current?-> 递归访问current.left -> 递归访问current.right

遍历为:60 55 45 57 59 100 67 107 101

(2)中序(递增显示)

递归访问current.left -> 访问current -> 递归访问current.right

遍历为:45 55 57 59 60 67 100 101 107(注意是先101才107 因为左边优先级大于右边)

(3)后序

递归访问current.left -> 递归访问current.right -> 访问current

遍历为:45 59 57 55 67 101 107 100 60

(4)深度优先(DFS)

访问root -> 任意顺序递归访问其left和right(前序遍历算是深度优先遍历的一个特例)

(5)广度优先(BFS)

逐层访问。访问root -> 从左往右访问其子结点 -> 再从左往右访问其孙结点2

遍历为:60 55 100 45 57 67 107 59 101

4、删除

删除操作要比插入操作复杂很多。删除结点之前需要定位current和current,然后分为有current.left和无current.left两个情况:

(1)无左子结点时

当current只有右子结点时,只需要:

  1. 将current.right和parent连接
  2. 删除current

(2)有左子结点时

找出current左子树中的最大元素记为rightMost(注意rightMost可能有左结点但绝对没有右结点),记rightMost的父结点为parentOfrightMost。接下来:

  1. 替换current中的元素值为rightMost
  2. 然后连接parentOfrightMost和rightMost.left
  3. 最后删除rightMost

三、定义一个树接口

我们定义Tree接口为Collection接口的子类:

import java.util.Collection;

public interface Tree<E> extends Collection<E> {
    public boolean search(E e);

    public boolean insert(E e);

    public boolean delete(E e);

    public int getSize();

    public default void inorder(){
    }

    public default void postorder(){
    }

    public  default void preorder(){
    }

    // 重写Collection中定义的方法
    @Override
    public default boolean isEmpty(){
        return size() == 0;
    }

    @Override
    public default boolean contains(Object e){
        return search((E)e);
    }

    @Override
    public default boolean add(E e){
        return insert(e);
    }

    @Override
    public default boolean remove(Object e){
        return delete((E)e);
    }

    @Override
    public default int size(){
        return getSize();
    }

    @Override
    public default boolean containsAll(Collection<?> c){
        return false;
    }

    @Override
    public default boolean addAll(Collection<? extends E> c){
        return false;
    }

    @Override
    public default boolean removeAll(Collection<?> c){
        return false;
    }

    @Override
    public default boolean retainAll(Collection<?> c){
        return false;
    }

    @Override
    public default Object[] toArray(){
        return null;
    }

    @Override
    public default <T> T[] toArray(T[] array){
        return null;
    }
}

四、实现BST类?

class BST<E extends Comparable<E>> implements Tree<E>{
    protected TreeNode<E> root;
    protected int size = 0;

    public BST(){
    }

    public BST(E[] objects) {
        for(int i =0; i < objects.length; i++) {
            add(objects[i]);
        }
    }

    // 查找
    @Override
    public boolean search(E e){
        TreeNode current = root;

        while(current != null){  //不为空时->
            if(e.compareTo((E) current.element) < 0){  // 小于->
                current = current.left;  //指到left->
            }else if(e.compareTo((E) current.element) > 0){  // 大于->
                current = current.right;  //指到right->
            }else return true; // 相同即找到
        }
        return false;  // 没找到
    }

    /* 递归search
    public boolean search(E e) {
        return search(root, e);
    }

    public boolean search(TreeNode<E> root, E e) {
        if (root == null)
            return false;
        else if (e.compareTo(root.element) < 0)
            return search(root.left, e);
        else if (e.compareTo(root.element) > 0)
            return search(root.right, e);
         else
            return true;
    }
    * */

    // 插入
    @Override
    public boolean insert(E e){
        if(root == null){
            root = createNewNode(e); //如果树为空,创建一个新的根结点
        }else {
            // 否则寻找新元素父节点的位置
            TreeNode<E> parent = null;
            TreeNode<E> current = root;

            while(current != null){
                if(e.compareTo(current.element) < 0){
                    parent = current;
                    current = current.left;
                }else if(e.compareTo(current.element) > 0){
                    parent = current;
                    current = current.left;
                }else return false;
            }
            // 链接父结点
            if(e.compareTo(parent.element) < 0)
                parent.left = createNewNode(e);
            else
                parent.right = createNewNode(e);
        }
        size++;
        return true;
    }

    protected TreeNode<E> createNewNode(E e){
        return new TreeNode<>(e);
    }

    // 中序遍历
    @Override
    public void inorder(){
        inorder(root);
    }
    protected void inorder(TreeNode<E> root){
        if(root == null){  // 树为空时结束递归
            return;
        }
        inorder(root.left);  //递归左结点
        System.out.print(root.element + " ");  // 中间结点
        inorder(root.right);  // 递归右结点
    }

    // 后序遍历
    @Override
    public void postorder(){
        postorder(root);
    }
    protected void postorder(TreeNode<E> root){
        if(root == null){
            return;
        }
        postorder(root.left);
        postorder(root.right);
        System.out.print(root.element + " ");
    }

    // 前序遍历
    public void preorder(){
        preorder(root);
    }
    protected void preorder(TreeNode<E> root){
        if(root == null){
            return ;
        }
        System.out.print(root.element + " ");
        preorder(root.left);
        preorder(root.right);
    }

    // 树结点class
    public static class TreeNode<E>{
        protected E element;
        protected TreeNode<E> left;
        protected TreeNode<E> right;

        public TreeNode(E e){
            element = e;
        }
    }

    @Override
    public int getSize(){
        return size;
    }

    public TreeNode<E> getRoot(){
        return root;
    }

    // 以数组线性表返回结点路径
    public java.util.ArrayList<TreeNode<E>> path(E e){
        java.util.ArrayList<TreeNode<E>> list = new java.util.ArrayList<>();
        TreeNode<E> current = root;

        while(current != null){
            list.add(current);
            if(e.compareTo(current.element) < 0){
                current = current.left;
            }else if(e.compareTo(current.element) > 0){
                current = current.right;
            }else break;
        }
        return list;
    }

    /**删除*/
    @Override
    public boolean delete(E e){
        TreeNode<E> parent = null;
        TreeNode<E> current = root;

        // 定位current和parent
        while(current != null){
            if(e.compareTo(current.element) < 0){
                parent = current;
                current = current.left;
            }else if(e.compareTo(current.element) > 0){
                parent = current;
                current = current.right;
            }else break;
        }

        if(current == null){
            return false;  // 结点不在树中
        }

        // 1、无左子结点
        if(current.left == null){
            if(parent == null){
                root = current.right;  // 没有父结点 就设为根结点
            }else {
                if(e.compareTo(parent.element) < 0){  // 删除current结点
                    parent.left = current.right;
                }else {
                    parent.right = current.right;
                }
            }
        }else{  // 2、有左子结点
            TreeNode<E> parentOfRightMost = current;
            TreeNode<E> rightMost = current.left;

            // 定位rightMost和parentOfRightMost
            while (rightMost.right != null){
                parentOfRightMost = rightMost;
                rightMost = rightMost.right;
            }
            current.element = rightMost.element;  // 用rightMost替换current

            // 删除rightMost结点
            if(parentOfRightMost.right == rightMost){
                parentOfRightMost.right = rightMost.left;
            }else{
                parentOfRightMost.left = rightMost.left;
            }
        }
        size--;
        return true;
    }

    // 遍历迭代器
    @Override
    public java.util.Iterator<E> iterator() {
        return new InorderIterator();
    }
    private class InorderIterator implements java.util.Iterator<E>{
        private java.util.ArrayList<E> list = new java.util.ArrayList<>();
        private int current = 0;

        public InorderIterator(){
            inorder();
        }

        private void inorder(){
            inorder(root);
        }

        private void inorder(TreeNode<E> root){
            if(root == null){
                return;
            }
            inorder(root.left);
            list.add(root.element);
            inorder(root.right);
        }

        @Override
        public boolean hasNext(){
            if(current < list.size()){
                return true;
            }
            return false;
        }

        @Override
        public E next(){
            return list.get(current++);
        }

        @Override
        public void remove(){
            if(current == 0){
                throw new IllegalStateException();
            }
            delete(list.get(--current));
            list.clear();
            inorder();
        }
    }

    @Override
    public void clear(){
        root = null;
        size = 0;
    }
}

五、数据压缩:哈夫曼编码

哈夫曼编码中的字符编码基于字符出现的次数使用二叉树构建。使用贪心算法,算法总是选择具有最小权重的两棵树,并且创造一个新的父结点

?接下来编写一个程序,用户输入字符串,程序将会输出每个字符出现的次数以及其哈夫曼编码:

import java.util.Scanner;

public class HuffmanCode {
    public static void main(String[] args){
        Scanner input = new Scanner(System.in);
        System.out.print("Enter text: ");
        String text = input.nextLine();

        // 计算字符出现次数
        int[] counts = getCharacterFrequency(text);

        System.out.printf("%-15s%-15s%-15s%-15s\n","ASCII Code","Character","Frequency","Code");

        // 基于counts得到哈夫曼树
        Tree tree = getHuffmanTree(counts);
        String[] codes = getCode(tree.root);

        for(int i = 0; i < codes.length; i++){
            if(counts[i] != 0){
                System.out.printf("%-15s%-15s%-15s%-15s\n",i, (char)i + "",counts[i],codes[i]);
            }
        }
    }

    // 获取每个叶子结点中字符的编码
    public static String[] getCode(Tree.Node root) {
        if(root == null){
            return null;
        }
        String[] codes = new String[2 *128];
        assignCode(root,codes);
        return codes;
    }

    // 给树中每个结点赋予编码
    private static void assignCode(Tree.Node root, String[] codes){
        if(root.left != null){
            root.left.code = root.code + "0";
            assignCode(root.left, codes);

            root.right.code = root.code + "1";
            assignCode(root.right,codes);
        }else {
            codes[(int) root.element] = root.code;
        }
    }

    // 返回一颗哈夫曼树
    public static Tree getHuffmanTree(int[] counts){
        // 创建单结点树并添加到堆中
        Heap<Tree> heap = new Heap<>();
        for(int i = 0; i < counts.length; i++){
            if(counts[i] > 0){
                heap.add(new Tree(counts[i],(char)i));
            }
        }

        // while迭代
        while(heap.getSize() > 1){
            Tree t1 = heap.remove(); //每次将权重最小的两个子树从堆中删除
            Tree t2 = heap.remove();
            heap.add(new Tree(t1, t2));  //t1,t2组合成新树。接着添加新树到堆中
        }
        return heap.remove();
    }

    // 创建一个数组计算256个ASCII字符出现的次数
    public static int[] getCharacterFrequency(String text){
        int[] counts = new int[256];

        for(int i = 0; i < text.length(); i++){
            counts[(int)text.charAt(i)]++;
        }
        return counts;
    }

    public static class Tree implements Comparable<Tree>{
        Node root;

        public Tree(Tree t1, Tree t2){
            root = new Node();
            root.left = t1.root;
            root.right = t2.root;
            root.weight = t1.root.weight + t2.root.weight;
        }

        public Tree(int weight, char element) {
            root  = new Node(weight, element);
        }

        @Override
        public int compareTo(Tree t) {
            if(root.weight < t.root.weight){
                return 1;
            }else if(root.weight == t.root.weight){
                return 0;
            }else return -1;
        }

        public class Node{
            char element;
            int weight;
            Node left;
            Node right;
            String code = "";

            public Node() {
            }

            public Node(int weight, char element){
                this.weight = weight;
                this.element = element;
            }
        }
    }
}

运行结果如下:?

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-07-03 11:03:35  更:2022-07-03 11:06:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 23:19:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码