树状数组:
https://www.cnblogs.com/xenny/p/9739600.html
https://www.cnblogs.com/yewanting/p/10801939.html
- 理解要点一:
树状树的结构是固定的,是由节点序号的二进制末尾0的个数决定的。
下表:1,独立;2管辖1;3独立;4管辖123;5独立,6管辖5,7独立;8管辖1~7
结点号 | 二进制 | 结论 |
1 | 00001 | 独立 |
2 | 00010 | 管辖1 |
3 | 00011 | 独立 |
4 | 00100 | 管辖123 |
5 | 00101 | 独立 |
6 | 00110 | 管辖5 |
7 | 00111 | 独立 |
8 | 01000 | 管辖1~7 |
- 理解要点二:修改数据影响的是本身及其领导。
比如,1的更新,同时要更新2,也要更新4和8.也就是所有的i+lowbit(i)的结点。
- 理解要点三:查询的时候要依次把 末尾的1减去后 得到的结点的值 相加
假设我们要查13这个位置的前缀和:
那么怎么知道为多少呢。
实际上就查询这三部分,那么这三部分是怎么来的
将13 化为2进制
1101 = 13; 管辖2^0 = 1个元素
现将末尾的1去掉变成
1100 = 12;管辖2^2 = 4个元素;
再将末尾的1去掉变成
1000 = 8;管辖2^3 = 8个元素
加起来刚好也是13个元素;
实际上13的前缀和为d[13]+d[12]+d[8] (这里也可以验证一个区间内的元素不会超过logn个,所以单次查询的复杂度最多为logn)
所以实际上如果要求这个我们就需要知道最低位的1在哪,将其减掉。
查询12的时候,就是c[12]+c[8]了
代码:
//单点修改,区间查询
int n;
int a[1005],c[1005]; //对应原数组和树状数组
int lowbit(int x){
return x&(-x);
}
void updata(int i,int k){ //在i位置加上k
while(i <= n){
c[i] += k;
i += lowbit(i);
}
}
int getsum(int i){ //求A[1 -> i]的和
int res = 0;
while(i > 0){
res += c[i];
i -= lowbit(i);
}
return res;
}
//区间修改,单点查询
void range_updata(int l,int r,int k){//在[l,r]区间加上k
update(l,k);
update(r+1,-k)
}
int ask(int i){
getsum(i)-getsum(i-1);
}
应用1:
面试题 10.10. 数字流的秩
假设你正在读取一串整数。每隔一段时间,你希望能找出数字 x 的秩(小于或等于 x 的值的个数)。请实现数据结构和算法来支持这些操作,也就是说:
实现 track(int x) 方法,每读入一个数字都会调用该方法;
实现 getRankOfNumber(int x) 方法,返回小于或等于 x 的值的个数。
来自 <https://leetcode-cn.com/problems/rank-from-stream-lcci/>
class StreamRank {
private:
int a[50002];
public:
StreamRank() {
for (int i=0;i<=50001;++i) a[i]=0;
}
void track(int x) {
++x;//++x是为了避免x=0;
for (int i=x;i<=50001;i+=i&(-i)) a[i]++;
}
int getRankOfNumber(int x) {
++x;
int sum=0;
for (int i=x;i;i-=i&(-i)) sum+=a[i];
return sum;
}
};
应用2
求逆序对:
前缀树
实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。
示例:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 true
trie.search("app"); // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");
trie.search("app"); // 返回 true
class Trie {
private:
struct Node{
bool isend;
Node* next[26];
Node(int x):isend(x){
memset(next,NULL,sizeof(next));//这个函数多用
}
};
Node* root;
public:
Trie() {
root=new Node(0);//这里一定要新建
}
void insert(string word) {
Node *cur=root;
for(char c:word){
if(!cur->next[c-'a'])//不存在则新建
cur->next[c-'a']=new Node(0);
cur=cur->next[c-'a'];
}
cur->isend=1;//很关键
}
bool search(string word) {
Node *cur=root;
for(char c:word){
if(!cur->next[c-'a'])
return false;
cur=cur->next[c-'a'];
}
return cur->isend;//关键
}
bool startsWith(string prefix) {
Node *cur=root;
for(char c:word){
if(!cur->next[c-'a'])
return false;
cur=cur->next[c-'a'];
}
return true;
}
};
设计一个方法,找出任意指定单词在一本书中的出现频率。
你的实现应该支持如下操作:
- WordsFrequency(book)构造函数,参数为字符串数组构成的一本书
- get(word)查询指定单词在书中出现的频率
class WordsFrequency {
private:
struct Node{
bool end;
int sum;
Node* next[26];
Node(int x):end(x),sum(0){
memset(next,NULL,sizeof(next));
}
};
Node *root;
public:
WordsFrequency(vector<string>& book) {
root=new Node(0);
for(string str:book){
Node *cur=root;
for(char c:str){
if(cur->next[c-'a']==NULL)
cur->next[c-'a']=new Node(0);
cur=cur->next[c-'a'];
}
cur->end=1;
cur->sum++;
}
}
int get(string word) {
Node* cur=root;
for(char c:word){
if(!cur->next[c-'a'])
return 0;
cur=cur->next[c-'a'];
}
if(cur->end) return cur->sum;
else return 0;
}
};