目录
- 线段树
- 线段树的实现
- leetcode相关题目
- 字典树
- 字典树的实现
- leetcode相关题目
线段树
线段树的实现
- 线段树不是完全二叉树
- 线段树是平衡二叉树
- 第n层的个数为2(n-1),前n层总数为2n,如果有m个元素,最后一排可能装不下,则拓展一排,空的用null表示,总共需要开辟4m的空间。
线段树的定义:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58public class SegmentTree<E> { private E[] tree; private E[] data; private Merger<E> merger; public SegmentTree(E[] arr, Merger<E> merger){ this.merger = merger; data = (E[])new Object[arr.length]; for(int i = 0 ; i < arr.length ; i ++) data[i] = arr[i]; tree = (E[])new Object[4 * arr.length]; buildSegmentTree(0, 0, arr.length - 1); } // 在treeIndex的位置创建表示区间[l...r]的线段树 private void buildSegmentTree(int treeIndex, int l, int r){ if(l == r){ tree[treeIndex] = data[l]; return; } int leftTreeIndex = leftChild(treeIndex); int rightTreeIndex = rightChild(treeIndex); // int mid = (l + r) / 2; int mid = l + (r - l) / 2; buildSegmentTree(leftTreeIndex, l, mid); buildSegmentTree(rightTreeIndex, mid + 1, r); tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]); } public int getSize(){ return data.length; } public E get(int index){ if(index < 0 || index >= data.length) throw new IllegalArgumentException("Index is illegal."); return data[index]; } // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引 private int leftChild(int index){ return 2*index + 1; } // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引 private int rightChild(int index){ return 2*index + 2; } }
merge操作可自己定义,不一定是相加
复制代码
1
2
3
4
5
6
7
8
9
10
11
12public interface Merger<E> { E merge(E a, E b); } //类的调用 public static void main(String[] args) { Integer[] nums = {-2, 0, 3, -5, 2, -1}; SegmentTree<Integer> segTree = new SegmentTree<>(nums, (a, b) -> a + b); //使用函数式接口 }
线段树的查询操作:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// 返回区间[queryL, queryR]的值 public E query(int queryL, int queryR){ if(queryL < 0 || queryL >= data.length || queryR < 0 || queryR >= data.length || queryL > queryR) throw new IllegalArgumentException("Index is illegal."); return query(0, 0, data.length - 1, queryL, queryR); } // 在以treeIndex为根的线段树中[l...r]的范围里,搜索区间[queryL...queryR]的值 private E query(int treeIndex, int l, int r, int queryL, int queryR){ if(l == queryL && r == queryR) return tree[treeIndex]; int mid = l + (r - l) / 2; // treeIndex的节点分为[l...mid]和[mid+1...r]两部分 int leftTreeIndex = leftChild(treeIndex); int rightTreeIndex = rightChild(treeIndex); if(queryL >= mid + 1) return query(rightTreeIndex, mid + 1, r, queryL, queryR); else if(queryR <= mid) return query(leftTreeIndex, l, mid, queryL, queryR); E leftResult = query(leftTreeIndex, l, mid, queryL, mid); E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR); return merger.merge(leftResult, rightResult); }
线段树的更新操作:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// 将index位置的值,更新为e public void set(int index, E e){ if(index < 0 || index >= data.length) throw new IllegalArgumentException("Index is illegal"); data[index] = e; set(0, 0, data.length - 1, index, e); } // 在以treeIndex为根的线段树中更新index的值为e private void set(int treeIndex, int l, int r, int index, E e){ if(l == r){ tree[treeIndex] = e; return; } int mid = l + (r - l) / 2; // treeIndex的节点分为[l...mid]和[mid+1...r]两部分 int leftTreeIndex = leftChild(treeIndex); int rightTreeIndex = rightChild(treeIndex); if(index >= mid + 1) set(rightTreeIndex, mid + 1, r, index, e); else // index <= mid set(leftTreeIndex, l, mid, index, e); tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]); }
leetcode相关题目
303. 区域和检索 - 数组不可变
解法1:
使用线段树的query操作,merge函数接口为元素相加。
解法2:(此方法不适合数组有大量更新的情况,比如下题)
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class NumArray { private int[] sum; // sum[i]存储前i个元素和, sum[0] = 0 // 即sum[i]存储nums[0...i-1]的和 // sum(i, j) = sum[j + 1] - sum[i] public NumArray(int[] nums) { sum = new int[nums.length + 1]; sum[0] = 0; for(int i = 1 ; i < sum.length ; i ++) sum[i] = sum[i - 1] + nums[i - 1]; } public int sumRange(int i, int j) { return sum[j + 1] - sum[i]; } }
307. 区域和检索 - 数组可修改
解法一:
上题的解法二能够通过测试,但是所消耗的时间很长。
解法二:
用线段树的操作。
字典树
字典树的实现
- 搜索1000000个单词中的一个,一般需要logn的时间复杂度,而使用字典树则只与单词的长度有关。
字典树的定义:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31public class Trie { private class Node{ public boolean isWord; public TreeMap<Character, Node> next; public Node(boolean isWord){ this.isWord = isWord; next = new TreeMap<>(); } public Node(){ this(false); } } private Node root; private int size; public Trie(){ root = new Node(); size = 0; } // 获得Trie中存储的单词数量 public int getSize(){ return size; } }
增加新的单词:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 向Trie中添加一个新的单词word public void add(String word){ Node cur = root; for(int i = 0 ; i < word.length() ; i ++){ char c = word.charAt(i); if(cur.next.get(c) == null) cur.next.put(c, new Node()); cur = cur.next.get(c); } if(!cur.isWord){ cur.isWord = true; size ++; } }
统计是否包含单词:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13// 查询单词word是否在Trie中 public boolean contains(String word){ Node cur = root; for(int i = 0 ; i < word.length() ; i ++){ char c = word.charAt(i); if(cur.next.get(c) == null) return false; cur = cur.next.get(c); } return cur.isWord; }
查询是否在Trie中有单词以prefix为前缀:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13// 查询是否在Trie中有单词以prefix为前缀 public boolean startsWith(String isPrefix){ Node cur = root; for(int i = 0 ; i < isPrefix.length() ; i ++){ char c = isPrefix.charAt(i); if(cur.next.get(c) == null) return false; cur = cur.next.get(c); } return true; }
leetcode相关题目
208. 实现 Trie (前缀树)
解法:使用上述前缀树即可,略。
211. 添加与搜索单词 - 数据结构设计
解法:(难点在于“.”代表任意字符)
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public boolean search(String word) { return match(root, word, 0); } private boolean match(Node node, String word, int index){ if(index == word.length()) return node.isWord; char c = word.charAt(index); if(c != '.'){ if(node.next.get(c) == null) return false; return match(node.next.get(c), word, index + 1); } else{ for(char nextChar: node.next.keySet()) if(match(node.next.get(nextChar), word, index + 1)) return true; return false; } }
677. 键值映射
解法:(难点在于先遍历到前缀的末尾,然后再sum操作)
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public int sum(String prefix) { Node cur = root; for(int i = 0 ; i < prefix.length() ; i ++){ char c = prefix.charAt(i); if(cur.next.get(c) == null) return 0; cur = cur.next.get(c); } return sum(cur); } private int sum(Node node){ int res = node.value; for(char c: node.next.keySet()) res += sum(node.next.get(c)); return res; }
最后
以上就是谦让鸡翅最近收集整理的关于数据结构之树(线段树,字典树)线段树字典树的全部内容,更多相关数据结构之树(线段树内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复