表 ADT
形如A1,A2,A3,.....,An這樣的表。這個表的大小是n,大小為0的表為空表。
對於除空表外的任何表,我們說A[i+1]后繼A[i]並且A[i-1]前驅A[i]。表中的第一個元素A[1]不定義前驅,最后一個元素A[N]不定義后繼。
表ADT上面的操作:PrintList,MakeEmpty,Find,FindKth,Insert,Delete。
表的簡單數組
對表的所有操作都可以通過使用數組來實現。需要對表的最大值進行預估,估計大了會嚴重的浪費空間。
數組實現使得PrintList和MakeEmpty以線性時間執行,而FindKth花費常數時間,但是插入和刪除的代價
是昂貴的。插入需要將后面的元素進行向后移動,刪除需要將后面的元素向前移動。平均來看,還是以線性時間執行。
鏈表
為了避免插入和刪除的開銷,我們允許使用不連續存儲。
鏈表由一系列不必再內存中相連的結構組成。每一個結構均包含有表元素和指向后繼元素的指針。我們稱之為Next指針。最后的Next指針指向NULL。
鏈表上面的操作
對於PrintList和Find只需間指針傳遞到該表的第一個元素,然后用一些Next指針傳遞即可,此時的時間是線性的。刪除命令可以通過修改指
針來實現,插入命令需要申請空間調用mallo函數,然后調整兩次指針。
下面是鏈表的相關操作的具體實現
鏈表的類型聲明
struct Node; typedef struct Node *PtrToNode; typedef PtrToNode List; typedef PtrToNode Position; struct Node{ ElementType Element; Position Next; };
判斷鏈表是否為空
int IsEmpty(List L){ return L->Next == NULL; }
判斷當前是否為鏈表的末尾位置
int IsLast(Position P,List L){ return P->Next == NULL; }
Find查找函數
Position Find(ElementType X, List L){ Position p; P = L->Next; while(p != NULL && p->Element != X){ p = p->Next; } return p; }
鏈表的刪除操作
void Delete(ElementType X, List L){ Position p, TepCell; p = FindPrevious(X); if(!IsLast(P, L)){ TmpCell = p->Next; p->Next = TmpCell->Next; free(TmpCell); } } Position FindPrevious(ElementType X, List L){ Position P; P = L; while(P->Next != NULL && P->Next->Element != X){ p=p->next; } return P; }
鏈表的插入操作
void Insert(ElementType X, List L, Position P){ Position TmpCell; TmpCell = malloc(sizeof(Struct Node)); if(TMpCell == NULL){ FatalError(”out of space”); } TmpCell->Element = X; TmpCell->Next = P->Next; P->Next = TmpCell; }
注意上述的方法中的參數,雖然有些參數在函數中沒有使用,但是之所以這么做是因為別的實現方法可能會用上。
對於上述的操作,最壞的情況是掃描整個表,平均來看運行時間是O(N),因為必須平均掃描半個表。
常見的錯誤
當指針為空時,指向的是非法空間,使用指針的屬性或者操作時將會產生錯誤,無論何時只要你確定一個指向,就必須要保證該指針不是NULL。
刪除表的不正確的做法
void DeleteList(List L){ Position P, Tmp; P = L->Next; L->Next = NULL; while(P != NULL){ Tmp = P->Next; free(P); P = Temp; } }
雙鏈表
有時候以倒序掃描鏈表很方便,標准的實現方法卻是無能為力了,然而解決辦法很簡單,就是在數據域附加上一個域,使它包含指向前一個單元的指針即可。其附加的開銷是它增加了空間,同時插入
和刪除的開銷增加了一倍,因為有更多的指針需要定位。另一方面,它簡化了刪除操作,不再使用指向前驅的的指針來訪問關鍵字。
循環鏈表
讓最后的元素反過來指向第一個元素是一種流行的做法,若有表頭,則最后的元素就指向表頭,並且它還可以是雙向鏈表。
多項式ADT
我們用表來定義一種一元(具有非負次冪)多項式的的抽象數據類型。如果多項式的大部分系數非零,那么可以用一個簡單的數組來存儲這些系數。
多項式ADT的數組實現類型聲明
typedef struct{ int Coeffarray[MaxDegree + 1]; int HighPower; }* Polynomial; //將多項式初始化為0的過程 void ZeroPolynomial(Polynomial Poly){ int i; for(i=0; i<=MaxDegree; i++){ Poly->CoffArray[i] = 0; } Poly->HighPower = 0; }
兩個多項式相加
void AddPolynomial(const Polynomial Poly1, const Polynomial Poly2, Polynomial PolySum){ int i; ZeroPolynomial(PolySum) PolySum->HighPower = Max(Poly1->HighPower, Poly2->HighPower); for(i=PolySum->HighPower; i>=0; i++){ PolySum->CoeffArray[i] = Poly1->CoeffArray[i] + Poly2->CoeffArray[i]; } }
兩個多項式相乘的過程
void MultPolynomial(onst Polynomial Poly1, const Polynomial Poly2, Polynomial PolyProd){ int i, j; ZeroPolynomial(PolyProd); PolyProd->HighPower = Poly1->HighPower + Poly2->HighPower; if(PolyProd->HighPower > MaxDegree){ Error(Except array size); }else{ for(i=0; i<=Poly1->HighPower; i++){ for(j=0; j<=Poly2->HighPower; j++){ PolyPord->CoeffArray[i + j] = Poly1->CoeffArray[i] * Poly2->CoeffArray[j]; } } } }
多項式的另一種表示方法:
通過單鏈表的方式表示多項式,多項式的每一項保存在鏈表元素中,並且按照冪的大小間須降序進行排列。
多項式的表示多用於較稀疏的多項式的情況,需要注意的操作時,當鏈表的多項式相乘時,需要進行多項式的合並同類型。
typedef struct Node *PtrToNode; struct Node{ int Coefficient; int Expoent; PtrToNode Next; }; typedef PtrToNode Polynomial;
基數排序
將鏈表用於基數排序(radix sort),基數排序有時也稱為卡式排序,因為在現代計算機出現之前,它一直用於老式穿孔卡的排序。
如果我們有N個整數,它的范圍是從1到M(或者是0到M-1),我們可以利用這個信息得到一種快速排序,叫做桶式排序。我們留置一個數組稱為Count,大小為M,並初始化為0。那么,Count有M個單元(桶),開始時他們都是空的。當Ai被讀入時令Count[Ai]+1。當所有的輸入結束后,掃描Count數組,打印好排序的數組。該算法花費的時間是O(M+N),如果M=N,那么桶排序為O(N)。
基數排序是這種方法的推廣。下面的這個例子就是詳細的說明,設我們有10個數,范圍是0到999之間,我對其排序。一般來說,這是0到N^P-1之間的N個數,p是某個常數。顯然我們不適合使用桶排序,因為這樣桶太多了。於是我們的策略是使用多次桶排序,通過用最低位優先的方式,進行桶排序,算法將得到正確的結果。如果使用最高位將會出現錯誤,而且還無法判斷最高位。當然,有時會有多個數落入到桶中,而且這些數還是不同的,因此我們需要使用一個表來保存,因為所有的數字都可能有某位,所以用簡單數組來保存的話,數組的空間需求是O(N^2)。
下面是10個數的桶式排序的具體例子。本例的輸入是:64,8,216,512,27,729,0,1,343,64。為了是問題簡化,此時操作按基是10進行,第一遍桶排序的結果是0, 1, 512, 343, 64, 125, 216, 27, 8, 729。在使用次低位對第一遍桶排序的結果進行排序,得到第二次桶排序的結果:0,1,8,512,216,125,27,729,343,64。最后按照最高位進行排序,最后得到的表:0,1,8,27,64,125,216,343,512,729。
第一趟桶排序結果
| 0 |
1 |
512 |
343 |
64 |
125 |
316 |
27 |
8 |
729 |
| 0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
第二趟桶排序結果
| 8 1 0 |
216 512 |
729 27 125 |
|
343 |
|
64 |
|
|
|
| 0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
第三趟桶排序結果
| 67 27 8 1 0 |
125 |
216 |
343 |
|
512 |
|
729 |
|
|
| 0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
在使用數組進行實現時,下面的算法的步驟:
1.計算待排序數組的最大位數。
2.對桶數組和下表數組進行初始化,根據位數進行循環。
3.根據當前的位數,對待排數組進行遍歷,計算出當前位的數值,根據數值將其放入對應的桶數組中,並更新下表數組。
4.將桶數組中的元素更新到待排數組中,
5.更新位數計算,跳轉至2步驟,直至位數大於最大位數。
該排序算法的時間復雜度是O(P(N+B)),其中P是排序趟數,N是要被排序的元素的個數,B是桶數。該算法的一大缺點是不能用於浮點數的排序。
當我們把32位機器所能便是的所有整數進行排序時,假設我們在大小為2^11的桶分三趟進行即可,並且算法總是O(N)的時間消耗。
多重表
常見的多重鏈表是十字鏈表,十字鏈表的特點是對於二維的數據,它也能夠通過不連續的空間進行表達,即水平方向和垂直方向都是都是鏈表,且可以是循環鏈表。
鏈表的游標實現
在一些不支持指針的語言中,如果需要鏈表而不能使用指針的話,那么游標法是一種實現方式。
鏈表的指針實現的重要特點
1.數據存儲在一組結構體中,每個結構體含有指向下一個結構體的指針。
2.一個新的結構體可以通過調用malloc從系統中得到,並通過調用free而被釋放。
游標實現必須要滿足上述條件,條件一可以通過全局結構體數組實現,通過數組下標代表一個地址。
下面是游標的實現
struct Node{ ElementType Element; Position Next; } struct Node CursoSpace[SpaceSize];
為了模擬條件二,讓CursorSpace數組中的單元代替malloc和free的職能。為此,我們保留一個表,表由不再任何表中的元素構成。該表將用0作為表頭,對於Next,0的值為NULL,為了執行malloc功能,將第一個元素從freelist中刪除,為了執行free功能,我們將該單元放在freelist的前段。
下面是malloc函數和free函數的實現
static Position CoursorAlloc(void){ Position P; P = CoursorSpace[0].next; CursirSpace[0].Next = CursorSpace[P].next; return P; } static void CursorFree(Position P){ CursorSpace[P].next = CursorSpace[0].next; CursorSpace[0].next = P; }
下面是一個鏈表游標實現的列表:
| slot |
Element |
Next |
| 0 |
- |
6 |
| 1 |
b |
9 |
| 2 |
f |
0 |
| 3 |
header |
7 |
| 4 |
- |
0 |
| 5 |
header |
10 |
| 6 |
- |
4 |
| 7 |
c |
8 |
| 8 |
d |
2 |
| 9 |
e |
0 |
| 10 |
a |
1 |
| slot |
Element |
Next |
| 0 |
- |
6 |
| 1 |
b |
9 |
| 2 |
f |
0 |
| 3 |
header |
7 |
| 4 |
- |
0 |
| 5 |
header |
10 |
| 6 |
- |
4 |
| 7 |
c |
8 |
| 8 |
d |
2 |
| 9 |
e |
0 |
| 10 |
a |
1 |
判斷鏈表是否為空 int IsEmpty(List L){ return CursorSpace[L].Next == 0; } 判斷P是否是鏈表的末尾 int IsLast(Position P, List L){ return CursorSpace[P].Next == 0; } 游標的Find實現 Position Find(Element X, List L){ Position P; P = CursorSpace[L].Next; while(P && CursorSpace[P].Element != X){ P = CursorSpace[P].Next; } reutrn P; }
鏈表的delete操作 void Delete(Element X, List L){ Position P, TmpCell; P = FindPrevious(X, L); if(!IsLast){ TmpCell = CursorSpace[P].Next; CursorSpace[P].Next = CursorSpace[TmpCell].Next; CursorFree(TmpCell); } }
