1. 程式人生 > 實用技巧 >連結串列的遊標實現(閱讀原始碼筆記)

連結串列的遊標實現(閱讀原始碼筆記)

見教材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 )

感覺上這個遊標陣列的理解要比普通的連結串列理解要難上一些,主要是它的記憶體和節點都放在一塊,讓人容易迷惑,不過經過一步步分析這個原始碼最終還是理解了,加油!

測試函式最後的輸出結果: