摘要
本文主要借助對C++的標准模板庫STL中實現的數據結構的學習和使用來加深對數據結構的理解,即聯系數據結構的理論分析和詳細的應用實現(STL),本文是系列總結的第二篇。主要針對線性表中的鏈表 STL std::list進行分析和總結。
引言
因為前段時間對台大的機器學習基石和技法課程進行了學習,發如今詳細的實現中經常涉及到各種類型的數據結構,比方線性表、二叉樹、圖等。在使用這些數據結構時感到有些吃力,主要是對一些主要的數據結構理解的不夠,所以趁着暑假假期,近期一段時間總會抽出時間復習一下數據結構,參考的教材是王立柱編著的《C/C++與數據結構》,在詳細的應用中採用的是C++標准模板庫STL中相應實現的數據結構,主要參考的是MSDN文檔。
跟着教材的一步一步推進。如今已經復習完了鏈表一章節。詳細的理論能夠參看我的博文:http://blog.csdn.net/lg1259156776/article/details/47018813
本次關注點在list模板類的使用。
正文
回想動態數組類
上一篇總結STL vector動態數組類的時候忘記了對還有一種跟vector很類似的動態數組類deque進行說明。
以下對此進行一下補充。
STL deque類須要包括<deque>和使用std。支持在數組的開頭和末尾插入或刪除元素,而vector僅僅能在末尾插入或刪除,即僅僅有push_back和pop_back。deque同意使用push_front和pop_front在開頭插入和刪除元素。其余的操作大致類似,不再贅述!
<span style="font-size:18px;">// deque_push_front.cpp // compile with: /EHsc #include <deque> #include <iostream> #include <string> int main( ) { using namespace std; deque <int> c1; c1.push_front( 1 ); if ( c1.size( ) != 0 ) cout << "First element: " << c1.front( ) << endl; c1.push_front( 2 ); if ( c1.size( ) != 0 ) cout << "New first element: " << c1.front( ) << endl; // move initialize a deque of strings deque <string> c2; string str("a"); c2.push_front( move( str ) ); cout << "Moved first element: " << c2.front( ) << endl; }</span>總結:在不知道須要存儲多少個元素時,務必使用動態數組vector或deque。並牢記vector僅僅能使用push_back在一端擴容,而deque能夠使用push_back和push_front在兩端擴容。另外。訪問動態數組時,不要跨越其邊界。
list 模板類
標准模板庫以模板類std::list的方式向程序猿提供了一個雙向鏈表。
雙向鏈表的主要長處是插入和刪除元素的速度快,且時間固定,不像順序表那樣須要移動元素。從C++ 11起,還能夠使用單向鏈表std::forward_list,僅僅沿着一個方向遍歷。
list的特點
鏈表是一系列節點,每一個節點除了包括對象或data之外,還包括指向下一個或者上一個節點的指針。list類的STL實現同意在開頭、末尾和中間插入元素,且所需的時間固定。
使用時包括<list>和std。
list的基本操作
實例化
list<int> listIntegers; list<float> listFloats;等等。要聲明一個指向list中元素的迭代器,能夠進行例如以下操作:
list<int> :: const_iterator iElementInSet; 還記得const_iterator吧,在上一篇vector的分析中講到過。指向一個僅僅讀的元素,假設要對容器中的內容進行改動,能夠使用iterator來進行。另一些與vector一樣的初始化操作,能夠參考上一篇博文,興許關於STL容器進行編程或者解說時。實例化的方式大致一樣。這樣的模式也將長期出現,興許博文便不再多提。
在list開頭或末尾插入或刪除元素
跟deque類相似。採用push_front/pop_front和push_back/pop_back的方法。
在list中間插入或刪除元素
list的特點之中的一個。上面講過。在中間插入或刪除元素所需的時間是固定的,使用函數insert()和erase()。
從上面的分析看,基本上全部的容器類(vector。list,deque...)所使用的方法模式都類似,這對於觸類旁通的學習非常有幫助。
list元素的反轉和排序
list的一個獨到之處就是指向元素的迭代器在list的元素又一次排列或插入元素后仍然有效,為實現這樣的特點,list提供了成員方法sort和reverse。盡管STL也提供了這兩種算法(在算法類中<algorithm>)。且這些算法相同能夠使用在list類上。
使用reverse()反轉元素的順序排列
list提供了成員函數reverse,沒有參數,功能是反轉list中元素的排列順序:
<span style="font-size:18px;">// list_reverse.cpp // compile with: /EHsc #include <list> #include <iostream> int main( ) { using namespace std; list <int> c1; list <int>::iterator c1_Iter; c1.push_back( 10 ); c1.push_back( 20 ); c1.push_back( 30 ); cout << "c1 ="; for ( c1_Iter = c1.begin( ); c1_Iter != c1.end( ); c1_Iter++ ) cout << " " << *c1_Iter; cout << endl; c1.reverse( ); cout << "Reversed c1 ="; for ( c1_Iter = c1.begin( ); c1_Iter != c1.end( ); c1_Iter++ ) cout << " " << *c1_Iter; cout << endl; }</span>輸出結果:
使用sort()對元素進行排序
list成員函數sort有兩個版本號。一是沒有參數,默認是升序排列。假設想降序排列,能夠升序后採用reverse。還有一個版本號是接受一個二元謂詞函數作為參數。制定排序的標准。比方以下一個二元謂詞函數GeaterThan,指定的排序標准是降序排列bool GeaterThan(const int& lsh, const int& rsh)
{
return(lsh > rsh);
}
<span style="font-size:18px;">// list_sort.cpp // compile with: /EHsc #include <list> #include <iostream> int main( ) { using namespace std; list <int> c1; list <int>::iterator c1_Iter; c1.push_back( 20 ); c1.push_back( 10 ); c1.push_back( 30 ); cout << "Before sorting: c1 ="; for ( c1_Iter = c1.begin( ); c1_Iter != c1.end( ); c1_Iter++ ) cout << " " << *c1_Iter; cout << endl; c1.sort( ); cout << "After sorting c1 ="; for ( c1_Iter = c1.begin( ); c1_Iter != c1.end( ); c1_Iter++ ) cout << " " << *c1_Iter; cout << endl; c1.sort( greater<int>( ) ); cout << "After sorting with 'greater than' operation, c1 ="; for ( c1_Iter = c1.begin( ); c1_Iter != c1.end( ); c1_Iter++ ) cout << " " << *c1_Iter; cout << endl; }</span>輸出為:
對包括對象的list進行排序及刪除當中的元素
在實際應用非常少使用STL容器來存儲整數,而是存儲用戶自定義的類型,比方類或結構等。那么這樣的排序怎樣做呢?
比方list中存儲的是電話簿,當中每一個元素都是一個對象,包括名稱、地址等內容,怎樣確保能依照名稱對其進行排序呢?
答案是採取下面兩種方式之中的一個:
<1> 在list包括相應所屬的類中,實現運算符<
<2> 提供一個排序二元謂詞(一個函數。接受兩個輸入值,並返回一個bool值。指出第一個值是否比第二個值小)
在實際的sort調用中。首先推斷有沒有輸入二元謂詞,假設沒有sort函數檢查相應的list的對象元素中是否實現了運算符<。假設實現了則依照該運算符指定的排序標准進行排序。假設輸入了二元謂詞函數。則依照其標准進行排序。
在用remove進行刪除的時候也是一樣,僅僅須要提供給元素對象中的某一個變量。就能夠直接刪除全部的對象中該變量值相等的元素。
即remove函數須要提供一個匹配標准才行,在對象類中實現==運算符。就是為remove時提供的刪除標准。比方電話簿中的名字,在類中實現一個例如以下所看到的的重載運算符==
bool operator == (const ContactItem& itemToCompare) const
{
return(itemToCompare.strContactsName == this->strContactsName);
}
forward_list模板類
從c++ 11之后,提供了forward_list來支持單向鏈表,包括頭文件<forward_lsit>。用法與list非常類似,就好像vector與deque的關系。forward_list僅僅能沿着一個方向移動迭代器,且插入元素的時候僅僅能使用函數push_front(),而不能使用push_back。當然是用insert是能夠在指定位置插入元素的。
總結
假設須要頻繁的插入和刪除元素,尤其是在中間插入或刪除,應當使用std::list,而不是使用std::vector。由於在這樣的情況下vector須要調整內部緩沖區大小。以支持數組語法。還需運行開銷高昂的復制操作。而list僅僅需建立或斷開鏈接。
對於使用list等STL容器存儲其對象的類,別忘了在當中實現運算符<和==,以提供默認排序的標准和刪除謂詞。
當無需頻繁插入和刪除元素時。請不要使用list。使用vector和deque的速度要更快。假設不想依據默認標准進行刪除或排序,別忘了給sort和remove提供一個謂詞函數。
*************************************************************************************************************************************
2015-7-23