數據結構與算法分析(二)--- STL簡介 + 線性表容器(C++11)

一、什麼是STL

每種數據結構都要提供相應的一組操作方法(也即 增、刪、改、查、排序 等算法)。對於相對底層的面向過程的C語言來說,要想使用一些複雜的數據結構,標準庫並沒有提供相應的接口,需要自己實現相應的一組操作方法,當然也可以使用第三方庫比如libcstl

對於面向對象的語言比如C++,在標準庫中就提供了常用的數據結構及其算法,這就是C++有名的STL(Standard Template Library),也是C++標準庫的核心,其它的編程語言一般也都提供有類似的STL標準模板庫。

C++標準庫的應用範圍比C語言第三方庫比如libcstl的應用範圍廣得多,所以本系列文章介紹數據結構實現及其操作算法的STL庫時,以C++11的STL爲準。介紹STL庫的目的也是在我們瞭解數據結構及其操作的基礎上,不用重複造輪子,直接使用標準庫既可以提高工作效率(標準庫比我們自己實現的算法更健壯高效),又可以方便參與到與別人共同開發的協作網絡中(跟別人使用同一套庫,程序兼容性與可移植性比較好)。

先簡單介紹下C++的STL,STL是由一些可適應不同需求的集合類(即數據結構類)和一些能夠在這些數據集合上運作的算法構成,STL內的所有組件都由模板構成,所以其元素可以是任意類型。STL組件主要包括以下部分:

  • 容器(container):用來管理某類對象的集合,容器可以是array、linked list、tree、hash map、set等;
  • 迭代器(iterator):用來在一個對象集合內遍歷元素,這個對象集合或許是個容器,或許是容器的一部分。迭代器和尋常的指針類似,調用operator ++ 就累進,調用operator * 就訪問被指向的元素值,你可以把迭代器視作一種智能指針,能夠把”前進至下一元素“的意圖轉換爲合適的操作;
  • 算法(algorithm):用來處理集合內的元素,它們可以出於不同的目的而插入、刪除、修改、查找、排序、使用元素,通過迭代器的協助,我們只需撰寫一次算法,就可以將它應用於任意容器,因爲所有容器的迭代器都提供一致的接口。

STL的基本觀念是將數據與操作分離,數據由容器類管理,操作則由可定製的算法負責,迭代器在兩者之間充當粘合劑,使任何算法都可以和任何容器交互運作。STL組件之間的關係如下圖所示:
STL各組件之間的關係

二、順序表的STL容器

2.1 STL容器:Array(C++11)

先看看C++11 STL容器最基本的類型array,它包覆了一個尋常的static C-style array並提供一個STL容器接口(同樣支持前面介紹的C-style Array的操作)。Array有固定大小,因此你無法藉由增加或移除元素而改變其大小,只允許你修改或查詢元素值。

C++11 STL Array的類模板定義如下(第一個參數T爲元素類型,第二個參數N爲元素個數):

// <array>
namespace std{
	template <typename T, size_t N>
	class array;
}

既然是面向對象的class array<>,先看看該類的構造函數:
數組構造函數
class array<>的比較、訪問等非更易型操作:
array的查詢比較操作
class array<>的賦值、交換等更易型操作:
array賦值修改操作
class array<>的迭代器支持的相關操作:
array迭代器相關操作
下面給出一個操作array的示例程序供參考:

// datastruct\array_demo.cpp

#include <iostream>
#include <array>
#include <algorithm>

template <typename T>
void printElements(const T& coll)
{
    for(auto iter = coll.begin(); iter != coll.end(); ++iter)
    {
        const auto& elem = *iter;
        std::cout << elem << '\t';
    }
    std::cout << std::endl;
}

int main(void)
{
    // create array with 10 ints
    std::array<int, 10> a = {31, 22, 19, 47};
    printElements(a);
    
    // modify last two elements
    a.back() = 99;  
    a[a.size() - 2] = 42;
    printElements(a);
    
    // sort the array elements
    std::sort(a.begin(), a.end());
    printElements(a);

    return 0;
}

上面array的示例代碼運行結果如下(需要注意編譯器是否支持C++11,比如g++在4.7以上版本才支持,添加-std=c++11即可):
array_demo運行結果

2.2 STL容器:Vector(C++11)

Class Array<>在初始化時就固定了大小(也即存儲元素的數量),後面幾乎不可調整大小,缺乏彈性,因此不支持插入或刪除元素的操作,只支持修改、查詢或排序元素的操作。這對於一般的場景倒也夠用,但有很多場景需要數據結構能支持動態調整大小的功能(畢竟需求隨着時間會變化),於是動態變長數組(Dynamic array)應運而生。

