單鏈表

(1)時刻謹記:鏈表就是用來解決數組的大小不能動態擴展的問題,所以鏈表其實就是當數組用的。直白點:鏈表能完成的任務用數組也能完成,數組能完成的任務用鏈表也能完成。但是靈活性不一樣。
(2)簡單說:鏈表就是用來存儲數據的。鏈表用來存數據相對於數組來說優點就是靈活性,需要多少個動態分配多少個,不佔用額外的內存。數組的優勢是使用簡單(簡單粗暴)。

// 構建一個鏈表的節點
struct node
{
    int data;                // 有效數據
    struct node *pNext;        // 指向下一個節點的指針
};

(1)鏈表的內存要求比較靈活,不能用棧,也不能用data數據段。只能用堆內存。
(2)使用堆內存來創建一個鏈表節點的步驟:1、申請堆內存,大小爲一個節點的大小(檢查申請結果是否正確);2、清理申請到的堆內存;3、把申請到的堆內存當作一個新節點;4、填充你哦個新節點的有效數據和指針區域。

// 定義頭指針
    struct node *pHeader = NULL;
    
    /********************************************************************/
    // 每創建一個新的節點,把這個新的節點和它前一個節點關聯起來
    // 創建一個鏈表節點
    struct node *= (struct node *)malloc(sizeof(struct node));
    if (NULL == p)
    {
        printf("malloc error.\n");
        return -1;
    }
    // 清理申請到的堆內存
    bzero(p, sizeof(struct node));   // 也可以用memset:memset(p,0,sizeof(struct node));
    // 填充節點
    p->data = 1;
    p->pNext = NULL;            // 將來要指向下一個節點的首地址
                                // 實際操作時將下一個節點malloc返回的指針賦值給這個
                                
    pHeader = p;    // 將本節點和它前面的頭指針關聯起來                    
    /********************************************************************/
    
    
    /********************************************************************/
    // 每創建一個新的節點,把這個新的節點和它前一個節點關聯起來
    // 創建一個鏈表節點
    struct node *p1 = (struct node *)malloc(sizeof(struct node));
    if (NULL == p1)
    {
        printf("malloc error.\n");
        return -1;
    }
    // 清理申請到的堆內存
    bzero(p1, sizeof(struct node));
    // 填充節點
    p1->data = 2;
    p1->pNext = NULL;            // 將來要指向下一個節點的首地址
                                // 實際操作時將下一個節點malloc返回的指針賦值給這個
                                
    p->pNext = p1;    // 將本節點和它前面的頭指針關聯起來                    
    /********************************************************************/
    /********************************************************************/
    // 每創建一個新的節點,把這個新的節點和它前一個節點關聯起來
    // 創建一個鏈表節點
    struct node *p2 = (struct node *)malloc(sizeof(struct node));
    if (NULL == p2)
    {
        printf("malloc error.\n");
        return -1;
    }
    // 清理申請到的堆內存
    bzero(p2, sizeof(struct node));
    // 填充節點
    p2->data = 3;
    p1->pNext = p2;            // 將來要指向下一個節點的首地址
                                // 實際操作時將下一個節點malloc返回的指針賦值給這個        

    // 訪問鏈表第1個節點的有效數據
    printf("node1 data: %d.\n", pHeader->data);    
    printf("p->data: %d.\n", p->data);            // pHeader->data等同於p->data
    
    // 訪問鏈表第2個節點的有效數據
    printf("node2 data: %d.\n", pHeader->pNext->data);    
    printf("p1->data: %d.\n", p1->data);    
    // pHeader->pNext->data等同於p1->data
    
    // 訪問鏈表第3個節點的有效數據
    printf("node3 data: %d.\n", pHeader->pNext->pNext->data);    
    printf("p2->data: %d.\n", p2->data);            
    // pHeader->pNext->pNext->data等同於p2->data                        

                                

將創建節點的代碼封裝成一個函數

