數據結構是一個容器,用于將一些數據組織到單個對象中。我們已經見過了幾個數據結構,比如apstring是一些字符組成,而apvector是一組相同類型(可以是任意數據類型)的元素組成。
有序集是由一些項組成的集合,它有兩個決定性的屬性:
**有序性**:集合中的元素都有一個相應的索引。我們可以通過這些索引確定集合中的元素。
**唯一性**:集合中每個元素只能出現一次。向集合中添加一個已經存在的元素是沒有效果的。
此外,我們實現的有序集還有下面一個屬性:
**大小任意**:隨著我們向集合中添加元素,它會擴充以容納新元素。apstring和apvector都是有序的;每個元素都有一個索引,我們可以通過索引來確定元素。但是我們見到的數據結構都不具有唯一性和大小任意這兩個屬性。
要滿足唯一性,我們編寫的add函數必須先查找以確定集合中是否存在要添加的元素。集合隨著添加元素擴張這一特點可以利用apvector的resize函數實現。
下面是Set類定義的開始部分:
~~~
class Set {
private:
apvector<apstring> elements;
int numElements;
public:
Set (int n);
int getNumElements () const;
apstring getElement (int i) const;
int find (const apstring& s) const;
int add (const apstring& s);
};
Set::Set (int n)
{
apvector<apstring> temp (n);
elements = temp;
numElements = 0;
}
~~~
實例變量包括字符串的向量和記錄集合中有多少元素的整型數。一定要記住集合中的元素數numElement與apvector的大小不是一個東西。通常前者會小一些。
Set的構造函數接受一個參數,該參數是apvector的初始大小。元素個數初始值總是0。
getNumElements和getElement是私有實例變量的訪問函數。 numElements是只讀變量,所以我們只提供了get函數而沒有提供set函數。
~~~
int Set::getNumElements () const
{
return numElements;
}
~~~
為什么我們必須阻止客戶程序修改getNumElements呢? 因為這是該類型的不變式,客戶程序怎么能破壞不變式呢。我們看下Set其余的成員函數,看你能否說服自己它們都維護了不變式。
當我們使用[]操作符訪問apvector時,它會檢查并確認索引值大于等于0且小于向量的長度。不過要訪問集合的元素,我們需要更強的條件驗證。index必須小于元素數,元素數可能是個比向量長度小的值。
~~~
apstring Set::getElement (int i) const
{
if (i < numElements) {
return elements[i];
} else {
cout << "Set index out of range." << endl;
exit (1);
}
}
~~~
如果getElement得到的索引值超出了范圍,它會打印錯誤信息(我承認,并不是最有用的信息)后退出。 find和add是比較有趣的函數。到目前為止,遍歷和查找的模式還是老樣子:
~~~
int Set::find (const apstring& s) const
{
for (int i=0; i<numElements; i++) {
if (elements[i] == s) return i;
}
return -1;
}
~~~
現在就剩下add了。像add這樣的函數的返回類型一般是void,不過在這個例子中,更有意義的可能是返回元素的索引。
~~~
int Set::add (const apstring& s)
{
// 如果元素已經在集合中,返回其索引
int index = find (s);
if (index != -1) return index;
// 如果apvector滿了,將它的大小調整為原來的2倍
if (numElements == elements.length()) {
elements.resize (elements.length() * 2);
}
// 添加新元素并返回其索引
index = numElements;
elements[index] = s;
numElements++;
return index;
}
~~~
這里有個技巧,numElements會以兩種方式使用:其一就是表示集合中的元素數目,其二是用作下一個要添加的元素的索引。
可能要花點時間才能相信這行得通,但是考慮一下:當元素數目為0時,下一個要加入元素的索引也是0。當元素數目等于向量的長度時,這就說明向量已經滿了,要加入新元素必須先通過resize分配更多的空間。
下面是一個Set對象的狀態圖,該對象初始包含2個元素的空間:?
現在我們可以使用Set類來記錄在文件中找到的城市。在main函數中我們以2為初始大小創建Set對象:
~~~
Set cities (2);
~~~
然后在processLine函數中我們把兩個城市添加到Set中,并保存返回的索引值。
~~~
int index1 = cities.add (city1);
int index2 = cities.add (city2);
~~~
我修改了processLine函數,使它以城市對象為第二個參數。
- 第1章 編程之路
- 1.1 什么是編程語言
- 1.2 什么是程序
- 1.3 什么是調試
- 1.4 形式語言與自然語言
- 1.5 第一個程序
- 1.6 術語表
- 第2章 變量和類型
- 2.1 更多的輸出
- 2.2 值
- 2.3 變量
- 2.4 賦值
- 2.5 輸出變量
- 2.6 關鍵字
- 2.7 操作符
- 2.8 操作順序
- 2.9 操作符
- 2.10 組合
- 2.11 術語表
- 第3章 函數
- 3.1 浮點數
- 3.2 double到int的轉換
- 3.3 數學函數
- 3.4 函數組合
- 3.5 添加新函數
- 3.6 定義與使用
- 3.7 多函數編程
- 3.8 參數與參數值
- 3.9 參數和變量的局部性
- 3.10 多參函數
- 3.11 有返回值的函數
- 3.12 術語表
- 第4章 條件和遞歸
- 4.1 取模操作符
- 4.2 條件執行
- 4.3 選擇執行
- 4.4 鏈式條件
- 4.5 嵌套條件
- 4.6 return語句
- 4.7 遞歸
- 4.8 無窮遞歸
- 4.9 遞歸函數的棧圖
- 4.10 術語表
- 第5章 有返回值的函數
- 5.1 返回值
- 5.2 程序開發
- 5.3 組合
- 5.4 重載
- 5.5 布爾值
- 5.6 布爾變量
- 5.7 邏輯操作符
- 5.8 布爾函數
- 5.9 從main函數返回
- 5.10 深入遞歸
- 5.11 思路跳躍
- 5.12 又一個例子
- 5.13 術語表
- 第6章 迭代
- 6.1 多次賦值
- 6.2 迭代
- 6.3 while語句
- 6.4 制表
- 6.5 二維表
- 6.6 封裝和泛化
- 6.7 函數
- 6.8 再說封裝
- 6.9 局部變量
- 6.10 再說泛化
- 6.11 術語表
- 第7章 字符串那些事兒
- 7.1 字符串的容器
- 7.2 apstring變量
- 7.3 從字符串中提取字符
- 7.4 字符串長度
- 7.5 遍歷
- 7.6 一個運行時錯誤
- 7.7 find函數
- 7.8 我們自己的find版本
- 7.9 循環與計數
- 7.10 增量與減量操作符
- 7.11 字符串連接
- 7.12 apstring是可變的
- 7.13 apstring是可比較的
- 7.14 字符分類
- 7.15 其他apstring函數
- 7.16 術語表
- 第8章 結構體
- 8.1 復合值
- 8.2 Point對象
- 8.3 訪問實例變量
- 8.4 對結構體的操作
- 8.5 作為參數的結構
- 8.6 傳值調用
- 8.7 傳引用調用
- 8.8 矩形
- 8.9 作為返回值的結構
- 8.10 按引用傳遞其他類型
- 8.11 獲取用戶輸入
- 8.12 術語表
- 第9章 再談結構體
- 9.1 Time結構體
- 9.2 printTime函數
- 9.3 對象函數
- 9.4 純函數
- 9.5 const參數
- 9.6 修改函數
- 9.7 填充函數
- 9.8 哪個最佳?
- 9.9 增量開發vs高屋建瓴
- 9.10 泛化
- 9.11 算法
- 9.12 術語表
- 第10章 向量
- 10.1 元素訪問
- 10.2 向量的復制
- 10.3 for循環
- 10.4 向量的長度
- 10.5 隨機數
- 10.6 統計
- 10.7 隨機數的向量
- 10.8 計數
- 10.9 檢查其他值
- 10.10直方圖
- 10.11一次遍歷的方案
- 10.12隨機種子
- 10.13術語表
- 第11章 成員函數
- 11.1 對象和函數
- 11.2 print
- 11.3 隱式變量訪問
- 11.4 另一個例子
- 11.5 再一個例子
- 11.6 更復雜的例子
- 11.8 初始化還是構造?
- 11.7 構造函數
- 11.9 最后一個例子
- 11.10 頭文件
- 11.11 術語表
- 第12章 對象的向量
- 12.1 組合
- 12.2 紙牌對象(Card)
- 12.3 printCard函數
- 12.4 equals函數
- 12.5 isGreater函數
- 12.6 紙牌的向量
- 12.7 printDeck函數
- 12.8 查找
- 12.9 二分查找
- 12.10 牌堆與子牌堆
- 12.11 術語表
- 第13章 基于向量的對象
- 13.1 枚舉類型
- 13.2 switch語句
- 13.3 牌堆
- 13.4 另一個構造函數
- 13.5 Deck成員函數
- 13.6 洗牌
- 13.7 排序
- 13.8 子牌堆
- 13.9 洗牌與發牌
- 13.10 歸并排序
- 13.11 術語表
- 第14章 類與不變式
- 14.1 私有數據和私有類
- 14.2 什么是類?
- 14.3 復數
- 14.4 訪問函數(Accessor functions)
- 14.5 輸出
- 14.6 復數相關函數(一)
- 14.7 復數相關函數(二)
- 14.8 不變式
- 14.9 先決條件
- 14.10 私有函數
- 14.11 術語表
- 第15章 文件輸入/輸出與apmatrix類
- 15.1 流
- 15.2 文件輸入
- 15.3 文件輸出
- 15.4 解析輸入
- 15.5 解析數字
- 15.6 集合數據結構Set
- 15.7 apmatrix類
- 15.8 距離矩陣
- 15.9 一個更合理的距離矩陣
- 15.10 術語表