形如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。函數
表的簡單數組spa
對錶的全部操做均可以經過使用數組來實現。須要對錶的最大值進行預估,估計大了會嚴重的浪費空間。指針
數組實現使得PrintList和MakeEmpty以線性時間執行,而FindKth花費常數時間,可是插入和刪除的代價code
是昂貴的。插入須要將後面的元素進行向後移動,刪除須要將後面的元素向前移動。平均來看,仍是以線性時間執行。blog
鏈表排序
爲了不插入和刪除的開銷,咱們容許使用不連續存儲。ip
鏈表由一系列沒必要再內存中相連的結構組成。每個結構均包含有表元素和指向後繼元素的指針。咱們稱之爲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); } }