stl 在 acm中的應用總結


總結一些在acm中常用的小技巧,小函數

之前嘗試着總結過很多次。都失敗了,因為總是擔心不全,理解的也不是很透徹。這次再來一次。。。其實之前保存了很多的草稿就不發布了,當然,下面說的很不全面,路過的大牛求指點,求補充。

標簽: 代碼姿勢


有關stl模板

函數模板

例子:

#include<iostream>

#include<string>

using namespace std;

//定義函數模板

template<class T>   //template 是關鍵字,T 表示一種待實例化的類型

                    //template<typename T>  也是對的

T MAX(T a, T b)//函數模板,函數名為 max,此函數有2個T類型的參數,返回類型為T

{

  return (a>b)?a:b; 

}

//在此例實例化的時候,T可以是多種類型的,int,char,string…

int main()

{

int x=2,y=6;

    double x1=9.123,y1=12.6543;

    cout<<"把T實例化為int:"<<MAX(x,y)<<endl;//實例化函數模板,把T實例化為int

    cout<<"把T實例化為double:"<<MAX(x1,y1)<<endl;  //把T實例化為double

}

類模板

例子:

#include<iostream>

using namespace std;

//定義名為ex_class的類模板

template < typename T>  class ex_class

{

    T value;

public:

    ex_class(T v) { value=v; }

    void set_value(T v) { value=v; }

    T get_value(void) {return value;}

};

//main()函數中測試ex_class類模板

int main()

{

    //測試int類型數據

    ex_class <int> a(5),b(10);

    cout<<"a.value:"<<a.get_value()<<endl;

    cout<<"b.value:"<<b.get_value()<<endl;

    //測試char類型數據

    ex_class <char> ch('A');

    cout<<"ch.value:"<<ch.get_value()<<endl;

    ch.set_value('a');

    cout<<"ch.value:"<<ch.get_value()<<endl;

    //測試double類型數據

    ex_class <double> x(5.5);

    cout<<"x.value:"<<x.get_value()<<endl;

    x.set_value(7.5);

    cout<<"x.value:"<<x.get_value()<<endl;

}

總結一下:

模板的主要作用是在一個函數要用到多重數據類型的時候可以通過這種形式使得代碼量減少。代碼看上去更加整潔。

有關stl容器

STL在ACM中的應用
STL 提供三種類型的組件:容器、迭代器和算法,它們都支持泛型程序設計標准。在ACM中充分利用STL可以大大的簡化程序,提高解題效率。
1、容器主要有兩類:順序容器和關聯容器。順序容器(vector/list/deque/string)等是一系列元素的有序集合。關聯容器(set/multiset/map/multimap)包含查找元素的鍵值。
2、迭代器的作用是遍歷容器。
3、STL算法庫包含四類算法:排序算法,不可變算法,變序算法和數值算法。

容器

  • vector
vector 基本操作
vector<int> v;  
v.begin();   //容器的起始位置  
v.end();    //容器最后一個位置后的位置  
v.front();v.back();   //返回第一個元素(最后一個元素,但不判斷時候存在  
v.empty();    //返回是否容器為空  
v.clear();    //清空容器  
v.erase(m);    //刪除m位置的數據,並返回下一個數據的地址(m是迭代器)  
v.erase(m,n);     //刪除m到n之間的數據,並返回下一個數據的地址  
v2.assign(8,1);   // 重新給vec2賦值,8個成員的初始值都為1
v.push_back(element);    //壓入一個元素到末端  
v.pop_back();    //彈出最后一個元素  
v.reserve(100);v.resize(101);    //resize已經創建空間如果再v.push_back();空間就會到101,而reserve只是預留空間並沒有真正創建,v.push_back();只是在第1位  
v.size();v.capacity();       //size表示的是已經創建的空間大小也可以表示元素個數可用v[]的形式直接訪問,capacity容器容量,是預留空間並沒有實際創建  
swap(a,b);      //交換兩個元素的位置如:swap(v[0],v[1]);  
vector<int>  v(10);    //創建一個前十個元素為int的容器  
vector<string> v(10,string("I"));  //使容器的前10個元素都為string型,並且都初始化為I  
vector<string> v1(v2);    //對於已經存在的v2創建一個v1副本  
v.insert(place,element);  
v.insert(place,n,element);     //在place(迭代器)位插入n個元素  
//注:對vector元素的訪問可以用類似c語言的v[],但是最好用v.at(),它會檢查是否越界更安全  
v[0];    // A
v.at[0];  // B 這樣越界的時候比較安全

