计算机基础--->数据结构(5)【线段树、Trie树(字典树)】

线段树(Segment Tree)

线段树主要解决哪些问题?

线段树是一种用来处理区间查询问题RMQ(Range Minimum/Maximum Query)的数据结构。主要思路就是将一个给定的区间划分成较小的区间,并为每个区间维护一些预先计算好的信息(如区间和、区间最大值)。通过这些信息,可以较快的完成各种区间操作,像区间求和、区间最大值、区间更新等。基本思想就是通过递归将大区间划分成两个子区间,并合并子区间的信息来构建整个线段树。总的来说线段树是处理区间查询问题的数据结构,通过递归划分区间并维护预先计算好的信息来高效完成各种区间操作。

线段树的构建

使用source存储原数组数据,使用data数组存储线段树,构建merger融合器

private int[] source;// 数据源
    private int[] data;// 线段树对应数组
    Merger<Integer> merger;// 构建融合器

    public SegmentTree(int[] arr, Merger<Integer> merger) {
        this.merger = merger;
        source = Arrays.copyOf(arr, arr.length);
        // 创建线段树数组,此时需要知道线段树数组的长度通,过公式2^n-1可知,此时需要求出高度n,此公式2^(n-1)>=source.length可用来求出线段树的高度,len/2+1
        int sLength = this.source.length;
        int n = (int) (Math.ceil(Math.log10(sLength) / Math.log10(2)) + 1);// 计算高度n
        int dataSize = (int) (Math.pow(2, n) - 1);// 计算线段树数组长
        this.data = new int[dataSize];
        buildSegmentTree(0, sLength - 1, 0);
    }

    // start和end代表区间,index代表在线段树中的位置
    private void buildSegmentTree(int start, int end, int index) {
        if (start == end) {
            this.data[index] = this.source[start];
            return;
        }
        int mid = start + (end - start) / 2;
        int leftIndex = 2 * index + 1;
        int rightIndex = 2 * index + 2;
        buildSegmentTree(start, mid, leftIndex);
        buildSegmentTree(mid + 1, end, rightIndex);

        // 融合器进行的操作
        this.data[index] = this.merger.merger(this.data[leftIndex], this.data[rightIndex]);
    }

区间求值

// 求区间[from, to]的值
    public int intervalVaule(int from, int to) {

        return rangValue(0, this.source.length - 1, 0, from, to);
    }

    private int rangValue(int start, int end, int index, int from, int to) {
        // 找到的时机
        if (from == start && to == end) {
            return this.data[index];
        }
        int mid = start + (end - start) / 2;
        int leftIndex = 2 * index + 1;
        int rightIndex = 2 * index + 2;
        if (to <= mid) {
            return rangValue(start, mid, leftIndex, from, to);
        } else if (from > mid) {
            return rangValue(mid + 1, end, rightIndex, from, to);
        } else {
            int leftVal = rangValue(start, mid, leftIndex, from, mid);
            int rightVal = rangValue(mid + 1, end, rightIndex, mid + 1, to);
            return this.merger.merger(leftVal, rightVal);
        }
    }

修改值

    public void updateArr(int oriIndex, int val) {
        update(0, this.source.length - 1, 0, oriIndex, val);
    }

    private void update(int start, int end, int index, int oriIndex, int val) {
        if (start == end && start == oriIndex) {
            this.data[index] = val;
            this.source[oriIndex] = val;
            return;
        }
        int mid = start + (end - start) / 2;
        int leftIndex = 2 * index + 1;
        int rightIndex = 2 * index + 2;
        if (oriIndex <= mid) {
            update(start, mid, leftIndex, oriIndex, val);
        } else if (oriIndex > mid) {
            update(mid + 1, end, rightIndex, oriIndex, val);
        }
        // 进行回溯
        this.data[index] = this.merger.merger(this.data[leftIndex], this.data[rightIndex]);
    }

融合器

/**
 * 融合器
 * @param <Integer>
 */
public interface Merger<Integer> {
    int merger(int t1, int t2);
}

代码汇总

import java.util.*;

public class SegmentTree {
    private int[] source;// 数据源
    private int[] data;// 线段树对应数组
    Merger<Integer> merger;// 构建融合器

    public SegmentTree(int[] arr, Merger<Integer> merger) {
        this.merger = merger;
        source = Arrays.copyOf(arr, arr.length);
        // 创建线段树数组,此时需要知道线段树数组的长度通,过公式2^n-1可知,此时需要求出高度n,此公式2^(n-1)>=source.length可用来求出线段树的高度,len/2+1
        int sLength = this.source.length;
        int n = (int) (Math.ceil(Math.log10(sLength) / Math.log10(2)) + 1);// 计算高度n
        int dataSize = (int) (Math.pow(2, n) - 1);// 计算线段树数组长
        this.data = new int[dataSize];
        buildSegmentTree(0, sLength - 1, 0);
    }

