数据结构---单链表
算法和数据结构总结---单链表
链表可以说是一种最基本的数据结构,链表通常以一种特定的组合将元素链接在一起,以便可以对元素实现方便的管理维护。这一点和我们常常使用的数组很相似,但是链表在最多的情况下可以带来比链表更为优势的操作,链表通常是在系统需要的时候动态开辟的,换句话说链表的存储空间是在程序运行的时候在进行分配的,这就说明链表的长度是可变的。在许多的时候,我们无法明确的确定数据的大小直接在编译前分配内存,这种动态分配的方法也是链表的优势之一。
单链表的定义
单链表(通常也被成为链表) 链表元素由彼此的内部的一个指针相链接。每个元素包括两个成员:数据成员和一个称为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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」