動態變長數組如何實現呢?雖然支持動態調整大小,其物理存儲結構依然是順序結構的數組(如果是鏈式結構實現就不叫數組了),要實現數組可變長度,最簡單的方法就是在初始化數組時,多申請一部分空間,爲以後的變長預留位置。C++11 STL容器中的變長數組也是這麼實現的,由於變長數組可以向一個方向增長,所以取名Vector,也算比較形象貼切。變長數組Vector與靜態數組Array的示意圖對比如下:
vector與array對比
變長數組Vector通過多申請空間,爲變長預留位置的方案雖然可以在一定程度上滿足我們的需求,但當預留空間用完之後,就蛻化爲靜態數組Array了,如果要繼續插入元素,就會再找一塊兒更大的連續地址空間(爲原地址空間的兩倍大小),將原地址空間的數據搬移到新的地址空間,繼續支持變長特性,這個操作稱爲變長數組的擴容。由於擴容後,原數據元素搬移導致地址變更,對這些元素的地址引用操作比如引用、指針、迭代器等都將失去作用,且擴容操作比較耗時。要想緩解擴容導致的問題,可以在創建變長數組時,根據我們的需求預留足夠的空間,但預留太多空間會導致空間浪費,這個取捨需要平衡。

C++11 STL Vector的類模板定義如下(第一個參數T爲元素類型,第二個參數Allocator定義內存模型,可省略直接使用默認值):

// <vector>

namespace std{
	template <typename T,
			  typename Allocator = allocator<T>>
	class vector;
}

class vector<>的構造函數與析構函數如下:
vector構造與析構函數
class vector<>的比較、訪問等非更易型操作:
vector比較訪問操作
跟前面array容器相比,這裏多了元素容量調整的接口函數,也是對前面介紹的變長數組實現方案中預留空間的操作,可以查詢當前vector容量,也可以擴大或者縮小預留空間。需要注意的是,對容量的調整(比如reserve或shrink_to_fit)會導致所有指向元素的引用、指針、迭代器等失效。

class vector<>的賦值、交換等更易型操作:
vector賦值交換操作
class vector<>的迭代器支持的操作跟前面介紹的array類似,這裏就不再贅述了。

class vector<>因爲增加了變長特性,就可以支持插入與刪除操作了,由於vector順序存儲結構的限制,在尾部插入或刪除元素的效率比較高(常數時間複雜度),在中間插入刪除的效率較低(線性時間複雜度)。Vector支持的插入或刪除操作如下:
vector插入與刪除操作
因爲容器內某元素的引用、指針、迭代器等都指向該元素的地址,任何改變該元素地址的行爲都會導致指向該元素的引用、指針、迭代器失效。前面介紹了vector容量的變更會導致其失效,這裏在vector容器中間插入或移除元素,會導致該插入或移除點後面的所有元素地址發生改變,也即會使該插入或移除點之後的所有元素的引用、指針、迭代器失效(在vector容器末尾插入或移除元素不受影響)。

布爾類型比較特殊,C語言的默認基本類型並不支持bool類型,如果想使用bool類型需要包含頭文件< stdbool.h >。C++標準庫針對bool類型的vector<>專門設計了一個特殊版本,目的是獲取一個優化的vector,使其耗用空間遠小於vector的一般實現版本。一般版本會爲每個bool元素分配至少 1 byte空間,而vector< bool >特化版本的內部只使用 1 bit 存放一個元素,空間節省8倍。不過有一點需要注意,C++的最小定址值仍是以byte爲單位的,所以必須對其迭代器進行特殊處理,即便如此vector< bool >特化版本也無法滿足其它vector的所有規定,其所有元素操作都必須轉化爲bit操作。

下面給出一個操作vector的示例程序供參考:

// datastruct\vector_demo.cpp

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

template <typename T>
void printElements(const T& coll)
{
    for(auto iter = coll.begin(); iter != coll.end(); ++iter)
    {
        const auto& elem = *iter;
        std::cout << elem << " ";
    }
    std::cout << std::endl;
}

int main(void)
{
    // create empty vector for strings
    std::vector<std::string> sentence;
    
    // reserve memory for five elements to avoid reallocation
    sentence.reserve(5);
    
    // append some elements
    sentence.push_back("Hello,");
    sentence.insert(sentence.end(), {"how", "are", "you", "?"});
    
    // print elements and some attribute value
    printElements(sentence);
    std::cout << "max_size: " << sentence.max_size() << std::endl;
    std::cout << "size:     " << sentence.size() << std::endl;
    std::cout << "capacity: " << sentence.capacity() << std::endl;
    std::cout << "address:  " << &sentence.at(0) << std::endl;
    
    //swap elements
    std::swap(sentence[1], sentence[3]);
    
    // insert elements before elements "?"
    sentence.insert(std::find(sentence.begin(), sentence.end(), "?"), "always");
    
    // assign "!" to the last elements
    sentence.back() = "!";
    
    // print elements and some attribute value
    printElements(sentence);
    std::cout << "size:     " << sentence.size() << std::endl;
    std::cout << "capacity: " << sentence.capacity() << std::endl;
    std::cout << "address: " << &sentence.at(0) << std::endl;
    
    // delete last two elements
    sentence.pop_back();
    sentence.pop_back();
    
    // shrink capacity
    sentence.shrink_to_fit();
    
    // print elements and some attribute value
    printElements(sentence);
    std::cout << "size:     " << sentence.size() << std::endl;
    std::cout << "capacity: " << sentence.capacity() << std::endl;
    std::cout << "address:  " << &sentence.at(0) << std::endl;

    return 0;
}

上面vector的示例代碼運行結果如下(需要注意編譯器是否支持C++11,比如g++在4.7以上版本才支持,添加-std=c++11即可):
vector_demo運行結果
從上面的運行結果也可以看出,當vector的容量不足時,再插入元素,會將原容量擴充一倍。當vector容量變化時,容器內元素的地址也會發生變化,所以指向容器內元素的引用、指針、迭代器都會失效。

2.3 STL容器:Deque(C++11)

瞭解了變長數組容器Vector,我們會好奇,既然STL提供了單向變長,只能在尾部方便插入、移除元素的方法,有沒有提供雙向變長,在首尾兩端都支持插入、移除元素方法的容器呢?如果有,會如何實現呢?

Vector容器當容量不足,擴展容量時需要將原先所有的元素複製到新的內存地址空間,這點在容器內元素數量很多時,就很影響效率。假如找不到更大的連續內存地址空間,Vector容器擴容將會失敗。這些缺點能否避免,同時又保留順序表高效隨機訪問的優點呢?

C++11 STL還真提供了一個可以雙向變長,在首尾兩端都能高效插入、移除元素的容器:Deque(Double-ended queue),類似於Vector容器,也是採用dynamic array來管理元素,並提供隨機訪問操作的,有着和Vector幾乎一模一樣的接口,和Vector不同的是,Deque能在首尾兩端進行快速插入或刪除元素。Deque元素間的邏輯結構如下:
Deque邏輯結構
Vector容器是通過預留更多的空間實現單向變長功能的,Deque容器會採用何種方法實現雙向變長功能呢?很容易想到,如果把兩個Vector起點拼接到一起,兩個Vector分別能向其中一端變長擴展,組合起來不就可以實現雙向變長擴展了嗎?

Deque容器也就是這麼設計的,但如果只是將兩個Vector簡單拼接,當兩個Vector擴容時,對內存連續地址空間的需求更高,複製元素的成本也更高,所以能否避免擴容時元素的複製呢?

要想避免擴容時元素的複製,就需要再增加新的地址空間後,能把原地址空間與新地址空間(可稱爲“區塊”)連接起來,每當一端擴容時,再找一個新的獨立區塊連接到原區塊後面就可以了,所以Deque容器是由一組獨立區塊(individual blocks,一般一個區塊大小爲512字節)構成的,第一個區塊朝某方向擴展,最後一個區塊朝另一個方向擴展。Deque的內部結構如下圖示:
Deque容器內部結構
Deque容器內部的獨立區塊並不都是連續的(擴容時可能原區塊後面空間不夠,需要在其它地方另找一個區塊),這些不連續的區塊如何管理呢?如果區塊間的管理不到位,就沒法實現順序表的隨機訪問功能。

