• 数据结构:从入门到精通


    其实这里只不过是个人学数据结构的总结罢了

    咕了,以后再更新。。。

    前言

    OI中的算法有很多,不同算法解决的问题不一样,时间复杂度也不一样,那么在众多OIer中,就有一些毒瘤,他们弄出了可以让程序变快的东西。那就是数据结构。另外,数据结构都很毒瘤,他们都有一个特点,就是代码特长

    基础数据结构入门

    其实并不是所有数据结构都很难,下面就讲一下最基础的数据结构,他们是“队列”,“优先队列”,“栈”,“动态数组”。

    队列

    队列是个什么呢?队列是一种先进先出的数列。

    简单的说,队列就像是食堂排队等饭,先来的先吃饭(不考虑插队情况)。然后排队当然有几种操作。

    1.看看队首那个人是谁,好扣他钱

    2.队首那个人打好了饭,赶走他

    3.又来新的人排队

    这就是队列的基本操作,他们分别对应的代码是

    q.front();
    
    q.pop();
    
    q.push(新来的人);
    

    这些只是STL的操作,那STL是什么呢

    STL

    STL是C++的一个库,他封装了C++很多的算法,使用起来非常方便,但相对于自己用数组实现起来会慢一些。但STL是针对于O2优化而开发的,所以在开O2情况下,STL会跑得很快,以至于比手写的还要快。本文章并不专讲STL,如果想全面了解STL可以到度娘那搜一下。

    想要调用STL的队列就要包括头文件:

    #include<queue>
    

    定义一个队列是这样的:

    queue<队列类型:int,char之类的>变量名;
    

    优先队列

    优先队列是在队列的基础之上,多了个优先级,也就是说,你本来排队快可以领饭盒了,这时候突然来了一个人,按理来说他肯定要排你后面,但是他竟然是有病条的人,那么他就可以排你前面,毕竟要尊重得病之人。如果此时来了个病更加重的人,那么他就排你俩前面了。

    那么优先队列就是这样的,病越重,优先级越高,就越靠近队首。

    和队列一样也是那三种操作:

    q.top();//和队列的q.front()意思一样
    
    q.pop();
    
    q.push(新来的人);
    

    和队列所包含的头文件一样,定义有点不一样:

    priority_queue<队列类型:int,char之类的>变量名;
    

    与队列相反,栈是一种先进后出的数据结构。就是说先来的会比后来的更晚出去。

    简单的说,你走入一个栈内,如果没有意外的话,一接到出栈命令就可以出栈了。但是,如果来了其他人,那么就要等到其他人都出完栈了,你才可以出栈。

    栈有三种基本操作:

    1.查看栈顶是谁(和队列的1操作一样)

    2.弹出栈顶元素(和队列的2操作一样)

    3.将新元素压入栈内(和队列的3操作一样)

    分别对应的代码:

    q.top();
    
    q.pop();
    
    q.push();
    

    这里使用的依然是STL。需包含头文件

    #include<stack>
    

    定义

    stack<队列类型:int,char之类的>变量名;
    

    动态数组

    从名字可以看出,这个东西的本质是数组,所以动态数组可以像数组一样随机访问。

    至于动态数组有什么优点,那就是省空间。有时候为了题目的限制,我们会将数组开得很大,然而实际上有很多空间是不需要用的,如我定义了一个100的数组,然而真正需要用的只有10这么多,那就浪费了90的空间。而动态数组的优点就在这里了,他不会有这种浪费,也就是动态数组的空间是会发生改变的!

    这里介绍几个动态数组的基本操作:

    1.push_back 将一个元素加入到动态数组

    v.push_back(要插入的元素);
    

    2.size() 查询动态数组此时的空间大小

    s=v.size();
    

    3.clear() 清空动态数组

    v.clear();
    

    这里还是用STL,需包含头文件

    #include<vector>
    

    定义:

    vector<队列类型:int,char之类的>变量名;
    

    结束言

    恭喜!你已经学会了最基础的数据结构了,打好基础,相信将来的复杂数据结构你一定都可以学会。


    数据结构进阶:线段树

    学会了基础的数据结构,那么就可以学一些比较毒瘤的数据结构了,在这些数据结构中,思想最简单,也是最容易实现的,就是线段树

    线段树解决是区间问题,例如:【模板】树状数组 1【模板】树状数组 2【模板】线段树 1

    首先看看线段树的结构,他是一颗树,据有树的性质。如下图:这就是一颗线段树。

    (color{red} ext{红色}) :节点编号 ; (color{blue} ext{蓝色}):感性理解(看图就懂了吧,父子关系);(color{green} ext{绿色}):每个点的权值。

    我们来看看(color{red} ext{红色})有什么奇妙的性质吧。

    有人就会说:依节点深度从左至右依次增加.

    对了吗?对!但能不能更具体一点?这样计算机听不懂啊!

    性质1:对于一个节点编号为(x),它的左儿子编号为((x*2)),右儿子编号为((x*2+1))

    转换为代码:

    #define L(node) (node*2)
    #define R(node) (node*2+1)
    

    或许有人看不懂#define 是什么意思,第一句语句的意思是:代码中凡是遇到L(node)这个语句,都将其解释(node*2),所以L(node)就相当于表示node左儿子。第二句同理。如果还是没看懂,没关系,我下面会结合代码解释。

    那建出这些节点有什么用呢?

    线段树维护信息

    细心的人会发现,上面的图,(1)号节点画得特别大,扩住了整个序列编号越大,节点越小(指形状)。

    那么这些节点的大小(指形状)代表什么呢? 代表区间的大小!

    例如:(1)号节点代表的区间是({1,2,3,4,5,6,7,8}) ,(5)号节点代表的区间是({3,4}),(13)号节点代表的是({6})

    明显:最底下的(8)叶子节点就代表整个序列的一个位置

    那么我们就可以将权值赋值到叶子节点上了。

    例如:(8)号节点的权值(1)(11)号节点的权值(6)(15)号节点的权值(7)

    假如我们要维护的是区间和。

    那么区间([1,2])的值就是(区间([1])的值+区间([2])的值)。

    代表区间([1,2])的节点是(4)号节点。代表区间([1])的节点是(8)号节点。代表区间([2])的节点是(9)号节点。

    (8)(9)号节点的权值我们都知道。

    那么(4)号节点的权值不就可以求出来了吗。

    并且我们惊奇的发现(8)(9)号节点就是(4)号节点两个儿子。

    性质2:对于一个节点(x)(x)的信息从(x)的两个儿子转移得到

    这个转移我们可以写成函数pushup(这是主流写法),例如要维护区间和:((tree)数组代表权值)

    void pushup(int node){
        tree[node]=tree[L(node)]+tree[R(node)];
    }
    

    由此可以看出线段树是自底向上的数据结构。

    线段树的实现(一)

    线段树的实现是通过递归实现的。

    建树

    我们只需要将叶子节点的信息都赋值好,剩下的可以不断pushup上去。

    递归时传(3)个参数:(node)当前节点编号,(begin)当前区间左边界,(end)当前区间右边界。

    那么我们从(1)号节点(根节点),向下递归,递归左儿子,递归右儿子。

    直到到叶子节点为止。((begin==end)

    叶子节点信息为序列当前位置的值。((tree[node]=a[begin])(a)数组为输入的初始值)

    递归完左、右儿子,pushup一遍,返回。


    到现在为止,我只不过是讲了些线段树的性质罢了。下面我们来讲线段树的操作。

    操作

    线段树最普遍的操作就是区间修改区间查询.(当然还有什么优化建边什么的,我之后再讲。)

    查询

    假如我们要查询一个位置(x)的值,怎么做?

    这不直接输出(a[x])就可以了吗?(???

    是的(汗)。那用线段树怎么做?

    嗯……(认真思考),递归到(x)所对应的叶子节点(node),输出权值。(灵机一动)

    没错,看来基础还挺扎实的吗。

    单点查找的线段树十分简单,只需要递归到相应的叶子节点,返回叶子节点信息即可。

    查询一:找到要修改位置对应的叶子节点位置,返回节点值

    假如我们要查询一个区间([x,y])的值,怎么做?

    前缀和!(得意

    是的(汗)。那用线段树怎么做?

    嗯……(认真思考),将区间所有点都像上面一样查一遍,相加。(虚

    ε=( o`ω′)ノ怒!线段树的节点意义忘了吗!

    哦……(咕

    例如我们要查询区间([2,6])的和。

    可以发现([2,6])区间是可以由几个区间组合而成的:([2]),([3,4]),([5,6]).

    如上图(很上的图),这三个区间都分别对应着线段树中的三个节点。

    那我们将那三个节点的值相加即可得到答案。(高明)

    修改

    假如我们要修改序列中一个位置(x)的值,怎么改?

    直接改掉!(暴力,傲娇

    那你爸怎么办?

    呃呃呃额,pushup上去吧(虚了一点

    太棒了!就这样。

    Σ(っ °Д °;)っ惊!(。。。

    单点修改的线段树就如上面对话的方式那样:

    修改一:找到要修改位置对应的叶子节点位置,直接改掉,然后pushup上去。

    假如我们要修改序列中一段区间([x,y])的值,怎么改?

    直接改掉!(暴力,傲娇

    怎么直接改?

    呃呃呃额,一个一个点修吧(虚了

    真是笨啊,线段树的节点意义忘了吗!

    难道……(咕

    #include<bits/stdc++.h>
    #define maxn 40000000
    #define int long long
    using namespace std;
    int tree[maxn],tag[maxn],a[maxn];
    int n,m,x,y,z,mode,t;
    void maketree(int node,int begin,int end){
        if(begin==end){
            tree[node]=a[begin];
            return ;
        }int mid=(begin+end)/2;
        maketree(node*2,begin,mid);
        maketree(node*2+1,mid+1,end);
        tree[node]=tree[node*2]+tree[node*2+1];
    }
    void pushdown(int node,int begin,int end){
        if(tag[node]){
            tag[node*2]+=tag[node];
            tag[node*2+1]+=tag[node];
            int mid=(begin+end)/2;
            tree[node*2]+=tag[node]*(mid-begin+1);
            tree[node*2+1]+=tag[node]*(end-mid);
            tag[node]=0;
        }
    }
    void update(int node,int begin,int end,int x,int y,int val){
        if(begin>=x&&end<=y){
            tree[node]+=val*(end-begin+1);
            tag[node]+=val;
            return ;
        }int mid=(begin+end)/2;
        pushdown(node,begin,end);
        if(x<=mid)update(node*2,begin,mid,x,y,val);
        if(y>mid)update(node*2+1,mid+1,end,x,y,val);
        tree[node]=tree[node*2]+tree[node*2+1];
    }
    int query(int node,int begin,int end,int x,int y){
        if(begin>=x&&end<=y){
            return tree[node];
        }int mid=(begin+end)/2,res=0;
        pushdown(node,begin,end);
        if(x<=mid)res+=query(node*2,begin,mid,x,y);
        if(y>mid)res+=query(node*2+1,mid+1,end,x,y);
        return res;
    }
    signed main(){
        scanf("%lld%lld",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        maketree(1,1,n);
        for(int i=1;i<=m;i++){
            scanf("%lld",&mode);
            if(mode==1){
                scanf("%lld%lld%lld",&x,&y,&z);
                update(1,1,n,x,y,z);
            }if(mode==2){
                scanf("%lld%lld",&x,&y);
                printf("%lld
    ",query(1,1,n,x,y));
            }
        }
        return 0;
    }
    
  • 相关阅读:
    Java实现各种内部排序算法
    Java实现堆排序(大根堆)
    Java对象的序列化和反序列化
    Java实现链式存储的二叉查找树(递归方法)
    337. House Robber III(包含I和II)
    318. Maximum Product of Word Lengths
    114. Flatten Binary Tree to Linked List
    106. Construct Binary Tree from Inorder and Postorder Traversal
    105. Construct Binary Tree from Preorder and Inorder Traversal
    96. Unique Binary Search Trees(I 和 II)
  • 原文地址:https://www.cnblogs.com/hyfhaha/p/10678326.html
Copyright © 2020-2023  润新知