PKU C++程序設計實習 學習筆記6 標準模板庫STL

標準模板庫STL

8.1 STL概述

1.泛型程序設計

C++ 語言的核心優勢之一就是便於軟件的重用

C++中有兩個方面體現重用:1.面向對象的思想:繼承和多態,標準類庫  2.泛型程序設計(generic programming) 的思想: 模板機制,以及標準模板庫 STL

簡單地說就是使用模板的程序設計法。

將一些常用的數據結構(比如鏈表,數組,二叉樹)和算法(比如排序,查找)寫成模板,以後則不論數據結構裏放的是什麼對象,算法針對什麼樣的對象,則都不必重新實現數據結構,重新編寫算法。

標準模板庫 (Standard Template Library) 就是一些常用數據結構和算法的模板的集合。

2.STL中的基本的概念

容器:可容納各種數據類型的通用數據結構,是類模板

迭代器:可用於依次存取容器中元素,類似於指針

算法:用來操作容器中的元素的函數模板。算法本身與他們操作的數據的類型無關,因此他們可以在從簡單數組到高度複雜容器的任何數據結構上使用。

int array[100];
該數組就是容器,而 int * 類型的指針變量就可以作爲迭代器,sort算法可以作用於該容器上,對其進行排序:
sort(array,array+70); //將前70個元素排序    array和array+70即是迭代器

3.容器概述

可以用於存放各種類型的數據(基本類型的變量,對象等)的數據結構,都是類模版,分爲三種:

  • 順序容器
        vector,deque  雙向隊列,list  雙向鏈表
  • 關聯容器
        set,multiset,map,multimap
  • 容器適配器
        stack,queue,priority_queue
對象被插入容器中時,被插入的是對象的一個複製品。許多算法,比如排序,查找,要求對容器中的元素進行比較,有的容器本身就是排序的,所以,放入容器的對象所屬的類,往往還應該重載 == 和 < 運算符

4.順序容器簡介

容器並非排序的,元素的插入位置同元素的值無關。

有vector,deque,list 三種

vector    頭文件<vector>

  • 動態數組。元素在內存連續存放。
  • 隨機存取任何元素都能在常數時間完成。
  • 在尾端增刪元素具有較佳的性能(大部分情況下是常數時間)。 但是當存儲空間不夠時,需要開闢新空間,把原來的元素拷過來後再插入,時間複雜度則是O(n)。
  • 在頭部或中部插入或刪除元素時間複雜度是O(n),因爲需要移動元素。

deque 頭文件 <deque>

  • 雙向隊列。元素在內存連續存放。
  • 隨機存取任何元素都能在常數時間完成(但次於vector)。
         次於vector的原因是,它是雙向隊列,計算元素地址時需要判斷是否超出末尾然後減去長度。
  • 在兩端增刪元素具有較佳的性能(大部分情況下是常數時間)。

list 頭文件 <list>

  • 雙向鏈表。元素在內存不連續存放。
  • 在任何位置增刪元素都能在常數時間完成。
  • 不支持隨機存取。

5.關聯容器簡介

  • 元素是排序的
  • 插入任何元素,都按相應的排序規則來確定其位置
  • 在查找時具有非常好的性能。通常以平衡二叉樹方式實現,插入和檢索的時間都是 O(log(N))
  • set/multiset         頭文件 <set>
        set 即集合。set中不允許相同元素,multiset中允許存在相同的元素。
  • map/multimap    頭文件 <map>
        map 與 set 的不同在於map中存放的元素有且僅有兩個成員變量,一個名爲first,另一個名爲second, map根據first值對元素進行從小到大排序,並可快速地根據first來檢索元素。
        map 同 multimap 的不同在於是否允許相同first值的元素。

6.容器適配器簡介

  • stack
        棧。後進先出。   頭文件 <stack>
  • queue
        隊列。先進先出。    頭文件 <queue>
  • priority_queue
        優先級隊列。優先級最高的元素總是位於隊首。    頭文件 <queue>

7.順序容器和關聯容器中都有的成員函數

  • begin 返回指向容器中第一個元素的迭代器
  • end 返回指向容器中最後一個元素後面的位置的迭代器
  • rbegin 返回指向容器中最後一個元素的迭代器
  • rend 返回指向容器中第一個元素前面的位置的迭代器
  • erase 從容器中刪除一個或幾個元素
  • clear 從容器中刪除所有元素

8.順序容器的常用成員函數

  • front :返回容器中第一個元素的引用
  • back : 返回容器中最後一個元素的引用
  • push_back : 在容器末尾增加新元素
  • pop_back : 刪除容器末尾的元素
  • erase :刪除迭代器指向的元素(可能會使該迭代器失效),或刪除一個區間,返回被刪除元素後面的那個元素的迭代器

9.迭代器

  • 用於指向順序容器和關聯容器中的元素
  • 迭代器用法和指針類似
  • 有const 和非 const兩種
  • 通過迭代器可以讀取它指向的元素
  • 通過非const迭代器還能修改其指向的元素
定義一個容器類的迭代器的方法可以是:
容器類名::iterator 變量名;
或:
容器類名::const_iterator 變量名;
訪問一個迭代器指向的元素:
* 迭代器變量名
迭代器上可以執行 ++ 操作, 以使其指向容器中的下一個元素。如果迭代器到達了容器中的最後一個元素的後面,此時再使用它,就會出錯,類似於使用NULL或未初始化的指針一樣。

常用兩種迭代器:雙向迭代器和隨機訪問迭代器。

10.雙向迭代器

若p和p1都是雙向迭代器,則可對p、p1可進行以下操作:
  • ++p, p++ 使p指向容器中下一個元素
  • --p, p-- 使p指向容器中上一個元素
  • * p 取p指向的元素
  • p = p1 賦值
  • p == p1 , p!= p1 判斷是否相等、不等

11.隨機訪問迭代器

若p和p1都是隨機訪問迭代器,則可對p、p1可進行以下操作:
  • 雙向迭代器的所有操作
  • p += i 將p向後移動i個元素
  • p -= i 將p向向前移動i個元素
  • p + i 值爲: 指向 p 後面的第i個元素的迭代器
  • p - i 值爲: 指向 p 前面的第i個元素的迭代器
  • p[i] 值爲: p後面的第i個元素的引用
  • p < p1, p <= p1, p > p1, p>= p1

8.2 STL概述續

1.容器上的迭代器

容器                      容器上的迭代器類別
vector                     隨機訪問
deque                    隨機訪問
list                           雙向
set/multiset            雙向
map/multimap      雙向
stack                       不支持迭代器
queue                    不支持迭代器
priority_queue     不支持迭代器

有的算法,例如sort,binary_search需要通過隨機訪問迭代器來訪問容器中的元素,那麼list以及關聯容器就不支持該算法!

list 的迭代器是雙向迭代器,
正確的遍歷list的方法:
list<int> v;
list<int>::const_iterator ii;
for( ii = v.begin(); ii != v.end ();++ii )
  cout << * ii;       //用的是解引用方式取元素。循環的判斷條件是!=
錯誤的做法:
for( ii = v.begin(); ii < v.end ();ii ++ )
  cout << * ii;
//雙向迭代器不支持 <,list沒有 [] 成員函數
for(int i = 0;i < v.size() ; i ++)
  cout << v[i]; 
可以看到,list 的迭代器是雙向迭代器,不支持 < 操作,沒有 [ ] 成員函數。

2.算法簡介

  • 算法就是一個個函數模板,大多數在<algorithm> 中定義
  • STL中提供能在各種容器中通用的算法,比如查找,排序等
  • 算法通過迭代器來操縱容器中的元素。許多算法可以對容器中的一個局部區間進行操作,因此需要兩個參數,一個是起始元素的迭代器,一個是終止元素的後面一個元素的迭代器。比如,排序和查找
  • 有的算法返回一個迭代器。比如 find() 算法,在容器中查找一個元素,並返回一個指向該元素的迭代器
  • 算法可以處理容器,也可以處理普通數組

3.算法示例:find()

聲明:(不同編譯器可能不一樣,這裏給出 Dev C++ 中的聲明)
template<class InIt, class T>
InIt find(InIt first, InIt last, const T& val);
  • first 和 last 這兩個參數都是容器的迭代器,它們給出了容器中的查找區間起點和終點[first,last)。區間的起點是位於查找範圍之中的,而終點不是。find在[first,last)查找等於val的元素
  • 用 == 運算符判斷相等
  • 函數返回值是一個迭代器。如果找到,則該迭代器指向被找到的元素。如果找不到,則該迭代器等於last
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int main() { //find算法示例
  int array[10] = {10,20,30,40};
  vector<int> v;
  v.push_back(1); v.push_back(2);
  v.push_back(3); v.push_back(4);
  vector<int>::iterator p;
  p = find(v.begin(),v.end(),3);
  if( p != v.end())
    cout << * p << endl; //輸出3
  p = find(v.begin(),v.end(),9);
  if( p == v.end())
    cout << "not found " << endl;
  p = find(v.begin()+1,v.end()-2,1); //整個容器:[1,2,3,4], 查找區間:[2,3)
  if( p != v.end())     //沒有找到,p則爲v.end()-2,指向3
    cout << * p << endl;
  int * pp = find( array,array+4,20);//數組名是迭代器
  cout << * pp << endl;
}
輸出:
3
not found
3
20

4.STL中“大”“小” 的概念

  • 關聯容器內部的元素是從小到大排序的
  • 有些算法要求其操作的區間是從小到大排序的,稱爲“有序區間算法
        例:binary_search
  • 有些算法會對區間進行從小到大排序,稱爲“排序算法
        例: sort
  • 還有一些其他算法會用到“大”,“小”的概念
  • 使用STL時,在缺省的情況下,以下三個說法等價
        1) x比y小
        2) 表達式“x<y”爲真
        3) y比x大

5.STL中“相等”的概念

  • 有時,“x和y相等”等價於“x==y爲真”
        例:在未排序的區間上進行的算法,如順序查找find……
  • 有時“x和y相等”等價於“x小於y和y小於x同時爲假”
        例:有序區間算法,如binary_search,
                關聯容器自身的成員函數 find……(關聯容器就是用來查找的,自身帶有成員函數find,你不能用find算法或其他的binary_search算法去查找關聯容器)

6.STL中“相等” 概念演示

#include <iostream>
#include <algorithm>
using namespace std;
class A {
  int v;
  public:
    A(int n):v(n) { }
    bool operator< ( const A & a2) const
    {
      cout << v << "<" << a2.v << "?" << endl;
      return false;
    }
    bool operator ==(const A & a2) const
    {
      cout << v << "==" << a2.v << "?" << endl;
      return v == a2.v;
    }
};
int main()
{
  A a [] = { A(1),A(2),A(3),A(4),A(5) };
  cout << binary_search(a,a+4,A(9)); //折半查找
  return 0;
}
//輸出結果:
3<9?
2<9?
1<9?
9<1? 
1
這裏可以看到,binary_search調用的是 < 操作符,沒有使用 == 操作符。這樣在查找中的相當概念即是“x小於y和y小於x同時爲假”

8.3 順序容器 vector

1.vector

  • 可變長的動態數組
  • 必須包含頭文件 #include <vector>
  • 支持 隨機訪問迭代器
        根據下標隨機訪問某個元素時間爲常數
        在尾部添加速度很快
        在中間插入慢
  • 所有STL算法 都能對vector操作

2.vector的成員函數

構造函數初始化
vector();                                           無參構造函數, 將容器初始化成空的
vector(int n);                                   將容器初始化成有n個元素
vector(int n, const T & val);          假定元素類型是T, 將容器初始化成有n個元素, 每個元素的值都是val
vector(iterator first, iterator last); 將容器初始化爲與別的容器上區間[first, last)一致的內容

其他常用函數
void pop_back();                           刪除容器末尾的元素
void push_back(const T & val);   將val添加到容器末尾
int size();                                        返回容器中元素的個數
T & font();                                      返回容器中第一個元素的引用
T & back();                                     返回容器中最後一個元素的引用

3.二維動態數組

vector< vector > v(3);//v有3個元素,每個元素都是vector 容器 

8.4 List 和 Deque

1.list 容器

  • 雙向鏈表   頭文件 #include <list>
  • 在任何位置插入/刪除都是常數時間
  • 不支持根據下標隨機存取元素
  • 具有所有順序容器都有的成員函數
還支持8個成員函數:
push_front    在鏈表最前面插入
pop_front      刪除鏈表最前面的元素
sort                排序 (list 不支持 STL 的算法 sort)
remove         刪除和指定值相等的所有元素
unique          刪除所有和前一個元素相同的元素
merge           合併兩個鏈表, 並清空被合併的鏈表
reverse         顛倒鏈表
splice            在指定位置前面插入另一鏈表中的一個或多個元素,並在另一鏈表中刪除被插入的元素

2.list容器之sort函數

list容器的迭代器不支持完全隨機訪問—>不能用標準庫中sort函數對它進行排序

list自己的sort成員函數
list<T> classname
classname.sort(compare);
//compare函數可以自己定義
classname.sort(); //無參數版本, 按<排序

list容器只能使用雙向迭代器
——>不支持大於/小於比較運算符, []運算符和隨機移動(即類似 “list的迭代器+2” 的操作)

很長的例子

3.deque 容器

  • 雙向隊列
  • 必須包含頭文件 #include <deque>
  • 所有適用於vector的操作——>都適用於deque
  • deque還有 push_front (將元素插入到容器的頭部)和 pop_front (刪除頭部的元素) 操作

8.5 函數對象

1.函數對象

若一個類重載了運算符 “()”,則該類的對象就成爲函數對象
class CMyAverage { //函數對象類
  public:
    double operator() ( int a1, int a2, int a3 ) {
      return (double)(a1 + a2+a3) / 3;
    }
};
CMyAverage average; //函數對象
cout << average(3,2,3); // average.operator()(3,2,3)
輸出 2.66667

2.函數對象的應用

Dev C++ 中的 Accumulate 源代碼1:

template<typename _InputIterator, typename _Tp>
_Tp accumulate(_InputIterator __first,  _InputIterator __last,_ Tp __init)
{
  for ( ; __first != __last; ++__first)
      __init = __init + *__first;
  return __init;
}
// typename 等價於class

Dev C++ 中的 Accumulate 源代碼2:

template<typename _InputIterator, typename _Tp, typename _BinaryOperation>
_Tp accumulate(_InputIterator __first,  _InputIterator __last,  _Tp __init,  _BinaryOperation __binary_op)
{
  for ( ; __first != __last; ++__first)
    __init = __binary_op(__init, *__first);
  return __init;
}
調用accumulate時, 和__binary_op對應的實參可以是個函數名、函數指針或函數對象

3.函數對象的應用示例

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <functional>
using namespace std;
int sumSquares( int total, int value) { return total + value * value; }
template <class T>
void PrintInterval(T first, T last)
{ //輸出區間[first,last)中的元素
  for( ; first != last; ++ first)
    cout << * first << " ";
    cout << endl;
}
template<class T>
class SumPowers
{
  private:
    int power;
  public:
    SumPowers(int p):power(p) { }
    const T operator() ( const T & total, const T & value)
    { //計算 value的power次方,加到total上
      T v = value;
      for( int i = 0;i < power - 1; ++ i)
        v = v * value;
      return total + v;
    }
};
int main()
{
  const int SIZE = 10;
  int a1[ ] = { 1,2,3,4,5,6,7,8,9,10 };
  vector<int> v(a1,a1+SIZE);
  cout << "1) "; PrintInterval(v.begin(),v.end());
  int result = accumulate(v.begin(),v.end(),0,SumSquares);
  cout << "2) 平方和:" << result << endl;
  result =accumulate(v.begin(),v.end(),0,SumPowers<int>(3));
  cout << "3) 立方和:" << result << endl;
  result =accumulate(v.begin(),v.end(),0,SumPowers<int>(4));
  cout << "4) 4次方和:" << result;
  return 0;
}
//輸出:
1) 1 2 3 4 5 6 7 8 9 10
2) 平方和:385
3) 立方和:3025
4) 4次方和:25333
int result = accumulate(v.begin(),v.end(),0,SumSquares);
實例化出:
int accumulate(vector<int>::iterator first, vector<int>::iterator last, int init,int ( * op)( int,int))
{
   for ( ; first != last; ++first)
     init = op(init, *first);
   return init;
}

accumulate(v.begin(),v.end(),0,SumPowers<int>(3));
實例化出:
int accumulate(vector<int>::iterator first,vector<int>::iterator last, int init, SumPowers<int> op)
{
  for ( ; first != last; ++first)
    init = op(init, *first);
  return init;
}

4.STL中的函數對象類模板

以下模板可以用來生成函數對象。
    equal_to
    greater
    less
    …….
頭文件: <functional>

greater 函數對象類模板

template<class T>
struct greater : public binary_function<T, T, bool> {
    bool operator()(const T& x, const T& y) const {
      return x > y;
    }
};

greater 的應用

list 有兩個sort成員函數
   void sort();  將list中的元素按 “<” 規定的比較方法升序排列。
   template <class Compare>  void sort (Compare op);將list中的元素按 op 規定的比較方法升序排列。即要比較x,y大小時,看op(x,y) 的返回值,爲true則認爲 x小於y

#include <list>
#include <iostream>
using namespace std;
class MyLess {
  public:
    bool operator()( const int & c1, const int & c2 )
    {
      return (c1 % 10) < (c2 % 10);
    }
};
template <class T>
void Print(T first,T last) {
  for( ; first != last ; ++ first ) cout << * first << ",";
}
int main()
{ 
  const int SIZE = 5;
  int a[SIZE] = {5,21,14,2,3};
  list<int> lst(a,a+SIZE);
  lst.sort(MyLess());        //MyLess()是一個函數對象,臨時對象的()成員函數
  Print( lst.begin(),lst.end());
  cout << endl;
  lst.sort(greater<int>()); //greater是一個類模板,greater<int>是一個類,greater<int>()是個臨時對象,它是函數對象
  Print( lst.begin(),lst.end());
  cout << endl;
  return 0;
}
//輸出:
21,2,3,14,5,
21,14,5,3,2,

5.在STL中使用自定義的“大”,“小”關係

關聯容器和STL中許多算法,都是可以用函數或函數對象自定義比較器的。在自定義了比較器op的情況下,以下三種說法是等價
的:
1) x小於y
2) op(x,y)返回值爲true
3) y大於x

例題:寫出MyMax模板

#include <iostream>
#include <iterator>
using namespace std;
class MyLess {
  public:
    bool operator() (int a1,int a2) {
      if( ( a1 % 10 ) < (a2%10) )
        return true;
      else
        return false;
    }
};

bool MyCompare(int a1,int a2)
{
  if( ( a1 % 10 ) < (a2%10) )
    return false;
  else
    return true;
}

int main()
{
  int a[] = {35,7,13,19,12};
  cout << * MyMax(a,a+5,MyLess())
    << endl;
  cout << * MyMax(a,a+5,MyCompare)
    << endl;
  return 0;
}
//輸出:19
12
怎麼寫這個模板呢?
template <class T, class Pred>
T MyMax( T first, T last, Pred myless)
{
  T tmpMax = first;
  for(; first != last; ++ first)
    if( myless( * tmpMax,* first))
      tmpMax = first;
  return tmpMax;
};

8.6 Set 和 Multiset

1.關聯容器

set, multiset, map, multimap

內部元素有序排列,新元素插入的位置取決於它的值,查找速度快。

除了各容器都有的函數外,還支持以下成員函數:
find:                       查找等於某個值 的元素(x小於y和y小於x同時不成立即爲相等)
lower_bound :     查找某個下界
upper_bound :    查找某個上界
equal_range :     同時查找上界和下界
count :                  計算等於某個值的元素個數(x小於y和y小於x同時不成立即爲相等)
insert:                   用以插入一個元素或一個區間

2.預備知識: pair 模板

template<class _T1, class _T2>
struct pair
{
  typedef _T1 first_type;
  typedef _T2 second_type;
  _T1 first;
  _T2 second;
  pair(): first(), second() { }
  pair(const _T1& __a, const _T2& __b): first(__a), second(__b) { }
  template<class _U1, class _U2>
  pair(const pair<_U1, _U2>& __p): first(__p.first), second(__p.second) { }
};

map/multimap容器裏放着的都是pair模版類的對象,且按first從小到大排序

第三個構造函數用法示例:

pair<int,int> p(pair<double,double>(5.5,4.6));
// p.first = 5, p.second = 4

3.multiset

template<class Key, class Pred = less<Key>, class A = allocator<Key> >
class multiset { …… };

Pred類型的變量決定了multiset 中的元素,“一個比另一個小”是怎麼定義的。

multiset運行過程中,比較兩個元素x,y的大小的做法,就是生成一個 Pred類型的變量,假定爲 op,若表達式op(x,y) 返回值爲true,則 x比y小。

Pred的缺省類型是 less<Key>

less 模板的定義:
template<class T>
struct less : public binary_function<T, T, bool>
{ 
  bool operator()(const T& x, const T& y) 
  { return x < y ; } const; 
};
less模板是靠 < 來比較大小的

multiset的成員函數

  • iterator find(const T & val);
        在容器中查找值爲val的元素,返回其迭代器。如果找不到,返回end()。
  • iterator insert(const T & val);
        將val插入到容器中並返回其迭代器。
  • void insert( iterator first,iterator last);
        將區間[first,last)插入容器。
  • int count(const T & val);
        統計有多少個元素的值和val相等。
  • iterator lower_bound(const T & val);
        查找一個最大的位置 it,使得[begin(),it) 中所有的元素都比 val 小。
  • iterator upper_bound(const T & val);
        查找一個最小的位置 it,使得[it,end()) 中所有的元素都比 val 大。
  • pair<iterator,iterator> equal_range(const T & val);
        同時求得lower_bound和upper_bound。
  • iterator erase(iterator it);
        刪除it指向的元素,返回其後面的元素的迭代器(Visual studio 2010上如此,但是在C++標準和Dev C++中,返回值不是這樣)。
        注意,刪除後,該迭代器失效。

multiset 的用法

#include <set>
using namespace std;
class A { };
int main() {
  multiset<A> a;
  a.insert( A()); //error
} 
multiset <A> a; 就等價於multiset<A, less<A>> a;插入元素時,multiset會將被插入元素和已有元素進行比較。由於less模板是用 < 進行比較的,所以,這都要求 A 的對象能用 < 比較,即適當重載了 < 

很長的例子...

4.set

template<class Key, class Pred = less<Key>,class A = allocator<Key> >
class set { … }
插入set中已有的元素時,忽略插入。


8.7 map和multimap

1.預備知識: pair 模板

同 8.6.2

2.multimap

template<class Key, class T, class Pred = less<Key>,class A = allocator<T> >
class multimap {
  ….
  typedef pair<const Key, T> value_type;
  …….
}; //Key 代表關鍵字的類型

multimap中的元素由 <關鍵字,值>組成,每個元素是一個pair對象,關鍵字就是first成員變量,其類型是Key

multimap 中允許多個元素的關鍵字相同。元素按照first成員變量從小到大排列,缺省情況下用 less<Key> 定義關鍵字的“小於”關係。


8.8 容器適配器

1.容器適配器

用某種順序容器來實現

讓已有的順序容器以棧/隊列的方式工作

  • stack: 頭文件 <stack>
        棧 -- 後進先出
  • queue: 頭文件 <queue>
        隊列 -- 先進先出
  • priority_queue: 頭文件 <queue>
        優先級隊列 -- 最高優先級元素總是第一個出列

都有3個成員函數:
  • push: 添加一個元素;
  • top: 返回棧頂部或隊頭元素的引用
  • pop: 刪除一個元素
容器適配器上沒有迭代器,STL中各種排序,查找,變序等算法都不適合容器適配器

2.stack

  • stack 是後進先出的數據結構
  • 只能插入,刪除,訪問棧頂的元素
  • 可用 vector, list, deque來實現
            缺省情況下, 用deque實現
            用 vector和deque實現, 比用list實現性能好
    template<class T, class Cont = deque<T> >
    class stack {
    …
    }; 

  • stack 中主要的三個成員函數:
    void push(const T & x);    將x壓入棧頂
    void pop();    彈出(即刪除)棧頂元素
    T & top();    返回棧頂元素的引用. 通過該函數, 可以讀取棧頂元素的值, 也可以修改棧頂元素

3.queue

  • 和stack 基本類似, 可以用 list和deque實現
  • 缺省情況下用deque實現
template<class T, class Cont = deque<T> >
class queue {
……
};
  • 同樣也有push, pop, top函數
            push發生在隊尾
            pop, top發生在隊頭, 先進先出

4.priority_queue

  • 和 queue類似, 可以用vector和deque實現
  • 缺省情況下用vector實現
  • priority_queue 通常用堆排序技術實現, 保證最大的元素總是在最前面
            執行pop操作時, 刪除的是最大的元素
            執行top操作時, 返回的是最大元素的引用
            不允許修改隊頭元素
  • 因爲是堆排序,它只能保證最大的元素在隊頭,不能保證內部元素的有序。特別適用於在一堆元素中間不停取最大值的情況。
  • 默認的元素比較器是小於運算符 less<T>,最大的元素在隊頭

8.8 算法

1.STL 算法分類

STL中的算法大致可以分爲以下七類:

  • 不變序列算法
  • 變值算法
  • 刪除算法
  • 變序算法
  • 排序算法
  • 有序區間算法
  • 數值算法

2.算法

大多重載的算法都是有兩個版本的

  • 用 “==” 判斷元素是否相等, 或用 “<” 來比較大小
  • 多出一個類型參數 “Pred” 和函數形參 “Pred op” 
第二個版本是,通過表達式 “op(x,y)” 的返回值: ture/false判斷x是否 “等於” y,或者x是否 “小於” y。認爲返回true表示x"小於"y。op參數可以是函數名或函數對象。

如下面的有兩個版本的min_element:

iterator min_element(iterator first, iterator last);
iterator min_element(iterator first, iterator last, Pred op);

2-1 不變序列算法

  • 該類算法不會修改算法所作用的容器或對象
  • 適用於順序容器和關聯容器
  • 時間複雜度都是O(n)  (因爲它們一般都會從頭到尾遍歷一下整個容器)

min、max比較常用。可自定義比較器一般都是有兩個版本,一個<,一個==。

find特別常用。後三個不常用。

find:
template<class InIt, class T>
InIt find(InIt first, InIt last, const T& val);
返回區間 [first,last) 中的迭代器 i ,使得 * i == val;沒有找到,返回last

find_if:
template<class InIt, class Pred>
InIt find_if(InIt first, InIt last, Pred pr);
返回區間 [first,last) 中的迭代器 i, 使得 pr(*i) == true

for_each:
template<class InIt, class Fun>
Fun for_each(InIt first, InIt last, Fun f);
對[first, last)中的每個元素e, 執行f(e), 要求 f(e)不能改變e

count:
template<class InIt, class T>
size_t count(InIt first, InIt last, const T& val);
計算[first, last) 中等於val的元素個數(x==y爲true算等於)

count_if:
template<class InIt, class Pred>
size_t count_if(InIt first, InIt last, Pred pr);
計算[first, last) 中符合pr(e) == true 的元素e的個數

min_element:
template<class FwdIt>
FwdIt min_element(FwdIt first, FwdIt last);
返回[first,last) 中最小元素的迭代器,以 “<” 作比較器
最小指沒有元素比它小, 而不是它比別的不同元素都小
因爲即便a!= b, a<b 和b<a有可能都不成立

max_element:
template<class FwdIt>
FwdIt max_element(FwdIt first, FwdIt last);
返回[first,last) 中最大元素(不小於任何其他元素)的迭代器
以 “<” 作比較器

#include <iostream>
#include <algorithm>
using namespace std;
class A {
  public:
    int n;
    A(int i):n(i) { }
};
bool operator<( const A & a1, const A & a2) {
  cout << “< called” << endl;
  if( a1.n == 3 && a2.n == 7 )  return true;//只有3<7成立
  return false;
}

int main() {
  A aa[] = { 3,5,7,2,1 };
  cout << min_element(aa,aa+5)->n << endl;
  cout << max_element(aa,aa+5)->n << endl;
  return 0;
}
輸出:
< called
< called
< called
< called
3   //開始時假設3是最小值,經過4次比較<都不成立,所以3是最小值
< called  //3<5不成立,最大值是3
< called  //3<7成立,最大值是7
< called
< called
7

