【算法自由之路】前缀树 & 桶排序之计数排序和基数排序
前缀树(字典树)
首先是前缀树,前缀树是由字符构成的树结构,它记录有多少前缀字符通过,以及有多少个同样的字符串,其找这类信息的时间复杂度是极快的,只与字符串本身长度有关,是 O(logK) 级别,K 为字符串长度,可以应用于一些列前缀问题中。
有些抽象,举一个经典的例子,给定一组仅小写字母组成的字符串,如何构建前缀树?
["abc","adui","abd","bcd","af"] 
这个数组构建出来的前缀树是这样的, pass 记录的是有多少个字符串通过了它,end 记录的是有多少字符串以该节点字符结尾

基本的方法有:
向前缀树中添加一个字符串 insert
查找某个字符串在前缀树中数量 getNum
查找有多少字符串以某个前缀开头 getPrfixNum
向前缀树中删除一个字符串 delete
package algorithmic.base;
import com.sun.tools.javac.util.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @program: algorithmic-total-solution
 * @description: 字典树
 * @author: wangzibin
 * @create: 2022-11-26
 **/
public class TrieTree {
    private Node root;
    public TrieTree() {
        root = new Node();
    }
    private static class Node {
        int pass;
        int end;
        Map<Integer, Node> next;
        public Node() {
            this.pass = 0;
            this.end = 0;
            this.next = new HashMap<>();
        }
    }
    public void insert(String str) {
        if (str == null || str.length() < 1) {
            return;
        }
        int length = str.length();
        Node current = root;
        root.pass++;
        for (int i = 0; i < length; i++) {
            char c = str.charAt(i);
            int index = c - 'a';
            Node node = current.next.get(index);
            if (node == null) {
                node = new Node();
                current.next.put(index, node);
            }
            node.pass++;
            current = node;
        }
        current.end++;
    }
    public void delete(String str) {
        if (getNum(str) == 0) {
            return;
        }
        int length = str.length();
        Node current = root;
        root.pass--;
        for (int i = 0; i < length; i++) {
            char c = str.charAt(i);
            int index = c - 'a';
            Node node = current.next.get(index);
            node.pass--;
            if (node.pass == 0){
                // 当某个通路 pass 已经是 0 则直接 remove 完成 delete
                current.next.remove(index);
                return;
            }
            current = node;
        }
        current.end--;
    }
    public int getNum(String str) {
        if (str == null || str.length() < 1) {
            return 0;
        }
        int length = str.length();
        Node current = root;
        for (int i = 0; i < length; i++) {
            char c = str.charAt(i);
            int index = c - 'a';
            Node node = current.next.get(index);
            if (node == null) {
                return 0;
            }
            current = node;
        }
        return current.end;
    }
    public int getPrefixNum(String str) {
        if (str == null || str.length() < 1) {
            return 0;
        }
        int length = str.length();
        Node current = root;
        for (int i = 0; i < length; i++) {
            char c = str.charAt(i);
            int index = c - 'a';
            Node node = current.next.get(index);
            if (node == null) {
                return 0;
            }
            current = node;
        }
        return current.pass;
    }
    public static void main(String[] args) {
        TrieTree trieTree = new TrieTree();
        trieTree.insert("abccd");
        trieTree.insert("abccd");
        trieTree.insert("abccd");
        trieTree.insert("abccd");
        trieTree.insert("abccd");
        trieTree.insert("abccde1123%^$$8*吧");
        trieTree.insert("abccdf");
        trieTree.insert("abccdq");
        System.out.println(trieTree.getNum("abccde1123%^$$8*吧"));
        System.out.println(trieTree.getPrefixNum("abccde1123%^$$"));
        System.out.println(trieTree.getNum("abccd"));
        System.out.println(trieTree.root.pass);
        trieTree.delete("abccde1123%^$$8*吧");
        trieTree.delete("abccde");
        trieTree.delete("abccd");
        trieTree.delete("abccd");
        trieTree.delete("abccd");
        trieTree.delete("abccd");
        trieTree.delete("abccd");
        trieTree.delete("abccd");
        System.out.println(trieTree.getPrefixNum("abccde1123%^$$8*吧"));
        System.out.println(trieTree.getPrefixNum("abccde1123%^$$"));
        System.out.println(trieTree.getNum("abccd"));
        System.out.println(trieTree.root.pass);
    }
}
如上 Node 节点记录了 pass 和 end 数量,在解决某些前缀问题时或可增加一些特性属性来进行解答。
桶排序
这里桶排序是一种统称,桶即指容器,这个容器可以是 Java 中的数组、链表、哈希表,只要是可以容纳一部分数据的结构都可以称为容器,或者桶。
计数排序、基数排序都是桶排序的一种,它们是不基于比较的排序,它们的排序时间复杂度可以达到 O(N) ,但其对排序数据本身要求比较苛刻,可使用场景有限。
计数排序
顾名思义:统计数据。
比如有这样一个场景,人口普查统计年龄,给所有人的年龄排序,基本上我们可以认为,人的年龄在 0 - 200 之间。
这个时候我们可以准备一个 200 个长度的数组,然后遍历年龄,年龄对应数组下标,遇到一个则 +1,最后遍历数组反向生成排序好的数组。
数组每个下标就是一个桶记录这个桶中有多少个数。
可以见得,这个对数据本身要求很高,比如我的数据如果变了取值范围,达到上亿的值,那准备上亿长度的数组显然不现实,同时就算可以,遍历这个数组的成本也不可忽略。
基数排序
基数:所谓基数,就是进位计数制的每位数上可能有的数码的个数
基数排序一般要求数据是十进制正整数,它是基于分配的排序,根据位数对数据进行分桶最终实现排序。
思路是,十进制,准备十个桶分别代表 0 1 2 3 …… 9 ,所有数据高位根据最大长度数补齐 0
比如
[193,21,33,34,5]
// 补齐后,这里不一定是真的补齐成字符串哈,实现看自己
[193,021,033,034,005]
有几位就进行几次分配入桶,先从低位入桶,这个桶是先进先出队列,这一位数字是几就进几号桶,然后从 0 号桶依次出桶,最终排序完成,这个其实是利用了高位数高于低位的优先级进行的分配排序

