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\n",*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\n",*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\n",*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\n",*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\n",*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\n",*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<<"\n";
    printf("%s\n",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<<"\n";
    cout<<(str1>str2)<<"\n";
    cout<<(str1==str2)<<"\n";
    cout<<(str1<str2)<<"\n";
    getline(cin,str1);
    cout<<str1<<"\n";
    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<<"\n";
    }
    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<<"\n";
    sort(S.rbegin(),S.rend());
    //for(auto X:S)cout<<X<<"\n";
    //sort(S.begin(),S.end());
    sort(S.begin(),S.end(),cmp);
    //for(auto X:S)cout<<X<<"\n";
    sort(S.begin(),S.end());
    sort(S.begin(),S.end(),greater<string>());
    //for(auto X:S)cout<<X<<"\n";
    sort(S.begin(),S.end(),less<string>());
    //for(auto X:S)cout<<X<<"\n";
    sort(S.begin(),S.end(),[]
    (const string &s,const string &c)
    {
    return s>c;
    }
    );
    //for(auto X:S)cout<<X<<"\n";
    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\n";
    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<<"\n";
    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,雙向隊列,自行查閱吧

最后為大家獻上一曲,計算機版達拉崩吧


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM