C++ multimap,STL multimap詳解

2020-07-16 10:04:28
multimap 是關聯容器的一種,multimap 的每個元素都分為關鍵字和值兩部分,容器中的元素是按關鍵字排序的,並且允許有多個元素的關鍵字相同。

注意,不能直接修改 multimap 容器中的關鍵字。因為 multimap 中的元素是按照關鍵字排序的,當關鍵字被修改後,容器並不會自動重新調整順序,於是容器的有序性就會被破壞,再在其上進行查詢等操作就會得到錯誤的結果。

使用 multimap 必須包含標頭檔案 map。multimap 的定義如下:

template < class Key, class T, class Pred = less<Key>, class A = allocator<T> >
class multimap
{
    ...
    typedef pair <const Key, T> value_type;
    ...
};


multimap 中的元素都是 pair 模板類的物件。元素的 first 成員變數也叫“關鍵字”,second 成員變數也叫“值”。multimap 容器中的元素是按關鍵字從小到大排序的。預設情況下,元素的關鍵之間用 less <Key> 比較大小,也就是用<運算子比較大小。multimap 允許多個元素的關鍵字相同。

multimap 中的 value_type 實際上就表示容器中元素的型別。C++ 允許在類的內部定義型別。

multimap 的成員函數(未列出每個函數的所有版本)如表 1 所示。

表1:multimap 的成員函數
成員函數或成員函數模板 作  用
iterator find( const Key & val); 在容器中查詢關鍵字等於 val 的元素,返回其疊代器;如果找不到,返回 end()
iterator insert (pair <Key, T> const &p); 將 pair 物件 p 插入容器中並返回其疊代器
void insert(iterator first, iterator last); 將區間 [first, last) 插入容器
int count( const Key & val); 統計有多少個元素的關鍵字和 val 相等
iterator lower_bound( const Key & val); 查詢一個最大的位置 it,使得 [begin( ), it) 中所有的元素的關鍵字都比 val 小
iterator upper_bound(const Key & val); 查詢一個最小的位置 it,使得 [it, end()) 中所有的元素的關鍵字都比 val 大
pair < iterator, iterator > equal_range (const Key & val); 同時求得 lower_bound 和 upper_bound
iterator erase(iterator it); 刪除 it 指向的元素,返回其後面的元素的疊代器(Visual Studio 2010 中如此,但是在 C++ 標準和 Dev C++ 中,返回值不是這樣)
iterator erase(iterator first, iterator last); 刪除區間 [first, last),返回 last(Visual Studio 2010 中如此,但是在 C++ 標準和 Dev C++ 中,返回值不是這樣)

multimap 及 map 中的 find 和 count 不用==運算子比較兩個關鍵字是否相等。如果x比y小y比x小同時為假,就認為 x 和 y 相等。

例題:一個學生成績錄入和查詢系統接受以下兩種輸入:
1) Add name id score
2) Query score

name 是一個字串,其中不包含空格,表示學生姓名。id 是一個整數,表示學號。score 是一個整數,表示分數。學號不會重複,分數和姓名都可能重複。

兩種輸入交替出現。
  • 第一種輸入表示要新增一個學生的資訊,碰到這種輸入,就記下學生的姓名、id 和分數。
  • 第二種輸入表示要查詢分數為 score 的學生的資訊,碰到這種輸入,就輸出已有記錄中分數比查詢分數低的最高分獲得者的姓名、學號和分數。如果有多個學生滿足條件,則輸出學號最大的學生的資訊。如果找不到滿足條件的學生,則輸出“Nobody”。

輸入樣例:
Add Jack 12 78
Query 78
Query 81
Add Percy 9 81
Add Marry 8 81
Query 82
Add Tom 11 79
Query 80
Query 81

輸出結果樣例:
Nobody
Jack 12 78
Percy 9 81
Tom 11 79
Tom 11 79

