一.单链表的基本概念
单链表是一种线性数据结构,由一系列节点组成,每个节点包含数据域和指针域。指针域存储下一个节点的地址,最后一个节点的指针域指向空(NULL
)。与数组不同,单链表的节点在内存中不必连续存储。
二.链表的优缺点:
优点:
1.动态分配内存内存:无需预先分配固定大小,可动态扩展收缩
2.高效插入删除数据:任意位置插入删除节点时间复杂度为O(1)
3.对内存的利用率高:只占用实际需要的空间,无内存浪费
缺点:
1.随机访问效率低:访问第n个元素要从头遍历时间复杂度O(n)
2.额外存储指针:每个节点存储指针,占用额外内存空间
3.缓存:节点在内存中不连续,缓存命中率低
三.数组(顺序表)和链表的区别:
数组(顺序表):分配连续内存,固定大小,随机访问相对快,插入删除相对慢。
链表:非连续内存,需要动态申请空间大小,随机访问相对慢,插入删除相对快。
四.单向链表的代码实现:
链表实现的设计思想:
1.设计结构体:实现数据的存储,并通过指针链接节点;
2.创建结构体:初始化节点,存放数据;
3.插入数据(头插法)(尾插法):对创建节点,通过头节点查找链表来插入数据;
4.删除数据:根据输入数据,通过头节点查找存储该数据节点的位置;
5.显示数据:通过头节点访问链表;
头文件代码:
#ifndef LIST_H
#define LIST_H
#include <stdbool.h>
//结构体初始化
struct List
{
int data;
struct List* next;
};//!!!!结构体的定义加分号
//创建结构体
struct List* create_list(int data);
//头插法插入数据
bool insert_list_head(struct List* head,int data);
//尾插法插入数据
bool insert_list_tail(struct List* head,int data);
//删除数据
bool delete_list(struct List* head,int data);
//显示数据
void display_list(struct List* head);
//注意结束符
#endif
实现代码部分展示:
1.设计结构体
实现原理:
定义一个简单的链表节点结构体 List
,包含两个成员:
int data
:用于存储节点的数据(整数类型)struct List* next
:指向下一个节点的指针
struct List //结构体名List
{
int data;
struct List* next;
};
2.创建结构体
实现原理:
(1).函数返回值类型为struct List*
,说明存在一个自定义的链表节点结构体;
(2).动态分配内存:使用malloc动态分配内存空间(注意头文件:#include <stdlib.h>);
(3).节点初始化:
存储传入数据
将next指针置空(为什么置空?)
确保链表正确终止,避免未定义行为和内存访问错误。
如果置空:
next
指针会包含随机值(垃圾值)
遍历链表时无法确定终点,可能导致无限循环或内存访问错误
(4).调用该函数后需检查返回值是否为NULL(malloc可能失败)。
代码:
//创建结构体
struct List* create_list(int data)
{
struct List* head = (struct List*)malloc(sizeof(struct List));
head->data = data;
head->next = NULL;
return head;//!!!返回值
}
3.插入数据(头插法)(尾插法)
头插法是一种在链表头部插入新节点的方法,新节点成为链表的第一个节点。这种方法的时间复杂度为 O(1),因为它不需要遍历链表。
函数名为insert_list_head
,接收两个参数:链表头节点指针head
和要插入的数据data
。
实现原理:
(1)检查头结点指针是否为空,为空则直接返回false
表示插入失败。处理头结点为空的边界情况;
(2)调用create_list
函数创建一个新结点;
(3)将新结点的next
指针指向头结点的下一个结点;
(4)头结点的next
指针指向新结点。
图示:
代码:
//头插法插入数据
bool insert_list_head(struct List* head,int data)
{
if(head == NULL)return false;
struct List* node = create_list(data);
node->next = head->next;
head->next = node;
return true;
}
尾插法是一种在链表尾部插入新节点的方法,新节点成为链表的最后一个节点。如果链表为空,新节点成为头节点。这种方法的时间复杂度为 O(n),因为需要遍历到链表末尾。
实现原理:
(1)接收链表头指针head和待插入数据data作为参数;
(2)检查头指针是否为空,若为空返回false代表插入失败;
(3)通过create_list函数创建包含data的新节点node;
(4)使用临时指针p遍历链表直到找到最后一个节点(p->next为NULL);
(5)将新节点node链接到最后一个节点的next指针上。
图示:
代码:
//尾插法插入数据
bool insert_list_tail(struct List* head,int data)
{
if(head == NULL)return false;
struct List* node = create_list(data);
struct List* p = head;
while(p->next !=NULL)
{
p = p->next;
}
p->next = node;
return true;
}
4.删除数据
函数接收链表头指针和要删除的数据作为参数,返回操作是否成功的布尔值。
实现原理:
(1)检查链表是否为空(head == NULL),如果是则直接返回false;
(2)创建临时指针p指向头节点,while循环遍历链表,寻找数据匹配的节点;
(3)循环条件检查p->next是否为NULL,确保不访问空指针;
(4)如果找到匹配的节点(p->next->data == data),循环提前终止;
(5)遍历结束后检查是否找到目标节点(p->next == NULL);
(6)找到目标节点后,先用临时指针q保存要删除的节点地址,然后修改前驱节点的next指针跳过被删除节点;
(7)最后释放被删除节点的内存。
代码:
//删除数据
bool delete_list(struct List* head,int data)
{
if(head == NULL)return false;
struct List* p = head;
while(p->next != NULL)
{
if(p->next->data == data)
{break;}
p = p->next;
}
if(p->next == NULL)return false;
struct List* q = p->next;
p->next = q->next;
free(q);
return true;
}
5.显示数据
打印链表中除头节点外的所有节点的数据值
实现原理:
(1)边界条件检查:如果链表为空(head == NULL
)或只有头节点(head->next == NULL
),直接返回不执行后续操作。
(2)初始化指针p
指向头节点的下一个节点(即第一个实际数据节点),跳过头节点。
(3)循环遍历链表:从p
指向的节点开始,依次打印每个节点的data
值,直到链表末尾(p == NULL
)。
代码:
//显示数据
void display_list(struct List* head)
{
if(head ==NULL || head->next == NULL)return;
struct List* p = head->next;//不赋值head,因为后面输出是会输出head
while(p != NULL)
{
printf("%d ",p->data);
p = p->next;
}printf("\n");
}
完整代码:
list.c
//list.c文件
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include "list.h"
//创建结构体
struct List* create_list(int data)
{
struct List* head = (struct List*)malloc(sizeof(struct List));
head->data = data;
head->next = NULL;
return head;//!!!返回值
}
//头插法插入数据
bool insert_list_head(struct List* head,int data)
{
if(head == NULL)return false;
struct List* node = create_list(data);
node->next = head->next;
head->next = node;
return true;
}
//尾插法插入数据
bool insert_list_tail(struct List* head,int data)
{
if(head == NULL)return false;
struct List* node = create_list(data);
struct List* p = head;
while(p->next !=NULL)
{
p = p->next;
}
p->next = node;
return true;
}
//删除数据
bool delete_list(struct List* head,int data)
{
if(head == NULL)return false;
struct List* p = head;
while(p->next != NULL)
{
if(p->next->data == data)
{break;}
p = p->next;
}
if(p->next == NULL)return false;
struct List* q = p->next;
p->next = q->next;
free(q);
return true;
}
//显示数据
void display_list(struct List* head)
{
if(head ==NULL || head->next == NULL)return;
struct List* p = head->next;//不赋值head,因为后面输出是会输出head
while(p != NULL)
{
printf("%d ",p->data);
p = p->next;
}printf("\n");
}
main.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "list.h"
int main()
{
struct List* head = create_list(0); // 创建头指针
for(int i = 0; i < 10; i++) // 插入数据
{
//insert_list_head(head, data); // 头插法
insert_list_head(head, i); // 尾插法
}
display_list(head); // 显示数据
printf("请输入需要删除的数据:\n");
int num = 0;
scanf("%d", &num);//!!!输入取地址
delete_list(head,num); // 删除数据
display_list(head);
}
四.单链表的应用场景
- 实现栈、队列等抽象数据类型。
- 内存管理中的动态分配。
- 需要频繁插入/删除的场景(如任务调度)。