連結串列的遊標實現(閱讀原始碼筆記)
阿新 • • 發佈:2020-09-17
見教材P42
cursor.h
typedef int ElementType; #define SpaceSize 100 /* START: fig3_28.txt */ // 連結串列遊標實現的宣告 #ifndef _Cursor_H #define _Cursor_H typedef int PtrToNode; typedef PtrToNode List; typedef PtrToNode Position; void InitializeCursorSpace( void ); List MakeEmpty( List L ); int IsEmpty( const List L ); int IsLast( const Position P, const List L ); Position Find( ElementType X, const List L ); void Delete( ElementType X, List L ); Position FindPrevious( ElementType X, const List L ); void Insert( ElementType X, List L, Position P ); void DeleteList( List L ); Position Header( const List L ); Position First( const List L ); Position Advance( const Position P ); ElementType Retrieve( const Position P ); Position getFirstMemory(); #endif /* _Cursor_H */ /* END */
cursor.c
#include "cursor.h" #include <stdlib.h> #include "fatal.h" /* Place in the interface file */ // 元素的結構體 struct Node { ElementType Element; // int Position Next; // PtrToNode }; struct Node CursorSpace[SpaceSize]; // SpaceSize 100 /* START: fig3_31.txt */ static Position CursorAlloc(void) // 從下面可以推斷出,這裡是分配空間給新的元素,並且取記憶體的地方是下面初始化的那塊有100個元素大小的區域 { Position P; // 這裡的Position是int型別的別名 P = CursorSpace[0].Next; // 最開始時這裡是1,也就是第一次取出的記憶體是0後面的那一塊 CursorSpace[0].Next = CursorSpace[P].Next; // 然後管理記憶體的頭節點就要往後挪一位,當所有的記憶體被分配完了之後,CursorSpace[ 0 ]會指向它自身,即其Next值為0 return P; // 返回取出的那一塊記憶體 } // 釋放一個節點的記憶體 static void CursorFree(Position P) // P就代表了索引,根據這個索引可以找到其元素 { // 下面這兩行程式碼就是根據記憶體的頭節點0,將歸還的記憶體放到freelist的最前面 CursorSpace[P].Next = CursorSpace[0].Next; CursorSpace[0].Next = P; // 釋放並歸還記憶體,從放到CursorSpace[0]這一處可以看出,但是問題是如何使用這塊歸還的記憶體是一個問題,有一點模糊!!!--> 在測試函式中添加了幾個測試部分,分析記憶體的擺放情況之後就清晰多了! } /* END */ // cursorSpace的初始化 // 這裡相當於是取出一塊記憶體,所以並沒有往裡面存資料 // cursorSpace數組裡面存放的是Node節點,裡面有Element和Next兩個成員 // 這裡只是初始化Next這一個成員,取記憶體的時候則是根據陣列的索引來 void InitializeCursorSpace(void) { int i; for (i = 0; i < SpaceSize; i++) CursorSpace[i].Next = i + 1; CursorSpace[SpaceSize - 1].Next = 0; } List MakeEmpty(List L) { if (L != NULL) DeleteList(L); L = CursorAlloc(); if (L == 0) FatalError("Out of memory!"); CursorSpace[L].Next = 0; // 空連結串列的頭節點指向0,同樣指向0的還有記憶體的最後一塊,還有非空連結串列的最後一個元素 return L; } /* START: fig3_32.txt */ /* Return true if L is empty */ // 判斷連結串列是否為空,判斷的方法是看連結串列的頭節點是否指向0 int IsEmpty(List L) { return CursorSpace[L].Next == 0; } /* END */ /* START: fig3_33.txt */ /* Return true if P is the last position in list L */ /* Parameter L is unused in this implementation */ // 判斷是否到了表的盡頭 int IsLast(Position P, List L) { return CursorSpace[P].Next == 0; // Next指向了0代表遍歷到了表的盡頭 } /* END */ /* START: fig3_34.txt */ /* Return Position of X in L; 0 if not found */ /* Uses a header node */ // 返回表L中的X的位置 Position Find(ElementType X, List L) { Position P; /* 1*/ P = CursorSpace[L].Next; /* 2*/ while (P && CursorSpace[P].Element != X) /* 3*/ P = CursorSpace[P].Next; /* 4*/ return P; // 返回的Position P有何用處?其用處就是作為索引,它本身也是通過上一個元素的Next找到的 } /* END */ /* START: fig3_35.txt */ /* Delete from a list */ /* Assume that the position is legal */ /* Assume use of a header node */ void Delete(ElementType X, List L) { Position P, TmpCell; P = FindPrevious(X, L); if (!IsLast(P, L)) /* Assumption of header use */ { /* X is found; delete it */ TmpCell = CursorSpace[P].Next; CursorSpace[P].Next = CursorSpace[TmpCell].Next; CursorFree(TmpCell); // 刪除,和普通的連結串列類似,這裡釋放記憶體的方式有些區別 } } /* END */ /* If X is not found, then Next field of returned value is 0 */ /* Assumes a header */ Position FindPrevious(ElementType X, List L) // 這裡根據ElementType來找前一個元素,那麼,如果有重複的現象,那麼就返回第一次找到的結果 { Position P; /* 1*/ P = L; /* 2*/ while (CursorSpace[P].Next && CursorSpace[CursorSpace[P].Next].Element != X) /* 3*/ P = CursorSpace[P].Next; /* 4*/ return P; } /* START: fig3_36.txt */ /* Insert (after legal position P) */ /* Header implementation assumed */ /* Parameter L is unused in this implementation */ /** * 插入操作 * @param X 要插入的元素值 * @param L 表的頭節點 * @param P 表示現存的表中的一個節點的位置,這裡就是將新插入的值插在P的後面 */ void Insert(ElementType X, List L, Position P) { Position TmpCell; // 先取出一塊記憶體,也就是分配空間 /* 1*/ TmpCell = CursorAlloc(); /* 2*/ if (TmpCell == 0) // 如果TemCell為0,則意味著當初分配的空間為SpaceSize用到了盡頭 /* 3*/ FatalError("Out of space!!!"); /* 4*/ CursorSpace[TmpCell].Element = X; /* 5*/ CursorSpace[TmpCell].Next = CursorSpace[P].Next; /* 6*/ CursorSpace[P].Next = TmpCell; } /* END */ /* Correct DeleteList algorithm */ // 刪除整個表,刪除之後頭節點變成了空頭,頭節點裡本身的Element的是是沒有賦的 void DeleteList(List L) { Position P, Tmp; /* 1*/ P = CursorSpace[L].Next; /* Header assumed */ // 先將第一個節點取出來 /* 2*/ CursorSpace[L].Next = 0; // 然後將連結串列頭歸零 // 迴圈,把整個表給刪除 /* 3*/ while (P != 0) { /* 4*/ Tmp = CursorSpace[P].Next; // 這裡並沒有把Element給清零,雖然最後並沒有什麼影響 /* 5*/ CursorFree(P); /* 6*/ P = Tmp; } } // 返回頭節點,即L這個節點本身 Position Header(List L) { return L; } // 返回第一個節點,即頭節點指向的第一個節點 Position First(List L) { return CursorSpace[L].Next; } // 相當於指標往後移動一位 Position Advance(Position P) // 這裡P是任意節點,這個函式的作用就是取出節點的下一個節點,相當於遊標往後移動一位,例如,P是L的情況下,CursorSpace[ P ].Next就是取出第一個元素的Position { return CursorSpace[P].Next; } // 取出資料 ElementType Retrieve(Position P) { return CursorSpace[P].Element; } // 為了測試,自己後加的函式 Position getFirstMemory() { return CursorSpace[0].Next; }
test.c(main.c),這是測試函式
#include <stdio.h> #include "cursor.h" // 打印表 void PrintList(const List L) { Position P = Header(L); if (IsEmpty(L)) printf("Empty list\n"); else { do { P = Advance(P); printf("%d ", Retrieve(P)); } while (!IsLast(P, L)); printf("\n"); } } int main() { List L; // List如果只是證明而不賦值就是NULL /*if (L == NULL) { printf("List為空\n"); }*/ Position P; int i; InitializeCursorSpace(); L = MakeEmpty(NULL); // 不傳參也代表NULL P = Header(L); PrintList(L); for (i = 10; i < 20; i++) { Insert(i, L, P); // L是頭節點 PrintList(L); P = Advance(P); } for (i = 10; i < 20; i += 2) // 隔開一位進行刪除,並且刪除的都是偶數 { Delete(i, L); // 測試刪除後釋放的記憶體是否放到正確的位置 printf("第一塊記憶體:%d \n", getFirstMemory()); } for (i = 10; i < 20; i++) if ((i % 2 == 0) == (Find(i, L) != NULL)) // 條件成立的情況: 1. 偶數 && 能找到這個數,然而,上面已經將所有偶數給刪除了,所以是不可能為true的;2. 奇數 && 找不到這個數,然而,奇數時可以找到的,所以這個條件也是不成立的 printf("Find fails\n"); printf("Finished deletions\n"); PrintList(L); // 測試一下歸還後的記憶體的使用 Insert(21, L, P); PrintList(L); Position position = Find(21, L); printf("新插入的記憶體是 %d \n", position); DeleteList(L); PrintList(L); return 0; }
fatal.h
#include <stdio.h>
#include <stdlib.h>
#define Error( Str ) FatalError( Str )
#define FatalError( Str ) fprintf( stderr, "%s\n", Str ), exit( 1 )
感覺上這個遊標陣列的理解要比普通的連結串列理解要難上一些,主要是它的記憶體和節點都放在一塊,讓人容易迷惑,不過經過一步步分析這個原始碼最終還是理解了,加油!
測試函式最後的輸出結果: