線性表,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 單向鏈表的優缺點
單向鏈表比數組更靈活,容易在鏈表中加入和刪除結點,但因爲鏈表只有一個入口,所以不能像數組那樣隨機訪問,離表頭近的結點很快能訪問到,但是離表頭遠的結點訪問相對費時。另外,單向鏈表也不能回溯,很難逆向遍歷。這個問題要交給雙向鏈表來解決。
下一篇將介紹“線性表之雙向鏈表”。