2-2 變值算法

  • 此類算法會修改源區間或目標區間元素的值
  • 值被修改的那個區間, 不可以是屬於關聯容器的  (因爲關聯容器是有序的,如果進行修改了,有序性就破壞了,再進行其他操作就會有問題。)

for_each與上一個版本的區別在於,函數名或函數對象的那個參數,一個是const,一個不是const
copy與copy_backward的區別在於,copy是從前往後複製,copy_backward是從後往前複製。它主要考慮兩個區間本身有重疊的時候,那麼如果都是從前往後拷貝的話,可能會出現後面的內容在沒有拷貝之前就已經被覆蓋掉了。重疊部分的內容還沒有被拷貝到目標區間就已經被覆蓋掉了。copy_backward它實現的實際上是從後往前拷貝,解決這個問題。

後面的幾個都很少用。

transform
template<class InIt, class OutIt, class Unop>
OutIt transform(InIt first, InIt last, OutIt x, Unop uop);

對[first,last)中的每個迭代器I,
• 執行 uop( * I ); 並將結果依次放入從 x 開始的地方
• 要求 uop( * I ) 不得改變 * I 的值
本模板返回值是個迭代器, 即 x + (last-first),即目標區域最後一個元素後面的那個位置。
• x可以和 first相等

#include <vector>
#include <iostream>
#include <numeric>
#include <list>
#include <algorithm>
#include <iterator>
using namespace std;
class CLessThen9 {//函數對象,小於9
  public:
    bool operator()( int n) { return n < 9; }
};
void outputSquare(int value ) { cout << value * value << " "; }//輸出平方
int calculateCube(int value) { return value * value * value; }//計算立方

int main() {
  const int SIZE = 10;
  int a1[] = { 1,2,3,4,5,6,7,8,9,10 };
  int a2[] = { 100,2,8,1,50,3,8,9,10,2 };
  vector<int> v(a1,a1+SIZE);
  ostream_iterator<int> output(cout," ");
  random_shuffle(v.begin(),v.end());//隨機打亂
  cout << endl << "1) ";
  copy( v.begin(),v.end(),output);
  copy( a2,a2+SIZE,v.begin());
  cout << endl << "2)";
  cout << count(v.begin(),v.end(),8);
  cout << endl << "3)";
  cout << count_if(v.begin(),v.end(),CLessThen9());
  cout << endl << "4) ";
  cout << * (min_element(v.begin(), v.end()));
  cout << endl << "5) ";
  cout << * (max_element(v.begin(), v.end()));
  cout << endl << "6) ";
  cout << accumulate(v.begin(), v.end(), 0); //求和
  cout << endl << "7) ";
  for_each(v.begin(), v.end(), outputSquare);
  vector<int> cubes(SIZE);
  transform(a1, a1+SIZE, cubes.begin(), calculateCube);
  cout << endl << "8) ";
  copy(cubes.begin(), cubes.end(), output);
  return 0;
}
輸出:
1) 5 4 1 3 7 8 9 10 6 2
2) 2
3) 6
//1) 是隨機的
4) 1
5) 100
6) 193
7)10000 4 64 1 2500 9 64 81 100 4
8)1 8 27 64 125 216 343 512 729 1000
ostream_iterator<int> output(cout ,“ ”);定義了一個 ostream_iterator<int> 對象,可以通過cout輸出以 “ ”(空格) 分隔的一個個整數

copy (v.begin(), v.end(), output);導致v的內容在 cout上輸出

copy 函數模板(算法)

template<class InIt, class OutIt>
OutIt copy(InIt first, InIt last, OutIt x);

本函數對每個在區間[0, last - first)中的N執行一次*(x+N) = *(first + N), 返回 x + N

對於copy(v.begin(),v.end(),output);first 和 last 的類型是 vector<int>::const_iteratoroutput 的類型是 ostream_iterator<int> 

copy 的源代碼:

template<class _II, class _OI>
inline _OI copy(_II _F, _II _L, _OI _X)
{
  for (; _F != _L; ++_X, ++_F)
    *_X = *_F;
  return (_X);
}
例子
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <iterator>
using namespace std;
int main(){
  int a[4] = { 1,2,3,4 };
  My_ostream_iterator<int> oit(cout,"*");
  copy(a,a+4,oit); //輸出 1*2*3*4*
  ofstream oFile("test.txt", ios::out);
  My_ostream_iterator<int> oitf(oFile,"*");
  copy(a,a+4,oitf); //向test.txt文件中寫入 1*2*3*4*
  oFile.close();
  return 0;
}
// 如何編寫 My_ostream_iterator? 
上面程序中調用語句 “copy( a,a+4,oit)” 實例化後得到copy如下:
My_ostream_iterator<int> copy(int * _F, int * _L, My_ostream_iterator<int> _X)
{
  for (; _F != _L; ++_X, ++_F)
    *_X = *_F;
  return (_X);
}
My_ostream_iterator類應該重載 “++” 和 “*” 運算符
“=” 也應該被重載
#include <iterator>
template<class T>
class My_ostream_iterator:public iterator<output_iterator_tag, T>{
  private:
    string sep; //分隔符
    ostream & os;
  public:
    My_ostream_iterator(ostream & o, string s):sep(s), os(o){ }
    void operator ++() { }; // ++只需要有定義即可, 不需要做什麼
    My_ostream_iterator & operator * () { return * this; }
    My_ostream_iterator & operator = ( const T & val)
    { os << val << sep; return * this; }
};
(上面這個示例不懂可能需要看視頻講解了。)

2-3 刪除算法

  • 刪除一個容器裏的某些元素
  • 刪除 -- 不會減少容器裏元素的個數
            將所有應該被刪除的元素看做空位子
            用留下的元素從後往前移, 依次去填空位子
            元素往前移後, 它原來的位置也就算是空位子,也應由後面的留下的元素來填上
            最後, 沒有被填上的空位子, 維持其原來的值不變
  • 刪除算法不應作用於關聯容器   (也是因爲關聯容器是有序的)