    // start和end代表区间,index代表在线段树中的位置
    private void buildSegmentTree(int start, int end, int index) {
        if (start == end) {
            this.data[index] = this.source[start];
            return;
        }
        int mid = start + (end - start) / 2;
        int leftIndex = 2 * index + 1;
        int rightIndex = 2 * index + 2;
        buildSegmentTree(start, mid, leftIndex);
        buildSegmentTree(mid + 1, end, rightIndex);

        // 融合器进行的操作
        this.data[index] = this.merger.merger(this.data[leftIndex], this.data[rightIndex]);
    }

    @Override
    public String toString() {
        return "SegmentTree{" +
                "data=" + Arrays.toString(data) +
                '}';
    }

    // 求区间[from, to]的值
    public int intervalVaule(int from, int to) {

        return rangValue(0, this.source.length - 1, 0, from, to);
    }

    private int rangValue(int start, int end, int index, int from, int to) {
        // 找到的时机
        if (from == start && to == end) {
            return this.data[index];
        }
        int mid = start + (end - start) / 2;
        int leftIndex = 2 * index + 1;
        int rightIndex = 2 * index + 2;
        if (to <= mid) {
            return rangValue(start, mid, leftIndex, from, to);
        } else if (from > mid) {
            return rangValue(mid + 1, end, rightIndex, from, to);
        } else {
            int leftVal = rangValue(start, mid, leftIndex, from, mid);
            int rightVal = rangValue(mid + 1, end, rightIndex, mid + 1, to);
            return this.merger.merger(leftVal, rightVal);
        }
    }

    public void updateArr(int oriIndex, int val) {
        update(0, this.source.length - 1, 0, oriIndex, val);
    }

    private void update(int start, int end, int index, int oriIndex, int val) {
        if (start == end && start == oriIndex) {
            this.data[index] = val;
            this.source[oriIndex] = val;
            return;
        }
        int mid = start + (end - start) / 2;
        int leftIndex = 2 * index + 1;
        int rightIndex = 2 * index + 2;
        if (oriIndex <= mid) {
            update(start, mid, leftIndex, oriIndex, val);
        } else if (oriIndex > mid) {
            update(mid + 1, end, rightIndex, oriIndex, val);
        }
        // 进行回溯
        this.data[index] = this.merger.merger(this.data[leftIndex], this.data[rightIndex]);
    }

    public static void main(String[] args) {
        int[] arr = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
        SegmentTree s = new SegmentTree(arr, (o1, o2) -> o1 + o2);
        System.out.println(s.intervalVaule(2, 5));
        System.out.println(s.toString());
        s.updateArr(3, 12);
        System.out.println(s.toString());
    }
}

Trie树

Trie树的作用?

Trie树也叫字典树,是一种用于处理字符串的数据结构。它可以搞笑的存储和检索一组字符串,适用于需要快速查找具有公共前缀的字符串集合。Trie树通过将字符串中每个字符存储在节点上,从根节点到叶节点的路径表示了一个字符串。通过在Trie树中进行遍历和搜索操作,可以实现字符串前缀匹配、字符串查找、统计等功能。总的来说Trie树是处理字符串的数据结构,用于高效存储和检索一组字符串,特别适用于具有公共前缀的字符串集合。

Trie树的创建

Trie的节点内包含着当前是否组成单词,还有Map用来存储下一节点

 private class Node{
        boolean isWord;
        Map<Character, Node> children;
        Node(){
            this(false);
        }
        Node(boolean isWord){
            this.isWord = isWord;
            children = new TreeMap<>();
        }
    }

    // 构建Trie树
    private Node root;// Trie树的根节点
    private int size;// 表示Trie中有多少个单词

    public Trie(){
        root = new Node();
        this.size = 0;
    }

插入单词

    // 插入单词
    public void add(String word){
        if (word == null || word.length() == 0) {
            return;
        }
        Node cur = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            Map<Character, Node> children = cur.children;
            if (!children.containsKey(c)){
                children.put(c, new Node());
            }
            cur = children.get(c);
        }
        cur.isWord = true;
        this.size++;
    }

查找单词是否存在

    // 查找单词是存在
    public boolean match(String search){
        Node cur = root;
        for (int i = 0; i < search.length(); i++) {
            char c = search.charAt(i);
            Map<Character, Node> children  = cur.children;
            if(!children.containsKey(c)){
                return false;
            }else{
                cur = children.get(c);
            }
        }
        return cur.isWord;
    }