爲了管理分段空間deque容器引入了Map數組(可看作多個獨立區塊的中央控制器),Map是一塊連續地址空間,其中每個元素是指向各個獨立區塊的指針,這些獨立區塊是deque存儲數據的主體,deque容器每創建一個獨立區塊,都將該區塊的首地址存入Map數組的相應數據項中。Map數組管理deque容器一組獨立區塊的結構關係圖示如下:
deque內部實現原理圖
由上圖可以看出,deque容器要想實現對分段連續地址空間的隨機訪問,迭代器的設計就比Vector複雜得多。在Map和deque塊的結構之下,deque使用了兩個迭代器M_start和M_finish,對首個deque塊和末deque塊進行控制訪問。迭代器iterator共有4個變量域,包括M_first、M_last、M_cur和M_node。M_node存放當前deque塊的Map數據項地址,M_first和M_last分別存放該deque塊的首尾元素的地址(M_last實際存放的是deque塊的末尾字節的地址),M_cur則存放當前訪問的deque雙端隊列的元素地址。

由於deque容器的迭代器比vector容器的迭代器複雜很多,所以通過deque容器的元素訪問和迭代器操作會比vector慢一些。所以C++ Standard建議:如非必要(比如需要經常在首尾兩端插入或移除元素,或及時釋放不再使用的元素等),優先選擇使用Vector容器。

C++11 STL Deque的類模板定義如下(第一個參數T爲元素類型,第二個參數Allocator定義內存模型,可省略直接使用默認值):

// <deque>

namespace std{
	template <typename T,
			  typename Allocator = allocator<T>>
	class deque;
}

class deque<>的構造函數與析構函數如下:
deque構造與析構函數
class deque<>的比較、訪問、迭代器等非更易型操作:
deque非更易型操作
Deque容器由於通過Map管理多個獨立區塊,在擴容時新增區塊,當某區塊地址空間不再使用時也會釋放該區塊。跟Vector容器類似,當擴展或縮減容量時,涉及到的獨立區塊內的元素地址會發生變化,指向這些元素的指針、引用、迭代器將會失效;除了首尾兩端,在中間插入或刪除元素時,該作用點後面的所有元素地址也會發生變化,指向這些元素的指針、引用、迭代器也會失效。

class deque<>的賦值、交換、插入、移除等更易型操作:
deque更易型操作
下面給出一個操作deque的示例程序供參考:

// datastruct\deque_demo.cpp

#include <iostream>
#include <deque>
#include <string>
#include <algorithm>

template <typename T>
void printElements(const T& coll)
{
    for(auto iter = coll.begin(); iter != coll.end(); ++iter)
    {
        const auto& elem = *iter;
        std::cout << elem << "\n";
    }
    std::cout << std::endl;
}

int main(void)
{
    // create empty deque of strings
    std::deque<std::string> coll;

    // insert several elements
    coll.assign(3, std::string("string"));
    coll.push_back("last string");
    coll.push_front("first string");
    printElements(coll);

    // remove first and last element
    coll.pop_front();
    coll.pop_back();

    // insert "another" into every element but the first
    for(int i = 1; i < coll.size(); ++i)
        coll[i] = "another " + coll.at(i);
    printElements(coll);

    //change size to four elements    
    coll.resize(4, "resized string"); 
    
    // sort the elements
    std::sort(coll.begin(), coll.end());
    printElements(coll);

    return 0;
}

上面deque的示例代碼運行結果如下(需要注意編譯器是否支持C++11,比如g++在4.7以上版本才支持,添加-std=c++11即可):
deque示例程序執行結果

三、鏈式表的STL容器

3.1 STL容器:List(C++11)

C++11 STL爲鏈表提供的默認容器是class list<>,list容器使用doubly linked list雙向鏈表管理其中的元素。由此可見,雙向鏈表的應用場景比單向鏈表更多。先看下list容器中元素的邏輯組織關係:
list元素邏輯結構
C++11 STL List的類模板定義如下(第一個參數T爲元素類型,第二個參數Allocator定義內存模型,可省略直接使用默認值):

// <list>

namespace std{
	template <typename T,
			  typename Allocator = allocator<T>>
	class list;
}

List容器的內部結構完全迥異於array、vector,List對象自身提供了兩個指針,用來指向第一個和最後一個元素(便於快速找到鏈表首結點與尾結點,從鏈表首位插入、刪除結點比較高效),每個元素都有兩個指針分別指向其前一個元素與後一個元素。如果想要插入新元素、移除指定元素,只需要操作對應大的指針即可,如下圖所示:
list插入新元素
list容器由於使用了指針存儲元素間的指向關係,相比array和vector等順序表容器,可以更高效的插入與刪除元素。由於不要求元素間連續存儲,自然也不需要類似vector的容量調整、空間重分配的操作。同樣的,list容器放棄了元素間的鄰接關係,自然就不支持下標訪問與隨機訪問了,對鏈表元素的排序就很不方便(可以在鏈表插入新元素時,按順序插入到指定位置,使其插入新元素前後都保持有序狀態)。