算法複雜度都是O(n)的
remove 和 unique 比較常用。經常需要把一個數組排序後刪除重複的元素,那可以先sort一下,然後unique一下;unique返回的是刪完後最後一個有效元素的下一個位置迭代器,則和begin()相減一下,還可以得到最後數組元素的個數。

或者是 sort + unique + erase。sort排序,unique刪除連續相等的元素、返回有效元素的下一個位置,erase刪除從有效元素下一個位置到最後一個元素這樣的範圍。(這個在課後一題裏用到了)

unique

template<class FwdIt>
FwdIt unique(FwdIt first, FwdIt last);
用 == 比較是否等
template<class FwdIt, class Pred>
FwdIt unique(FwdIt first, FwdIt last, Pred pr);
用 pr (x,y)爲 true說明x和y相等
對[first,last) 這個序列中連續相等的元素, 只留下第一個
返回值是迭代器, 指向元素刪除後的區間的最後一個元素的後面

int main(){
  int a[5] = { 1,2,3,2,5 };
  int b[6] = { 1,2,3,2,5,6 };
  ostream_iterator<int> oit(cout,",");
  int * p = remove(a,a+5,2);
  cout << "1) "; copy(a,a+5,oit); cout << endl; //輸出 1) 1,3,5,2,5,
  cout << "2) " << p - a << endl; //輸出 2) 3
  vector<int> v(b,b+6);
  remove(v.begin(), v.end(),2);
  cout << "3) "; copy(v.begin(), v.end(), oit); cout << endl;
  //輸出 3) 1,3,5,6,5,6,
  cout << "4) "; cout << v.size() << endl;
  //v中的元素沒有減少,輸出 4) 6
  return 0;
} 
在cout<<"1)"的地方可以看到,remove後 a 數組是1,3,5,2,5,因爲刪除兩個2後、其他元素往前移、最後兩個元素維持原樣。

可以想一下,remove可以作用於容器可能可以真的刪除了元素,但remove也可以作用於數組,對於數組怎麼把元素刪了呢。所以它只能告訴你最後一個有效元素的位置,而不能改變容器中元素的個數。


2-4 變序算法

  • 變序算法改變容器中元素的順序
  • 但是不改變元素的值(意思應該是元素值不變,但容器中元素的順序會變的)
  • 變序算法不適用於關聯容器
  • 算法複雜度大部分是O(n)的

reverse常用,其他不常用。

stable_patition
把區間內滿足某個條件的元素移到前面不滿足該條件的移到後面
而對這兩部分元素, 分別保持它們原來的先後次序不變

random_shuffle

template<class RanIt>
void random_shuffle(RanIt first, RanIt last);
隨機打亂[first,last) 中的元素, 適用於能隨機訪問的容器

reverse

template<class BidIt>
void reverse(BidIt first, BidIt last);
顛倒區間[first,last)順序

next_permutation

template<class InIt>
bool next_permutaion (Init first,Init last);
求下一個排列

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int main(){
  string str = "231";
  char szStr[] = "324";
  while (next_permutation(str.begin(), str.end())){
    cout << str << endl;
  }
  cout << "****" << endl;
  while (next_permutation(szStr,szStr + 3)){
    cout << szStr << endl;
  } 
  sort(str.begin(), str.end());
  cout << "****" << endl;
  while (next_permutation(str.begin(), str.end()))
  {
    cout << str << endl;
  }
  return 0;
}
//輸出:
312
321
****
342
423
432
//輸出:
132
213
231
312
321
#include <iostream>
#include <algorithm>
#include <string>
#include <list>
#include <iterator>
using namespace std;
int main(){
  int a[] = { 8,7,10 };
  list<int> ls(a, a+3);
  while( next_permutation(ls.begin(), ls.end())) {
    list<int>::iterator i;
    for( i = ls.begin(); i != ls.end(); ++i)
      cout << * i << " ";
      cout << endl;
    }
}
輸出:
8 10 7
10 7 8
10 8 7


2-5 排序算法

  • 比前面的變序算法複雜度更高, 一般是O(nlog(n))
  • 排序算法需要隨機訪問迭代器的支持
  • 不適用於關聯容器和list (list上的迭代器是雙向的,不是隨機訪問迭代器)


sort 快速排序
template<class RanIt>
void sort(RanIt first, RanIt last);
按升序排序
判斷x是否應比y靠前, 就看 x < y 是否爲true
template<class RanIt, class Pred>
void sort(RanIt first, RanIt last, Pred pr);
按升序排序
判斷x是否應比y靠前, 就看 pr(x,y) 是否爲true
#include <iostream>
#include <algorithm>
using namespace std;
class MyLess {
  public:
    bool operator()( int n1,int n2) {
      return (n1 % 10) < ( n2 % 10);
    }
};//按個位數大小排序,按降序排序
int main() {
  int a[] = { 14,2,9,111,78 };
  sort(a, a + 5, MyLess());
  int i;
  for( i = 0;i < 5;i ++)
  cout << a[i] << " ";
  cout << endl;
  sort(a, a+5, greater<int>()); //greater之前說了,誰在數學上大,反而是小。
  for( i = 0;i < 5;i ++)
    cout << a[i] << " ";
}
<pre name="code" class="cpp">//輸出:
111 2 14 78 9
111 78 14 9 2

sort 實際上是快速排序, 時間複雜度 O(n*log(n))
  • 平均性能最優
  • 但是最壞的情況下, 性能可能非常差 O(n*n)
如果要保證 “最壞情況下” 的性能, 那麼可以使用 stable_sort
  • stable_sort 實際上是歸併排序, 特點是能保持相等元素之間的先後次序
  • 平均複雜度和最壞複雜度都是 O(n*log(n))
  • 在有足夠存儲空間的情況下, 複雜度爲 n * log(n), 否則複雜度爲 n * log(n) * log(n)
  • stable_sort 用法和 sort相同。
