通用單鏈表
我們在學習鏈表的時候,大部分老師和大部分書籍都只會教我們常規的鏈表,此處常規是指鏈表的數據部分只適用單一的數據類型,對於怎麼擴展到適用所有的數據類型,大多都是讓學生自己去摸索,甚至根本就沒有提及,可能中國的老師和作者都覺得每個學習數據結構的人都跟linux的創始人一樣聰明吧!
我在學習和寫代碼的過程中,覺得鏈表兩個比較重要點大多數老師和書籍都語焉不詳。
1. 鏈表節點的結構
2. 如何動態的添加節點
下面我會盡自己所能用最通俗的語言和最簡單的代碼把這兩個問題解釋清楚,如有寫的不好的地方,歡迎隨時指出,謝謝!
就我所知道的,一個通用鏈表,可以用兩種結構來實現
# 第一種:節點的數據部分用一個void類型的指針來表示
typedef struct _node {
struct _node *next; /* 指針域 */
void *data; /* 數據域 */
} node_t;
/* 鏈表的頭結點 */
typedef struct _head {
int length; /* 鏈表長度 */
node_t head;
}
# 第二種:節點只包含一個指針域
typedef struct _node {
struct _node *next; /* 指針域 */
} node_t;
節點結構定義好了,最關鍵是如何動態添加節點,對於第一種結構:
先定義一個你需要的數據元素的數據類型,爲了例子能具有通用性,這裏我定義一個結構體,結構體有兩個成員變量,一個int型,一個字符數組型。
typedef struct _student {
int id;
char name[64];
} student_t;
int main(void)
{
student_t stu;
stu.id = 1;
strcpy(stu.name, "James");
/* 一. 創建一個單鏈表 此處只是創建了一個鏈表的頭結點並初始化 */
head_t *list = list_create();
/*
二. 插入節點至該鏈表
假設我現在要把main函數裏面的局部變量stu動態插入到鏈表節點中
分析:
要把一個局部變量動態插入到一個鏈表中,需要在堆內存中爲該節點動態申請一塊內存
注意是動態插入,網上好多文章都是先在main函數的棧空間中定義一個變量,然後把這塊棧空間插入鏈表中,想象一下,假如你需要添加100個節點,你需要在main函數
中定義100個這樣的變量,是不是要罵娘了?所以這裏需要在堆中創建內存,只需要在main函數中定義一個臨時變量即可
再思考一個問題,在插入的時候都需要哪些參數?
1:插入到哪個鏈表 2:插入到什麼位置 3:插入的數據
現在你能在網上找到的鏈表插入的函數,百分之八九十都是這三個參數,但是如果要使用malloc在堆中分配內存,需要分配多大的內存呢?我們在寫
鏈表的插入函數時並不知道,所以需要從調用者那裏傳遞過來,也就是我們的第四個參數,插入數據的大小, 如下:
函數原型:list_insert(head_t *list, int pos, void *data, int datasize);
*/
list_insert(list, 0, (void *)&stu, sizeof(student_t));
return 0;
}
接下來也就是最重要,插入函數該如何實現:
/* 創建鏈表 */
head_t *list_create()
{
head_t *list = (head_t *)malloc(sizeof(head_t));
if ( list == NULL ) {
printf("Error: list create failed!\n");
return NULL;
}
list->length = 0;
list->head.next = NULL; /* 初始化時只有一個頭結點,所以這裏爲NULL */
}
/* 動態插入節點 */
void list_insert(head_t *list, int pos, void *data, int datasize)
{
/* 判斷參數合法性 */
if ( list == NULL || data == NULL ) {
printf("invalid list or data point to NULL!\n");
return;
}
if ( pos < 0 || pos > list->length ) {
printf("position error!\n");
return;
}
int i;
node_t *p_cur = &list->head; /* 定義一個輔助指針 */
/* 1. 給新節點分配一塊內存空間 內存佈局如下圖 */
node_t *tmp = (node_t *)malloc(sizeof(node_t)+datasize);
tmp->data = (void *)(tmp + 1);
/* 2. 將參數data傳遞進來的數據複製給新創建節點的void *data成員, 大小爲datasize */
memcpy(tmp->data, data, datasize);
/* 3. 插入操作 */
for ( i=0; i<pos; i++ ) {
p_cur = p_cur->next;
}
tmp->next = p_cur->next;
p_cur->next = tmp;
list->length++;
}
至於第二種結構的實現其實差不多,有興趣的可以自己試試。自己實現和看別人的思路再去實現其實區別很大,因爲中間會有一個思考的過程,這個過程的作用可能短時間體現不出來,但只要你長期堅持去思考,最後肯定會收穫不一樣的東西的。
此鏈表完整的函數實現和測試程序請移步github: