链表的基本定义:
链表是一种数据结构,用于存储和组织数据。它由一系列称为结点的元素组成,每个结点包含数据和一个指向下一个结点的指针。结点通过指针的链接顺序连接在一起,形成一个链式结构。
与数组不同,链表的元素在内存中不是连续存放的,而是通过指针相互连接。这种特性使得链表具有动态性,可以在运行时快速地插入和删除元素,而无需移动其他元素。
链表可以分为单向链表和双向链表。单向链表中,每个结点只有一个指向下一个结点的指针;双向链表中,每个结点既有一个指向下一个结点的指针,又有一个指向前一个结点的指针。
链表的组成:
链表的基本单位是结点,链表的结点一般由两个域组成:数据域(data filed)和指针域(pointer filed)。
1、数据域:存储链表所持有的数据。它可以是任意类型的数据,如整形、字符、对象等,根据实际需求而定。
2、指针域:指针域存储指向下一个结点(或上一个结点,如果是双向链表)的指针。它保存了连接结点之间关系的信息,使得链表能够按顺序访问各个结点。如果指针为 null(空指针),表示没有下一个结点,链表在此处结束。
链表的构造:
数据结构的基本单位一般由结构体来构造完成,这里分别用两个不同的结构体来构造链表的结点和链表本身。
1、结点定义:data指向数据域,*next指向下一个结点
typedef struct node
{
int data;
struct node* next;
}Node;
2、单链表定义:head指向头结点,end指向尾结点,length为链表的长度
typedef struct linklist
{
Node* head;
Node* end;
int length;
}LL;
链表功能的实现:
如今链表的基本单位已经构造完成,下面开始实现单链表的基本功能。
先定义单链表的基本功能函数。
下面是单链表的功能函数头文件 linklist.h。
//linklist.h
//用C语言实现单链表基本功能
#pragma once
#ifndef LINKLIST
#define LINKLIST
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
typedef int Type;//数据域的数据类型
#define TYPE_P "%d->"
#define TYPE_S "%d"
typedef struct node{
Type data;
struct node * next;
}Node;
typedef struct linklist{
Node *head;
Node *end;
int length;
}LL;
//1.单链表的创建
LL* list_init();//链表的初始化函数
//链表结点的初始化函数
Node* node_init(Type val);
//2.单链表数据的结点插入
//①向单链表尾部插入数据(尾插)
void list_insert_end(LL* list, Type val);
//②向单链表指定位置插入数据
void list_insert(LL* list, Type val, int index);
//3.单链表结点的删除,并返回删除的数据
Type list_delete_Node(LL* list, int index);//根据位置删除
//4.单链表的输出
void list_print(LL* list);
//5.单链表的销毁
void list_destroy(LL* list);
#endif
接着开始实现链表的基本功能。
以下是单链表的功能函数源文件 linklist.c。
//linklist.c
//单链表功能实现
#include "linklist.h"
//1.单链表的创建
LL* list_init()//链表的初始化函数
{
//创建一个单链表结构体的动态内存
LL* temp = (LL*)malloc(sizeof(LL));
if (NULL == temp)
{//如果动态内存开辟失败,指针指向了NULL
puts("动态内存开辟失败!\n");
return NULL;
}
temp->head = temp->end = NULL;//单链表头结点和尾结点指向空
temp->length = 0;//单链表的长度初始化为0
return temp;//返回单链表结构体的动态内存
}
//链表结点的初始化函数
Node* node_init(Type val)
{
//创建一个单链表结点结构体的动态内存
Node* New = (Node*)malloc(sizeof(Node));
if (NULL == New)
{//如果单链表结点动态内存开辟失败,New指针指向了NULL
puts("动态内存开辟失败!\n");
return NULL;
}
New->data = val;
New->next = NULL;
return New;//返回单链表结点结构体的动态内存
}
//2.单链表数据的结点插入
//①向单链表尾部插入数据(尾插)
void list_insert_end(LL* list, Type val)
{
if (NULL == list)
{//如果链表不存在,则不允许插入数据
puts("链表不存在,数据插入失败!\n");
return;
}
if (NULL == list->head)
{//如果是创建链表的第一个结点,它既是链表的头部也是链表的尾部
list->head = list->end = node_init(val);
list->length++;//链表长度+1
}
else
{//如果不是创建链表的第一个结点,就需要将新节点连接上链表的上一个结点
list->end->next = node_init(val);//创建一个新结点,并连接上链表的尾部
list->end = list->end->next;//新结点成为链表尾部
list->length++;//链表长度+1
}
}
//②向单链表指定位置插入数据
void list_insert(LL* list, Type val, int index)
{
if (NULL == list)
{//如果链表不存在,则不允许插入数据
puts("链表不存在,数据插入失败!\n");
return;
}
if (index <= 0 || index > list->length + 1)
{//如果插入的位置小于0或者大于链表长度+1,则不允许插入数据
puts("插入数据的位置错误,数据插入失败!\n");
return;
}
if (1 == index)
{//如果插入的位置在头结点之前
Node* New = node_init(val);//创建一个新结点
New->next = list->head;//新结点链接上头结点
list->head = New;//使新结点成为新的头结点
list->length++;
return;
}
if (index == list->length + 1)
{//如果插入结点在尾结点后一个位置
//Node* New = node_init(val);//创建一个新结点
//list->end->next = New;//链表的尾部链接上新的结点
//list->end = New;//新结点成为链表的尾结点
//list->length++;
list_insert_end(list,val);//调用链表的尾插函数完成尾部插入
return;
}
else
{//在链表的中间插入结点
Node *temp = list->head;
for (int i = 1; i < index - 1; ++i)
{//找到插入位置的前一个结点
temp = temp->next;
}
Node* New = node_init(val);//创建一个新结点
New->next = temp->next;//新结点连接上插入之后的结点(先连接后面的结点,防止先连接前面后无法再找到改位置后面的位置索引)
temp->next = New;//插入位置之前的结点连接上新结点
list->length++;//链表长度 + 1
}
}
//3.单链表结点的删除,并返回删除的数据
Type list_delete_Node(LL* list, int index)
{//根据位置删除
if (NULL == list)
{//如果链表不存在,则不允许删除数据
puts("链表不存在,数据删除失败!\n");
return -1;
}
if (NULL == list->head)
{//如果链表的头结点不存在,则不允许删除数据
puts("链表头结点不存在,数据删除失败!\n");
return -1;
}
if (index <= 0 || index > list->length + 1)
{//如果插入的位置小于0或者大于链表长度+1,则不允许删除数据
puts("删除数据的位置错误,数据删除失败!\n");
return -1;
}
if (1 == index)
{//删除头结点
Node* temp = list->head;//记录被删除的头结点
list->head = list->head->next;//头结点的下一个结点成为新的头部
Type val = temp->data;//记录被删除的结点数据
free(temp);
list->length--;
return val;
}
if (index == list->length)
{//如果删除尾结点
Node* temp = list->head;//创建一个临时链表
for (int i = 1; i < index; ++i)
{//找到被删除位置的前一个结点
temp = temp->next;
}
Type val = temp->data;//记录被删除的数据
free(list->end);//释放尾部结点
list->end = temp;//尾部结点前的一个结点成为新的尾部
list->length--;
return val;
}
else
{//删除中间结点
Node* temp1 = list->head;//创建一个临时链表1
for (int i = 1; i < index - 1; ++i)
{//找到删除位置的前一个结点
temp1 = temp1->next;
}
Node* temp2 = temp1->next;//记录被删除结点的位置
Type val = temp2->data; //记录被删除结点的数据
temp1->next = temp2->next;//删除位置的前一个结点跳过删除位置,指向下一个结点
free(temp2);
list->length--;
return val;//返回被删除的结点数据
}
}
//4.单链表的输出
void list_print(LL* list)
{
if (NULL == list)
{//如果链表不存在,则不允许输出数据
puts("链表不存在,数据输出失败!\n");
return;
}
if (NULL == list->head)
{//如果链表的头结点不存在,则不允许输出数据
puts("链表头结点不存在,数据输出失败!\n");
return;
}
//单链表存在,正常输出
for (Node* temp = list->head; temp != NULL; temp = temp->next)
{
printf(TYPE_P, temp->data);
}
puts("NULL");
}
//5.单链表的销毁
void list_destroy(LL* list)
{
if (NULL == list)
{//如果链表不存在,则不用再销毁链表
return;
}
Node* currNode = list->head;//创建一个链表记录当前要释放的结点
Node* nextNode;//创建一个链表记录下一个结点
while (currNode != NULL)//遍历链表释放每个结点的内存
{//当当前链表的结点不为空时,删除当前结点并记录下一个结点
nextNode = currNode->next;//暂存下一个结点的数据
free(currNode);//释放当前结点的数据
currNode = nextNode;//将刚才暂存的结点值赋给当前结点
}
free(list);//释放链表结构体的内存
return;
}
链表的功能测试:
编写主函数来测试linklist功能。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include "linklist.h"
void linklist_test();//链表功能测试函数
int main()
{
linklist_test();
system("pause");
return 0;
}
void linklist_test()//链表功能测试函数
{
LL* LL1 = list_init();//单链表的创建
Type values;//创建临时变量
puts("请输入单链表的数据(以回车结束):");
do{
scanf(TYPE_S, &values);//输入数据进临时变量
list_insert_end(LL1,values);//链表尾插变量
} while ('\n' != getchar());
puts("单链表当前的数据为:");
list_print(LL1);//输出链表数据
list_destroy(LL1);//使用完成以后销毁链表
}
运行结果:输入1 2 3 5 6 4
链表测试成功。