class list<>的構造函數與析構函數如下:
list構造與析構函數
class list<>的比較、訪問等非更易型操作(相比順序表不支持下標訪問了):
list非更易型操作
class list<>的賦值、交換等更易型操作:
list更易型操作
class list<>的迭代器支持的操作跟前面介紹的array類似,這裏就不再贅述了。

class list<>容器的插入與刪除操作比順序表要強大豐富些,不僅支持鏈表首尾的插入與移除,還支持條件移除(移除所有某條件爲真的元素):
list插入移除操作
class list<>容器得益於元素間指向關係調整比較高效的優勢,其支持的更易型操作也比順序表強大豐富得多,支持鏈表的拼接、合併、反序、移除重複元素等。由於鏈表不支持隨機訪問,不適用於算法庫中的排序函數,爲此list容器爲自己量身定做了用於內部元素排序的成員函數,如果我們想要對鏈表元素排序,只能使用其成員函數list::sort:
list容器特殊的更易型操作
class list<>容器的splice鏈表拼接操作,實際上也是改變兩個鏈表拼接位置附近元素的指針指向關係,如下圖所示:
list拼接操作示意圖
下面給出一個操作list的示例程序供參考:

// datastruct\list_demo.cpp

#include <iostream>
#include <list>
#include <algorithm>

template <typename T>
void printElements(const T& coll)
{
    for(auto iter = coll.begin(); iter != coll.end(); ++iter)
    {
        if(iter != coll.begin())
            std::cout << " <--> ";
        const auto& elem = *iter;
        std::cout << elem;
    }
    std::cout << std::endl;
}

void printLists(const std::list<int>& list1, const std::list<int>& list2)
{
    std::cout << "list1: ";
    printElements(list1);
    std::cout << "list2: ";
    printElements(list2);
    std::cout << std::endl;
}

int main(void)
{
    // create two empty lists
    std::list<int> list1, list2;

    // fill both lists with elements
    for(int i = 0; i < 6; ++i)
    {
        list1.push_back(i);
        list2.push_front(i);
    }
    printLists(list1, list2);

    // insert all elements of list1 before the first element with value 3 of list2
    list2.splice(std::find(list2.begin(), list2.end(), 3), list1);
    printLists(list1, list2);

    // move first element of list2 to the end
    list2.splice(list2.end(), list2, list2.begin());
    printLists(list1, list2);

    // sort second list, assign to list1 and remove duplicates
    list2.sort();
    list1 = list2;
    list2.unique();
    printLists(list1, list2);

    // merge both sorted lists into the first list
    list1.merge(list2);
    printLists(list1, list2);

    return 0;
}

上面list的示例代碼運行結果如下(需要注意編譯器是否支持C++11,比如g++在4.7以上版本才支持,添加-std=c++11即可):
list示例程序運行結果

3.2 STL容器:Forward List(C++11)

C++11 STL既然提供了雙向鏈表容器list,有沒有提供單向鏈表容器呢?之前覺得單向鏈表的使用不如雙向鏈表那麼方便,所以並沒有爲單向鏈表單獨提高一個容器,從C++11開始才爲單向鏈表專門提供了一個容器forward list,其內部是以singly linked list管理元素的。forward list容器內元素的邏輯結構如下圖示(forward list前向鏈表,即往前面靠近首結點的地方插入移除元素比較高效,省去了不少遍歷時間):
forward list邏輯結構
C++11 STL Forward List的類模板定義如下(第一個參數T爲元素類型,第二個參數Allocator定義內存模型,可省略直接使用默認值):

// <forward_list>

namespace std{
	template <typename T,
			  typename Allocator = allocator<T>>
	class forward_list;
}

Forward List可以看作一個行爲受限的List,只能單向前進,不能走回頭路。凡是List不支持的功能,Forward List也不支持。那麼,既然已經有了List,爲了又提供了一個更雞肋的Forward List呢?

C++ standard這樣描述forward list:

我們希望forward list和你自己手寫的C-style singly linked
list相較之下沒有任何空間或時間上的額外開銷,任何性質如果與這個目標相牴觸,我們就放棄該性質。

由此可見,forward list相比list的優勢就是內存佔用更少,運行效率更高。凡事有收益就有代價,forward list相較list有着諸多約束,比如只能前向遍歷,沒有提供指向最後一個元素的指針,自然也不提供在鏈表末尾插入或刪除元素的操作。

