關於c語言單項鏈表尾添加


猶豫了幾天,看了很多大牛寫的關於c語言鏈表,感觸很多,終於下定決心,把自己對於鏈表的理解隨之附上,可用與否,自行裁奪。由於作者水平有限也是第一次寫,不足之處,竭誠希望得到各位大神的批評指正。制作不易,不喜勿噴,謝謝!!!

在正文開始之前,我先對數組和鏈表進行簡單的對比分析。

 鏈表也是一種很常見的數據結構,不同於數組的是它是動態進行存儲分配的一種結構。數組存放數據時,必須要事先知道元素的個數。舉個例子,比如一個班有40個人,另一個班有100個人,如果要用同一個數組先后來存放這兩個班的學生數據,那么必須得定義長度為100的數組。如果事先不確定一個班的人數,只能把數組定義的足夠大,以能存放任何班級的學生數據。這樣就很浪費內存,而且數組對於內存的要求必須是是連續的,數據小的話還好說,數據大的話內存分配就會失敗,數組定義當然也就失敗。還有數組對於插入以及刪除元素的效率也很低這就不一一介紹了。然而鏈表就相對於比較完美,它很好的解決了數組存在的那些問題。它儲存數據時就不需要分配連續的空間,對於元素的插入以及刪除效率就很高。可以說鏈表對於內存就是隨用隨拿,不像數組要事先申請。當然,有優點就必然有缺點,就比如說鏈表里每一個元素里面都多包含一個地址,或者說多包含一個存放地址的指針變量,所以內存開銷就很大。還有因為鏈表的內存空間不是連續的,所以想找到其中的某一個數據就沒有數組那么方便,必須先得到該元素的上一個元素,根據上一個元素提供的下一元素地址去找到該元素。所以不提供“頭指針”(下文中“頭指針”為“PHead”),那么整個鏈表將無法訪問。鏈表就相當於一條鐵鏈一環扣一環(這個稍后會詳細的說)。

 

 鏈表

上面我提到過鏈表是動態進行存儲分配的一種結構。鏈表中的每一個元素稱為“結點”,每個結點都包括兩部分:一部分為用戶需要的實際數據,另一部分為下一結點的地址。鏈表有一個“頭指針(PHead)”變量,存放着一個地址,該地址指向第一個結點,第一個結點里面存放着第二個結點的地址,第二個結點又存放着第三個結點地址。就這樣頭指針指向第一個結點,第一個結點又指向第二個......直到最后一個結點。最后一個結點不再指向其他結點,地址部分存放一個“NULL”。 見下圖:(表中有一個尾指針(PEnd)其作用后面會解釋)

 

c語言單項鏈表尾添加整體代碼如下:(詳解附后)

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>


//函數聲明
//尾添加
void wei_tian_jia(struct NODE** PHEAD, struct NODE** PEND, int shu_ju);
//尾添加(沒有尾指針)
void wei_tian_jia_(struct NODE** PHEAD, int shu_ju);
//釋放鏈表
void shi_fang_lian_biao(struct NODE* PHEAD);
//釋放鏈表(並是頭指針(PHead)尾指針(PEnd)指向空)
void shi_fang_lian_biao_free(struct NODE** PHEAD, struct NODE** PEnd);
//輸出鏈表
void shu_chu(struct NODE* PHEAD);


//定義一個鏈表結構體
struct NODE
{
    int shu_ju;           //用戶需要的實際數據
    struct NODE* PNext;   //下一結點的地址

};


int main(void)
{
    //創建頭尾指針
    struct NODE* PHead = NULL;
    struct NODE* PEnd = NULL;


    //尾添加
    wei_tian_jia(&PHead, &PEnd, 17);
    wei_tian_jia(&PHead, &PEnd, 21);
    wei_tian_jia(&PHead, &PEnd, 34);
    wei_tian_jia(&PHead, &PEnd, 8);
    wei_tian_jia(&PHead, &PEnd, 24);

    //尾添加(沒有尾指針)
    //wei_tian_jia_(&PHead, 23);
    //wei_tian_jia_(&PHead, 17);
    //wei_tian_jia_(&PHead, 11);

    //輸出鏈表
    shu_chu(PHead);

    //釋放鏈表
    //shi_fang_lian_biao(PHead);

    //釋放鏈表(並是頭指針(PHead)尾指針(PEnd)指向空)
    shi_fang_lian_biao_free(&PHead, &PEnd);


    system("pause");
    return 0;
}

