算法和数据结构总结---单链表
2020-01-15 07:35
330 查看
链表
链表可以说是一种很基本的数据结构,链表通常以一种特定的组合将元素链接在一起,以便可以对元素实现方便的管理维护。这一点和我们常常使用的数组很相似,但是链表在最多的情况下可以带来比链表更为优势的操作,链表通常是在系统需要的时候动态开辟的,换句话说链表的存储空间是在程序运行的时候在进行分配的,在许多的时候,我们无法明确的确定数据的大小直接在编译前分配内存,这种动态分配的方法也是链表的优势之一。
单链表
单链表(通常也被成为链表) 链表元素由彼此的内部的一个指针相链接。每个元素包括两个成员:数据成员和一个称为
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)); 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
指针指向新的链表元素。
最后整个
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 == 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)中
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; }
- 点赞
- 收藏
- 分享
- 文章举报
相关文章推荐
- 算法与数据结构总结(二)-------数组、链表概述
- 算法和数据结构总结---循环链表
- 算法和数据结构总结---双向链表
- 要完整准确的 均以单链表作存储结构 试编写算法将A表和B表归并成一个按元素值递减有序的线性表C 【数据结构】假设有两个按元素值递增有序的线性表A和B 并要求利用原表的空间存放C。谁会么
- 数据结构和算法面试总结
- 单链表的归并算法思路总结
- 数据结构入门学习系列-5(链表的基本操作算法)
- 数据结构与算法之链表反转
- 《Delphi 算法与数据结构》学习与感悟[8]: 单向链表的添加、删除与遍历
- 数据结构和算法设计专题之---单链表中在指定的节点前面插入以及删除一个节点
- Java 数据结构和经典算法经验总结
- 左程云_算法与数据结构 — 链表问题 — 03删除链表的中间节点和a/b处的节点
- Linux c 算法与数据结构--双向链表
- 算法总结之 反转部分单向链表
- 算法题目总结 -- 链表中环的入口节点
- 数据结构链表总结一
- 数据结构链表创建,遍历,是否为空,求长度,插入,删除算法的演示
- 算法与数据结构面试题(1)-把二元查找树转变成排序的双向链表
- 数据结构与算法3:链表2
- 算法与数据结构——入门总结与自学资料推荐