1. 程式人生 > 實用技巧 >C語言實現簡單的單鏈表

C語言實現簡單的單鏈表

為了方便,建立標頭檔案ElemType規定操作狀態碼和資料元素型別以及用於資料元素型別的匹配函式

typedef double ElemType;

//操作成功
#define OK 1
//操作錯誤
#define ERROR 0
//操作異常
#define OVERFLOW -2
//定義元素型別,int可使用基本資料型別和使用者自定義資料型別替換,根據使用場景選擇
typedef int Status ;

double absForDouble(double a);

/**
 * 求絕對值
 * @param a
 * @return 返回a的絕對值
 */
double absForDouble(double
a) { if (a < 0)return -a; return a; } /** * 實現兩個ElemType型別變數的匹配 * @param e1 ElemType型別變數e1 * @param e2 ElemType型別變數e2 * @return 匹配結果,1表示匹配,0表示不匹配 */ int match(ElemType e1, ElemType e2){ if(absForDouble(e1-e2)<1e-6) return 1; return 0; }

單鏈表的定義和實現

/**
 * 單鏈表的定義和實現
 
*/ /** * 線性表的鏈式儲存結構 * 什麼是線性表的鏈式儲存結構? * 使用一組任意的儲存單元儲存線性表的資料元素,這些儲存單元可以是連續的,也可以是不連續的,而為了表現線性表中各元素的直接後繼關係,在資料元素的基礎上 * 額外的使用指標來儲存當前資料元素的直接後繼的位置資訊,這樣的包含了資料元素的資料和資料元素的直接後繼位置資訊的資料結構稱之為結點,而由n個結點連結 * 成一個連結串列,即為線性表的鏈式儲存結構。一般的,把線性表的鏈式儲存結構簡稱為單鏈表或者線性連結串列,而結點儲存資料元素本身資訊的部分稱之為資料域,而儲存 * 資料元素的直接後繼的位置資訊的部分稱之為指標域。換而言之,在單鏈表中用以儲存線性表的邏輯關係的是指標域
*/ /** * 單鏈表的定義 * 關於LNode* 和LinkedList的區別:一般的,LinkedList表示的是指向單鏈表的頭結點(在首元結點前預設的額外的結點,其資料域不儲存任何資訊)的指標,而LNode*則是指指向單鏈表的結點的指標。 * 頭指標:指向連結串列中第一個結點指標(若不預設頭結點,則指向首元結點,否則指向頭結點) * 首元結點:儲存線性表第一個資料元素的結點 * 為何預設頭結點? * 1、便於首元結點的處理,首元結點作為頭結點的直接後繼 * 2、便於頭指標對空表和非空表作同一處理,無論線性表是否為空表,頭指標都是指向頭結點的非空指標 */ typedef struct { //資料域 ElemType data; //指標域,儲存當前結點直接後繼的地址 struct LNode *next; } LNode, *LinkedList;

單鏈表基本操作的實現

/**
 * 單鏈表基本操作的實現
 */

/**
 * 初始化單鏈表:初始化單鏈表L
 * 演算法描述:
 * 1、新建頭結點,新建失敗返回OVERFLOW
 * 2、使頭指標指向頭結點
 * 3、置頭結點的指標域為NULL
 * 4、初始化成功,返回OK
 * 5、返回前置指標head指向NULL
 * @param L 指向頭指標的指標L(以實現動態記憶體的傳遞)
 * @return  操作結構狀態碼
 */
Status initLinkedList(LinkedList *L) {
    //新建頭結點
    LNode *head = (LNode *) malloc(sizeof(LNode));
    //新建失敗返回OVERFLOW
    if (!head)return ERROR;
    //使頭指標指向頭結點
    *L = head;
    //置頭結點的指標域為NULL
    head->next = NULL;
    head = NULL;
    //初始化成功,返回OK
    return OK;
}

/**
 * 取值:取單鏈表L上序號i的結點的資料域並儲存到指標e所指向的記憶體單元
 * 演算法描述:
 * 1、定義指向當前結點的指標p,使p指向首元結點
 * 2、定義計數器j,記錄由指標p首元結點移動到i結點移動的次數,初始值為零(首元結點移動到首元結點移動零次)
 * 3、迴圈使指標p後移,計數器自增,直至p指向NULL(表示訪問了n+1個元素,n為單鏈表當前結點個數)或者j = i -1(表示移動了i-1次,如果i合法,p現在指向結點i)
 * 4、判斷i是否合法,i取值範圍是從1到n的整數,若迴圈結束時,p指向NULL表示訪問了n個結點仍然未找到結點i,i>n,若j > i -1表示未移動到i結點,因為i移動到i結點一定需要一定i-1次,除非i<1,i不合法
 * 5、i不合法返回ERROR
 * 6、i合法,使用指標變數e指向的儲存單元儲存結點i資料域的資訊
 * 7、返回OK
 * 8、返回前置指標p指向NULL
 * @param L 單鏈表L的頭指標
 * @param i 想要訪問的結點序號i
 * @param e 用來儲存i結點資料域的變數的指標e
 * @return 操作結果狀態碼
 */
Status getElem(LinkedList L, int i, ElemType *e) {
    //定義指向當前結點的指標p,使p指向首元結點
    LNode *p = L->next;
    //定義計數器j,記錄由指標p首元結點移動到i結點移動的次數,初始值為零(首元結點移動到首元結點移動零次)
    int j = 0;
    //迴圈使指標p後移,計數器自增,直至p指向NULL(表示訪問了n+1個元素,n為單鏈表當前結點個數)或者j = i -1(表示移動了i-1次,如果i合法,p現在指向結點i)
    while (p && j < i - 1) {
        p = p->next;
        ++j;
    }
    //判斷i是否合法,i取值範圍是從1到n的整數
    // 若迴圈結束時,p指向NULL表示訪問了n個結點仍然未找到結點i,i>n
    // 若j > i -1表示未移動到i結點,因為i移動到i結點一定需要一定i-1次,除非i<1,i不合法
    if (!p || j > i - 1) {
        //i不合法返回ERROR
        return ERROR;
    }
    //i合法,使用指標變數e指向的儲存單元儲存結點i資料域的資訊
    *e = p->data;
    //返回OK
    p = NULL;
    return OK;
}

/**
 * 查詢:查詢單鏈表L上資料域與引數e匹配的結點的結點序號和地址
 * 演算法描述:
 * 1、定義指向當前結點的指標p,使其指向單鏈表L的首元結點
 * 2、定義計數器j記錄結點序號,初始值為1
 * 3、迴圈改變指標p的指向並修改計數器j的值直至p=NULL(表示當前結點序號n+1,n時單鏈表L的當前長度,即不存在資料域和e匹配的結點,查詢失敗),或者當前結點資料域與e匹配,表示查詢成功
 * 4、判斷迴圈結束的原因,若p=NULL,則說明查詢失敗,設定匹配的結點序號為-1,返回ERROR,表示查詢失敗,否則查詢成功,計數器j的值即結點序號,返回OK
 * 5、返回前置指標p指向NULL
 * @param L 指標單鏈表頭指標L
 * @param e 查詢的資料引數e
 * @param locate 指向符合條件的結點的指標locate
 * @param i 合條件的結點的序號i
 * @return
 */
Status indexOf(LinkedList L, ElemType e, LNode **locate, int *i) {
    //定義指向當前結點的指標p,使其指向單鏈表L的首元結點
    LNode *p = L->next;
    //定義計數器j記錄結點序號,初始值為1
    int j = 1;
    //迴圈改變指標p的指向並修改計數器j的值直至p=NULL(表示當前結點序號n+1,n時單鏈表L的當前長度,即不存在資料域和e匹配的結點,查詢失敗),或者當前結點資料域與e匹配,表示查詢成功
    while (p && !match(p->data, e)) {
        p = p->next;
        ++j;
    }
    //判斷迴圈結束的原因,若p=NULL,則說明查詢失敗,設定匹配的結點序號為-1,返回ERROR,表示查詢失敗
    if (!p) {
        *i = -1;
        *locate = p;
        return ERROR;
    }
    // 否則查詢成功,計數器j的值即結點序號,返回OK
    *i = j;
    *locate = p;
    p = NULL;
    return OK;
    //返回前置指標p指向NULL
}


/**
 * 插入:在單鏈表L上的結點序號為i-1和結點序號為i的結點之間插入一個數據域為e的新結點
 * 演算法描述:
 * 1、定義指向當前結點的指標p,使其指向頭結點
 * 2、定義計數器j記錄指標p從頭結點移動到結點i-1所需移動的次數,考慮到i的合法範圍是從1到n+1的整數,n是單鏈表當前結點個數,故j的合法範圍是1到n
 * 3、迴圈移動指標p以及修改計數器直至p=NULL(表示移動到了第n+1個結點,仍未找到第i-1個結點,i > n+1),或者j = i -1(表示移動到了第i-1個結點,可以進行插入操作了)
 * 4、判斷i是否合法,若p=NULL,說明i > n+1,i非法,返回ERROR,若j > i-1說明沒有移動到第i-1個結點(因為正常情況下移動到第i-1個結點,恰好需要移動i-1次,說明i < 1,i非法,返回ERROR)
 * 5、若i合法,進行插入操作
 * 6、新建一個結點並使用指標s指向新結點,置新結點的資料域為e
 * 7、置新結點的指標域指向單鏈表序號為i的結點(置新結點的指標域為指向結點i-1的當前結點指標p的指標域的指向的地址)
 * 8、置當前結點指標p指向的序號為i-1的結點的指標域為新結點的地址
 * 9、令指標p和指標s指向NULL
 * 10、返回OK
 * @param L 指向頭結點指標的指標(實現動態記憶體的傳遞)
 * @param i 插入位置,整數,合法範圍1到n+1,n為單鏈表的當前長度
 * @param e 插入的新結點的資料域
 * @return
 */
Status insertNode(LinkedList *L, int i, ElemType e) {
    //定義指向當前結點的指標p,使其指向頭結點
    LNode *p = *L;
    //定義計數器j記錄指標p從頭結點移動到結點i-1所需移動的次數,初始值為零,考慮到i的合法範圍是從1到n+1的整數,n是單鏈表當前結點個數,故j的合法範圍是1到n
    int j = 0;
    //迴圈移動指標p以及修改計數器直至p=NULL(表示移動到了第n+1個結點,仍未找到第i-1個結點,i > n+1),或者j = i -1(表示移動到了第i-1個結點,可以進行插入操作了)
    while (p && j < i - 1) {
        p = p->next;
        ++j;
    }
    //判斷i是否合法,若p=NULL,說明i > n+1,i非法,返回ERROR,若j > i-1說明沒有移動到第i-1個結點(因為正常情況下移動到第i-1個結點,恰好需要移動i-1次,說明i < 1,i非法,返回ERROR)
    if (!p || j > i - 1) {
        return ERROR;
    }
    //若i合法,進行插入操作
    //新建一個結點並使用指標s指向新結點,置新結點的資料域為e
    LNode *s = (LNode *) malloc(sizeof(LNode));
    s->data = e;
    //置新結點的指標域指向單鏈表序號為i的結點(置新結點的指標域為指向結點i-1的當前結點指標p的指標域的指向的地址)
    s->next = p->next;
    //置當前結點指標p指向的序號為i-1的結點的指標域為新結點的地址
    p->next = s;
    //令指標p和指標s指向NULL
    s = NULL;
    p = NULL;
    //返回OK
    return OK;
}

/**
 * 刪除:刪除單鏈表上結點序號為i的結點,即刪除單鏈表上序號i-1和i+1之間的結點(由於i值的不同,結點i-1和i+1可能不存在),i的合法範圍是從1到n的整數,n為當前單鏈表的結點個數
 * 演算法描述:
 * 1、定義指向當前結點的指標p,使其指向單鏈表的頭結點
 * 2、定義計數器j記錄指標p移動至i-1結點移動的次數,因此j的合法範圍是從0到n-1的整數
 * 3、迴圈移動指標p和修改計數器直至p->next=NULL(表示當前結點無後需結點,無法刪除下一個結點),或者j=i-1(表示已經移動到了序號為i-1的結點,可以進行刪除操作了)
 * 4、判斷i值是否合法,若p->next=NULL,當前指標p已經移動了單鏈表的最後一個結點,即無結點可刪除,返回ERROR,若j>i-1表示未移動到i-1結點,但是迴圈終止了,i<1,i值非法,返回ERROR
 * 5、若i值合法,定義指向結點i的臨時指標t,使其指向結點i,即指標p的指標域所指向的地址
 * 6、置指標p指向的序號為i-1的結點的指標域為結點i的指標域指向的地址,若結點i無直接後繼(此時結點i的指標域指向NULL),結點i-1在刪除結點i後即最後一個結點,指標域指向NULL依舊合理
 * 7、釋放指標t指向的結點i所佔用的記憶體單元
 * 8、使指標p、t指向NULL
 * 9、返回OK
 * @param L 指向單鏈表的頭指標的指標L(實現動態記憶體的傳遞)
 * @param i 需刪除的結點在單鏈表的結點序號i
 * @return 操作結果狀態碼
 */
Status deleteNode(LinkedList *L, int i) {
    //定義指向當前結點的指標p,使其指向單鏈表的頭結點
    LNode *p = *L;
    //定義計數器j記錄指標p移動至i-1結點移動的次數,j初始值為零,因此j的合法範圍是從0到n-1的整數
    int j = 0;
    //迴圈移動指標p和修改計數器直至p->next=NULL(表示當前結點無後需結點,無法刪除下一個結點),或者j=i-1(表示已經移動到了序號為i-1的結點,可以進行刪除操作了)
    while (p->next && j < i - 1) {
        p = p->next;
        ++j;
    }
    //判斷i值是否合法,若p->next=NULL,當前指標p已經移動了單鏈表的最後一個結點,即無結點可刪除,返回ERROR,若j>i-1表示未移動到i-1結點,但是迴圈終止了,i<1,i值非法,返回ERROR
    if (!p->next || j > i - 1) {
        return ERROR;
    }
    //若i值合法,定義指向結點i的臨時指標t,使其指向結點i,即指標p的指標域所指向的地址
    LNode *t = p->next;
    //置指標p指向的序號為i-1的結點的指標域為結點i的指標域指向的地址,若結點i無直接後繼(此時結點i的指標域指向NULL),結點i-1在刪除結點i後即最後一個結點,指標域指向NULL依舊合理
    p->next = t->next;
    //釋放指標t指向的結點i所佔用的記憶體單元
    free(t);
    //使指標p、t指向NULL
    p = NULL;
    t = NULL;
    //返回OK
    return OK;
}

測試單鏈表和單鏈表基本操作的功能

int main() {
    LinkedList L = NULL;
    if (initLinkedList(&L)) {
        //test initialize LinkedList
        printf("successfully initialized the LinkedList \n");
    }
    //test insert node into LinkedList
    insertNode(&L, 1, 2);
    printf("insert a element which index is 1 and data is 2 into the LinkedList\n");
    insertNode(&L, 2, 2.5);
    printf("insert a element which index is 2 and data is 2.5 into the LinkedList\n");
    double d;
    LNode *p = NULL;
    int i;
    //test find a element on LinkedList
    if (indexOf(L, 2, &p, &i)) {
        printf("now the index of element which data is 2 is %d\n", i);
        //test get a element's data from LinkedList by index
        getElem(L, i, &d);
        printf("now the data of the element which index is %d is %lf\n", i, d);
    } else {
        printf("index = %d\n", i);
    }
    //test delete a element from LinkedList
    printf("now delete a element which index is 1 from the LinkedList\n");
    deleteNode(&L, 1);
    if (indexOf(L, 2.5, &p, &i)) {
        printf("inow the index of element which data is 2.5 is %d\n", i);
        printf("now the data of the element which address is %d is %lf\n", p, d);
    } else {
        printf("index = %d\n", i);
    }
    return 0;
}

為了結果清晰明瞭,可刪除printf函式內的英文

執行結果: