算法和數據結構總結---單鏈表
鏈表可以說是一種最基本的數據結構,鏈表通常以一種特定的組合將元素鏈接在一起,以便可以對元素實現方便的管理維護。這一點和我們常常使用的數組很相似,但是鏈表在最多的情況下可以帶來比鏈表更為優勢的操作,鏈表通常是在系統需要的時候動態開辟的,換句話說鏈表的存儲空間是在程序運行的時候在進行分配的,這就說明鏈表的長度是可變的。在許多的時候,我們無法明確的確定數據的大小直接在編譯前分配內存,這種動態分配的方法也是鏈表的優勢之一。
單鏈表的定義
單鏈表(通常也被成為鏈表) 鏈表元素由彼此的內部的一個指針相鏈接。每個元素包括兩個成員:數據成員和一個稱為next
的指針成員,每個元素通過next
指針指向下一個鏈表元素,實現鏈表的鏈接。鏈表的開始處稱之為鏈表的 “頭”,鏈表的最后結束部分稱為鏈表的 “尾”。單鏈表只允許一個一個方向遍歷鏈表,盡管有的時候我們保存鏈表的指針信息。下圖為一個標准的單鏈表結構。
單鏈表接口的公共接口
在實現一個鏈表,我們先定義一個鏈表需要哪些操作進行實現。
-
void list_init(List* list, void (*destroy)(void* data));
- 返回值:無
- 描述:這個是單鏈表初始化函數,必須在鏈表進行其他操作其使用,List* list是單鏈表的頭尾信息結構體。當調用
list_destroy
時,destroy
參數提供一種釋放動態內存的方法。當鏈表中的元素有動態分配的內存,在摧毀鏈表時必須free
掉分配的動態內存。destroy
作為一個用戶自己可以設置的析構函數,提高了鏈表的穩定性和靈活性,如果鏈表當中不存在應該釋放的動態內存,destroy
的值應該為NULL
- 時間復雜度:O(1) 在鏈表初始化時,時間是固定的。
-
void list_destroy(List* list)
- 返回值:無
- 描述:銷毀鏈表
list
,在銷毀后不可以對list
進行任何的數組操作,除非重新在對數組進行初始化操作,如果傳給list_init
函數的形參destroy
不是NULL
的話,則每次移除鏈表元素都要執行該函數一次。 - 時間復雜度:O(n) n代表鏈表中元素的個數。
-
int list_ins_next(List* list, ListElement* element, const void* data);
- 返回值:如果成功插入鏈表返回0,出現錯誤返回-1
- 描述:將元素插入
list
指向的單向鏈表的element
元素之后,如果element
為NULL
,則新元素插入鏈表的頭部。新元素包含一個指向data
的指針。 - 時間復雜度:O(1)
-
int list_rem_next(List* list, ListElement* element, void** data);
- 返回值:如果返回值成功則返回0,出現錯誤則返回-1
- 描述:函數的功能是移除,如果
element
等於NULL
則移除頭元素。調回返回后data
指向已移除的那個鏈表元素的數據,由用戶可以靈活的使用data
的存儲空間。 - 復雜度:O(1) 移除鏈表元素的時間是固定的
-
宏定義列表
#define list_size(list) ((list)->size)
#define list_tail(list) ((list)->tail)
#define list_is_head(list,element) ((element) == (list)->head ? 1 : 0)
#define list_is_tail(element) ((element)->next == NULL ? 1 : 0)
#define list_data(element) ((element)->data)
#define list_next(element) ((element)->next)
- 復雜度:O(1) 宏定義的時間都是固定的,利用宏定義來命名為了提高代碼的可讀性
單鏈表的頭文件list.h
//list.h
#pragma
#ifndef LIST_H
#define LIST_H
#include <stdlib.h>
//Define a structure for list element
typedef struct ListElement_
{
void* data;
struct ListElement_* next;
}ListElement;
//Define a structure for linked element
typedef struct List_
{
int size;
int (*match)(const void* key1, const void* key2);
void (*destroy)(void* data);
ListElement* head;
ListElement* tail;
} List;
// Public Interface
void list_init(List* list, void (*destroy)(void* data));
void list_destroy(List* list);
int list_ins_next(List* list, ListElement* element, const void* data);
int list_rem_next(List* list, ListElement* element, void** data);
#define list_size(list) ((list)->size)
#define list_tail(list) ((list)->tail)
#define list_is_head(list,element) ((element) == (list)->head ? 1 : 0)
#define list_is_tail(element) ((element)->next == NULL ? 1 : 0)
#define list_data(element) ((element)->data)
#define list_next(element) ((element)->next)
#endif
單鏈表函數的實現原理
鏈表初始化
void list_init(List* list, void (*destroy)(void* data));
鏈表初始化的操作很簡單,只要創造一個空鏈表就可以,將存儲鏈表頭和尾信息的結果體置
為空指針,鏈表的長度size
為0。鏈表的析構函數置為我們指定的析構函數。
void list_init(List* list, void (*destroy)(void* data))
{
list->size = 0;
list->destroy = destroy;
list->head = NULL;
list->tail = NULL;
return;
}
鏈表摧毀
void list_destroy(List* list)
void list_destroy(List* list)
函數用於銷毀鏈表,其作用是要移除鏈表中所有的元素,如果鏈表初始化時list
中的destroy
參數不為0,則表示在移走每個鏈表元素時還必須對鏈表元素的數據進行處理,所以思路為當鏈表的長度不為0和析構函數的指針不是空指針時,不停的移走頭鏈表,並返回移除鏈表的數據指針通過析構函數對數據釋放,最后在list
的區域清理干凈。
void list_destroy(List* list)
{
void* data;
// 移走每個鏈表元素
while (list_size(list) > 0)
{
if (list_rem_next(list,NULL,(void**)&data) == 0 && list->destroy != NULL)
{
list->destroy(data);
}
}
memset(list, 0, sizeof(list));
return;
}
鏈表后插
int list_ins_next(List* list, ListElement* element, const void* data)
鏈表后插的操作很簡單,插入就兩種大情況要分類討論,一種是當int list_ins_next(List* list, ListElement* element, const void* data)
中的element元素為NULL是表示插入頭接單,另一種就是在一個鏈表元素后插。
- 當
element
為 0 插入頭鏈表。當整個鏈表不存在鏈表元素時,這個時候鏈表頭即是鏈表為鏈表尾,
這個時候要更新list
的tail
信息。
當整個鏈表存在鏈表元素時候,這個時候新的鏈表就取代原來的鏈表頭,list
的鏈表頭信息就更新。 - 當
element
不為 0 就要注意你插入的元素是不是鏈表尾,如果是鏈表尾,則要更新list
的鏈表尾指針。否則就讓新鏈表的的next指針指向element
的netx
指針指向的鏈表,element
的next
指針指向新的鏈表元素。
這整個過程如圖3所示:
最后整個list
的長度要加1;
int list_ins_next(List* list, ListElement* element, const void* data)
{
ListElement* new_element;
if ((new_element = (ListElement*)malloc(sizeof(ListElement)))== NULL)
{
return -1;
}
new_element->data = (void*)data;
if (element == NULL)
{
if (list_size(list) == 0)
{
list->tail = new_element;
}
new_element->next = list->head;
list->head = new_element;
}
else
{
if (element->next == NULL)
{
list->tail = new_element;
}
new_element->next = element->next;
element->next = new_element;
}
list->size++;
return 0;
}
鏈表后刪
int list_rem_next(List* list, ListElement* element, void** data)
這個函數的功能就是要移除int list_rem_next(List* list, ListElement* element, void** data)
中element
元素后的一個元素鏈表,並不是元素釋放,這一點要清楚。和插入鏈表的操作相同這個也要分類兩種情況進行分析:移除頭結點和其他結點。
- 當
element
為 0 移除頭鏈表。先把頭結點的數據指針位置保持,並用old_element
保持要移除的鏈表的空間內存地址,這個時候就用原本頭結點的下一鏈表取代原本的頭結點。如果這個時候鏈表的長度為1。 - 如果
element
的下一個元素就是空指針,那不能刪,返回錯誤,否則把element
的下一個鏈表的數據空間和鏈表位置保存。利用element->next = element->next->next;
更新鏈表結構。如果刪掉是最后一個鏈表結點,要注意更新鏈表信息的尾信息。
最后整個list
的長度要減1;
int list_rem_next(List* list, ListElement* element, void** data)
{
ListElement* old_element;
if (list_size(list) == 0)
{
return -1;
}
if (element == NULL)
{
*data = list->head->data;
old_element = list->head;
list->head = list->head->next;
if (list->size == 1)
{
list->tail = NULL;
}
}
else
{
if (element->next == NULL)
{
return -1;
}
*data = element->next->data;
old_element = element->next;
element->next = element->next->next;
if (element->next == NULL)
{
list->tail = element;
}
}
free(old_element);
list->size--;
return 0;
}
完整代碼
//list.h
#pragma
#ifndef LIST_H
#define LIST_H
#include <stdlib.h>
//Define a structure for list element
typedef struct ListElement_
{
void* data;
struct ListElement_* next;
}ListElement;
//Define a structure for linked element
typedef struct List_
{
int size;
int (*match)(const void* key1, const void* key2);
void (*destroy)(void* data);
ListElement* head;
ListElement* tail;
} List;
// Public Interface
void list_init(List* list, void (*destroy)(void* data));
void list_destroy(List* list);
int list_ins_next(List* list, ListElement* element, const void* data);
int list_rem_next(List* list, ListElement* element, void** data);
#define list_head(list) ((list)->head)
#define list_size(list) ((list)->size)
#define list_tail(list) ((list)->tail)
#define list_is_head(list,element) ((element) == (list)->head ? 1 : 0)
#define list_is_tail(element) ((element)->next == NULL ? 1 : 0)
#define list_data(element) ((element)->data)
#define list_next(element) ((element)->next)
#endif
// list.c
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
#include <string.h>
/*
void list_init(List* list, void (*destroy)(void* data))
list_init 鏈表初始化
鏈表初始化只需要把鏈表的size成員設置為0,把函數指針成員設置為析構函數函數
*/
void list_init(List* list, void (*destroy)(void* data))
{
list->size = 0;
list->destroy = destroy;
list->head = NULL;
list->tail = NULL;
return;
}
/*
void list_destroy(List* list);
鏈表摧毀函數,功能就是摧毀鏈表中的全部元素,如果調用list_init時
destroy的參數不為NULL,則當每個元素被移除的時候都將調用list_destroy
一次
*/
void list_destroy(List* list)
{
void* data;
// Remove each element
while (list_size(list) > 0)
{
if (list_rem_next(list,NULL,(void**)&data) == 0 && list->destroy != NULL)
{
list->destroy(data);
}
}
memset(list, 0, sizeof(list));
return;
}
/*
int list_ins_next(List* list, ListElement* element, const void* data);
*/
int list_ins_next(List* list, ListElement* element, const void* data)
{
ListElement* new_element;
if ((new_element = (ListElement*)malloc(sizeof(ListElement)))== NULL)
{
return -1;
}
new_element->data = (void*)data;
if (element == NULL)
{
if (list_size(list) == 0)
{
list->tail = new_element;
}
new_element->next = list->head;
list->head = new_element;
}
else
{
if (element->next == NULL)
{
list->tail = new_element;
}
new_element->next = element->next;
element->next = element;
}
list->size++;
return 0;
}
/*
int list_rem_next(List* list, ListElement* element, void* data)
*/
int list_rem_next(List* list, ListElement* element, void** data)
{
ListElement* old_element;
if (list_size(list) == 0)
{
return -1;
}
if (element == NULL)
{
*data = list->head->data;
old_element = list->head;
list->head = list->head->next;
if (list->size == 1)
{
list->tail = NULL;
}
}
else
{
if (element->next == NULL)
{
return -1;
}
*data = element->next->data;
old_element = element->next;
element->next = element->next->next;
if (element->next == NULL)
{
list->tail = element;
}
}
free(old_element);
list->size--;
return 0;
}