學習過C++的朋友們應該對STL和泛型編程這兩個名詞不會陌生。兩者之間的關系不言而喻,泛型編程的思想促使了STL的誕生,而STL則很好地體現了泛型編程這種思想。這次想簡單說一下STL在ACM中的一些應用。我們知道,在ACM競賽中,經常需要用到數組、字符串、隊列、堆棧、鏈表等數據結構和排序、搜索等算法,以提高程序的時間、空間運行效率。然而如果這些數據結構總是需要手工來編寫,那無疑會是一件很麻煩的工作,而STL的出現很好地解決了這個問題。
我們簡單來了解一下STL。STL提供了三種類型的組件:容器、迭代器和算法。容器主要有兩類:順序容器和關聯容器。順序容器(vector、list、deque和string等)是一系列元素的有序集合。關聯容器(set、multiset、map和multimap)包括查找元素的鍵值。迭代器的作用是遍歷容器。STL算法庫包含四類算法:排序算法、不可變序算法、變序性算法和數值算法。下面就簡單介紹一下STL里常用的容器。
1. vector向量容器
vector向量容器不但像數組一樣對元素進行隨機訪問,還能在尾部插入元素,是一種簡單、高效的容器,完全可以替代數組。由於vector具有內存自動管理的功能,對於元素的插入和刪除,可動態調整所占的內存空間,因此使用時不需要考慮釋放空間的問題。使用vector向量容器時,需要包含頭文件“vector”。(即#include <vector>)對於vector容器的容量定義,可以事先定義一個固定大小,事后可以隨時調整其大小;(例如vector<int> v(10); //定義一個用來存儲10個int類型元素的向量容器)也可以事先不用定義其大小,使用push_back()方法從尾部擴張元素,也可以使用insert()在某個元素位置前插入新元素。
vector容器有兩個重要的方法,begin()和end()。begin()返回的是首元素位置的迭代器;end()返回的是最后一個元素的下一個元素位置的迭代器。通常在遍歷vector所有元素時會用到這兩個方法。例如:
vector<int> v(3); vector<int>::iterator it; for(it=v.begin(); it!=v.end(); it++){...}
至於vector元素的刪除,調用erase()方法可以刪除vector中迭代器所指的一個元素或一段區間中的所有元素,clear()方法則可以刪除vector中所有元素。
通過使用size()方法可以返回向量的大小,即元素個數,調用empty()方法返回向量是否為空。
再簡單看一下在vector中常用到的算法。使用reverse()反向排列算法,需要定義頭文件“#include <algorithm>”,reverse()算法可將向量中某段迭代器區間元素反向排列;使用sort算法可以對向量排序,默認情況下對元素進行升序排列,也可以自己設計排序比較函數,具體使用細節就不贅述了。
2. string基本字符系列容器
C語言中對於字符串只能使用字符數組來處理,顯得十分不方便。C++ STL提供了string基本字符序列容器來處理字符串,可以把string理解為字符串類,它提供了添加、刪除、替換、查找和比較等豐富方法。使用string容器需要包含頭文件聲明“#include <string>”。
3. set集合容器
set集合容器實現了紅黑樹的平衡二叉檢索樹的數據結構,在插入元素時,它會自動調整二叉樹的排列,把該元素放到適當的位置,以確保每個子樹根節點的鍵值大於左子樹所有節點的鍵值,而小於右子樹所有節點的鍵值;另外,還得確保根節點左子樹的高度與右子樹的高度相等,因為二叉樹高度最小,檢索速度最快。這里要注意的是,set容器不會插入相同鍵值的元素。
平衡二叉檢索樹的檢索使用中序遍歷算法,檢索效率高於vector、deque和list等容器。另外,采用中序遍歷算法可將鍵值由小到大遍歷出來,所以,可以理解為平衡二叉檢索樹在插入元素時,就會自動將元素按鍵值由小到大的順序排列。由於構造set集合的主要目的就是為了快速檢索,對於set容器中的鍵值,不可直接去修改。multiset、map和multimap的內部結構也是平衡二叉檢索樹。
4. multiset多重集合容器
multiset與set一樣,也是使用紅黑樹來組織元素數據的,唯一不同的是,multiset允許重復的元素鍵值插入,而set則不允許。multiset也需要聲明頭文件包含“#include <set>”,由於它包含重復元素,所以在插入元素、刪除元素、查找元素上較set有差別。
5. map映照容器
map映照容器的元素數據是由一個鍵值和一個映照數據組成的,鍵值與映照數據之間具有一一映照的關系。map映照容器的數據結構也是采用紅黑樹來實現的,插入元素的鍵值不允許重復,比較函數只對元素的鍵值進行比較,元素的各項數據可通過鍵值檢索出來。由於map與set采用的都是紅黑樹的數據結構,所以它們的用法基本類似。使用map容器需要頭文件包含語句“#include <map>”。
6. multiset多重映照容器
multiset與map基本相同,唯獨不同的是,mulitiset允許插入重復鍵值的元素。由於允許重復鍵值存在,所以multiset的元素插入、刪除、查找都與map不相同。
7. deque雙端隊列容器
deque雙端隊列容器與vector一樣,采用線性表順序存儲結構。但與vector唯一不同的是,deque采用分塊的線性存儲結構來存儲數據,每塊的大小一般為512字節,稱為一個deque塊,所有的deque塊使用一個Map塊進行管理,每個Map數據項紀錄每個deque塊的首地址。這樣,deque塊在頭部和尾部都可插入和刪除元素,而不需移動其他元素。一般來說,當考慮到容器元素的內存分配策略和操作的性能時,deque相對於vector會更有優勢。使用deque需要聲明頭文件包含“#include <deque>”。
8. list雙向鏈表容器
list容器實現了雙向鏈表的數據結構,數據元素是通過鏈表指針串連成邏輯意義上的線性表,這樣,對鏈表的任一位置的元素進行插入、刪除和查找都是極快速的。由於list對象的節點並不要求在一段連續的內存中,所以對於迭代器,只能通過“++”或“--”的操作將迭代器移動到后繼/前驅節點元素處。而不能對迭代器進行+n或-n的操作,這點是與vector等不同的地方。使用list需要聲明頭文件包含“#include <list>”。
9. stack堆棧容器
stack堆棧是一個后進先出的線性表,插入和刪除元素都只能在表的一端進行。插入元素的一端稱為棧頂,另一端稱為棧底。插入元素叫入棧,元素的刪除稱為出棧。要使用stack必須聲明頭文件包含語句“#include <stack>”。
10. queue隊列容器
queue隊列容器是一個先進先出的線性存儲表,元素的插入只能在隊尾,元素的刪除只能在隊首。使用queue需要聲明頭文件包含語句“#include <queue>” 。