树状数组 || 前缀树

树状数组:

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 (前缀树)

实现一个 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;

 

}

};

面试题 16.02. 单词频率

设计一个方法,找出任意指定单词在一本书中的出现频率。

你的实现应该支持如下操作:

  • 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;

    }

};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值