1. 程式人生 > >演算法精解(一):C語言描述(連結串列)

演算法精解(一):C語言描述(連結串列)

1.連結串列認知

 一場病,斷了好久。這幾天算是基本沒什麼問題了。是時候繼續了。

連結串列我想可以認為是,點到線的過程。

一個個點就是一個個連結串列的節點,以特定的順序組合或連結後,行成了一條線,即連結串列。所以新增,刪除一個點是相對較容易的(因為可以動態的追加,刪除節點),但是查詢某個點相對較麻煩(陣列中只需要a[i]即可取得資料,連結串列則需要遍歷)。所以,對於未知大小長度的資料來說,具有相當的優勢。但已知資料量來說,陣列可能更好一些。

單鏈表

單鏈表(通常簡稱為連結串列)由各個元素之間通過一個指標彼此連結起來而組成。每個元素包含兩部分:資料成員和一個稱為nexr的指標。通過採用這種二成員結構,將每個元素的next指標設定為指向其後面的元素。最後一個元素的next指標置為NUL,簡單地表示連結串列的尾端。連結串列開始處的元素是“頭”,連結串列末尾的元素稱為“尾。

簡單來說,火車是最好理解的連結串列了。車頭就是“頭”,每節車廂裡面裝的人救贖資料,車廂後面連線下一個車廂的鐵栓就是next指標。

單鏈表程式碼實現

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct ListElmt   //定義節點
{
	void *data;
	struct ListElmt  *next;
};

typedef struct List  //定義連結串列
{
	int size;
	int(*match)(const void *key1, const void *key2); //建構函式
	void(*destroy)(void *data);  //解構函式
	ListElmt *head;
	ListElmt *tail;
};

void list_init(List *list, void(*destroy)(void *data));     //新建連結串列
void list_destroy(List *list);                             //移除連結串列中的元素
void list_size(List *list, void (*destroy)(void *data));    //連結串列長度
int list_ins_next(List *list, ListElmt *element, const void *data);  //尾插
int list_rem_next(List *list, ListElmt *element, void **data);   //移除

#define list_is_head(list,element) ((element) == (list)->head ? 1:0)
#define list_is_tail(element) ((element)->next == NULL ? 1: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 *data;

	while (list->size > 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, ListElmt *element, const void *data)
{
	ListElmt *new_element;
	
	if ((new_element = (ListElmt *)malloc(sizeof(ListElmt))) == NULL)
		return -1;

	new_element->data = (void *)data;

	if (element == NULL)
	{
		if (list->size == 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;
}

//刪除element後的一個元素
int list_rem_next(List *list, ListElmt *element, void **data)
{
	ListElmt *old_element;
	if (list->size == 0)
		return -1;
	if (element == NULL)
	{
		*data = list->head->data;
		old_element = list->head->next;
		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;
}

 雙鏈表

雙向連結串列,如同其名字所暗示的那樣,連結串列元秦之間由兩個指標連結。雙向連結串列中的每元素都由3部分組成:除了資料成員和nexr指標外,每個元素還包含一個指向共前驅元素的指標,稱為prev指標。雙向連結串列的組成是這樣的:將一些元素連結在一起使得每個元素的 next指標都指向其後繼的元素,而每個元素的rev指標都指向其前驅元素。為了標識連結串列的頭和尾,將第一個元素的prev指標和最後一個元素的nexr指標設定為NULL。要反向遍歷整個雙向連結串列,使用prev指標以從尾到頭的順序連續訪問各個元素。因此,為每個元素増加了一個指標的代價,換來的是雙向連結串列比單錐表提供了更為靈活的訪問方式。當我們知道某個元素儲存在連結串列中的某處時,我們可以明智地途擇按照何種方式訪問到它,這會非常有幫助。例如,雙向連結串列的一種靈活性在於它提供了一種比單鏈表更直觀的方式以移除一個元素

具體程式碼實現

#include<stdlib.h>
#include<string.h>
typedef struct DListElmt    //節點屬性
{
	void *data;
	struct DListElmt *prev;
	struct DListElmt *next;
}DListElmt;

typedef struct DList
{
	int size;
	int(*match)(const void *key1, const void *key2);
	void(*destroy)(void *data);
	DListElmt *head;
	DListElmt *tail;
}DList;

#define dlist_is_head(element) ((element)->perv == NULL ? 1:0)  //是否是頭
#define dlist_is_tail(element) ((element)->next == NULL ? 1:0)  //是否是尾

//建立連結串列
void dlist_init(DList *list, void(*destroy)(void *data))
{
	list->size = 0;
	list->destroy = destroy;
	list->head = NULL;
	list->tail = NULL;

	return;
}

//刪除節點
int dlist_remove(DList *list, DListElmt *element, void **data)
{
	if (element == NULL || list->size == 0)
		return 1;
	*data = element->data;

	if (element == list->head)       //如果是頭節點,則判空。
	{
		if (list->head == NULL)    //為空則是最後一個節點,清空連線
			list->tail = NULL;
		else
			element->next->prev = NULL;  //非空,則摘除節點,準備刪除
	}
	else   //如果不是頭結點,摘除。刪除
	{
		element->prev->next = element->next; //刪除節點的前一個指向刪除節點的後一個
		if (element->next == NULL)    //判斷是否為最後一個節點
			list->tail = element->prev;
		else
			element->next->prev = element->prev; //刪除節點的後一個指向刪除節點的前一個
	}

	free(element);
	list->size--;
	return 0;
}

//刪除連結串列
void dlist_destroy(DList *list)
{
	void *data;
	while (list->size > 0)
	{
		if (dlist_remove(list, list->tail, (void **)&data) == 0
			&& list->destroy != NULL)
			//如果節點刪除成功,則刪除節點對應儲存的資料
			list->destroy(data);
	}
}

//尾插法
int dlist_ins_next(DList *list, DListElmt *element, const void *data)
{
	DListElmt *new_element;

	if (element == NULL && list->size != 0)
		return -1;

	if ((new_element = (DListElmt*)malloc(sizeof(DListElmt))) == NULL)
		return -1;
	new_element->data = (void *)data;

	if (list->size == 0)    //如果為連結串列頭
	{
		list->head = new_element;
		list->head->prev = NULL;
		list->head->next = NULL;
		list->tail = new_element;
	}
	else
	{
		new_element->next = element->next;
		new_element->prev = element;

		if (element->next == NULL)
			list->tail = new_element;
		else
			element->next->prev = new_element;
		element->next = new_element;
	}
	list->size++;
	return 0;
}

//頭插法
int dlist_ins_prev(DList *list, DListElmt *element, const void *data)
{
	DListElmt *new_element;

	if (element == NULL  && list->size != 0)
		return -1;
	if ((new_element = (DListElmt*)malloc(sizeof(DListElmt))) == NULL)
		return -1;
	new_element->data = (void *)data;

	if (list->size == 0)
	{
		list->head = new_element;
		list->head->prev = NULL;
		list->head->next = NULL;
		list->tail = new_element;
	}
	else
	{
		new_element->next = element;
		new_element->prev = element->prev;
		if (element->prev == NULL)
			list->head = new_element;
		else
			element->prev->next = new_element;
		element->prev = new_element;
	}

	list->size++;
	return 0;
}