數據結構---單鏈表


算法和數據結構總結---單鏈表

鏈表可以說是一種最基本的數據結構,鏈表通常以一種特定的組合將元素鏈接在一起,以便可以對元素實現方便的管理維護。這一點和我們常常使用的數組很相似,但是鏈表在最多的情況下可以帶來比鏈表更為優勢的操作,鏈表通常是在系統需要的時候動態開辟的,換句話說鏈表的存儲空間是在程序運行的時候在進行分配的,這就說明鏈表的長度是可變的。在許多的時候,我們無法明確的確定數據的大小直接在編譯前分配內存,這種動態分配的方法也是鏈表的優勢之一。

單鏈表的定義

單鏈表(通常也被成為鏈表) 鏈表元素由彼此的內部的一個指針相鏈接。每個元素包括兩個成員:數據成員和一個稱為next的指針成員,每個元素通過next指針指向下一個鏈表元素,實現鏈表的鏈接。鏈表的開始處稱之為鏈表的 “頭”,鏈表的最后結束部分稱為鏈表的 “尾”。單鏈表只允許一個一個方向遍歷鏈表,盡管有的時候我們保存鏈表的指針信息。下圖為一個標准的單鏈表結構。
單鏈表圖

圖1 標准的單鏈表邏輯模型
從物理內存上在看,鏈表不是連續的內存存儲,但是在邏輯上可以理解鏈表是線性的。我們可以類似的畫出鏈表的內存存儲方式:

image

圖2 標准的單鏈表內存模型
元素和元素之間的連接關系只是為了可以確保所有的元素可以訪問的到,倒是萬事萬物不可能是十全十美的,你的優點有多強,你的弱點就有多弱。如果我們錯誤的丟失一個節點,則從這一節點后的所有元素均無法訪問的到。

單鏈表接口的公共接口

在實現一個鏈表,我們先定義一個鏈表需要哪些操作進行實現。

  • 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元素之后,如果elementNULL,則新元素插入鏈表的頭部。新元素包含一個指向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是表示插入頭接單,另一種就是在一個鏈表元素后插。

  1. element 為 0 插入頭鏈表。當整個鏈表不存在鏈表元素時,這個時候鏈表頭即是鏈表為鏈表尾,
    這個時候要更新listtail信息。
    當整個鏈表存在鏈表元素時候,這個時候新的鏈表就取代原來的鏈表頭,list的鏈表頭信息就更新。
  2. element 不為 0 就要注意你插入的元素是不是鏈表尾,如果是鏈表尾,則要更新list的鏈表尾指針。否則就讓新鏈表的的next指針指向elementnetx指針指向的鏈表,elementnext指針指向新的鏈表元素。
    這整個過程如圖3所示:
    最后整個list的長度要加1;

在這里插入圖片描述

圖3 單鏈表的插入過程
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元素后的一個元素鏈表,並不是元素釋放,這一點要清楚。和插入鏈表的操作相同這個也要分類兩種情況進行分析:移除頭結點和其他結點。

  1. element 為 0 移除頭鏈表。先把頭結點的數據指針位置保持,並用old_element保持要移除的鏈表的空間內存地址,這個時候就用原本頭結點的下一鏈表取代原本的頭結點。如果這個時候鏈表的長度為1。
  2. 如果element的下一個元素就是空指針,那不能刪,返回錯誤,否則把element的下一個鏈表的數據空間和鏈表位置保存。利用element->next = element->next->next;更新鏈表結構。如果刪掉是最后一個鏈表結點,要注意更新鏈表信息的尾信息。

最后整個list的長度要減1;


在這里插入圖片描述

圖4 單鏈表的后刪過程
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;
}



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM