Trie, Segment Tree,树状数组-----Other Kinds of Tree
字典树(前缀树):
Leetcode208 Implement Trie:
class Trie {
TrieNode root; //attribute, which can show a trie's identity
/** Initialize your data structure here. */
public Trie() { //constuctor, which used to create a new trie instance
root = new TrieNode();
}
//we need to implement three methods: insert, search and startsWith
/** Inserts a word into the trie. */
public void insert(String word) {
TrieNode cur = root;
for(char c: word.toCharArray()){
if(cur.children[c-'a'] == null){
cur.children[c-'a'] = new TrieNode();
}
cur = cur.children[c-'a'];
}
cur.isWord = true;
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
return search(root, word, 0);
}
private boolean search(TrieNode cur, String word, int index){
if(index == word.length()){ //when inddex pointer reach the end of word
return cur.isWord;//check current node has attribute isWord is true or not.
}
char c = word.charAt(index);
TrieNode temp = cur.children[c-'a']; //until you've search every c, which means temp can't be null in every level, if temp == null, then it means trie stoped here but word is not finished
return temp != null && search(temp, word, index+1);
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) { //check if a given prefix is the prefix of current trie
TrieNode cur = root;
for(char c: prefix.toCharArray()){
if(cur.children[c-'a'] == null){
return false;
}
cur = cur.children[c-'a'];
}
return true;
}
}
//Trie Node
class TrieNode {
TrieNode[] children; //children attribute, which is a trieNode array
boolean isWord; //isWord attribute, check if current node is a word or not
public TrieNode(){ //constructor, we don;t have to pass anything for this node,
children = new TrieNode[26]; //initialize children and isWord attribute
isWord = false;
}
}
the code above is very understandable, but there may be many trandormation when we meet sme problems who needs trie.
and we will talk about this in a single article.
线段树
线段树是一种二叉搜索树,与区间树相似,他将一个区间划分为一些单元区间。每个区间单元对应线段树的一个叶节点。也就是说对于线段树中的每一个非叶子节点[a, b],他的左儿子表示[a, (a+b)/2] 右儿子表示的区间为[(a+b).2+1, b]。因此线段树也是balanced bianry tree的一种。所以最后子节点的数目为N,即整个线段区间的长度。
线段树的非常典型的作用就是用来求sumRange的,可以快速地得到一串数组中的任意区间的和(或者积),而且如果即使数组中的某个值要被更新的话,更新+查询 仍然只需要logn
public class SegmentTreeByTreeNode {
SegmentTreeNode root; //like trie, the indentity of segment tree is its root
public SegmentTreeByTreeNode(int[] nums) { //the constuctor takes an array as the parameter, and build them
root = buildTree(nums, 0, nums.length - 1); //
}
private SegmentTreeNode buildTree(int[] nums, int start, int end) {//Build tree using given nums
if (start > end) {
return null;
}
SegmentTreeNode cur = new SegmentTreeNode(start, end); //initialize the root
if (start == end) {
cur.sum = nums[start]; //or nums[end], whatever
} else {
int mid = start + (end - start) / 2;
cur.left = buildTree(nums, start, mid);//左右子树各自递归一手 进行建立
cur.right = buildTree(nums, mid + 1, end);
cur.sum = cur.left.sum + cur.right.sum;//与此同时 也要维护每个节点的sum属性
}
return cur;//
}
//implement two methods: update and sumRange
public void update(int i, int val) {//线段树的一个方法update
update(root, i, val);
}
private void update(SegmentTreeNode cur, int pos, int val) { //需要更新某pos处的值为val
//利用递归进行更新,自己想一下 某个叶子节点的值是一定要更新的 而且越靠近根节点
if (cur.start == cur.end) {//当cur指向的是叶子节点
cur.sum = val;//那么就只更新这个叶子节点的sum属性为val即可
return;//直接返回即可
}
int mid = cur.start + (cur.end - cur.start) / 2;//否则就一层一层的采用递归定位 pos的位置
if (pos <= mid){
update(cur.left, pos, val);
} else {
update(cur.right, pos, val);
}
cur.sum = cur.left.sum + cur.right.sum;//而且针对每一层的cur的sum属性都要进行更新 虽然看不太懂 但是大概就是这个意思
}
public int sumRange(int i, int j) {//线段树的另一个方法sumRange
return sumRange(root, i, j);
}
private int sumRange(SegmentTreeNode cur, int start, int end) { //query range is from given start and end
if (cur.end == end && cur.start == start) { //when we find that specific interval
return cur.sum; //we return the sum. and the total time to find such a interval is logn
}
int mid = cur.start + (cur.end - cur.start) / 2;
if (end <= mid) {//说明 start~end全部都在mid前面
return sumRange(cur.left, start, end);
} else if (start >= mid + 1) {//说明start~end全部都在mid+1后面
return sumRange(cur.right, start, end);
} else {//如果start~end中包含了mid 就说明横跨了 分成两个区段继续递归 是真的巧妙
return sumRange(cur.left, start, end) + sumRange(cur.right, start, end);
}
}
}
public class SegmentTreeNode { //Node class
int start;//one node needs 5 attributes to show its indentity
int end;
SegmentTreeNode left;
SegmentTreeNode right;
int sum;
public SegmentTreeNode(int start, int end) { //and we need two parameter to construct a node
this.start = start;
this.end = end;
this.right = null;
this.left = null;
this.sum = 0;
}
}
树状数组(binary indexed tree)
与线段树相似 这也是一种可以高效处理:**对一个存储数字的列表进行更新以及求前缀和的数据结构,**但是与线段树的树的本质不同 树状数组是通过数组实现的这种高效处理的功能。
因此 树状数组也可以叫:基于数组实现的线段树。
仔细观察下面的代码 树状数组甚至有点像基于数组的heap(因为起父节点与子节点的index关系都为index–2index+1–2index+2)
public class SegmentTreeByArray {
private int[] segmentTree;
private int length;
public SegmentTreeByArray(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
segmentTree = new int[4 * nums.length];//这也从侧面反映了为什么空间复杂度是4n 为什么4n,因为本来2n就够了 但是其叶子节点仍然要判断2*index+1和2*index+2即两个左右孩子 因此还需要乘以2
length = nums.length - 1;
buildTree(nums, 0, nums.length -1, 0);
}
private int buildTree(int[] nums, int start, int end, int index) {//用数组建立起线段树的方式
if (start == end) {
segmentTree[index] = nums[start];
return segmentTree[index];
}
int mid = start + (end - start) / 2;
int left = buildTree(nums, start, mid, index * 2 + 1);//注意递归的最后一个参数是2倍 为什么是这样呢?这就是在完全二叉树中的数组表示的左孩子右孩子和父节点index之间的关系
int right = buildTree(nums, mid + 1, end, index * 2 + 2);//双递归建立起这个树状数组,就像线段树的建立一样
segmentTree[index] = left + right;
return segmentTree[index];//最后返回根节点的值
}
public void update(int i, int val) {
update(0, length, 0, i, val);
}
private void update(int start, int end, int index, int pos, int val) {
if (start == end) {
segmentTree[index] = val;
return;
}
int mid = start + (end - start) / 2;
if (pos <= mid) {
update(start, mid, index * 2 + 1, pos, val);
} else {
update(mid + 1, end, index * 2 + 2, pos, val);
}
segmentTree[index] = segmentTree[index * 2 + 1] + segmentTree[index * 2 + 2];
}
public int sumRange(int i, int j) {
return sumRange(0, length, 0, i, j);
}
private int sumRange(int start, int end, int index, int left, int right) {
if (left <= start && end <= right) {
return segmentTree[index];
}
int mid = start + (end - start) / 2;
if (right <= mid) {
return sumRange(start, mid, index * 2 + 1, left, right);
} else if (left > mid) {
return sumRange(mid + 1, end, index * 2 + 2, left, right);
} else {
return sumRange(start, mid, index * 2 + 1, left, right)
+ sumRange(mid + 1, end, index * 2 + 2, left, right);
}
}
}
总结:总体来说,Segment Tree和树状数组属于同一种,而Trie实际上和另外两种没什么关系。

浙公网安备 33010602011771号