(1)封裝時的關鍵點就是函數的接口(函數參數和返回值)的設計                                
struct node * create_node(int data)
{
    struct node *= (struct node *)malloc(sizeof(struct node));
    if (NULL == p)
    {
        printf("malloc error.\n");
        return NULL;
    }
    // 清理申請到的堆內存
    bzero(p, sizeof(struct node));
    // 填充節點
    p->data = data;
    p->pNext = NULL;    
    
    return p;
}                                                        
    pHeader = create_node(1);    
    // 將本節點和它前面的頭指針關聯起來                    
    pHeader->pNext = create_node(432);        
    // 將本節點和它前面的頭指針關聯起來                    
    pHeader->pNext->pNext = create_node(123);                
    // 將來要指向下一個節點的首地址                                
                                
從鏈表尾部插入新節點
// 思路:由頭指針向後遍歷,直到走到原來的最後一個節點。原來最後一個節點裏面的pNext是NULL,現在我們只要將它改成new就可以了。添加了之後新節點就變成了最後一個。
void insert_tail(struct node *pH, struct node *new)
{
    // 分兩步來完成插入
    // 第一步,先找到鏈表中最後一個節點
    struct node *= pH;
    while (NULL != p->pNext)
    {
        p = p->pNext;                // 往後走一個節點
    }
    
    // 第二步,將新節點插入到最後一個節點尾部
    p->pNext = new;
}

// 計算添加了新的節點後總共有多少個節點,然後把這個數寫進頭節點中。
//由上一個函數修改所得
void insert_tail(struct node *pH, struct node *new)
{
    int cnt = 0;
    // 分兩步來完成插入
    // 第一步,先找到鏈表中最後一個節點
    struct node *= pH;
    while (NULL != p->pNext)
    {
        p = p->pNext;                // 往後走一個節點
        cnt++;
    }
    // 第二步,將新節點插入到最後一個節點尾部
    p->pNext = new;
    pH->data = cnt + 1;
}        
                    
int main(void)
{
    // 定義頭指針
    //struct node *pHeader = NULL;            // 這樣直接insert_tail會段錯誤。
    struct node *pHeader = create_node(0);
    
    insert_tail(pHeader, create_node(1));
    insert_tail(pHeader, create_node(2));
    insert_tail(pHeader, create_node(3));    
    return 0;
}                            
                                
4.9.5.從鏈表頭部插入新節點        
// 思路:
void insert_head(struct node *pH, struct node *new)
{
    // 第1步: 新節點的next指向原來的第一個節點
    new->pNext = pH->pNext;
    
    // 第2步: 頭節點的next指向新節點的地址
    pH->pNext = new;
    
    // 第3步: 頭節點中的計數要加1
    pH->data += 1;
}

int main(void)
{
    // 定義頭指針
    //struct node *pHeader = NULL;            // 這樣直接insert_tail會段錯誤。
    struct node *pHeader = create_node(0);
    
    insert_head(pHeader, create_node(1));
    insert_tail(pHeader, create_node(2));
    insert_head(pHeader, create_node(3));

    printf("beader node data: %d.\n", pHeader->data);    

    // 訪問鏈表第1個節點的有效數據
    printf("node1 data: %d.\n", pHeader->pNext->data);    
    
    // 訪問鏈表第2個節點的有效數據
    printf("node2 data: %d.\n", pHeader->pNext->pNext->data);    
    
    // 訪問鏈表第3個節點的有效數據
    printf("node3 data: %d.\n", pHeader->pNext->pNext->pNext->data);    
    
    return 0;
}

4.9.6.單鏈表的算法之遍歷節點
// 遍歷單鏈表,pH爲指向單鏈表的頭指針,遍歷的節點數據打印出來
void bianli(struct node*pH)
{
    //pH->data                // 頭節點數據,不是鏈表的常規數據,不要算進去了
    //struct node *p = pH;        // 錯誤,因爲頭指針後面是頭節點
    struct node *= pH->pNext;    // p直接走到第一個節點
    printf("-----------開始遍歷-----------\n");
    while (NULL != p->pNext)        // 是不是最後一個節點
    {
        printf("node data: %d.\n", p->data);
        p = p->pNext;                // 走到下一個節點,也就是循環增量
    }
    printf("node data: %d.\n", p->data);
    printf("-------------完了-------------\n");
}
// 1、思考下爲什麼這樣能解決問題;2、思考下設計鏈表時爲什麼要設計頭節點
void bianli2(struct node*pH)
{
    //pH->data                // 頭節點數據,不是鏈表的常規數據,不要算進去了
    struct node *= pH;        // 頭指針後面是頭節點

    printf("-----------開始遍歷-----------\n");
    while (NULL != p->pNext)        // 是不是最後一個節點
    {
        p = p->pNext;                // 走到下一個節點,也就是循環增量
        printf("node data: %d.\n", p->data);
    }

    printf("-------------完了-------------\n");
}

