zoukankan      html  css  js  c++  java
  • 数据结构入门

    单调栈与单调队列 Minimum stack / Minimum queue

    问题1:如何O(1)地访问一个栈的最小值?

    Ans:我们给栈里的数附上第二个域,代表在这个数之前(包含这个数)的最小值。

    stack<pair<int, int>> st;
    
    void push(int new_elem){
        int new_min = st.empty() ? new_elem : min(new_elem, st.top().second);
        st.push({new_elem, new_min});
    }
    

    我们单独观察second元素,发现是一个非递增的单调递减数列,所以叫它单调栈。
    刷题时,常常不用存本来的数据(first域),也不需要栈,只用一个数组储存第二个域。

    问题2:如何O(1)地访问一个队列的最小值?

    另一个表述是你有一个长度为n的数组,如何O(n)第从左到右询问所有长度为m的子数组的最小值?

    Ans:
    实现1:单调栈不能从头部pop,如果强行增加pop函数,那么pop掉第一个数会需要O(n)地更新后面的数的second。
    我们考虑只维护一个单调递减序列。
    具体实现如下:
    q.front()存的是队列最小值。
    push时将队列尾部所有大于new_element的数都pop掉。
    pop时先判一下要删的数,如果已经被删掉了就不操作。否则pop掉当前的头。

    deque<int> q;
    void push(int new_element){
        while (!q.empty() && q.back() > new_element)
            q.pop_back();
        q.push_back(new_element);
    }
    void pop(int remove_element){
        if (!q.empty() && q.front() == remove_element)
            q.pop_front();
    }
    

    但是上面的pop操作需要读入一个数,很不自然。
    我们可以通过增加一个储存下标的second域来解决,

    deque<pair<int, int>> q;
    int cnt_added = 0;
    int cnt_removed = 0;
    void push(int new_element){
        while (!q.empty() && q.back().first > new_element)
            q.pop_back();
        q.push_back({new_element, cnt_added});
        cnt_added++;
    }
    void pop(){
        if (!q.empty() && q.front().second == cnt_removed) 
            q.pop_front();
        cnt_removed++;
    }
    

    实现2:
    上面的单调队列都把除了单调递减的数列的数删完了,如何保存所有元素呢?

    可以直接用两个单调栈来模拟。
    往s1里push新元素。
    从s2里pop。 如果s2空了,那么就把s1全都放到s2里面。

    这样就通过s2可以访问栈的开头元素了。

    stack<pair<int, int>> s1, s2;
    int query(){
        int minimum=0;
        if (s1.empty() || s2.empty())
            minimum = s1.empty() ? s2.top().second : s1.top().second;
        else
            minimum = min(s1.top().second, s2.top().second);
        return minimum;
    }
    
    void push(int new_element){
        int minimum = s1.empty() ? new_element : min(new_element, s1.top().second);
        s1.push({new_element, minimum});
    }
    
    void pop(){
        if (s2.empty()) {
            while (!s1.empty()) {
                int element = s1.top().first;
                s1.pop();
                int minimum = s2.empty() ? element : min(element, s2.top().second);
                s2.push({element, minimum});
            }
        }
        int remove_element = s2.top().first;
        s2.pop();
    }
    

    ST表

    intruduction

    ST表,O(nlogn)时间预处理,O(nlogn)空间,O(logn)地查询。
    思想是倍增地去查询,二分地预处理。
    记st[i][j]存的是[i,i+2^j)区间的信息,对于类似max,min,sum的f(),我们有递推方程:
    $ st[i][j] = f(st[i][j-1], st[i +2^{j - 1})][j - 1]);$

    实现

    const int MAXN=1e7;
    const int K=25;
    int st[MAXN][K + 1];
    int a[MAXN];
    int N;
    int f(int a,int b){
        return min(a,b);
    }
    
    void init(){
        for (int i = 0; i < N; i++)
            st[i][0] = a[i];
    
        for (int j = 1; j <= K; j++)
            for (int i = 0; i + (1 << j) <= N; i++)
                st[i][j] = f(st[i][j-1], st[i + (1 << (j - 1))][j - 1]);
    }
    int query(int L,int R){
        int ret=1e9;//change when query max or sum
        //long long sum = 0;
        for (int j = K; j >= 0; j--) {
            if ((1 << j) <= R - L + 1) {
                ret=f(ret,st[L][j]);
                L += 1 << j;
            }
        }
        return ret;
    }   
      
    

    ST最强大的地方在于对于Idempotence的函数(类似max,min这样“同一个元素被运算多次不会产生影响的函数”)可以做到O(1)查询。
    我们考虑将区间分为两段前后有重叠部分的区间:
    (min( ext{st}[L][j], ext{st}[R - 2^j + 1][j]) quad ext{ where } j = log_2(R - L + 1))

    具体的实现方法就是

    int log[MAXN+1];
    void  initlog(){
    
        log[1] = 0;
        for (int i = 2; i <= MAXN; i++)
            log[i] = log[i/2] + 1;
    }
    int query(int L,int R){
        int j = log[R - L + 1];
        int minimum = min(st[L][j], st[R - (1 << j) + 1][j]);
    }
    

    并查集Disjoint Set Union/ Union Find

    问题

    n个集合,支持O(1)合并两个集合与查询某元素所属集合操作。


    intro

    思想就是对每个集合建一棵树。对每个元素维护一个father数组,指向其父亲结点。
    于是查找就可以用O(n)地暴力找到跟结点。
    合并就是将两个根节点并到一起。


    实现

       void make_set(int v) {
       parent[v] = v;
    }
    
    int find_set(int v) {
       if (v == parent[v])
           return v;
       return find_set(parent[v]);
    }
    
    void union_sets(int a, int b) {
       a = find_set(a);
       b = find_set(b);
       if (a != b)
           parent[b] = a;
    }
       
    

    路径压缩+压行

         //int n;int fa[maxn];
    
    void init() {for (int i = 1; i <= n; i++) fa[i] = i;}
    int find(int x) {if (fa[x] == x) {return x;}return fa[x] = find(fa[x]);}
    void un(int x, int y) { int xx = find(x), yy = find(y); fa[yy] = xx; }
    
    

    心路历程

    
    

    成功的路并不拥挤,因为大部分人都在颓(笑)
  • 相关阅读:
    15、TSA数据上传(https://www.ncbi.nlm.nih.gov/genbank/tsaguide/#SP)
    14、SRA数据上传
    14、批量处理文件
    .net mvc 利用分部视图局部刷新.
    观察者模式(Observer)
    内存和性能
    DOM中的事件对象(event)
    HTML事件处理程序
    惰性载入函数
    Comet之SSE(Server
  • 原文地址:https://www.cnblogs.com/SuuT/p/10690957.html
Copyright © 2011-2022 走看看