帶頭節點的單鏈表的插入操作優化


1.偶然看到了十字鏈表的一些東西,想到之前在《數據結構與算法分析》的鏈表一章中,需要用多重表實現一個簡單的查詢功能。功能需求如下:

 “已知 學生 和 學校課程 總數 分別為 40000 和 2500,現在需要得到兩份報告,一份顯示每門課成注冊的所有學生信息,

 一份顯示每個學生注冊了哪些課程。”

 顯然可以用一個 40000 * 2500 個元素的二維數組來解決,但是每個學生選課數目很少,因此會浪費很多空間。因此選擇十字鏈表來實現。

 既然是鏈表,那么肯定要有插入操作,於是便有了本文。算是對功能實現前的鋪墊。

2.本文會介紹兩種帶頭節點的插入操作,說是兩種,其實后一種只是前一種的優化,覺得不錯,分享給大家。

 根據代碼分析:

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

typedef int Elem;
    
struct LNode
{    
    Elem elem;
    struct LNode *next; 
};    

static void*
MALLOC(int num, size_t size)
{
    void *new = calloc(num, size);
    if (new == NULL)
    {
        fprintf(stderr, "malloc failed: [%d]\n", (int)size);
        exit(1);
    }

    return new;
}    

void
print_link(struct LNode *head)
{
    struct LNode *cur = head;

    while(cur)
    {
        printf("%d -> ", cur->elem);
        cur = cur->next;
    }

    printf("end\n");
}

void insert_link_m1 (struct LNode **head, Elem e) //普通插入
{
    struct LNode *cur, *prev, *new;
    cur = *head;
    prev = NULL;

    while(cur != NULL && cur->elem < e)
    {
        prev = cur;
        cur = cur->next;
    }

    new = (struct LNode *)MALLOC(1, sizeof(*new));
    new->elem = e;

    //insert
    if (prev == NULL)
    {
        *head = new;
    }
    else
    {
        prev->next = new;
    }

    new->next = cur;
}

void insert_link_m2 (struct LNode **head, Elem e)  //優化后插入
{
    struct LNode *cur, *new;

    while((cur = *head) != NULL && cur->elem < e)
    {
        head = &cur->next;
    }

    new = (struct LNode *)MALLOC(1, sizeof(*new));
    new->elem = e;

    new->next = cur;
    *head = new;
}

void
delete_link(struct LNode *head)
{
    struct LNode *cur, *prev;
    cur = head;
    prev = NULL;

    while(cur != NULL)
    {
        prev = cur;
        cur = cur->next;

        free(prev);
        memset(prev, 0, sizeof(*prev));
    }
}    

void
create_link(struct LNode **head)
{    
    struct LNode *cur, *new;
    Elem e;

    cur = *head;

    while(scanf("%d", &e) == 1)
    {
        new = (struct LNode *)MALLOC(1, sizeof(*new));
        new->elem = e;

        if(cur == NULL)
        {
            *head = new;
            cur = new;
        }
        else
        {
            cur->next = new;
            cur = new;
        }
    }
}    

void
create_ordered_link(struct LNode **head, void (*insert_link) (struct LNode **head, Elem e))
{
    Elem e;

   while(scanf("%d", &e) == 1) { insert_link(head, e); } } int main(int argc, char *argv[]) { struct LNode *head = NULL; printf("enter link nums:\n"); create_link(&head); print_link(head); delete_link(head); printf("\n-----------------------------\n"); printf("enter link nums:\n"); head = NULL; create_ordered_link(&head, insert_link_m1); print_link(head); delete_link(head); printf("\n-----------------------------\n"); printf("enter link nums:\n"); head = NULL; create_ordered_link(&head, insert_link_m2); print_link(head); delete_link(head); return 0; }

 輸入用例格式: 數字+空格+數字+空格...輸入完以后按下回車到下一行,ctrl+d結束輸入。

 1)先來區分一下頭節點和首節點:

  首節點:鏈表中第一個存放 Elem元素 的節點。

  頭節點:指向這個首節點的一個指針,給節點不存放Elem元素

 2)帶頭節點的普通插入:

  看 void insert_link_m1 (struct LNode **head, Elem e ) 參數 **head,為指向頭節點的指針,

  因為剛開始head指向NULL(main里邊的初始化),我們要用這個head為頭節點創建單鏈表,自然要改變head指向的值,

  因此要傳進來 &head .接下來:

  struct LNode *cur, *prev, *new;

  cur = *head;

  prev = NULL;

  *head是head指針里的地址值,而head里的地址值就是首節點的位置,因此,cur = *head便是讓cur指向首節點,

  再將 prev置為NULL,這個操作很關鍵,因為prev是否為NULL,用來當作要插入的位置是否位於首節點之前,即待插入的

  節點是否是新的首節點。

  好了接下來就是遍歷鏈表找到新節點的插入位置:

  while(cur != NULL && cur->elem < e)

  { prev = cur; cur = cur->next; }

  new = (struct LNode *)MALLOC(1, sizeof(*new));

  new->elem = e;

  接下來是插入操作:

  if (prev == NULL)

  { *head = new; }

   else

  { prev->next = new; }

  new->next = cur;

  prev指向當前節點的前一個節點,如果插入的不是鏈表的首節點的位置,自然便有 prev->next = new; new->next = cur;

  但是若鏈表為空,或者待插入的位置為首節點的位置,那么此時 prev = NULL。不能按上邊那樣操作,需要單獨處理。

   因此用prev是否等於NULL,便成了判斷的標識。 *head = new; new->next = cur;

 3)帶頭節點的優化插入:

  優化插入其實是思路上的優化,優化針對的地方便是是否能在真正執行插入的地方,不用區分是否插入的地方是首節點的位置。

  即直接執行插入操作 new->next = cur; *head = new; 這里*head是存放new的地址的地方,也即指向new的箭頭尾部的地方。

  

  我們可以看到,要想成功執行一次鏈表的插入操作 只需要 一個指向新節點new的指針x(*head或->next) 和一個指向當前節點的cur指針(->next)。 

  在普通的插入操作中 x可以是頭節點指針(當新節點需要插入到頭節點后邊時),x也可以是 prev->next 當新節點不需要插入到頭節點后邊時。

  於是我們可以用一個LNode ** 類型的指針N,它既可以存 放頭節點的地址,又可以存放 其它節點中->next成員的地址。即可以存放 &x

  當需要執行真正的插入操作的時候可以有 *N = new; new = cur;

   因為N中存放着頭節點的地址,或->next成員的地址,當對其進行解引用*N時便是 相當於 *head = new 或 prev-next = new;

   根據這個思路便有了優化插入的代碼:

void
insert_link_m2 (struct LNode **head, Elem e)  //優化后插入
{
    struct LNode *cur, *new;

    while((cur = *head) != NULL && cur->elem < e)
    {
        head = &cur->next;
    }

    new = (struct LNode *)MALLOC(1, sizeof(*new));
    new->elem = e;

    new->next = cur;
    *head = new;
}

   (1)while第一次操作時cur是指向首節點,頭節點指向cur,head中存放頭節點的地址。倘若條件不滿足直接跳出循環,便有new->next = cur; *head = new;

   head存放了頭節點的地址, *head作為左值的時候,*head = new;便使得頭節點指向了new。

   (2)若條件不滿足時,及插入位置不是頭節點后邊,便執行 head = &cur->next;然后 cur = *head; 此時 head存放cur->next的地址,而cur = *head,便將cur->next的值,即下一個節點的地址賦值給cur,

    倘若此時跳出循環, head中存放了此時cur的前一個節點中(next)成員的地址,cur指向當前位置,於是執行 new->next = cur; *head = new后,便自動完成了鏈接.

   這樣一個技巧便省去了判斷prev == NULL的步驟。

  

    參考文獻:《c與指針》第十二章。

    思路就是這樣,難免表述不清,結合代碼,畫畫圖便知,歡迎指正與指點。

  

  

  

  

 


免責聲明!

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



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