静谧的夜最适合刷算法题了。刷着刷着发现了一个好玩的数据结构叫作线段树,听说是算法竞赛的常客哦,因而就本身写一个简单的玩玩。算法
想了解线段树的同志们请移步,里边有原理和示意图: 百度百科,线段树数组
我用它来主要是为了快速找到数组某区间内的数字和,而且在修改数组某几个元素以后再次找区间内的数字和。可想而知,我有两个需求:求和,修改。数据结构
正常状况下,由于咱们并不想给数组排序,那么,咱们能够用O(1)的复杂度进行修改,用O(n)的复杂度遍历区间来实现求和。可是,当咱们须要频繁进行求和的操做时,看起来很美的O(n)就变成了墙上的蚊子血,不再是当初的红玫瑰了,因而,咱们只能优化它。性能
我选择了线段树,典型的空间换时间。我用数组实现了二叉的线段树,用了4倍的额外空间。这颗二叉树的叶子节点是原始数组的各个元素,非叶子节点存储的为某段区间的和,一直到根节点逐步合并区间,根节点正好就是原始数组从头至尾的最大区间的元素和,具体的能够看看代码,我的以为代码比文字更直观哈哈。这样的处理将求和操做变成了O(logN),而相应的修改操做也增长到了O(logN),两个对数级别每每老是要好过一个常数的和一个线性的,不是么?优化
Talk is cheap. Show me the code.ui
首先是构建线段树,明白原理以后就像二叉树同样直接递归着搞就好啦:spa
type segmentTree struct {
data []int //原始数组
tree []int //线段树数组
}
//初始化线段树,至于为何须要4倍空间,只要我们理解了二叉树就一目了然了,线段树每一个节点存储的就是某一段的区间和
func NewSegmentTree(num []int) *segmentTree {
countNum := len(num)
data := make([]int, countNum)
for k, v := range num {
data[k] = v
}
tree := make([]int, 4*countNum)
if countNum > 0 {
var buildTree func(int, int, int) buildTree = func(index, left, right int) {
if left == right {
tree[index] = num[left]
return
}
leftChild := leftChild(index)
rightChild := rightChild(index)
mid := left + ((right - left) >> 1)
buildTree(leftChild, left, mid)
buildTree(rightChild, mid+1, right)
tree[index] = tree[leftChild] + tree[rightChild]
}
buildTree(0, 0, countNum-1)
}
return &segmentTree{data, tree}
}
复制代码
而后就是求和操做了,咱们把各个区间的和分别保存好了,本质上就变成了二叉树上找常数个节点,因此固然是O(logN)了:code
//求和操做,只须要经过递归来找到最近的区间和就好
func (st *segmentTree) SumRange(start, end int) int {
var sum func(int, int, int, int, int) int sum = func(index, left, right, start, end int) int {
if left == start && right == end {
return st.tree[index]
}
leftChild := leftChild(index)
rightChild := rightChild(index)
mid := left + ((right - left) >> 1)
if start >= mid+1 {
return sum(rightChild, mid+1, right, start, end)
} else if end <= mid {
return sum(leftChild, left, mid, start, end)
}
return sum(leftChild, left, mid, start, mid) + sum(rightChild, mid+1, right, mid+1, end)
}
return sum(0, 0, len(st.data)-1, start, end)
}
复制代码
修改操做,好记性不如烂笔头,用笔画画就能够:cdn
//修改操做,递归找到叶子节点改掉索引对应的值,而后回溯的过程改掉包含索引的全部区间的和
func (st *segmentTree) Update(i int, value int) {
countNum := len(st.data)
if i >= len(st.data) {
return
}
st.data[i] = value
var up func(int, int, int) up = func(index, left, right int) {
if left == right {
st.tree[index] = value
return
}
leftChild := leftChild(index)
rightChild := rightChild(index)
mid := left + ((right - left) >> 1)
if i >= mid+1 {
up(rightChild, mid+1, right)
} else if i <= mid {
up(leftChild, left, mid)
}
st.tree[index] = st.tree[leftChild] + st.tree[rightChild]
}
up(0, 0, countNum-1)
}
func leftChild(i int) int {
return (i << 1) + 1
}
func rightChild(i int) int {
return (i << 1) + 2
}
复制代码
新手的代码老是有很大的优化空间的,走过路过的大爷们要不吝赐教哦。排序
固然了,这仅仅只是一个简单的线段树,只能解决个人小需求。还有更多的方案,好比用链表来构建树确定要更灵活,并且线段树并不只仅用来求和,能够实现更多面向区间的操做,更好玩的是能够懒惰更新,每次修改不着急从新构建整棵树,能够一点一点地来,这样更加优化了性能。你们若是对线段树感兴趣的话,查资料去吧~o( ̄︶ ̄)o
算法梦想家,来跟我一块儿玩算法,玩音乐,聊聊文学创做,我们一块儿天马行空!