此題如果用 vector 存放所有學生的資訊,然後進行順序查詢的話,在學生數量很大和查詢很多的情況下非常費時,因為順序查詢的時間複雜度是 O(n)。將 vector 排序後再查詢也不行,因為會不斷插入新元素,每次插入新元素就要進行元素的移動,而這一步驟的時間複雜度是O(n),這會導致效率低下。

下面程式的思路是用 multimap 存放學生資訊,使學生資訊按照分數排序。

要新增學生時,就用 insert 成員函數插入學生記錄,這步操作的時間複雜度是 O(log(n))。

輸入一個要查詢的分數 score 時,就用 lower_bound 求得該分數對應的下界——疊代器 p(這一步的時間複雜度是 O(log(n))。 *p 這個元素的分數是大於或等於 score 的,往前再找一個元素,其分數就是低於 score 的最高分了。繼續往前遍歷所有等於該分數的元素,找出 id 最大的元素輸出即可。

解題程式如下:
#include <iostream>
#include <map>  //使用multimap需要包含此標頭檔案
#include <string>
using namespace std;
class CStudent
{
public:
    struct CInfo  //類的內部還可以定義類
    {
        int id;
        string name;
    };
    int score;
    CInfo info;  //學生的其他資訊
};
typedef multimap <int, CStudent::CInfo> MAP_STD;
int main()
{

    MAP_STD mp;
    CStudent st;
    string cmd;
    while (cin >> cmd) {
        if (cmd == "Add") {
            cin >> st.info.name >> st.info.id >> st.score;
            mp.insert(MAP_STD::value_type(st.score, st.info));
        }
        else if (cmd == "Query") {
            int score;
            cin >> score;
            MAP_STD::iterator p = mp.lower_bound(score);
            if (p != mp.begin()) {
                --p;
                score = p->first;  //比要查詢分數低的最高分
                MAP_STD::iterator maxp = p;
                int maxId = p->second.id;
                for (; p != mp.begin() && p->first == score; --p) {
                    //遍歷所有成績和score相等的學生
                    if (p->second.id > maxId) {
                        maxp = p;
                        maxId = p->second.id;
                    }
                }
                if (p->first == score) { //如果上面的迴圈因為 p == mp.begin()
                                         //而終止,則p指向的元素還要處理
                    if (p->second.id > maxId) {
                        maxp = p;
                        maxId = p->second.id;
                    }
                }
                cout << maxp->second.name << " " << maxp->second.id << " "
                    << maxp->first << endl;
            }
            else  //lower_bound 的結果就是 begin,說明沒有分數比查詢分數低
                cout << "Nobody" << endl;
        }
    }
    return 0;
}
multimap 容器中的元素必須是 pair 類別範本物件。本題需要用 multimap 來存放學生資訊,然而學生資訊由三部分組成:姓名、學號、分數,解決的辦法就是將用於排序的 score 作為一個成員變數,而且把其他部分一起作為一個 CInfo 物件,這樣,第 16 行範例化出來的類 multimap <int, CStudent::CInfo> 中的元素的型別就會是如下 pair 模板類:

class pair <int, CStudent::CInfo>
{
    int first;  //對應於CStudent::score
    CStudent::CInfo second;  //對應於 CStudent::info
};


第 26 行如下:

mp.insert( MAP_STD::value_type(st.score, st.info) );

參看 multimap 的定義,MAP_STD::value_type 就是容器中元素的型別,該型別是 pair <int, CStudent::CInfo>。型別名後面跟建構函式的參數列就代表一個物件。因此,此條語句生成了一個 pair <int, CStudent::CInfo> 物件並將其插入 multimap 容器中。該物件內部存放的資訊和 st 相同,first 對應於 st.score,second 對應於 st.info。

第 31 行,lower_bound 的返回結果 p 滿足以下條件:[begin(), p) 中的分數都比查詢分數低,但是 *p 的分數不比查詢分數低。所以執行 --p 操作之後,*p 的分數就是低於查詢分數的最高分了。