• c++ stl在acm的入门及使用


    stl的全称为Standard Template Library,即为标准模板库,它主要依赖于模板,而不是对象,所以你需要对这个模板进行实例化,选择你要使用的类型。我们用的都是一些简单的容器吧

    这里可以查到很多c++的文档http://www.cplusplus.com

    http://zh.cppreference.com/ 这个网站查文档也挺友好的,这个是中文版的

    模板是一个怎样的东西呢,可以看下这个TOJ5250

    题意就是让我去实现一个不定长可以放任意内容的数组。

    我的实现代码

    #include <iostream>
    #include <malloc.h>
    using namespace std;
    template<typename T>
    class Vector{
       private:T *p;
       int size;
       int n;
       public:
         Vector()
        {p=(T*)malloc(10*sizeof(T));
        size=10;
        n=0;}
        void Push_back(const T a){
        if(n==size){p=(T*)realloc(p,10*sizeof(T));size+=10;}
        *(p+n)=a;
        n++;
        }
        typedef T* Iterator;
        T* Begin()
        {return p;}
        T* End()
        {return p+n;}
    };
    int vector, deque, list, forward_list, array, string;//禁止使用vector, list等
    int main()
    {
        Vector<int> a;
        for(int i=1;i<=5;i++)
            a.Push_back(i);
        Vector<int>::Iterator it;
        for(it=a.Begin();it!=a.End();++it)
        {
            cout<<*it<<endl;
        }
    }

    里面我用了一些类的东西,暂且跳过。

    void Push_back(const T a){
        if(n==size){p=(T*)realloc(p,10*sizeof(T));size+=10;}
        *(p+n)=a;
        n++;
        }
    看下这个部分好了,就是给了一个模板T,我每次进行查询,不够的话我就多开10个(当然stl内的实现和这个不一样,他是直接开二倍了,stl往往比
    手写的要强大要安全)
    网上资料很多,不懂得可以百度,但是要知道这些我们往往是不用懂的,你只用知道怎么用就行了,知道哪些函数就可以了,那些代码编译器会自动帮你生成

    所以我们现在就是要知道怎么用就行,可以百度,可以查阅文档

    百度一般会给你相关示例,你只要学会操作就行。

    比如百度c++ vector就能得到各种各样有用的信息,我直接筛选了一篇博客园的点我

    但是我还可以查文档,直接在刚才的网站search下vector,即可得到如下结果点我

    由于我们搞进来了不同的类型进来,以往的内存管理不再有效,往往使用迭代器进行操作,这里就能看到他提供了这么多的直接得到的迭代器

    我们对vector进行从前往后遍历可以

    #include<stdio.h>
    #include<iostream>
    #include<vector>
    using namespace std;
    int main()
    {
        vector<int> vec;
        //vec.push_back(1);
        vector<int>::iterator it;
        for(it=vec.begin();it!=vec.end();it++)
        {
            cout<<*it<<endl;
            //printf("%d
    ",*it);
        }
        return 0;
    }

    我们对vector进行从后往前遍历可以

    #include<stdio.h>
    #include<iostream>
    #include<vector>
    using namespace std;
    int main()
    {
        vector<int> vec;
        //vec.push_back(1),vec.push_back(2);
        vector<int>::reverse_iterator it;
        for(it=vec.rbegin();it!=vec.rend();it++)
        {
            cout<<*it<<endl;
            //printf("%d
    ",*it);
        }
        return 0;
    }

    因为我们要用反向迭代器,所以你的类型应该换为reverse_iterator,否则编译器要提示两者为不同类型的

    在C++11里,还有一种是auto大法,auto可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型

    所以从前往后的遍历也可以这样(前提是必须支持C++11,也许你的CB无法编译,可以看下这个

    代码就被我缩短成了这样 。

    #include<stdio.h>
    #include<iostream>
    #include<vector>
    using namespace std;
    int main()
    {
        vector<int> vec;
        //vec.push_back(1),vec.push_back(2);
        for(auto X:vec)
        {
            cout<<X<<endl;
            //printf("%d
    ",*it);
        }
        return 0;
    }

     这里面告诉了你vector的相关元素,比如push_back就是在最后加一个元素,pop_back就是删除最后一个元素,还有erase,swap之类的,所以这些都是要对元素或者迭代器进行操作

    vec.push_back(1);//将1放进去这个容器
    vec.push_back(2);//将2放进去这个容器
    vec.pop_back();//将最后一个删除
    vec.erase(vec.begin());//将第一个元素删除

    大致也可以看出来你需要传递的参数,push_back是要传递你定义的类型,pop_back不需要传递参数,erase需要传递你要删除的元素的迭代器

    开一个二维数组,我们可以使用[]运算符,这个经常在图的相关题中使用,写起来比较简单而且不会遇到空间的问题

    所以我们现在进行操作就要确定下我们第一维的位置

    刚才的代码就可以这样写了

    #include<stdio.h>
    #include<iostream>
    #include<vector>
    using namespace std;
    int main()
    {
        vector<int> vec[10];
        vec[0].push_back(1);//将1放进去这个容器
        vec[0].push_back(2);//将2放进去这个容器
        vec[0].pop_back();//将最后一个删除
        vec[0].erase(vec[0].begin());//将第一个元素删除
        for(auto X:vec[0])
        {
            cout<<X<<endl;
            //printf("%d
    ",*it);
        }
        return 0;
    }

    这里向我们说明了vector里面有下标运算符,还有函数可以直接获得他的size(),这样就能和C的数组一样使用简单了

    #include<stdio.h>
    #include<iostream>
    #include<vector>
    using namespace std;
    int main()
    {
        vector<int> vec;
        //vec.push_back(1);//将1放进去这个容器
        //vec.push_back(2);//将2放进去这个容器
        for(int i=0;i<(int)vec.size();i++)
        {
            cout<<vec[i]<<endl;
            //printf("%d
    ",*it);
        }
        return 0;
    }

    倒序的话也可以这样

    #include<stdio.h>
    #include<iostream>
    #include<vector>
    using namespace std;
    int main()
    {
        vector<int> vec;
        //vec.push_back(1);//将1放进去这个容器
        //vec.push_back(2);//将2放进去这个容器
        for(int i=vec.size()-1;i>=0;i--)
        {
            cout<<vec[i]<<endl;
            //printf("%d
    ",*it);
        }
        return 0;
    }

     接下来就不是不定长的字符串了,我们使用的是string

    str的输入要使用cin和cout,用将其转变为c数组要用c_str,这样就能printf了

    #include<iostream>
    #include<stdio.h>
    #include<string>
    using namespace std;
    int main()
    {
        string str;
        cin>>str;
        cout<<str<<"
    ";
        printf("%s
    ",str.c_str());
        return 0;
    }

    这里可以看到它支持+操作,当然也可以使用函数append,函数也更优秀

    但是直接+,直接进行比较显然比我在C时代进步很多了

    strcat strcmp strcpy这些函数只会让我感到复杂,读取一行在C里面用gets,string的话直接getline

    #include<iostream>
    #include<stdio.h>
    #include<string>
    using namespace std;
    int main()
    {
        string str1="123",str2="124";
        cout<<str1+str2<<"
    ";
        cout<<(str1>str2)<<"
    ";
        cout<<(str1==str2)<<"
    ";
        cout<<(str1<str2)<<"
    ";
        getline(cin,str1);
        cout<<str1<<"
    ";
        return 0;
    }

    有length(),也有size(),还有下标运算符,所以也可以像vec那样进行操作,不再赘述。

    所以vector+string会擦出怎样的火花呢

    #include<iostream>
    #include<vector>
    #include<string>
    using namespace std;
    int main()
    {
        vector<string>vec;
        string s;
        while(cin>>s)
        {
            vec.push_back(s);
        }
        for(auto X:vec)
        {
            cout<<X<<"
    ";
        }
        return 0;
    }

    那有人就要不服气了,为什么不直接string数组呢,我string一个数组,string s[N],但是这样操作起来并不能用vector的一些

    函数了,所以这个用什么还是要自己去选择

    上次训训练赛贪心取字典序 CF496C

    我这样写就感觉很简单了,不知道要比用C简单多少

    我采用的思路就是每次加上这一列,看其合不合法

    #include<bits/stdc++.h>
    using namespace std;
    string s[105],t[105],c[105];
    int main()
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=0; i<n; i++)
            cin>>s[i];
        for(int j=0; j<m; j++)
        {
            int f=1;
            for(int i=0; i<n; i++)
                t[i]+=s[i][j];
            for(int i=1; i<n; i++)
                if(t[i]<t[i-1])f=0;
            if(f)
            {
                for(int i=0; i<n; i++)c[i]=t[i];
            }
            else
            {
                for(int i=0; i<n; i++)t[i]=c[i];
            }
        }
        printf("%d",s[0].size()-c[0].size());
        return 0;
    }

     接下来介绍下算法头文件的一些函数

    比较常用的就是sort了

    algorithm http://www.cplusplus.com/reference/algorithm/

    函数很多呢,很多也挺常用,特别是Sorting:下的sort,sort非常快,但是它不是稳定排序,如果需要特别稳定的排序请用stable_sort

    default (1)
    template <class RandomAccessIterator>
      void sort (RandomAccessIterator first, RandomAccessIterator last);
    
    custom (2)
    template <class RandomAccessIterator, class Compare>
      void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

    可以看到sort的使用方法,需要传给他排序的头尾迭代器,和比较函数

    #include<bits/stdc++.h>
    using namespace std;
    vector<string>S;
    int cmp(string s,string c)
    {
        return s>c;
    }
    int main()
    {
        S.push_back("123");
        S.push_back("112");
        S.push_back("012");
        S.push_back("12345");
        sort(S.begin(),S.end());
        //for(auto X:S)cout<<X<<"
    ";
        sort(S.rbegin(),S.rend());
        //for(auto X:S)cout<<X<<"
    ";
        //sort(S.begin(),S.end());
        sort(S.begin(),S.end(),cmp);
        //for(auto X:S)cout<<X<<"
    ";
        sort(S.begin(),S.end());
        sort(S.begin(),S.end(),greater<string>());
        //for(auto X:S)cout<<X<<"
    ";
        sort(S.begin(),S.end(),less<string>());
        //for(auto X:S)cout<<X<<"
    ";
        sort(S.begin(),S.end(),[]
        (const string &s,const string &c)
        {
        return s>c;
        }
        );
        //for(auto X:S)cout<<X<<"
    ";
        return 0;
    }

    我这里还是提供了很多种写法

    sort(S.begin(),S.end());//对字符串进行字母序从小到大排序,因为string重载了<运算符
    sort(S.rbegin(),S.rend());//对字符串进行字母序从大到小排序,这里直接利用那个rbegin()和rend(),省得写得麻烦
    sort(S.begin(),S.end(),cmp);//这里是对运算符进行了重新定义
    sort(S.begin(),S.end(),greater<string>());//从大到小排序,这个在优先队列里也会使用
    sort(S.begin(),S.end(),less<string>());//补充上缺省函数,即默认值

    然后比较常用的就是nth_element,它会进行部分排序,使你尽快找到这个第几大的数,也就是左边全是小于他的,右边全是大于他的

    里面的二分也很舒服,但必须查询的是有序容器,查询有没有可以binary_search

    这里介绍下lower_bound()

    函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置

    举例如下:

    一个数组number序列为:4,10,11,30,69,70,96,100.设要插入数字3,9,111.pos为要插入的位置的下标

    pos = lower_bound( number, number + 8, 3) - number,pos = 0.即number数组的下标为0的位置。

    pos = lower_bound( number, number + 8, 9) - number, pos = 1,即number数组的下标为1的位置(即10所在的位置)。

    pos = lower_bound( number, number + 8, 111) - number, pos = 8,即number数组的下标为8的位置(但下标上限为7,所以返回最后一个元素的下一个元素)。

    所以,要记住:函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置,且last的位置是越界的!!~

    这个相关题目和题解在这里

    还有可以用fill+二分实现最长递增子序列的,暑假上海的那个A序列

    fill(g,g+n,infinity);
    for(int i=0;i<n;i++) {
        int j=lower_bound(g, g+n,a[i])-g;
        f[i]=j+1;
        g[j]=a[i];
    }

    还有个东西是heap也挺好用的,请自行百度或者查文档

    还有也常用最大最小这些

    数据结构还会有链表和队列和栈

    在c++里分别是list,queue,stack

    list

    insert() O(1)  //链表实现,所以插入和删除的复杂度的O(1)
    erase()  O(1)

    vector的这两个操作都是O(n),这个就是链表的特性了

    #include<bits/stdc++.h>
    using namespace std;
    list<int>S;
    int main()
    {
        S.push_front(3);
        S.push_back(1);
        S.push_front(2);
        S.pop_front();
        S.pop_back();
        list<int>::iterator it=S.begin();
        it++;
        S.insert(it,10);
        S.insert(it,2,20);
        S.erase(it);
        return 0;
    }

     注意这两个insert操作不同,而且必须直接给它迭代器,你会发现以前的S.begin()+n不能用了,因为我们是链表实现的,只能一个往下一个遍历,但是我们事先存起来就好了

    queue队列

    先进先出,我们排队买东西,但是我先到就要先得

    #include<bits/stdc++.h>
    using namespace std;
    queue<int>S;
    int main()
    {
        S.push(1);
        S.push(2);
        S.pop();
        int n=S.size();
        if(S.empty()){}
        return 0;
    }

    还经常用到一个队列是优先队列,它是用的二叉堆,每次插入删除都是一次调整,实现排序

    这个对哈夫曼编码很好用的

    while(!S.empty())S.pop();将队列为空,不是空就pop

    (其实他用的就是make_heap(), pop_heap(), push_heap() )

    #include<bits/stdc++.h>
    using namespace std;
    priority_queue<int>S;
    int main()
    {
        S.push(2);
        S.push(1);
        cout<<S.top();
        S.pop();
        if(S.empty()){}
        while(!S.empty())S.pop();
        cout<<" "<<S.size();
        return 0;
    }

    默认为大到小,从小到大这样写就好,当然也可以选择重载这个struct的运算符

    #include<bits/stdc++.h>
    using namespace std;
    priority_queue<int, vector<int>, greater<int> >S;
    int main()
    {
        S.push(2);
        S.push(1);
        cout<<S.top();
        S.pop();
        if(S.empty()) {}
        while(!S.empty())S.pop();
        cout<<" "<<S.size();
        return 0;
    }

    stack

    后进先出,就是火车的进站这种操作,你进来了就得先出去

    #include<bits/stdc++.h>
    using namespace std;
    stack<int>S;
    int main()
    {
        S.push(2);
        S.push(1);
        cout<<S.top();
        S.pop();
        if(S.empty()) {}
        while(!S.empty())S.pop();
        cout<<" "<<S.size();
        return 0;
    }

     和之前操作差不多的,其实我什么都没改

    我们还常用基于红黑树的hashmap,即map和set

    map就是键值一一对应的关系,即key和value,map<Key,Value>M;

    set和map的插入删除效率都很高,都是logn的,而且这个映射关系还是很好用的,自己实现hash有些情况很复杂(要解决冲突

    我的map还有set的一篇小文章

    map的基本操作函数
    begin() 返回指向map头部的迭代器
    clear() 删除所有元素 
    count() 返回指定元素出现的次数 
    empty() 如果map为空则返回true 
    end() 返回指向map末尾的迭代器 
    equal_range() 返回特殊条目的迭代器对 
    erase() 删除一个元素 
    find() 查找一个元素 
    get_allocator() 返回map的配置器 
    insert() 插入元素 
    key_comp() 返回比较元素key的函数 
    lower_bound() 返回键值>=给定元素的第一个位置 
    upper_bound() 返回键值>给定元素的第一个位置 
    max_size() 返回可以容纳的最大元素个数 
    rbegin() 返回一个指向map尾部的逆向迭代器 
    rend() 返回一个指向map头部的逆向迭代器 
    size() 返回map中元素的个数 
    swap() 交换两个map 
    value_comp() 返回比较元素value的函数

    #include<bits/stdc++.h>
    using namespace std;
    map<int,int>M;
    int main()
    {
        M[13232]=1;
        M[32]=1;
        cout<<M[32]<<endl;//因为我确定有这个元素,这样查询就不重新建立新的键
        if(M[11])
        {
            printf("1exist!");
        }
        if(M[11])
        {
            printf("2exist!");
        }
        return 0;
    }

    这样试下输出好像并没有问题?但是我们还是看下map的size吧,你会发现他多了一个,因为下标查询的问题啊,其实你插入了一个键为下标,值为初始值的一个东西

    所以查询还是用函数比较好,千万不要试图用下表访问来提高速度,这样还会被卡内存,因为人家是查询,你是要建立新的键值。

    #include<bits/stdc++.h>
    using namespace std;
    map<int,int>M;
    int main()
    {
        M[13232]=1;
        M[32]=1;
        if(M.count(32))
            cout<<"32exist
    ";
        M.insert(make_pair(123,7));
        M.erase(32);
        map<int,int>::iterator it;
        for(it=M.begin();it!=M.end();it++)
            cout<<it->first<<" "<<it->second<<"
    ";
        return 0;
    }

    cpp的set和map都是基于红黑树的,红黑树只需要重载小于号,double有精度损失,所以重载小于号就好了。大多数情况都要调下eps。

    这个重载小于号就是==的时候不要认为是<

    #include<bits/stdc++.h>
    using namespace std;
    const double eps=1e-6;
    struct T
    {
        double x;
        bool operator <(const T a)const
        {
            return x<a.x&&a.x-x>=eps;
        }
    }a;
    int main()
    {
        set<T>S;
        int f=0;
        while(cin>>a.x)
        {
            S.insert(a);
            f++;
        }
        cout<<f<<" "<<S.size();
    }

    这个stl的源码大概就是我实现的那些

    只hash的话是unordered_map,这个在需要hash次数多的时候比较好用,当然直接map也可以啊

    突然发现还少了一个常用的deque,双向队列,自行查阅吧

    最后为大家献上一曲,计算机版达拉崩吧

  • 相关阅读:
    0055. Jump Game (M)
    0957. Prison Cells After N Days (M)
    Java
    Java
    Java桌面应用程序打包
    JavaGUI练习
    Java贪吃蛇小游戏
    Java GUI编程
    Java异常处理机制
    抽象类与接口
  • 原文地址:https://www.cnblogs.com/BobHuang/p/8320307.html
Copyright © 2020-2023  润新知