複習用書:《數據結構與算法分析 C++版》第二版 [美] Clifford A.Shaffer著,張銘、劉曉丹 等譯 電子工業出版社
一、線性表
線性表是這樣一種表結構,它的元素element存放在一個連續的表中,這個表的關鍵之處就在於“連續”(注意,這裏僅是指邏輯上的連續,在線性表中,有一種鏈表實現,其內存不要求連續),就是說當一個元素添加進一個表,或一個元素從表中刪除的時候,表中元素的排列就像有一條線把它們串起來一樣。我相信這樣的解釋已經顯得囉嗦了,因爲線性表這個名字,本來就很形象。
從程序設計的角度,我們先定義用戶所關心的線性表的界面,就好像我們找女友的時候,先在心中描繪一個夢中情人的形象:
定義線性表的界面,作爲順序表或鏈表的公用接口
*/
#ifndef LISTINTERFACE_H
#define LISTINTERFACE_H
template<class T> class ListInterface
{
public:
//向線性表的柵欄的right partition的第一個位置插入一個元素,返回值表示插入是否成功
virtual bool insert(const T&)=0;
//刪除柵欄的right partition的第一個位置的元素,返回值表示刪除是否成功,被刪除的值通過形參返回
virtual bool remove(T&)=0;
//清空所有元素,但不銷燬內存
virtual void clear()=0;
//還有其他接口函數,比較簡單,不作重點.
};
#endif
一般有兩種實現:
1、基於數組的順序表(Array_based List):訪問第i個元素的時間代價爲O(1),刪除或插入一個元素的代價爲O(n)。
2、鏈表(Linked List):訪問第i個元素的時候代價爲O(1),刪除或插入一個元素的代價爲O(n)。
由此可見,這兩種表各具特色。在應用中,應該考慮對錶中元素是訪問多,還是修改多。前者採用AList,後者採用LList。
以下是這兩種實現:(由於是複習,我只挑我認爲關鍵的寫出來。這對於找工作的人,應付筆試也許有一定幫助)
#include <iostream>
using namespace std;
//用數組實現線性表AList(array_based list)
template<class T> class AList
:public ListInterface<T>
{
private:
int maxSize; //順序表的最大長度
int listSize; //實際長度
int fence; //柵欄位置,後面將發現這與鏈表的柵欄是不同的
T *listArray; //一個指向順序表數組的指針
public:
AList(int size = 100)
{
listArray = new T[size];
maxSize = size;
fence = listSize = 0;
}
~AList()
{
delete [] listArray;
}
bool insert(const T& element)
{
if (listSize == maxSize)
{
return false; //表滿,無法再插入元素
}
for (int i=listSize-1; i>=fence; i--) //把柵欄右邊的所有元素往後挪一位
{
listArray[i+1] = listArray[i];
}
listArray[fence] = element; //給插入的元素賦值
++listSize;
return true;
}
bool remove(T& elemet)
{
if (fence == listSize)
{
return false; //柵欄右邊沒有元素可以刪除
}
elemet = listArray[fence]; //把將要刪除的值傳給外面的參數
for (int i=fence+1; i<listSize; i++) //把柵欄右邊第二個及後面的元素往前挪一位
{
listArray[i-1] = listArray[i];
}
--listSize;
return true;
}
void clear()
{
delete [] listArray;
listArray = new T[maxSize];
}
};
/*
用鏈表實現線性表,首先定義一個結點類型Node
其次,定義一個鏈表(Linked_List)LList
*/
template<class T> class LList; //注意,聲明友元模板時,必須先進行前向聲明
template<class T> class Node
{
friend class LList<T>;//聲明友元類,這樣LList便可以訪問Node類的私有成員
private:
T element;
Node * next; //指向下一個結點的指針
public:
Node(const T& eVal, Node * nVal = NULL)
{
element = eVal;
next = nVal;
}
Node(Node * nVal = NULL)
{
element = 0;
next = nVal;
}
~Node(){}
};
template<class T> class LList
:public ListInterface<T>
{
private:
Node<T> * head; //頭指針
Node<T> * tail; //尾指針
Node<T> * fence; //注意,鏈表的柵欄跟順序表AList已經完全不同了
int leftcnt,rightcnt; //柵欄左,右兩邊的元素個數,注意,柵欄所指的元素,屬於左邊,
//這與順序表不同.原因在於,鏈表有一個頭結點(head)不屬於表中元素
//由於已經約定了柵欄右邊的元素都必須是表中元素,而柵欄可能指
//向頭結點,故不應該把柵欄所指的結點歸爲右邊的元素
public:
LList()
{
head = tail = fence = new Node<T>;
leftcnt = rightcnt = 0;
}
~LList()
{
clear();
delete head;
}
void clear()
{
while (head->next != NULL)
{
fence = head;
head = head->next;
delete fence;
}
}
bool insert(const T& element)
{
fence->next = new Node<T>(element,fence->next); //完成插入
if (fence == tail) //如果在尾部插入了一個新元素
{
tail = fence->next;
}
++rightcnt;
return true;
}
bool remove(T& element)
{
if (fence == tail)
{
return false; //柵欄右邊沒有元素可刪除
}
Node<T> * tmpPtr = fence->next;
element = tmpPtr->element; //把將刪除的值傳給外面的參數
fence->next = tmpPtr->next; //完成移去
if (tail == tmpPtr) //如果鏈表尾就是將要被刪除的結點
{
tail = fence; //重新設置表尾指針
}
delete tmpPtr; //完成刪除內存
--rightcnt;
return true;
}
};
應該如何把這麼多數據結構的知識牢記在腦中,是我一直經常思考的問題。但我到現在還沒有找到一種一勞永逸的方法,事實上,我覺得一勞永逸的想法或許從根本上就是錯誤的。
要理解它!要用圖形的方法去想象它!比如鏈表的插入刪除,書中都有圖示,那就是一種把抽象的程序形象化的方法。寫程序的時候,應該是按照腦中想象的那個畫面來寫,而不是看着筆下的那張白紙。
編程,有時候就是畫畫。