zoukankan      html  css  js  c++  java
  • 数据结构:从入门到精通

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

    咕了,以后再更新。。。

    前言

    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;
    }
    
  • 相关阅读:
    黑客防线
    基于onvif的码流转换专利
    8168开发之---1g内存换成512M的内存映射配置
    图像处理之基础---内积、点积
    3s 简介
    嵌入式开发之工具---比开发手册更重要的一个命令 man page
    图像处理之基础---频域分析
    lbp纹理特征
    28.Docker介绍与目录
    09.客户端集成IdentityServer
  • 原文地址:https://www.cnblogs.com/hyfhaha/p/10678326.html
Copyright © 2011-2022 走看看