对于基数排序,有一个很有意思的实现,即我们不需要真的准备 10 个这样的桶,只需要一个长度为 10 的数组即可。
这个数组每个下标位表示小于等于该位的数有几个,从后往前遍历原数组,取当前位,找到当前数小于等于当前位的数总共有 x 个,根据之前先进先出原则,此时是从后遍历,可以推算出当前数弹出时位置即 arr[x-1] 这个位置
package algorithmic.base;
import util.RandomUtil;
import java.util.Arrays;
import java.util.Random;
/**
 * @program: algorithmic-total-solution
 * @description:
 * @author: wangzibin
 * @create: 2022-11-28
 **/
public class RadixSort {
    public static void sort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        int len = arr.length;
        int digit = 0;
        for (int i = 0; i < len; i++) {
            digit = Math.max(digit, getDigitNum(arr[i]));
        }
        int[] help = new int[len];
        for (int i = 0; i < digit; i++) {
            int[] count = new int[10];
            // 计算 i 位 等于 j 的有多少
            for (int j = 0; j < len; j++) {
                count[getDigit(arr[j], i)]++;
            }
            // 计算出 i 位 小于等于 j 的有多少
            for (int j = 1; j < 10; j++) {
                count[j] = count[j] + count[j - 1];
            }
            for (int j = len - 1; j >= 0; j--) {
                // 模拟先进先出,
                int currentD = getDigit(arr[j], i);
                int num = count[currentD];
                help[num - 1] = arr[j];
                count[currentD]--;
            }
            for (int j = 0; j < len; j++) {
                arr[j] = help[j];
            }
        }
    }
    private static int getDigitNum(int num) {
        int i = 0;
        while (num > 0) {
            num /= 10;
            i++;
        }
        return i;
    }
    private static int getDigit(int num, int digit) {
        for (int i = 0; i < digit; i++) {
            num /= 10;
        }
        return num % 10;
    }
    public static void main(String[] args) {
        int[] arr;
        int[] arr2;
        SelectionSort selectionSort = new SelectionSort();
        Random random = new Random();
        for (int i = 0; i < 1000; i++) {
            int len = random.nextInt(20) + 5;
            arr = new int[len];
            arr2 = new int[len];
            for (int j = 0; j < len; j++) {
                int item = random.nextInt(100);
                arr[j] = item;
                arr2[j] = item;
            }
            sort(arr);
            selectionSort.sort(arr2);
            for (int k = 0; k < len; k++) {
                if (arr[k] != arr2[k]) {
                    System.out.println(Arrays.toString(arr));
                    System.out.println(Arrays.toString(arr2));
                    System.out.println("error " + i);
                    return;
                }
            }
        }
        System.out.println("success");
    }
}





![[数据结构] 并查集](https://img-blog.csdnimg.cn/540a23b1473a483cbd3614ae7da39b53.png#pic_center)

![[附源码]SSM计算机毕业设计小超市进销存管理系统JAVA](https://img-blog.csdnimg.cn/89f77eb45cce4f02a79a39744966d935.png)