搜索是否存在与当前匹配的字符

  public boolean search(String word) {
        if (word == null || word.length() == 0) {
            throw new IllegalArgumentException("search must be not empty");
        }
        return search(root, word, 0);
    }

    /**
     * @param node  要查询trie树的根结点
     * @param word  查询的单词
     * @param index 当前匹配的字符
     * @return
     */
    private boolean search(Node node, String word, int index) {
        // 递归到底的情况
        if (index == word.length()) {
            return node.isWord;
        }
        // 递归操作
        char c = word.charAt(index);
        Map<Character, Node> children = node.children;
        if (c != '.') {
            if (!children.containsKey(c)) {
                return false;
            } else {
                return search(children.get(c), word, index + 1);
            }
        } else {
            for (Map.Entry<Character, Node> entry : children.entrySet()) {
                if (search(entry.getValue(), word, index + 1)) {
                    return true;
                }
            }
            return false;
        }
    }

删除单词

    public void delete(String word) {
        if (word == null || word.length() == 0) {
            return;
        }
        Node cur = root;
        Node multifyNode = null; // 记录分叉结点
        int multifyIndex = -1; // 记录分叉字符索引

        // 找到单词的最后一个字符
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            Map<Character, Node> children = cur.children;
            if (!children.containsKey(c)) {
                return;
            } else {
                cur = children.get(c);
                if (cur.children.size() > 1 || (cur.children.size() == 1 && cur.isWord)) {
                    multifyNode = cur;// 记录多分支
                    multifyIndex = i;
                }
            }
        }
        // 真正删除
        if (cur.isWord) {
            if (multifyNode == null) {
                this.root.children.remove(word.charAt(0));
            } else if (cur.children.size() >= 1) {
                cur.isWord = false;
            } else {
                multifyNode.children.remove(word.charAt(multifyIndex + 1));
            }
            this.size--;
        }
    }

全部代码

import java.util.*;

// Trie树,只能处理字符串
public class Trie {
    private class Node{
        boolean isWord;
        Map<Character, Node> children;
        Node(){
            this(false);
        }
        Node(boolean isWord){
            this.isWord = isWord;
            children = new TreeMap<>();
        }
    }

    // 构建Trie树
    private Node root;// Trie树的根节点
    private int size;// 表示Trie中有多少个单词

    public Trie(){
        root = new Node();
        this.size = 0;
    }

    // 插入单词
    public void add(String word){
        if (word == null || word.length() == 0) {
            return;
        }
        Node cur = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            Map<Character, Node> children = cur.children;
            if (!children.containsKey(c)){
                children.put(c, new Node());
            }
            cur = children.get(c);
        }
        cur.isWord = true;
        this.size++;
    }

    // 查找单词是存在
    public boolean match(String search){
        Node cur = root;
        for (int i = 0; i < search.length(); i++) {
            char c = search.charAt(i);
            Map<Character, Node> children  = cur.children;
            if(!children.containsKey(c)){
                return false;
            }else{
                cur = children.get(c);
            }
        }
        return cur.isWord;
    }

    public int getSize(){
        return this.size;
    }

    public boolean search(String word) {
        if (word == null || word.length() == 0) {
            throw new IllegalArgumentException("search must be not empty");
        }
        return search(root, word, 0);
    }

    /**
     * @param node  要查询trie树的根结点
     * @param word  查询的单词
     * @param index 当前匹配的字符
     * @return
     */
    private boolean search(Node node, String word, int index) {
        // 递归到底的情况
        if (index == word.length()) {
            return node.isWord;
        }
        // 递归操作
        char c = word.charAt(index);
        Map<Character, Node> children = node.children;
        if (c != '.') {
            if (!children.containsKey(c)) {
                return false;
            } else {
                return search(children.get(c), word, index + 1);
            }
        } else {
            for (Map.Entry<Character, Node> entry : children.entrySet()) {
                if (search(entry.getValue(), word, index + 1)) {
                    return true;
                }
            }
            return false;
        }
    }

    public void delete(String word) {
        if (word == null || word.length() == 0) {
            return;
        }
        Node cur = root;
        Node multifyNode = null; // 记录分叉结点
        int multifyIndex = -1; // 记录分叉字符索引

        // 找到单词的最后一个字符
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            Map<Character, Node> children = cur.children;
            if (!children.containsKey(c)) {
                return;
            } else {
                cur = children.get(c);
                if (cur.children.size() > 1 || (cur.children.size() == 1 && cur.isWord)) {
                    multifyNode = cur;// 记录多分支
                    multifyIndex = i;
                }
            }
        }
        // 真正删除
        if (cur.isWord) {
            if (multifyNode == null) {
                this.root.children.remove(word.charAt(0));
            } else if (cur.children.size() >= 1) {
                cur.isWord = false;
            } else {
                multifyNode.children.remove(word.charAt(multifyIndex + 1));
            }
            this.size--;
        }
    }

    public static void main(String[] args) {
        Trie trie = new Trie();
        String[] str = new String[]{"dog", "door", "panda", "pan", "deer", "orange"};
        for (int i = 0; i < str.length; i++) {
            trie.add(str[i]);
        }
        System.out.println(trie.search("d.g"));
        System.out.println(trie.search("d..g"));
        trie.delete("door");
        System.out.println(trie.getSize());
        System.out.println(trie.search("d..r"));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值