關於v.reserve(100);v.resize(101);我的理解並不深刻,也沒有自己用到過,這里有一個講解的很好的博客,收存

vector 遍歷

vector 有兩種遍歷方法
一種是使用迭代器

vector<int> ::iterator it;
for(it = v.begin(); it!=v.end(); it++)
{
    cout<<(*it)<<endl;
}

還有就是直接用下標的方式訪問

v[0];    // A
v.at[0];  // B 這樣越界的時候比較安全
vector 算法

(1) 使用reverse將元素翻轉:需要頭文件#include

reverse(vec.begin(),vec.end());將元素翻轉(在vector中,如果一個函數中需要兩個迭代器,

一般后一個都不包含.)

(2)使用sort排序:需要頭文件#include

sort(vec.begin(),vec.end());(默認是按升序排列,即從小到大).

可以通過重寫排序比較函數按照降序比較,如下:

定義排序比較函數:

bool Comp(const int &a,const int &b)
{
    return a>b;
}
調用時:sort(vec.begin(),vec.end(),Comp),這樣就降序排序。

注:vector是順序容器,沒有find函數、

這里一定要強調的一種用法就是vector實現二維數組,因為在圖論中用vector存圖也是一種很好的方法。vector可以代替head數組。

之前網絡流有一個題用vector建邊,自己看看就懂了,和head是一樣原理的

  • deque雙端隊列

容器類與vector類似,支持隨機訪問和快速插入刪除,它在容器中某一位置上的操作所花費的是線性時間。與vector不同的是,deque還支持從開始端插入數據:push_front()。此外deque也不支持與vector的capacity()、reserve()類似的操作。

#include<queue>
deque <int> deq;//初始化對象為空
deque <int> deq1(10,6);//對象初始化有10個值為6的元素
deque <int>:: iterator it;
for(it = deq.begin(); it!=deq.end(); it++){
    cout<<*it<<endl;
}
deq.push_back(ele);//從隊列尾部插入
deq.push_front(ele);//從隊列頭部插入
deq.insert(deq.begin()+1,3,9);//從隊列中間插入三個9
//和vector一樣,雙向隊列也可以用下標的形式訪問,也可以用at
deq.at(n);//返回的是n這個下標的值
//也可以直接deq[n]

deq1.at(1)=10;
deq1[2]=12;
//從deq1序列的前后各移去一個元素
deq1.pop_front();
deq1.pop_back();

deq.erase(deq.begin()+1);//清除deq的第二個元素

//對deq2賦值並顯示
deq2.assign(8,1);
cout<<"deq2.assign(8,1):"<<endl;
put_deque(deq2,"deq2");
//erase(),assign()是大多數容器都有的操作
  • 集合set(特點是沒有重復元素且元素時有序的)

注:set是STL中一種標准關聯容器(vector,list,string,deque都是序列容器,而set,multiset,map,multimap是標准關聯容器),它底層使用平衡的搜索樹——紅黑樹實現,插入刪除操作時僅僅需要指針操作節點即可完成,不涉及到內存移動和拷貝,所以效率比較高。set,顧名思義是“集合”的意思,在set中元素都是唯一的,而且默認情況下會對元素自動進行升序排列,支持集合的交(set_intersection),差(set_difference) 並(set_union),對稱差(set_symmetric_difference) 等一些集合上的操作,如果需要集合中的元素允許重復那么可以使用multiset

關於set,必須說明的是set關聯式容器。set作為一個容器也是用來存儲同一數據類型的數據類型,並且能從一個數據集合中取出數據,在set中每個元素的值都唯一,而且系統能根據元素的值自動進行排序。默認的是從小到大的排序
應該注意的是set中數元素的值不能直接被改變。
C++ STL中標准關聯容器set, multiset, map, multimap內部采用的就是一種非常高效的平衡檢索二叉樹:紅黑樹,也成為RB樹(Red-Black Tree)。RB樹的統計性能要好於一般平衡二叉樹,所以被STL選擇作為了關聯容器的內部結構。

同樣,給出一個set用法學習的網站

其實數組的去重也是很容易的
但是注意數組的去重是在數組排序的基礎上實現的,它去重的原理也不是刪除這些元素,而是將重復的元素放到數組的最后面,返回的是數組最后一個有效元素的地址,所以我們經常這么做

int mp[N];
sort(mp,mp+n);
int len = unique(mp,mp+n)-mp;
set 基本操作 遍歷 及其對應算法
set<int> s;
s.insert(element);//插入元素
//遍歷整個幾何(不重復的有序序列)
set<int>:: iterator it;
for(it = s.begin(); it!=s.end(); it++){
    cout<<(*it)<<endl;
}
s.size();//返回元素個數
s.find(ele);//返回元素的下標,沒找到的話返回s.end();
//獲得兩個set的並
set<int> s1;
set<int> s2;
set<int> s3;//存結果
set_union(s1.begin(),s1.end(),s2.begin(),s2.end(),insert_iterator<set<int> >(s3,s3.begin()));
//輸出也可以用下面的形式
copy(s3.begin(),s3.end(),ostream_iterator<int>(cout," "));
cout<<endl;
//獲得兩個set的交,注意進行集合操作之前接收結果的set要調用clear()函數清空一下
s3.clear();
set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(),insert_iterator<set<int> >(s3.s3.begin()));
copy(s3.begin(),s3.end(),ostream_iterator<int>(cout," "));
cout<<endl;

//獲得兩個set的差
s3.clear();
set_difference(s1.begin(),s1.end(),s2.begin(),s2.end(),insert_iterator<set<int> >(s3,s3.begin()));
cout<<"Difference:";
copy(s3.begin(),s3.end(),ostream_iterator<int>(cout," "));
cout<<endl;
//獲得兩個set的對稱差,也就是假設兩個集合分別為A和B那么對稱差為AUB-A∩B
   eg3.clear();
   set_symmetric_difference(s1.begin(),s1.end(),s2.begin(),s2.end(),insert_iterator<set<int> >(s3,s3.begin()));
   copy(s3.begin(),s3.end(),ostream_iterator<int>(cout," "));
   cout<<endl;

特別注意:常用的是將set升序排列

set<int,less<int> > set1;//降序排列
set<int,greater<int> > set1;//升序排列

注意:

  1. set只能通過迭代器訪問,不可以通過下標直接訪問。
  2. set是有序的序列。所以可以使用stl自帶的二分函數(二分函數將在后面介紹)
  • map 和 Multimap映射

可對數據進行快速高效檢索,而且map支持通過下標直接訪問及\(map["string"] = 4\)這種形式,可以實現兩個值得對應,常見的定義形式有$map<string ,int>mapstring; $

map的基本操作
#include<map>

map<int, string> mym;
//三種插入方法
mym.insert(pair<int,string>(120,"haha"));
mym.insert(map<int,string>::value_type(312,"gaga"));
mym[120] = "haha";
//find()函數返回一個迭代器指向鍵值為key的元素,如果沒找到就返回指向map尾部的迭代器。
map<int ,string >::iterator l_it; 
l_it=maplive.find(112);//返回的是一個指針
if(l_it==maplive.end())
    cout<<"we do not find112"<<endl;
else
    cout<<"wo find112"<<endl;

//也可以用這種方式來查找
map<int ,string> m;
m[1] = "haa";
m[44] = "xixi";
if(m[12]=="") puts("NO");

//刪除一個元素
map<int,string> m;
map<int,string>::iterator it;
it = m.find(112);
if(it == m.end()){ exit;}
else m.erase(it);
//類似的也可以通過賦值為空來將一個元素刪除

//Map中的swap不是一個容器中的元素交換,而是兩個容器交換

begin() //返回指向map頭部的迭代器 
clear()// 刪除所有元素 
count()// 返回指定元素出現的次數 
empty() //如果map為空則返回true 
end() //返回指向map末尾的迭代器 
equal_range()// 返回特殊條目的迭代器對 
erase() //刪除一個元素 
find() //查找一個元素 
insert() //插入元素 
lower_bound() //返回鍵值>=給定元素的第一個位置 
max_size() //返回可以容納的最大元素個數 
rbegin() //返回一個指向map尾部的逆向迭代器 
rend() //返回一個指向map頭部的逆向迭代器 
size() //返回map中元素的個數 
swap()// 交換兩個map 
upper_bound()// 返回鍵值>給定元素的第一個位置 
value_comp() //返回比較元素value的函數

multimap基本用法:

這里一定要注意的是multimap

//multimap允許重復的鍵值插入容器
//        **********************************************************      
//        * pair只包含一對數值:pair<int,char>                       *
//        * map是一個集合類型,永遠保持排好序的,                   *
//  pair  * map每一個成員就是一個pair,例如:map<int,char>           *
//        * map的insert()可以把一個pair對象作為map的參數,例如map<p> *
//        ***********************************************************
#pragma warning(disable:4786)
#include<map>
#include<iostream>
using namespace std;

int main(void)
{
 multimap<int,char*> m;
 //multimap的插入只能用insert()不能用數組
 m.insert(pair<int,char*>(1,"apple"));
    m.insert(pair<int,char*>(1,"pear"));//apple和pear的價錢完全有可能是一樣的
 m.insert(pair<int,char*>(2,"banana"));
 //multimap的遍歷只能用迭代器方式不能用數組
 cout<<"***************************************"<<endl;
 multimap<int,char*>::iterator i,iend;
 iend=m.end();
 for(i=m.begin();i!=iend;i++)
 {
  cout<<(*i).second<<"的價錢是"
   <<(*i).first<<"元/斤\n";
 }
 cout<<"***************************************"<<endl;
    //元素的反相遍歷
 multimap<int,char*>::reverse_iterator j,jend;
 jend=m.rend();
 for(j=m.rbegin();j!=jend;j++)
 {
  cout<<(*j).second<<"的價錢是"
   <<(*j).first<<"元/斤\n";
 }
 cout<<"***************************************"<<endl;
 //元素的搜索find(),pair<iterator,iterator>equal_range(const key_type &k)const
    //和multiset的用法一樣
 multimap<int,char*>::iterator s;
 s=m.find(1);//find()只要找到一個就行了,然后立即返回。
 cout<<(*s).second<<"    "
  <<(*s).first<<endl;
 cout<<"鍵值等於1的元素個數是:"<<m.count(1)<<endl;
 cout<<"***************************************"<<endl;
 //刪除 erase(),clear()
 m.erase(1);
    for(i=m.begin();i!=iend;i++)
 {
  cout<<(*i).second<<"的價錢是"
   <<(*i).first<<"元/斤\n";
 }
    return 0;
}

  • queue與priority_queue

元素先進先出,只能充隊列末端插入和刪除元素

隊列基本操作
#include<queue>
queue<int> q1;
q.push(x);//將元素插入到隊列末尾
q.pop();//彈出隊列的第一個元素(隊首),並不會返回元素的值
q.front();//訪問隊首的元素
q.back();//訪問隊尾元素
q.size();//返回隊列中元素的個數

頭文件中,還定義了一個非常有用的模版類priority_queue(優先隊列),優先隊列與隊列的差別在於優先隊列不是按照入隊的順序出隊,而是按照隊列中元素的優先權順序出隊(默認為大者優先,也可以通過指定算子來指定自己的優先順序)。

priority_queue

*基本操作和queue一樣注意的就是它每次出隊都是隊列中最大的元素。(也可以更改成最小的)
*

priority_queue<int >q1;
priority_queue<pair<int,int> >q2;
priority_queue<int,vector<int>,greater<int> >q3;//定義小的先出隊


  • stack 棧

棧是后進先出的容器,操作和queue很像

stack 基本操作

#include<stack>
stack<int> s;
s.push();//壓棧
s.pop();//出棧
s.top();//獲取棧頂元素
s.empty();
s.size();
s.end();
s.begin();
其實還有堆的操作。。。stl還有一些其他的容器,因為不常用,所以,我也就不列舉了

下面介紹一下stl中的一些常用的算法

  • find(); count(); unique(); sort(); 這些在前面介紹了就不說了

  • copy:
    \(copy(v.begin(),v.end(),l.begin());\)將v中的元素復制到l中。

  • 堆操作
make_heap(v.begin(),v.end());//創建堆
sort_heap(v.begin(),v.end());//堆排序
push_heap(v.begin(),v.end());//堆入隊
pop_heap(v.begin(),v.end());//堆出隊

有一個博客:
可以參考

  • 全排列函數
    兩個重載函數,第二個帶謂詞參數_Comp,其中只帶兩個參數的版本,默認謂詞函數為"小於".

返回值:bool類型

分析next_permutation函數執行過程:

假設數列 d1,d2,d3,d4……

范圍由[first,last)標記,調用next_permutation使數列逐次增大,這個遞增過程按照字典序。例如,在字母表中,abcd的下一單詞排列為abdc,但是,有一關鍵點,如何確定這個下一排列為字典序中的next,而不是next->next->next……

若當前調用排列到達最大字典序,比如dcba,就返回false,同時重新設置該排列為最小字典序。

返回為true表示生成下一排列成功。下面着重分析此過程:

根據標記從后往前比較相鄰兩數據,若前者小於(默認為小於)后者,標志前者為X1(位置PX)表示將被替換,再次重后往前搜索第一個不小於X1的數據,標記為X2。交換X1,X2,然后把[PX+1,last)標記范圍置逆。完成。

要點:為什么這樣就可以保證得到的為最小遞增。

從位置first開始原數列與新數列不同的數據位置是PX,並且新數據為X2。[PX+1,last)總是遞減的,[first,PX)沒有改變,因為X2>X1,所以不管X2后面怎樣排列都比原數列大,反轉[PX+1,last)使此子數列(遞增)為最小。從而保證的新數列為原數列的字典序排列next。

明白了這個原理后,看下面例子:

int main(){
int a[] = {3,1,2};
do{
cout << a[0] << " " << a1 << " " << a2 << endl;
}
while (next_permutation(a,a+3));
return 0;
}

輸出:312/321 因為原數列不是從最小字典排列開始。

所以要想得到所有全排列

int a[] = {3,1,2}; change to int a[] = {1,2,3};

另外,庫中另一函數prev_permutation與next_permutation相反,由原排列得到字典序中上一次最近排列。

所以

int main(){
int a[] = {3,2,1};
do{
cout << a[0] << " " << a1 << " " << a2 << endl;
}
while (prev_permutation(a,a+3));
return 0;
}

才能得到123的所有排列。

兩個數組,一個編號一個數組的內容,按照其中一個排序
(找松哥要代碼!!!!)

安利學習資料:
資料鏈接


免責聲明!

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



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