一、概述
1、標準庫定義了三種順序容器類型:vector、list 和 deque。
2、他們差別在於訪問元素 的方式,以及添加或刪除元素相關操作的運行代價。
3、單純的類容器只定義了少量操作,大多數額外操作由算法庫提供。
二、容器的初始化
1、構造函數初始化
#include<iostream>
#include<vector>
#include<list>
#include<deque>
using namespace std;
int main()
{
vector<int> ivec;
vector<int> ivec2(ivec); // ok
//list<int> ilist(ivec); // error
//vector<double> dvec(ivec); // error
vector<int> a(10);// a包含10個0值元素
vector<int> b(10,1);//b爲10個值爲1的元素
}
2、用迭代器範圍賦值
int main()
{
vector<string> svec;
//用下面方法不要求迭代器的元素類型或者容器類型完全相同,只要相互兼容即可,兼容性待考證
list<string> slist(svec.begin(), svec.end()); //迭代器標記了複製元素的範圍,由此方法可以將其他容器的子序列複製
vector<string>::iterator i = svec.begin() + svec.size() / 2;
deque<string> front(svec.begin(), i);//前半部分給front
deque<string> back(i, svec.end());
}
當然迭代器就是指針了,所以我們也可以用自定義的迭代器進行賦值:
#include<iostream>
#include<vector>
#include<list>
#include<deque>
#include<string>
using namespace std;
int main()
{
char *kk[] = { "kkk", "zlk", "123", "abv", "love" };
int size_kk = sizeof(kk) / sizeof(char*);
list<string> list_kk(kk, kk + size_kk);
vector<char*> vector_kk(kk, kk + size_kk);
list<string>::iterator i = list_kk.begin();
while (i != list_kk.end())
{
cout << *i++ << endl;
}
for (vector<char*>::iterator j = vector_kk.begin(); j < vector_kk.end(); j++)
{
cout << *j << endl;
}
}
指定數目初始化:
const list<int>::size_type list_size = 64;
list<sting> kk(list_size,"kk");//kk是含有64個元素,每個都是kk的容器
list<string> kk(list_size);//64個元素初始化爲0
問題:複製容器對象的構造函數和使用兩個迭代器的構造函數有什麼區別?
複製容器對象:只能將一個容器初始化爲另一個容器的副本,要求兩個容器的類型,元素的類型完全相等。
使用兩個迭代器:用一個容器的子序列初始化另外一個容器,不要求兩個容器的類型是相同的。
二、順序容器的迭代器
1、end()迭代器指向的從來就不是最後一個元素,而是最後一個元素的下一個位置!所以其迭代器範圍爲:
[begin(), end()),左開右閉區間。
2、順序容器中定義的類型別名
三、對順序容器的操作
1、添加操作
需要注意的是,vector和deque在添加元素時候會導致全部迭代器失效例如:
int main()
{
string k[] = { "i", "have", "a", "dream" };
deque<string>kk = {k,k+4};
string word("ha");
deque<string>::iterator first = kk.begin(), last = kk.end();
while (first!=last)
{
kk.insert(first, word);
first++;
}
}
在用了循環體的添加運算後,last 失效,不再指向kk.end();
所以:
不要存儲 end 操作返回的迭代器。添加或刪除 deque 或 vector 容器內的元素都會導致存儲的迭代器失效。
以下是正確的代碼:
int main()
{
string k[] = { "i", "have", "a", "dream" };
deque<string>kk = {k,k+4};
string word("ha");
deque<string>::iterator first = kk.begin(), last = kk.end();
while (first!=kk.end())
{
first=kk.insert(first, word);
++first;
++first;
}
for (deque<string>::iterator i = kk.begin(); i != kk.end(); i++)
{
cout << *i << endl;
}
}
補充:
一、vector的size和capacity的區別:
int main()
{
vector<int> kk;
cout << kk.size() << endl;//0
cout << kk.capacity() << endl;//0
for (int i = 0; i < 20; i++)
{
kk.push_back(i);
}
cout << kk.size() << endl;//20
cout << kk.capacity() << endl;//28
kk.reserve(50);
cout << kk.size() << endl;//20
cout << kk.capacity() << endl;//50
}
二、順序容器的選擇法則:
a. 如果程序要求隨機訪問元素,則應使用 vector 或 deque 容器。
b. 如果程序必須在容器的中間位置插入或刪除元素,則應採用 list 容器。
c. 如果程序不是在容器的中間位置,而是在容器首部或尾部插入或刪除元 素,則應採用 deque 容器。
d. 如果只需在讀取輸入時在容器的中間位置插入
原因:
1、vector 容器,除了容器尾部外,其他任何位置上的插入(或刪除)操 作都要求移動被插入(或刪除)元素右邊所有的元素。
2、list 容器不支持隨機訪問,訪問某 個元素要求遍歷涉及的其他元素。但list容器可以高效地 insert 或 erase 一個元素。
3、在 deque 容器首部或尾部插入元素不會使任何迭代器失效,而首部或尾 部刪除元素則只會使指向被刪除元素的迭代器失效。
三、支持順序容器操作的string
理解:string的很多功能也是用stl實現的,所以支持很多順序容器的操作
用迭代器初始化string:
char *cp = "Hiya"; // null-terminated array
char c_array[] = "World!!!!"; // null-terminated
char no_null[] = {'H', 'i'}; // not null-terminated
string s1(cp); // s1 == "Hiya"
string s2(c_array, 5); // s2 == "World"
string s3(c_array + 5, 4); // s3 == "!!!!"
string s4(no_null); // runtime error: no_null not null-terminated
string s5(no_null, 2); // ok: s5 == "Hi"
特殊的一些函數:
string s("hello world"); // return substring of 5 characters starting at position 6 string s2 = s.substr(6, 5); // s2 = world
string s3=s.append("!!!!"); // s3= hello world!!!!
int i=s.find("world"); //i=6 是world第一次出現的下標,沒有找到會返回-1
string kong(" ");
int j = s.find_first_of(kong);//j爲5,查找第一次出現kong內字符的位置
例題1:搜索字符串內的數字和字母:
int main()
{
int x = 0, y = 0, z = 0;
string k = ("ab2c3d7R4E6");
string num = ("0123456789");
string ar = ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
int a1[100];
int a2[100];
while (1)
{
z = k.find_first_of(num, z); //find_first_of函數要求z<k.size()-1,即z小於最後一個元素的下標!!
cout << "下標爲:" << z << "值爲:" << k[z] << endl;
if ((z >= k.size()-2)||z==-1)
{
break;
}
++z;
}
z = 0;
cout << endl;
while (1)
{
z = k.find_first_of(ar, z); //z<k.size()-1
cout << "下標爲:" << z << "值爲:" << k[z] << endl;
if ((z >= k.size() - 2) || z == -1)
{
break;
}
++z;
}
cout << endl;
cout << k.size();
}
例題2:查找單詞個數,與最長最短單詞
//單詞分隔符號爲空格的時候
int main()
{
string line1 = "We were her pride of 10 she named us:";
string line2 = "Benjamin, Phoenix, the Prodigal";
string line3 = "and perspicacious pacific Suzanne";
string sentence = line1 + ' ' + line2 + ' ' + line3;
string temps;
int counter = 0;
int max = 0;
int min = 9999;
stringstream ss(sentence);
while (ss >> temps)
{
++counter;
if (temps.size() > max)
{
max = temps.size();
}
if (temps.size() < min)
{
min = temps.size();
}
}
cout << "單詞數爲:" << counter << endl;
stringstream sss(sentence);
while (sss >> temps)
{
if (temps.size() == max)
{
cout << "最長單詞爲:" << temps << endl;
}
if (temps.size() == min)
{
cout << "最短單詞爲:" << temps << endl;
}
}
}
//單詞分隔符號任意(可爲,:\t\n的時候)
int main()
{
string line1 = "We were her pride of 10 she named us:";
string line2 = "Benjamin, Phoenix, the Prodigal";
string line3 = "and perspicacious pacific Suzanne";
string sentence = line1 + ' ' + line2 + ' ' + line3;
string temps;
vector<string> maxs;
vector<string> mins;
int max = 0, min = 9999;
int counter = 0;
int wordlen;
int start = 0, end = 0;
string fenge(" ,.\t\n\\");
while (1)
{
start = sentence.find_first_not_of(fenge, end); //start是從結束座標開始第一個不是分隔符的元素下標
++counter;
end = sentence.find_first_of(fenge, start);//end變爲單詞末尾的下一個分隔符下標
if (end == -1)
{
break;
}
wordlen = end - start;
if (wordlen > max)
{
maxs.clear();
temps.assign(sentence.begin() + start, sentence.begin() + end);
maxs.push_back(temps);
max = wordlen;
}
else if (wordlen == max)
{
temps.assign(sentence.begin() + start, sentence.begin() + end);
maxs.push_back(temps);
}
if (wordlen < min)
{
mins.clear();
temps.assign(sentence.begin() + start, sentence.begin() + end);
mins.push_back(temps);
min = wordlen;
}
else if (wordlen == min)
{
temps.assign(sentence.begin() + start, sentence.begin() + end);
mins.push_back(temps);
}
if (start > sentence.size() - 2 || end > sentence.size() - 2)
{
break;
}
}
cout <<"單詞數目爲:" <<counter << endl;
for (vector<string>::iterator iter = maxs.begin(); iter != maxs.end(); iter++)
{
cout << "最長的單詞爲" << *iter << endl;
}
for (vector<string>::iterator iter = mins.begin(); iter != mins.end(); iter++)
{
cout << "最短的單詞爲" << *iter << endl;
}
}
容器適配器
除了順序容器,標準庫還提供了三種順序容器適配器:queue、priority_queue和stack
一、什麼是容器適配器:
容器適配器讓一種已存在的容器類型採用另一種 不同的抽象類型的工作方式實現。例如,stack(棧)適配器可使任何一種順序容器以棧的方式工作。
deque<int> deqint;
stack<int> stk(deqint);// 用deqint容器初始化一個新的棧
stack<string, vector<string>> string_stk; //在vector的基礎上覆蓋空棧
實現:
1、默認的 stack 和 queue 都基於 deque 容器實現
2、默認的priority_queue 則在 vector 容器上實現。
有以下規則:
1、stack 棧可以建立在 vector、list 或者 deque 容器之上。
2、queue 適配器要求其關聯的基礎容器 必須提供 push_front 運算,因此只能建立在 list 容器上(此處我認爲deque也可以,書上的表述應該不清楚),而不能建立在 vector 容器上。
3、priority_queue 適配器要求提供隨機訪問功能,因此可建立在 vector 或 deque 容器上,但不能建立在 list 容器。
二、棧適配器stack
先進後出
int main()
{
const int size = 10;
stack<int> sta_int;
int i = 0, j = 1;
while (sta_int.size() != size)
{
int tem = j;
sta_int.push(j); //元素壓棧
j += i;
i = tem;
}
while (!sta_int.empty())
{
int val = sta_int.top(); //讀出棧頂元素
sta_int.pop(); //刪除棧頂元素,完成棧頂出棧過程
cout << val << ends;
}
}
三、適配器隊列和優先級隊列
queue標準隊列:先進先出,新進的元素在隊尾;
priority_queue優先級隊列:用戶爲隊列設置優先級,新進入的元素比較優先級,將自己放在比他低優先級的元素前面;
四、關於這幾個適配器的應用實例
1、stack應用:括號匹配
//遇到左括號的時候將其標記,遇到右括號時彈出括號間的元素
int main()
{
stack<char> sta_char;
string exp;
cin >> exp;
string::iterator iter = exp.begin();
while (iter != exp.end())
{
if (*iter == '(')
{
++iter;
while (*iter != ')')
{
sta_char.push(*iter);
++iter;
}
while (!sta_char.empty())
{
cout << sta_char.top()<<ends;
sta_char.pop();
}
sta_char.push('#');//表示表達式已經彈出
}
++iter;
}
}
2、queue的應用:圖的廣度優先遍歷
//無向圖的廣度優先遍歷
#include<iostream>
#include<queue>
using namespace std;
#define MAX 20
int map[MAX][MAX];
int arr[MAX] = { 0 }; //標記是否已經被訪問
void bfs(int n) //圖的廣度優先遍歷
{
int top;
queue<int> que;
arr[0] = 1;
cout << 1 << ends;
for (int i = 0; i < n; i++)
{
if (map[0][i] == 1 && arr[i] == 0) //從第0個頂點開始入隊聯通且沒有被標記的點
{
que.push(i);
arr[i] = 1;
}
}
while (!que.empty())
{
top = que.front();
que.pop();
cout << top+1 << ends;
for (int i = 0; i < n; i++)
{
if (map[top][i] == 1 && arr[i] == 0)
{
que.push(i);
arr[i] = 1;
}
}
}
}
int main()
{
int vex, edge, x, y;
cin >> vex >> edge;
for (int i = 0; i < MAX; i++)
{
for (int j = 0; j < MAX; j++)
map[i][j] = 0;
}
for (int i = 0; i < edge; i++)
{
cin >> x >> y;
map[x-1][y-1] = map[y-1][x-1] = 1;
}
bfs(vex);
}
3、最高獎勵問題:
//有N個任務,每個任務有一個最晚結束時間以及一個對應的獎勵。
//在結束時間之前完成該任務,就可以獲得對應的獎勵。完成每一個任務所需的時間都是1個單位時間。
//有時候完成所有任務是不可能的,因爲時間上可能會有衝突,這需要你來取捨。
//求能夠獲得的最高獎勵。
//input:第1行:一個數N,表示任務的數量(2 <= N <= 50000)
//第2 - N + 1行,每行2個數,中間用空格分隔,表示任務的最晚結束時間E[i]以及對應的獎勵W[i]。
//(1 <= E[i] <= 10 ^ 9,1 <= W[i] <= 10 ^ 9)
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
struct r{
int e, w;
friend bool operator< (r a, r b) //定義了比較方式之後,優先級隊列的算符也會改變
{
return a.w>b.w; //將價值最低的放在top用以替換
}
};
//分析一波:每個任務完成需要一個單位時間。那麼不妨先將任務按結束時間排序。
//依次遍歷每個任務,如果當前時間足夠完成該任務,則加入隊列,如果衝突,則比較隊列中價值最小的元素,
//如果該任務價值高,則替換之。
int cmp(r a, r b) //sort 函數的從小到大排序
{
if (a.e < b.e)
{
return true;
}
else
return false;
}
int main()
{
r p[20];
int N;
cin >> N;
for (int i = 0; i < N; i++)
{
cin >> p[i].e >> p[i].w;
}
//建立一個優先級隊列,將結束時間晚的放到最後,爭取利益最大化
priority_queue<r> pro_que;
sort(p, p + N, cmp); //先把所有任務按時間排序(從小到大)
for (int i = 0; i < N; i++)
{
if (pro_que.size() < p[i].e) //如果當前隊列執行完的時間小於 任務的結束時間(任務未結束)
{
pro_que.push(p[i]); //按照任務價值排進優先級隊列
}
else if (p[i].w>pro_que.top().w) //如果當前時間等於任務結束的時間(即可以替換)
{
pro_que.pop();
pro_que.push(p[i]);
}
}
long ans = 0;
while (!pro_que.empty())
{
ans += pro_que.top().w;
pro_que.pop();
}
cout << ans;
}