class forward_list<>的構造函數與析構函數如下:
forward_list構造與析構函數
class forward_list<>的比較、訪問等非更易型操作(相比雙向鏈表不支持末尾結點和元素數量訪問了):
forward_list非更易型操作
class forward_list<>的賦值、交換等更易型操作:
forward_list更易型操作
class forward_list<>只能前向遍歷元素,不支持反向迭代器,因此其支持的迭代器操作同樣受限。C++11 STL爲forward_list提供了兩個特殊迭代器before_begin()和before_cbegin(),可以獲得第一個元素的前一位置,forward_list支持的迭代器如下:
forward_list迭代器操作
class forward_list<>容器的插入與刪除操作跟list類似,但不支持末尾插入或刪除元素的操作:
forward_list插入或刪除操作
C++ STL爲forward_list單獨提供迭代器before_begin()可以獲得第一個元素的前一個位置,實際上也就是前篇我們介紹單向鏈表中的鏈表頭位置,獲取這個位置更方便在鏈表最前端(第一個元素前)插入或刪除元素。

在中間插入或刪除元素時,需要獲得查找到插入或刪除目標元素的前一個位置,就可以使用兩個迭代器,分別從begin()與before_begin()開始往後遍歷查找,待begin()查找到目標元素位置後,其前驅結點的位置即爲before_begin()。這兩個過程的操作圖示如下:
forward_list插入或刪除結點圖示
class forward_list<>容器跟list容器類似,也提供鏈表的拼接、合併、反序、移除重複元素等。同樣的,forward_list也不適用於算法庫中的排序函數,容器自身提供了用於內部元素排序的成員函數forward_list::sort,這些操作方法如下:
forward_list特殊的更易型操作
class forward_list<>容器的splice_after鏈表拼接操作與list容器提供的splice操作類似,但爲何命名爲splice_after呢?由於forward_list只能前向遍歷的特點,傳給splice_after的是“splice所作用元素位置”的前一個元素位置,所以splice_after拼接操作是作用到傳入位置的後一個位置的。splice_after拼接操作圖示如下:
forward_list拼接操作圖示
下面給出一個操作forward_list的示例程序供參考:

// datastruct\forward_list_demo.cpp

#include <iostream>
#include <forward_list>
#include <string>
#include <algorithm>

template <typename T>
void printElements(const T& coll)
{
    for(auto iter = coll.begin(); iter != coll.end(); ++iter)
    {
        if(iter != coll.begin())
            std::cout << " --> ";
        const auto& elem = *iter;
        std::cout << elem;
    }
    std::cout << std::endl;
}

void printLists(const std::string& str, const std::forward_list<int>& list1, 
                                        const std::forward_list<int>& list2)
{
    std::cout << str << std::endl;
    std::cout << "list1: ";
    printElements(list1);
    std::cout << "list2: ";
    printElements(list2);
    std::cout << std::endl;
}

int main(void)
{
    // create two forward lists
    std::forward_list<int> list1 = {1, 2, 3, 4};
    std::forward_list<int> list2 = {77, 88, 99};
    printLists("Initial:", list1, list2);

    // insert new element at the beginning of list2
    list2.insert_after(list2.before_begin(), 99);
    list2.push_front(10);
    list2.insert_after(list2.before_begin(), {10, 11, 12, 13});
    printLists("Insert new element:", list1, list2);

    // insert all elements of list2 at the beginning of list1
    list1.insert_after(list1.before_begin(), list2.begin(), list2.end());
    printLists("List2 into list1:", list1, list2);

    // delete second element, and elements after element with value 99
    list2.erase_after(list2.begin());
    list2.erase_after(std::find(list2.begin(), list2.end(), 99), list2.end());
    printLists("Delete some elements:", list1, list2);

    // sort list1, assign it to list2, and remove duplicates
    list1.sort();
    list2 = list1;
    list2.unique();
    printLists("Sorted and unique:", list1, list2);

    // merge both sorted lists into list1
    list1.merge(list2);
    list1.unique();
    printLists("Merged:", list1, list2);

    return 0;
}

上面forward_list的示例代碼運行結果如下(需要注意編譯器是否支持C++11,比如g++在4.7以上版本才支持,添加-std=c++11即可):
forward_list示例程序運行結果

本章數據結構實現源碼下載地址:https://github.com/StreamAI/ADT-and-Algorithm-in-C/tree/master/datastruct

更多文章:

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