單鏈表的算法之刪除節點
// 從鏈表pH中刪除節點,待刪除的節點的特徵是數據區等於data
// 返回值:當找到並且成功刪除了節點則返回0,當未找到節點時返回-1
int delete_node(struct node*pH, int data)
{
    // 找到這個待刪除的節點,通過遍歷鏈表來查找
    struct node *= pH;            // 用來指向當前節點
    struct node *pPrev = NULL;        // 用來指向當前節點的前一個節點

    while (NULL != p->pNext)        // 是不是最後一個節點
    {
        pPrev = p;                    // 在p走向下一個節點前先將其保存
        p = p->pNext;                // 走到下一個節點,也就是循環增量
        // 判斷這個節點是不是我們要找的那個節點
        if (p->data == data)
        {
            // 找到了節點,處理這個節點
            // 分爲2種情況,一個是找到的是普通節點,另一個是找到的是尾節點
            // 刪除節點的困難點在於:通過鏈表的遍歷依次訪問各個節點,找到這個節點
            // 後p指向了這個節點,但是要刪除這個節點關鍵要操作前一個節點,但是這
            // 時候已經沒有指針指向前一個節點了,所以沒法操作。解決方案就是增加
            // 一個指針指向當前節點的前一個節點
            if (NULL == p->pNext)
            {
                // 尾節點
                pPrev->pNext = NULL;        // 原來尾節點的前一個節點變成新尾節點
                free(p);                    // 釋放原來的尾節點的內存
            }
            else
            {
                // 普通節點
                pPrev->pNext = p->pNext;    // 要刪除的節點的前一個節點和它的後一個節點相連,這樣就把要刪除的節點給摘出來了
                free(p);
            }
            // 處理完成之後退出程序
            return 0;
        }
    }
    // 到這裏還沒找到,說明鏈表中沒有我們想要的節點
    printf("沒找到這個節點.\n");
    return -1;
}

delete_node(pHeader, 12);

4.9.8.2、單鏈表逆序算法分析
//思路:首先遍歷原鏈表,然後將原鏈表中的頭指針和頭節點作爲新鏈表的頭指針和頭節點,原鏈表中的有效節點挨個依次取出來,採用頭插入的方法插入新鏈表中即可。
// 將pH指向的鏈表逆序
void reverse_linkedlist(struct node *pH)
{
    struct node *= pH->pNext;        // pH指向頭節點,p指向第1個有效節點
    struct node *pBack;                // 保存當前節點的後一個節點地址
    
    // 當鏈表沒有有效節點或者只有一個有效節點時,逆序不用做任何操作
    if ((NULL ==p) || (NULL == p->pNext))
        return;
    
    // 當鏈表有2個及2個以上節點時才需要真正進行逆序操作
    while (NULL != p->pNext)        // 是不是最後一個節點
    {
        // 原鏈表中第一個有效節點將是逆序後新鏈表的尾節點,尾節點的pNext指向NULL
        pBack = p->pNext;            // 保存p節點後面一個節點地址
        if (p == pH->pNext)
        {
            // 原鏈表第一個有效節點
            p->pNext = NULL;
        }
        else
        {
            // 原鏈表的非第1個有效節點
            p->pNext = pH->pNext;
        }
        pH->pNext = p;
        
        //p = p->pNext;        // 這樣已經不行了,因爲p->pNext已經被改過了
        p = pBack;            // 走到下一個節點
    }
    // 循環結束後,最後一個節點仍然缺失
    insert_head(pH, p);
}











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章