鏈表能夠說是一種最爲基礎的數據結構了,而單向鏈表更是基礎中的基礎。鏈表是由一組元素以特定的順序組合或連接在一塊兒的,不一樣元素之間在邏輯上相鄰,可是在物理上並不必定相鄰。在維護一組數據集合時,就可使用鏈表,這一點和數組很類似。可是,鏈表有着數組所不具備的優點。一方面,鏈表在執行插入刪除操做時擁有更高的效率;另外一方面,鏈表是在堆區動態的開闢存儲空間,而大多數的數據在編譯時大小並不能肯定,所以這種動態開闢空間的特性也能夠說是鏈表的一個優勢。node
就像圖像所示,單向鏈表各個元素之間經過一個指針前後連接起來。每一個元素包含兩個部分,分別是數據域和指針域。前一個元素經過next指針指向後一個元素,鏈表的開始的元素爲鏈表頭,即head指針所指,鏈表結束的元素爲鏈表尾,尾部元素的next指針指向NULL。可見,單向鏈表爲線性結構。數組
若想訪問鏈表中的一個元素,咱們只能從鏈表的頭部開始,順着指針指向逐查找。若是從鏈表頭移動到指定的元素,而這時候咱們又想訪問當前元素以前的某個元素,這時候只能從頭在次遍歷鏈表。這相比數組可以經過下標直接訪問要麻煩的多,不過咱們應該根據不一樣的應用場景選擇數組仍是鏈表,它們只有在對的地方纔能發揮出巨大的威力。數據結構
typedef struct node//鏈表元素的結構 { void *data;//節點中的數據域,設置爲無類型指針,數據類型大小由使用者定義 struct node *next;//指向下一節點的指針 }Node; typedef struct list//鏈表的結構 { int size;//鏈表中節點個數 void (*destroy)(void *data);//因爲鏈表節點中的數據是用戶自定義的,故須要調用者提供釋放空間的函數 void (*print_data)(const void *data);//同,由用戶自定義打印數據的函數 int (*match)(const void *key1, const void *key2);//同,由用戶自定義數據的比較方式 Node *head;//記錄鏈表的頭部位置 Node *tail;//記錄鏈表的尾部位置 }List;
示意圖以下:
函數
下面是鏈表操做函數的接口,以及簡單介紹:優化
extern void list_init(List *list, void (*destroy)(void *data), void (*print_data)(const void *data), \ int (*match)(const void *key1, const void *key2));//初始化一個鏈表 extern int list_ins_head(List *list, const void *data);//鏈表的插入,將節點從頭部插入 extern int list_ins_tail(List *list, const void *data);//鏈表的插入,將節點從尾部插入 extern int list_ins_sort(List *list, const void *data);//鏈表的插入,插入後鏈表是一個有序的鏈表 extern void* list_search(List *list, const void *data);//在鏈表中查找指定數據,若找到返回數據的地址 extern void* list_remove(List *list, const void *data);//在鏈表中刪除指定數據,若找到刪除節點並將數據地址返回 extern void list_reverse(List *list);//將鏈表逆置 extern void list_sort(List *list);//將鏈表按照必定方式排序 extern void print_list(List *list);//打印鏈表 extern void list_destroy(List *list);//刪除整個鏈表 #define list_size(list) (list->size) //返回鏈表節點個數
使用list_init函數初始化一個鏈表,以便鏈表的其餘操做。3d
void list_init(List *list, void (*destroy)(void *data), void (*print_data)(const void *data), \ int (*match)(const void *key1, const void *key2)) { list->size = 0;//初始時,鏈表沒有節點,設置爲0 list->head = NULL;//頭和尾置空 list->tail = NULL; list->match = match;//初始化鏈表的成員函數 list->destroy = destroy; list->print_data = print_data; return; }
使用list_ins_head函數,在鏈表的頭部插入數據。示意圖以下:指針
從示意圖能夠看出,單向鏈表的部插入邏輯很是簡單。仔細觀察,標綠的部分代碼有重複,能夠優化code
/*在鏈表的頭部插入數據*/ int list_ins_head(List *list, const void *data) { Node *new_node = (Node *)calloc(1, sizeof (Node)); //建立插入的節點 if(new_node == NULL) return -1; new_node->data = (void *)data;//關聯節點與數據 /* if(list_size(list) == 0)//鏈表爲空時,插入節點 { list->tail = new_node; new_node->next = NULL; list->head = new_node; } else //鏈表非空時將節點插入頭部 { new_node->next = list->head; list->head = new_node; } */ if(list_size(list) == 0)//鏈表爲空時,插入節點 list->tail = new_node; new_node->next = list->head; list->head = new_node; list->size ++;//維護鏈表size屬性 return 0; }
使用list_ins_tail函數,在鏈表的尾部插入數據,示意圖以下:blog
/*在鏈表的尾部插入數據*/ int list_ins_tail(List *list, const void *data) { Node *new_node = (Node *)calloc(1, sizeof (Node)); //建立插入的節點 if(new_node == NULL) return -1; new_node->data = (void *)data;//關聯節點與數據 if(list_size(list) == 0) list->head = new_node; else list->tail->next = new_node; list->tail = new_node; new_node->next = NULL; list->size ++; return 0; }
使用list_ins_sort函數,進行鏈表的有序插入。排序
鏈表的有序插入大體能夠分爲兩種狀況:
其一,鏈表爲空時直接插入;
其二,鏈表非空時,在此時又分爲三種小狀況;
鏈表爲空時,操做方法和頭尾部插入相似。鏈表非空時,咱們須要先尋找到插入位置,而後在將數據插入鏈表。
在此以前咱們已經瞭解瞭如何在鏈表的頭部和尾部插入元素,那麼,如今惟一須要處理的即是 在鏈表中部插入節點 ,這是鏈表插入操做的核心。
注意:在鏈表中部插入節點時,必須獲得前一節點的位置,即圖中指向藍色節點的指針p_pre.
插入節點的邏輯瞭解後,處理在非空鏈表狀況下插入節點就清晰多了。
/*在鏈表的有序插入數據*/ int list_ins_sort(List *list, const void *data) { Node *new_node = (Node *)calloc(1, sizeof (Node)); //建立插入的節點 if(new_node == NULL) return -1; new_node->data = (void *)data;//關聯節點與數據 if(list_size(list) == 0)//鏈表爲空時,插入節點 { list->tail = new_node; new_node->next = NULL; list->head = new_node; } else//鏈表非空 { Node *p_cur = list->head; Node *p_pre = list->head; while(p_cur != NULL && list->match(new_node->data, p_cur->data) > 0)//查找鏈表的插入位置 { p_pre = p_cur; p_cur = p_cur->next; } if(p_cur != NULL)//插入位置在頭部和中間時 { if(p_cur == list->head)//插入位置在頭部 { new_node->next = list->head; list->head = new_node; } else//位置在鏈表中間 { new_node->next = p_pre->next; p_pre->next = new_node; } } else//插入位置在鏈表尾部 { list->tail->next = new_node; list->tail = new_node; new_node = NULL; } } list->size ++; return 0; }
使用list_search函數,查找鏈表中與數據匹配的節點,並返回節點指針。
此處查找邏輯與list_ins_sort中的查找邏輯基本相似,不作贅述。
/*查找鏈表中與數據匹配的節點,並返回節點指針*/ void* list_search(List *list, const void *data) { if(list_size(list) == 0) { printf("list is empty\n"); return NULL; } else { Node *p_cur = list->head; while(p_cur != NULL && list->match(p_cur->data, data) != 0)//查找數據在鏈表中的位置 p_cur = p_cur->next; if(p_cur != NULL)//找到返回數據地址,不然返回NULL return p_cur->data; else return NULL; } }
使用list_remove函數,刪除節點,並將節點中的數據返回,交由用戶處理。
此處查找邏輯與list_ins_sort中的查找邏輯基本相似,不作贅述。
和插入節點中分爲頭部、中部尾部相似,刪除也分爲頭中尾部。
刪除頭部節點
不過刪除頭部節點時須要注意一點,就是當鏈表僅有一個節點時,咱們須要維護一下tail指針。
刪除中部節點
刪除尾部節點
注意:代碼中將中部與尾部的刪除進行了合併。
/*刪除指定數據的節點*/ void* list_remove(List *list, const void *data) { void *old_data = NULL; Node *p_cur = list->head; Node *p_pre = list->head; while (p_cur != NULL && list->match(p_cur->data, data) !=0) { p_pre = p_cur; p_cur = p_cur->next; } if(p_cur != NULL && list->match(p_cur->data, data) ==0)//刪除位置在頭部和中間時 { if(p_cur == list->head)//刪除位置在頭部 { list->head = p_cur->next; if(p_cur->next == NULL) list->tail = NULL; } else//中部時或尾部 { p_pre->next = p_cur->next; if(p_cur->next == NULL)//判斷是否爲尾部 list->tail = p_pre; } old_data = p_cur->data; free(p_cur); list->size --; } return old_data; }
使用list_reverse函數將鏈表逆置。
逆置過程:
觀察能夠發現,逆置的過程本質上就是將原來的鏈表逐個摘下頭節點,插入逆置後的鏈表的頭部
void list_reverse(List *list) { if(list_size(list) != 0) { Node *p_pre = list->head; Node *p_cur = list->head->next; list->head->next = NULL; list->tail = list->head; while(p_cur!= NULL) { p_pre = p_cur; p_cur = p_cur->next; p_pre->next = list->head; list->head = p_pre; } } return; }
使用list_sort函數對鏈表進行排序,此處使用選擇排序法。
void list_sort(List *list) { if(list_size(list) != 0) { Node *p_i = list->head; while(p_i->next != NULL) { Node *p_min = p_i; Node *p_j = p_min->next; while (p_j != NULL) { if(list->match(p_min->data, p_j->data) > 0) p_min = p_j; p_j = p_j->next; } if(p_min != p_i) { void *data = p_i->data; p_i->data = p_min->data; p_min->data = data; } p_i = p_i->next; } } return; }
void print_list(List *list) { if(list->head == NULL)//鏈表爲空 { printf("list is empty\n"); } else //鏈表非空 { Node * p_cur = list->head; while (p_cur) { list->print_data(p_cur->data); p_cur = p_cur->next; } } return; }
void list_destroy(List *list) { Node *p_cur = list->head; while (p_cur != NULL) { list->head = list->head->next; list->destroy(p_cur->data);//釋放節點中的數據 free(p_cur);//釋放節點 p_cur = list->head; } memset(list, 0, sizeof (List)); return; }