//尾添加 void wei_tian_jia(struct NODE** PHEAD, struct NODE** PEND, int SHU_JU) { //創建結點 struct NODE* PTEMP = (struct NODE*)malloc(sizeof(struct NODE)); if (PTEMP != NULL) { //節點賦值 PTEMP->shu_ju = SHU_JU; PTEMP->PNext = NULL; //把結點連起來 if (NULL == *PHEAD) // 因為PHEAD如果是NULL的話 PEND也就是NULL 所以條件里面不必要寫 { *PHEAD = PTEMP; *PEND = PTEMP; } else { (*PEND)->PNext = PTEMP; *PEND = PTEMP; } } } //尾添加(沒有尾指針) void wei_tian_jia_(struct NODE** PHEAD1, int SHU_JU) { //創建結點 struct NODE* PTEMP = (struct NODE*)malloc(sizeof(struct NODE)); if (PTEMP != NULL) { //結點成員賦值 PTEMP->shu_ju = SHU_JU; PTEMP->PNext = NULL; //把結點連一起 if (NULL == *PHEAD1) { *PHEAD1 = PTEMP; } else { struct NODE* PTEMP2 = *PHEAD1; while (PTEMP2->PNext != NULL) { PTEMP2 = PTEMP2->PNext; } PTEMP2->PNext = PTEMP; } } } //輸出鏈表 void shu_chu(struct NODE* PHEAD) { while (PHEAD != NULL) { printf("%d\n", PHEAD->shu_ju); PHEAD = PHEAD->PNext; } } //釋放鏈表 void shi_fang_lian_biao(struct NODE* PHEAD) { struct NODE* P = PHEAD; while (PHEAD != NULL) { struct NODE* PTEMP = P; P = P->PNext; free(PTEMP); } free(PHEAD); } //釋放鏈表(並是頭指針(PHead)尾指針(PEnd)指向空) void shi_fang_lian_biao_free(struct NODE** PHEAD, struct NODE** PEnd) { while (*PHEAD != NULL) { struct NODE* PTEMP = *PHEAD; *PHEAD = (*PHEAD)->PNext; free(PTEMP); } *PHEAD = NULL; *PHEAD = NULL; }

 

 

部分代碼詳解:

(再次申明:由於作者水平有限,所以有的變量名用的拼音。見笑,莫怪!!!為了簡單明了,方便起見,我定義了一個實際數據。)

 

“頭指針”(PHead)以及“尾指針”(PEnd):

 

 

 

 頭指針很好理解指向首結點用於遍歷整個數組,而尾指針呢?我們先看下面兩段代碼一段是有尾指針的一段是沒有尾指針的:

 顯然這是一段有尾指針的代碼。這里的思想就是當寫入第一個成員進鏈表的時候,此時鏈表就一個成員,即是頭(PHEAD),也是尾(PEND),當寫入第二個成員的時候,鏈表頭(PHEALD)不動鏈表尾(PEND)向后移,指向最后一個結點。
//尾添加
void wei_tian_jia(struct NODE** PHEAD, struct NODE** PEND, int SHU_JU)
{
    //創建一個結點
    struct NODE* PTEMP = (struct NODE*)malloc(sizeof(struct NODE));
    if (PTEMP != NULL)
    {
        //節點成員賦值(一定要每個成員都要賦值)
        PTEMP->shu_ju = SHU_JU;
        PTEMP->PNext = NULL;

        //把結點連起來
        if (NULL == *PHEAD) // 因為PHEAD如果是NULL的話 PEND也就是NULL  所以條件里面不必要寫
        {
            *PHEAD = PTEMP;
            *PEND = PTEMP;
        }
        else
        {
            //把尾指針向后移
            (*PEND)->PNext = PTEMP;
            *PEND = PTEMP;

        }

    }
    
}

那么下面這段代碼是沒有尾指針的。它的思想就是頭指針一直指向第一個結點,然后通過遍歷來找到最后一個結點,從而使最后一個結點里面的指針指向所要插入的元素。

//尾添加(沒有尾指針)
void wei_tian_jia_(struct NODE** PHEAD1, int SHU_JU)
{
    //創建結點
    struct NODE* PTEMP = (struct NODE*)malloc(sizeof(struct NODE));

    if (PTEMP != NULL)
    {
        //結點成員賦值
        PTEMP->shu_ju = SHU_JU;
        PTEMP->PNext = NULL;

        //把結點連一起    
        if (NULL == *PHEAD1)
        {
            *PHEAD1 = PTEMP;

        }
        else 
        {
            struct NODE* PTEMP2 = *PHEAD1;
            while (PTEMP2->PNext != NULL)
            {
                PTEMP2 = PTEMP2->PNext;
            }
            PTEMP2->PNext = PTEMP;
        }

    }

}

我把上面代碼里面的一段摘出來說明一下。

這段代碼里面可以看到我又定義了一個PTEMP2指針變量,為什么呢?前面我提到過沒有尾指針的時候添加結點的思想就是要遍歷數組,從而找到最后一個結點然后讓它指向我們要插入的結點,如果沒有這個PHEAD2,我們遍歷完鏈表以后我們的頭指針PHEAD1就已經指向了最后一個結點了,單項鏈表如果頭指針移動了,數據就會找不到了。所以我定義了一個中間變量裝着頭指針然后去遍歷鏈表,讓頭指針永遠指向鏈表的頭。

        else 
        {
            struct NODE* PTEMP2 = *PHEAD1;
            while (PTEMP2->PNext != NULL)
            {
                PTEMP2 = PTEMP2->PNext;
            }
            PTEMP2->PNext = PTEMP;
        }

 

可以看到有尾指針的代碼和沒有尾指針的代碼里面,有尾指針的鏈表里面我每次添加完數據都讓尾指針指向最后一個結點,然后通過尾指針來添加數據。而沒有尾指針的鏈表里面每次添加數據都要通過循環來遍歷鏈表找到最后一個結點然后指向所添加的結點。如果一個鏈表里面有幾萬個結點,每次都通過循環遍歷鏈表來添加數據,那么速度就相對於有尾指針的鏈表慢很多。總而言之,還是看個人愛好吧。不管黑貓還是白貓能抓到耗子都是好貓。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM