概述
文章目录
- 1. 字典树/Trie Tree
- 1.1 场景
- 1.2 思路
- 1.3 代码
- 2. 线段树/Segment Tree
- 2.1 场景
- 2.2 思路
- 2.3 代码
- 3. 树状数组
1. 字典树/Trie Tree
1.1 场景
- 在海量字符串中做查询操作(但不局限于字符串问题)
- 节约空间:10万个只包含小写字母的字符串,采用字典树可以减少内存消耗
- 节约时间:检索效率高
- 扩展:
- 对数字的二进制01串构造字典树可以快速按位查询数字是否存在
- 利用KMP的利用失配信息思想快速继续匹配,可以实现AC自动机算法
- 对构造好的字典树进行BFS预处理并增加直达叶子节点的指针时,可以解决输入最小操作数的问题
1.2 思路
-
根节点不包含字符,每条边代表一个字符
-
从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
-
每个节点的所有子节点包含的字符都不相同
参考链接:https://blog.csdn.net/kuronekonano/article/details/100063157
1.3 代码
class TrieNode {
char val; // 当前节点存储的字符
TrieNode[] next = new TrieNode[26]; // 节点下的孩子数组,通常为26个字母
boolean isEnd = false; // 标记是否为待存储的最后一个字符,下面没有子节点
public TrieNode() {
}
public TrieNode(char val) {
this.val = val;
}
}
class Trie {
TrieNode root;
public Trie() {
root = new TrieNode();
root.val = ' ';
}
// 1.插入一个单词到Trie树中
public void insert(String word) {
TrieNode cur_node = root;
char[] chars = word.toCharArray();
int len = chars.length;
for (int i = 0; i < len; i ++) {
char c = chars[i];
if (cur_node.next[c - 'a'] == null) {
cur_node.next[c - 'a'] = new TrieNode(c);
}
cur_node = cur_node.next[c - 'a'];
}
cur_node.isEnd = true;
}
// 2.判断某个单词是否在Trie树之中
public boolean get(String word) {
TrieNode cur_node = root;
char[] chars = word.toCharArray();
int len = chars.length;
for (int i = 0; i < len; i ++) {
char c = chars[i];
// 在单词还未走完的时候发现已经不匹配了
if (cur_node.next[c - 'a'] == null) {
return false;
}
cur_node = cur_node.next[c - 'a'];
}
// 如果后面还有,说明未储存word
return cur_node.isEnd;
}
// 3.判断当前的单词是否为Trie树某个单词的前缀
public boolean isPrefix(String word) {
TrieNode cur_node = root;
char[] chars = word.toCharArray();
int len = chars.length;
for (int i = 0; i < len; i ++) {
char c = chars[i];
// 在单词还未走完的时候发现已经不匹配了
if (cur_node.next[c - 'a'] == null) {
return false;
}
cur_node = cur_node.next[c - 'a'];
}
return true;
}
}
public class Test {
public static void main(String[] args) {
Trie trie = new Trie();
trie.insert("abcdefg");
trie.insert("abomn");
trie.insert("acg");
trie.insert("dkz");
System.out.println(trie.get("abcdefg")); // true
System.out.println(trie.isPrefix("abc")); // true
}
}
2. 线段树/Segment Tree
2.1 场景
给定一个数组,随机截取一段,计算区间和;随机修改数组中的元素。假设数组很大,截取或者修改的操作很频繁
2.2 思路
1.通过遍历对区间内数字进行累加:O(N),修改:O(1)
2.使用前缀和,先计算出前缀和,利用前缀和计算区间和:O(1),修改:O(N)
如何平衡计算区间和、修改的时间复杂度?使用线段树!
构造线段树:根节点包含整个区间,然后依此对区间二分,构造新节点
依据填入的叶子节点数据构造完整的线段树:
计算[2,5]
区间和:在根节点处分割区间为[2],[3,5]
,从左子树找到区间[2]
的元素,右子树根节点即[3,5]
,最后得出区间和:32=5+27
修改索引为4的元素为6:
将线段树转换成满二叉树,对中间的叶子节点建立虚节点:
将线段树转成数组:(注意满二叉树节点编号的性值)
2.3 代码
import java.util.Arrays;
public class SegmentTree {
private static final int MAX_LEN=20;
public static void main(String[] args) {
int[] arr=new int[] {1,3,5,7,9,11};
int[] tree=new int[MAX_LEN];
build_tree(arr,tree,0,0,arr.length-1);//node=0,start=0,end=5
//[36, 9, 27, 4, 5, 16, 11, 1, 3, 0, 0, 7, 9, 0, 0, 0, 0, 0, 0, 0]
System.out.println(Arrays.toString(tree));
//[33, 9, 24, 4, 5, 13, 11, 1, 3, 0, 0, 7, 6, 0, 0, 0, 0, 0, 0, 0]
update_tree(arr, tree, 0, 0, arr.length-1, 4, 6);
System.out.println(Arrays.toString(tree));
//29
int res=query_tree(arr, tree, 0, 0, arr.length-1, 2, 5);
System.out.println(res);
}
/**
* 1.对初始数组arr[]的start-end的区间构造tree[]
* @param node tree[]根节点
* @param start arr[]左边界
* @param end arr[]右边界
*/
public static void build_tree(int[] arr,int[] tree,int node,int start,int end) {
//设置递归出口
if(start==end) {
tree[node]=arr[start];
return;
}
int mid=(start+end)/2;//mid=(0+5)/2=2
int left_node=node*2+1;//left=1
int right_node=node*2+2;//right=2
build_tree(arr,tree,left_node,start,mid);
build_tree(arr,tree,right_node,mid+1,end);
tree[node]=tree[left_node]+tree[right_node];
}
/**
* 2.修改初始数组arr[]的idx位置的元素为val
*/
public static void update_tree(int[] arr,int[] tree,int node,int start,int end,int idx,int val) {
//设置递归出口
if(start==end) {
arr[idx]=val;
tree[node]=val;
return;
}
int mid=(start+end)/2;
int left_node=node*2+1;
int right_node=node*2+2;
//改左子树
if(idx>=start && idx<=mid) {
update_tree(arr, tree, left_node, start, mid, idx, val);
}
//改右子树
else {
update_tree(arr, tree, right_node, mid+1, end, idx, val);
}
tree[node]=tree[left_node]+tree[right_node];
}
/**
* 3.计算初始数组arr[]的[L,R]的区间和
*/
public static int query_tree(int[] arr,int[] tree,int node,int start,int end,int L,int R) {
//设置递归出口
if(R<start || end<L) {
return 0;
}
if(start==end) {
return tree[node];
}
if(L<=start && end<=R) {
return tree[node];
}
int mid=(start+end)/2;
int left_node=node*2+1;
int right_node=node*2+2;
int sum_left=query_tree(arr, tree, left_node, start, mid, L, R);
int sum_right=query_tree(arr, tree, right_node, mid+1, end, L, R);
return sum_left+sum_right;
}
}
完整教程:
bilibili - 正月点灯笼 - 【数据结构】线段树(Segment Tree)
3. 树状数组
高效计算数组的前缀和、区间和以及解决单点更新问题
不能解决数组有增加或修改的情况
它可以以O(logn) 的时间得到任意前缀和,并同时支持在O(logn) 时间内支持动态单点值的修改
空间复杂度O(n)
例题:
LeetCode 315. 计算右侧小于当前元素的个数
给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
输入:nums = [5,2,6,1]
输出:[2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素
完整教程:
bilibili - liweiwei1419 -「树状数组」知识入门(「力扣」第 315 题)
最后
以上就是高贵大白为你收集整理的数据结构与算法——高级数据结构:字典树/Trie树+线段树+树状数组1. 字典树/Trie Tree2. 线段树/Segment Tree3. 树状数组的全部内容,希望文章能够帮你解决数据结构与算法——高级数据结构:字典树/Trie树+线段树+树状数组1. 字典树/Trie Tree2. 线段树/Segment Tree3. 树状数组所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复