數據結構(一)——線性表之單向鏈表

線性表,Linear List,是最基本、最簡單、最常用的一種數據結構。一個線性表是n個具有相同特性的數據元素的有限序列,例如(a1,…,ai-1,ai,ai+1,…,an)表示一個順序表,ai-1領先於ai,ai領先於ai+1,稱ai-1是ai的直接前驅元素,ai+1是ai的直接後繼元素。特別的,第一個元素a1僅有一個直接後繼,無直接前驅;最後一個元素an僅有一個直接前驅,無直接後驅。

線性表有順序存儲結構和鏈式存儲結構。

1.鏈式存儲結構

1.1 概念及特性
鏈式存儲結構的線性表,簡稱鏈表,利用一組連續或非連續存儲單元存儲線性表的元素。

鏈表的結構特點:鏈表存儲不僅存儲元素本身,還要存儲一個指向其直接後繼元素的地址。這種存儲結構被稱爲結點(node)。存儲元素的叫數據域,存儲地址的叫指針域,此即爲鏈(link)。結點的數據域,是給用戶進行數據運算用的;結點的指針域,是爲了尋找下一個結點。

鏈表數據結構定義的C代碼形式:

typedef struct link_list {
    data_type data;
    struct link_list *p_next;
}list_node;

鏈表示意圖:

特別地,第一個結點稱爲鏈表頭結點,由於沒有直接前驅結點,因此需要定義一個指向它的頭指針p_head。

list_node *p_head;

若該鏈表爲空,則頭指針爲空。最後一個結點(尾結點)沒有直接後繼元素,所以將其指針域設置爲“NULL”空。

1.2 單向鏈表的操作接口
①鏈表初始化
鏈表初始化,就是鏈表的頭結點指針域指向NULL。

/* 功能:鏈表初始化,指向鏈表頭的指針賦予空指針。
* 參數:pp_head——指向表頭的指針的指針。
* 返回:0——運行完成
*/
int init_list(list_node **pp_head)
{
    if((*pp_head=(list_node)malloc(sizeof(list_node)))==NULL)
    {
        exit(-1);   // 檢查頭結點分配內存空間,若無則退出
    }
    (*pp_head)->next = NULL; //將單鏈表的頭結點指針域指向空
    return 0;
}

這裏要注意,初始化函數的形參爲指針的指針,這樣在調用函數時,可以直接修改傳入函數的雙重指針對應的指針變量。即是說,要在函數調用中修改某變量,就要向函數傳入變量的指針;如果直接傳入變量,是沒有作用的(聯繫函數調用的過程來理解)。

②新增結點
在鏈表中增加新結點,分兩個步驟進行:a.修改新結點的p_next,使其指向插入位置結點指向的下一個結點;b.修改被插入節點的p_next,使其指向新結點。

/* 功能:在鏈表任意位置添加新的結點
*  參數:①p_pos——添加結點的位置指針;②p_node——新增結點的指針。
*  返回:0——運行成功。
*/
int list_add_node(list_node *p_pos,list_node *p_node)
{
    p_node->p_next = p_pos->p_next;   //指針域重新賦值
    p_pos->p_next = p_node;
    return 0;
}

③結點定位
結點定位,即是找到某結點的前驅結點。單向鏈表的結點中沒有指向上一個結點的指針,所有需要從頭結點開始遍歷鏈表,當某一結點的p_next指向當前定位結點時,此即爲當前結點的上一個結點。

/* 功能:尋找指定結點的上一個結點
* 參數:①p_head——指向鏈表頭結點的指針;②p_pos——指向待定位結點的指針。
* 返回:指向待定位結點的上一個結點的指針;若待定位結點不在鏈表中,則返回NULL。
*/
list_node *list_prev_get(list_node *p_head,list_node *p_pos)
{
    list_node *p_temp = p_head;      // 定義一箇中間結點指針
    while((p_temp != NULL)&&(p_temp->p_next != p_pos))  
    {
        p_temp = p_temp->p_next;             // 遍歷鏈表
    }                                        // 當輪到結點的p_next指向p_pos時退出遍歷,
                                             // 或者整個鏈表查詢完畢
    return p_temp;
}

④刪除結點
刪除結點,也分兩步進行:a.修改待刪結點的上一個結點的p_next,使其指向待刪結點的下一個結點;b.將待刪結點的p_next指向NULL。

/* 功能:刪除單鏈表中的某結點
* 參數:①p_head——指向表頭的指針;②p_node——指向待刪除結點的指針。
* 返回:0——運行成功;-1——沒有刪除結點
*/
int list_del_node(list_node *p_head,list_node *p_node)
{
    list_node *p_prev = list_prev_ get(p_head,p_node);  //尋找待刪除結點的上一個結點,參考③
    if(p_prev)                       // 判斷是否爲空指針
    {
        p_prev->p_next = p_node->p_next;     // 指針域轉移
        p_node->p_next = NULL;               // 被刪結點指針域賦空
        return 0;
    }
    else
        return -1;
}

1.3 單向鏈表的優缺點
單向鏈表比數組更靈活,容易在鏈表中加入和刪除結點,但因爲鏈表只有一個入口,所以不能像數組那樣隨機訪問,離表頭近的結點很快能訪問到,但是離表頭遠的結點訪問相對費時。另外,單向鏈表也不能回溯,很難逆向遍歷。這個問題要交給雙向鏈表來解決。

下一篇將介紹“線性表之雙向鏈表”。

 

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