排序算法要求隨機存取迭代器的支持,所以list不能使用排序算法,要使用list::sort 

2-6 有序區間算法

  • 要求所操作的區間是已經從小到大排好序的
  • 需要隨機訪問迭代器的支持
  • 有序區間算法不能用於關聯容器和list
binary_search折半查找,複雜度是 O(log(n))要求容器已經有序且支持隨機訪問迭代器, 返回是否找到
template<class FwdIt, class T>
bool binary_search(FwdIt first, FwdIt last, const T& val);
上面這個版本, 比較兩個元素x, y 大小時, 看 x < y 和 y<x 同時不成立纔是相等。
template<class FwdIt, class T, class Pred>
bool binary_search(FwdIt first, FwdIt last, const T& val, Pred pr);
上面這個版本, 比較兩個元素x, y 大小時, 若 pr(x,y) 爲true, 則認爲x小於y

lower_bound

template<class FwdIt, class T>
FwdIt lower_bound(FwdIt first, FwdIt last, const T& val);
要求[first,last)是有序的
查找[first,last)中的, 最大的位置 FwdIt, 使得[first,FwdIt)中所有的元素都比 val 小
即,對[first,FwdIt)中任一元素x,都有 x<val 成立

upper_bound

template<class FwdIt, class T>
FwdIt upper_bound(FwdIt first, FwdIt last, const T& val);
要求[first,last)是有序的
查找[first,last)中的, 最小的位置 FwdIt, 使得[FwdIt,last)中所有的元素都比 val 大
即,對[FwdIt,last)中任一元素x,都有 val<x 成立

equal_range

template<class FwdIt, class T>
pair<FwdIt, FwdIt> equal_range(FwdIt first, FwdIt last,const T& val);
要求[first,last)是有序的,
返回值是一個pair, 假設爲 p, 則:
• [first,p.first) 中的元素都比 val 小
• [p.second,last)中的所有元素都比 val 大  (即同時求得lower_bound和upper_bound的結果)
• p.first 就是lower_bound的結果
• p.last 就是 upper_bound的結果 

merge

template<class InIt1, class InIt2, class OutIt>
OutIt merge(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2,OutIt x);
用 < 作比較器
template<class InIt1, class InIt2, class OutIt, class Pred>
OutIt merge(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2,OutIt x, Pred pr);
用 pr 作比較器

把[first1,last1), [ first2,last2) 兩個升序序列合併, 形成第3 個升序序列, 第3個升序序列以 x 開頭(後面必須要有足夠的空間

includes

template<class InIt1, class InIt2>
bool includes(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2);
template<class InIt1, class InIt2, class Pred>
bool includes(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2,Pred pr);
判斷 [first2,last2)中的每個元素, 是否都在[first1,last1)中
• 第一個用 <作比較器,x<y和y<x都不成立
• 第二個用 pr 作比較器, pr(x,y) == true說明 x,y相等

set_difference

template<class InIt1, class InIt2, class OutIt>
OutIt set_difference(InIt1 first1, InIt1 last1, InIt2 first2,InIt2 last2, OutIt x);
template<class InIt1, class InIt2, class OutIt, class Pred>
OutIt set_difference(InIt1 first1, InIt1 last1, InIt2 first2,InIt2 last2, OutIt x, Pred pr);
求出[first1,last1)中, 不在[first2,last2)中的元素, 放到 從 x開始的地方
如果 [first1,last1) 裏有多個相等元素不在[first2,last2)中,則這多個元素也都會被放入x代表的目標區間裏
“在”和“不在”的概念與前面類似,用<或自定義比較器來判斷。

後面的set_intersection、set_symmetric_difference、set_union類似
set_intersection求共有的元素。若某個元素e 在[first1,last1)裏出現 n1次, 在[first2,last2)裏出現n2次, 則該元素在目標區間裏出現min(n1,n2)次;
set_symmetric_difference把兩個區間裏相互不在另一區間裏的元素放入x開始的地方;
set_union求兩個區間的並。若某個元素e 在[first1,last1)裏出現 n1次, 在[first2,last2)裏出現n2次, 則該元素在目標區間裏出現max(n1,n2)次。


3. bitset

template<size_t N>
class bitset
{
   …..
};
實際使用的時候, N是個整型常數
如:
• bitset<40> bst;
• bst是一個由40位組成的對象
• 用bitset的函數可以方便地訪問任何一位

bitset的成員函數:
• bitset<N>& operator&=(const bitset<N>& rhs);
• bitset<N>& operator|=(const bitset<N>& rhs);
• bitset<N>& operator^=(const bitset<N>& rhs);
• bitset<N>& operator<<=(size_t num);
• bitset<N>& operator>>=(size_t num);
• bitset<N>& set(); //全部設成1
• bitset<N>& set(size_t pos, bool val = true); //設置某位
• bitset<N>& reset(); //全部設成0
• bitset<N>& reset(size_t pos); //某位設成0
• bitset<N>& flip(); //全部翻轉
• bitset<N>& flip(size_t pos); //翻轉某位 58
reference operator[](size_t pos); //返回對某位的引用
bool operator[](size_t pos) const; //判斷某位是否爲1
reference at(size_t pos);
bool at(size_t pos) const;
unsigned long to_ulong() const; //轉換成整數
string to_string() const; //轉換成字符串
size_t count() const; //計算1的個數
size_t size() const;
bool operator==(const bitset<N>& rhs) const;
bool operator!=(const bitset<N>& rhs) const;
59
bool test(size_t pos) const; //測試某位是否爲 1
bool any() const; //是否有某位爲1
bool none() const; //是否全部爲0
bitset<N> operator<<(size_t pos) const;
bitset<N> operator>>(size_t pos) const;
bitset<N> operator~();
static const size_t bitset_size = N;
注意: 第0位在最右邊


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章