資料結構與演算法分析筆記(c++)_抽象資料型別(ADT)、表ADT、STL中的向量和表

roockiet發表於2020-12-22

第三章 表、棧和佇列
本章討論最簡單和最基本的三種資料結構。實際上,每一個有意義的程式都將明晰地至少使用一個這樣的資料結構,而棧則在程式中總是要間接地用到,而不管你在程式中是否進行了宣告。本章將:
介紹抽象資料型別(ADT)的概念。
闡述如何對錶進行高效的操作。
●介紹棧ADT及其在實現遞迴方面的應用。
●介紹佇列ADT及其在作業系統和演算法設計中的應用。
在本章中,給出了兩個庫類 vector和1ist的重要子集的實現程式碼。

3.1抽象資料型別(ADT)
抽象資料型別(abstract data type,ADT)是帶有一組操作的一些物件的集合。抽象資料型別是數學的抽象;在ADT的定義中根本沒有提到這組操作是如何實現的。諸如表、集合、圖以及與它們各自的操作一起形成的這些物件都可以看做是抽象資料型別
可以有像加(add)、刪除(remove)、大小(size)以及包含(contains)這樣些操作。當然,也可以只要兩種操作:並(union)和查詢(find),這兩種操作又在該集合上定義了一種不同的ADT。
C++的類也考慮到ADT的實現,不過適當地隱藏了實現的細節。
如果由於某種原因需要改變實現的細節,那麼通過僅僅改變執行這些ADT操作的例程應該是很容易做到的。理想情況下,這種改變對於程式的其餘部分是完全透明的。
對於每種ADT並不存在什麼法則來告訴我們必須要有哪些操作;這是一個設計決策。錯誤處理和結構調整(在適當的地方)一般也取決於程式的設計者。

3.2表ADT
我們將處理形如Ao,A2,A3,…,AN-11的一般的表。這個表的大小是N。我們將稱大小為0的表為空表(empty list。
後繼和前驅:對於除空表外的任何表,我們說Ai後繼Ai-1(或繼Ai-1之後)並稱Ai-1(i<N)前驅Ai(i>1)。
與這些“定義”相關的是我們要在表ADT上進行操作的集合。
printlist和 makeEmpty是常用的操作,其功能是顯而易見的;
find返回 項 首次出現的位置;
insert和 remove般是從表的某個位置插入和刪除一些元素;
而findkth則返回某個位置上(作為引數而被指定)的元素。
當然,一個函式的功能怎樣才算恰當,完全要由程式設計者來確定。這類似於對特殊情況的處理。我們還可以自己新增一些運算
3.2.1表的簡單陣列實現
對錶的所有操作都可以使用陣列來實現。雖然陣列是靜態分配的,但是內部儲存陣列的vector類允許在需要的時候將陣列的大小增加一倍(實現方法:開闢新空間,複製資料到新空間,刪除原空間)。這解決了使用陣列的最嚴重的問題。也就是在使用陣列時需要對錶的大小的最大值進行估計的問題。這個估計現在就不再需要了。
陣列實現使得 printlist以線性時間執行,而 findkth則花費常數時間,這是最好的結果了。然而,插入和刪除的花費卻有可能是昂貴的,這取決於插入和刪除發生的位置。在最壞的情況下,在位置0(換伺話說是在表的前面)插入需要將整個陣列後移一個位置以空出空間來而刪除第一個元素則需要將表中的所有元素前移個位置,因此這兩種操作的最壞情況為O(N。
如果所有的操作都發生在表的末尾,就不需要移動任何元素,那麼新增和刪除的操作都花費O(1)的時間
在許多情況下,表是通過在末尾插入元素來建立的,之後只有陣列訪問(例如 finakth操作)
發生。在這種情況下,陣列是適合的。然而,如果插入和刪除在整個表中都發生,特別是在表的前端發生的話,陣列就不是一個好選擇了。下一節討論另一種選擇:連結串列
3.2.2簡單連結串列
為了避免插入和刪除的線性開銷,我們需要允許表可以不連續儲存,否則表的部分或全部就需要整體移動。圖3-1表達了連結串列(linked list)的一般思想。
在這裡插入圖片描述
連結串列由一系列不必在記憶體中相連的結點組成。每一個結點均含有表元素和到包含該元素後繼元的結點的鏈(link)。我們稱之為next鏈。最後一個單元的next鏈指向NUL。
與陣列實現一樣,遍歷顯然是花費線性時間的,但是這個常數可能比用陣列實現時要大。
remove方法可以通過修改一個next引用來實現。
在這裡插入圖片描述
lnsert方法需要使用new操作符從系統取得一個新結點,此後執行兩次引用調整。
在這裡插入圖片描述
如果知道發生變化的位置,從連結串列中插入和刪除一個元素不需要移動很多的元素,而只是對結點連結進行固定的幾個改變。
刪除最後一項有點麻煩,因為必須找到最後項前面的項,更改其next連結到NUll,然後更新這個保持為最後一項的連結。在經典的連結串列裡每個結點儲存指向下一結點的連結,但是沒有提供關於上一個結點的任何資訊。
我們將連結串列中的每一個結點都新增一個指向上一項的連結。如圖34所示,這稱為雙向連結串列(doubly linked list)
在這裡插入圖片描述
3.3STL中的向量和表
在C+語言的庫中包含有公共資料結構的實現。C++中的這部分內容就是眾所周知的標準模板庫(Standard Template Library,STL)。表ADT就是在STL中實現的資料結構之一。其他的資料結構我們將在第4章中介紹。一般來說,這些資料結構稱為集合(collection)或容器(container)。

表ADT有兩個流行的實現。vector給出了表ADT的可增長的陣列實現。使用 vector的優點在於其在常量的時間裡是可索引的。缺點是插入新項或刪除已有項的代價是昂貴的,除非是這些操作發生在 vector的末尾。1ist提供了表ADT的雙向連結串列實現。使用1ist的優點是,如果變化發生的位置已知的話,插入新項和刪除已有項的代價是很小的。缺點是1ist不容易索引。vector和1ist兩者在查詢時效率都很低。在本討論中,list總是指STL中的雙向連結串列,而“表”則是指更一般的ADT表
公共方法:
int size()const:返回容器內的元素個數
void clear():刪除容器中所有的元素。
bool empty():如果容器沒有元素,返回true,香則返回 false。
vector和list兩者都支援在常量的時間內在表的末尾新增或刪除項。vector和list兩者都支援在常量的時間內訪問表的前端的項。
void push_back(const object &x):在表的未尾新增x。
void pop back():刪除表的末尾的物件。
const object &back()const:返回表的末尾的物件(也提供返回引用的修改函式)
const Object &front()const:返回表的前端的物件(也提供返回引用的修改函式)。
因為雙向連結串列允許在表的前端進行高效的改變,但是 vector不支援,所以,下面的兩個方法僅對1ist有效
void push front(const object &x):在1ist的前端新增x
void pop front():在1ist的前端刪除物件
vecto也有1ist所不具有的特有的方法。有兩個方法可以進行高效的索引。另外兩個方法允許程式設計師觀察和改變 vector的內部容量。這些方法是
Object & operator[](int idx):返回 vector中idx索引位置的物件,不包含邊界檢測(也提供返回常量引用的訪問函式)。過載中括號
Object & at(int idx):返回 vector中i∝索引位置的物件,包含邊界檢測(也提供返回常量引用的訪問函式)
int capacity()const:返回 vector的內部容量(詳細資訊參見3.4節)。
void reserve(int new Capacity):設定 vector的新容量。如果已有良好的估計的話,這可以避免對 vector進行擴充套件(詳細資訊見34節)。
3.3.1迭代器
一些表的操作,例如那些在表的中部進行插入和刪除的操作,需要位置標記。在STL中,通過內建型別 iterator來給出位置。
對 vector,位置由類 vector::iterator給出
1.獲得迭代器
iterator begin():返回指向容器的第一項的一個適當的迭代器
iterator end():返回指向容器的終止標誌(容器中最後一項的後面的位置)的一個適當的迭代器。

在這裡插入圖片描述
這段程式碼引出了第二個問題,迭代器必須有關聯的方法(這裡未知的方法由??代替)。
2.迭代器方法
因此,迭代器有許多方法,並且許多方法都使用操作符過載。除了複製以外,迭代器的最常見的操作如下:
itr++和++itr:推進迭代器itr至下一個位置。字首和字尾兩種形式都是允許的。
*itr:返回儲存在迭代器ix指定位置的物件的引用。返回的引用或許能、或許不能被修改(後面我們將討論這些細節)。
itr1==itx2:如果ir1和itr2都指向同一個位置就返回true,否則,返回 false。
itr1!=itr2:如果itr1和ir2都指向不同位置就返回true,否則,返回fa1se。

在這裡插入圖片描述
在這裡插入圖片描述
3.需要迭代器的容器操作
特定位置新增或刪除項:
iterator insert(iterator pos,const Object& x):新增x到表中迭代器pos所指向的位置之前的位置。這對1ist是常量時間操作,但是對 vector則不是。返回值是個指向插入項位置的迭代器
iterator erase(iterator pos):刪除迭代器所給出位置的物件。這對1ist來說是常量時間操作,但對 vector則不是。返回值是呼叫之前pos所指向元素的下一個元素的位置這個操作使pos失效。pos不再有用,因為它所指向的容器變數已經被刪除了。
iterator erase(iterator start,iterator end):刪除所有的從位置 start開始、直到位置end(但是不包括ena)的所有元素。注意,整個表的刪除可以呼叫c.erase(c begin(),c,end())
3.3.2示例:對錶使用erase
在這裡插入圖片描述
對list線性,對vector平方演算法
3.3.3const_iterator
itr的結果不只是迭代器指向的項的值,也是該項木身。這個區別使得迭代器的功能很強,但也使其更復雜。
在這裡插入圖片描述
這段程式碼是非法的,並且不會被編譯。STL提供的解決方案是每一個集合不僅包含巢狀的iterator型別,也包含巢狀的 const_a terator型別。iterator和 Const iterator之間的主要區別是:const_iterator的 operator
返回常量引用,這樣 const_iterator的*itr就不能出現在賦值語句的左邊。
更進一步地,編譯器還會要求必須使用 const iterator類遍歷常量集合

在這裡插入圖片描述
兩個版本的 begin可以在同一個類裡面,因為方法(例如,無論是訪問函式還是修改函式)的定常性被認為是標號的一部分。
在這裡插入圖片描述

相關文章