1 概念
单链表:线性表的链式存储
线性表:数据之间是一对一的关系
链式存储:不需要在内存中开辟一块连续的空间,所以每一个数据不再是基本数据,而是优两部分组成,数据域和指针域,数据域保存数据,指针域保存下一个结点的地址
![](https://img-blog.csdnimg.cn/img_convert/155a32d498db454bbfc9e2841217a4de.png)
单链表:就是单向的链表,前者结点可以找到后者结点,但是后者无法找到前者
2 单链表的操作
2.1 定义结点结构体
//定义数据类型
typedef int DataType;
//定义结点结构体
typedef struct node{
DataType data; //数据域
struct node *next; //指针域,为了能够操作后面结点,
//所以指针的类型定义为当前结构体类型
}linklist;
2.2 创建一个空的单链表
![](https://img-blog.csdnimg.cn/img_convert/3cbce7af4d6444cb8601912d2ba17ee9.png)
//创建一个空的单链表
//如果是有头结点的单链表,
//定义一个头结点,让数据域不赋值,指针域为空来表示当前链表为空
linklist *LinklistCreate()
{
//定义一个头结点,在堆区开辟空间
linklist *Head = (linklist *)malloc(sizeof(linklist));
//初始化指针域标识为空
Head->next = NULL;
return Head;
}
2.3 头插法插入数据
![](https://img-blog.csdnimg.cn/img_convert/9608eb7bb3bc494aab41aa5fe00fd9f8.png)
//插入数据(头插法插入数据)
void LinklistInserHead(linklist *Head, DataType value)
{
//开辟空间并赋值
//(因为创建空单链表时只是开辟了头结点的空间,所以每次插入数据之前需要重新开辟空间)
linklist *temp = (linklist *)malloc(sizeof(linklist));
temp->data = value;
temp->next = NULL;
//将原本头结点后面结点的地址保存在新插入结点的指针域里面
//头结点后面结点的地址:Head->next
//新插入结点的指针域:temp->next
temp->next = Head->next;
//将新插入结点的地址保存在头结点的指针域里面
//新插入结点的地址:temp
//头结点的指针域:Head->next
Head->next = temp;
return ;
}
2.4 遍历单链表
![](https://img-blog.csdnimg.cn/img_convert/4397072435b94e9f98f92046492691aa.png)
//遍历单链表
void LinklistPrint(linklist *Head)
{
//定义一个指针遍历保存第一个有数据的结点的地址
linklist *p = Head->next;
//循环遍历单链表,直到p保存结点的指针域为NULL时循环结束
while(p->next != NULL)
{
//打印数据
printf("%d ", p->data);
//p指向下一个结点(p保存下一个结点的地址)
p = p->next;
}
printf("%d\n", p->data);
}
2.5 尾插法插入数据
![](https://img-blog.csdnimg.cn/img_convert/457f26b60f304a5484fbc18dff8a1041.png)
//尾插法插入数据
void LinklistInsertTail(linklist *Head, DataType value)
{
//申请空间并赋值
linklist *temp = (linklist *)malloc(sizeof(linklist));
temp->data = value;
temp->next = NULL;
//找到最后一个结点
linklist *p = Head;
while(p->next != NULL)
{
p = p->next;
}
//将新插入的结点保存在最后一个结点的后面
//将新插入的结点的地址保存在p指向的结点的指针域里面
p->next = temp;
//将新插入结点的指针域置为NULL
temp->next = NULL;
return ;
}
2.6 判断单链表是否为空
//判断单链表是否为空
//如果为空返回1,否则返回0
int LinklistIsEmpty(linklist *Head)
{
return Head->next == NULL ? 1 : 0;
}
2.7 头删法删除数据(返回删除的数据)
![](https://img-blog.csdnimg.cn/img_convert/006bcb04c8d84a388d8e52c5ed355f81.png)
//头删法删除数据(返回删除的数据)
DataType LinklistDeleteHead(linklist *Head)
{
if(LinklistIsEmpty(Head) == 1)
{
printf("删除失败,链表为空\n");
return (DataType)-1;
}
DataType value = Head->next->data;
linklist *temp = Head->next;
Head->next = Head->next->next;
free(temp);
temp = NULL;
return value;
}
2.8 按照数据修改数据
//按照数据修改数据
void LinklistUpdate(linklist *Head, DataType Oldvalue, DataType Newvalue)
{
if(LinklistIsEmpty(Head))
{
printf("链表为空\n");
return ;
}
int flags = 0;
//遍历链表对比每一个数据
while(Head->next != NULL)
{
Head = Head->next;
if(Head->data == Oldvalue)
{
Head->data = Newvalue;
flags = 1;
}
}
if(flags == 0)
{
printf("数据%d不存在,修改失败\n", Oldvalue);
}
}
2.9 按照位置查找数据
![](https://img-blog.csdnimg.cn/img_convert/5bf5e01e52cd4581b605c1d60ddf6ae6.png)
//按照位置查找数据
DataType LinklistSearchData(linklist *Head, int pos)
{
if(LinklistIsEmpty(Head))
{
printf("链表为空\n");
return (DataType)-1;
}
if(pos < 1)
{
printf("位置有误\n");
return (DataType)-1;
}
linklist *p = Head;
int i;
for(i = 1; i <= pos; i++)
{
if(p->next == NULL)
{
printf("位置有误\n");
return (DataType)-1;
}
p = p->next;
}
return p->data;
}
2.10 按照数据查找位置
//按照数据查找位置
int LinklistSearchPos(linklist *Head, DataType value)
{
if(LinklistIsEmpty(Head))
{
printf("链表为空\n");
return -1;
}
linklist *p = Head;
int pos = 0;
while(p->next != NULL)
{
pos++;
p = p->next;
if(p->data == value)
{
return pos;
}
}
printf("数据%d不存在,查找失败\n", value);
return -1;
}
2.11 按照位置插入数据
![](https://img-blog.csdnimg.cn/img_convert/16957b4b6ffb4bf7bffe07d132696ab7.png)
//按照位置插入数据
void LinklistInsertByPos(linklist *Head, int pos, DataType value)
{
if(pos < 1)
{
printf("位置有误\n");
return ;
}
linklist *temp = (linklist *)malloc(sizeof(linklist));
temp->data = value;
temp->next = NULL;
if(LinklistIsEmpty(Head) == 1)
{
temp->next = Head->next;
Head->next = temp;
}
else
{
int i;
for(i = 1; i < pos; i++)
{
if(Head->next == NULL)
{
printf("位置有误\n");
return ;
}
Head = Head->next;
}
temp->next = Head->next;
Head->next = temp;
}
}
2.12 直接插入排序
![](https://img-blog.csdnimg.cn/img_convert/6d8bfb669c864cecbd1384a15453e7e1.png)
//直接插入排序
void LinklistSortInsert(linklist *Head, DataType value)
{
linklist *temp = (linklist *)malloc(sizeof(linklist));
temp->data = value;
temp->next = NULL;
while(Head->next != NULL && Head->next->data < temp->data)
{
Head = Head->next;
}
temp->next = Head->next;
Head->next = temp;
}
3 整体代码
linklist.h
#ifndef _LINKLIST_H_
#define _LINKLIST_H_
//一般.h里面一般放取别名、结构体、函数的声明
#include <stdio.h>
#include <stdlib.h>
//定义数据类型
typedef int DataType;
//定义结点结构体
typedef struct node{
DataType data; //数据域
struct node *next; //指针域,为了能够操作后面结点,
//所以指针的类型定义为当前结构体类型
}linklist;
//创建一个空的单链表
extern linklist *LinklistCreate();
//插入数据(头插法插入数据)
extern void LinklistInserHead(linklist *Head, DataType value);
//遍历单链表
extern void LinklistPrint(linklist *Head);
//尾插法插入数据
extern void LinklistInsertTail(linklist *Head, DataType value);
//判断单链表是否为空
extern int LinklistIsEmpty(linklist *Head);
//头删法删除数据(返回删除的数据)
extern DataType LinklistDeleteHead(linklist *Head);
//按照数据修改数据
extern void LinklistUpdate(linklist *Head, DataType Oldvalue, DataType Newvalue);
//按照位置查找数据
extern DataType LinklistSearchData(linklist *Head, int pos);
//按照数据查找位置
extern int LinklistSearchPos(linklist *Head, DataType value);
//按照位置插入数据
extern void LinklistInsertByPos(linklist *Head, int pos, DataType value);
//直接插入排序
extern void LinklistSortInsert(linklist *Head, DataType value);
#endif
linklist.c
#include "linklist.h"
//.c主要放函数的定义
//创建一个空的单链表
//如果是有头结点的单链表,
//定义一个头结点,让数据域不赋值,指针域为空来表示当前链表为空
linklist *LinklistCreate()
{
//定义一个头结点,在堆区开辟空间
linklist *Head = (linklist *)malloc(sizeof(linklist));
//初始化指针域标识为空
Head->next = NULL;
return Head;
}
//插入数据(头插法插入数据)
void LinklistInserHead(linklist *Head, DataType value)
{
//开辟空间并赋值
//(因为创建空单链表时只是开辟了头结点的空间,所以每次插入数据之前需要重新开辟空间)
linklist *temp = (linklist *)malloc(sizeof(linklist));
temp->data = value;
temp->next = NULL;
//将原本头结点后面结点的地址保存在新插入结点的指针域里面
//头结点后面结点的地址:Head->next
//新插入结点的指针域:temp->next
temp->next = Head->next;
//将新插入结点的地址保存在头结点的指针域里面
//新插入结点的地址:temp
//头结点的指针域:Head->next
Head->next = temp;
return ;
}
//遍历单链表
void LinklistPrint(linklist *Head)
{
//定义一个指针遍历保存第一个有数据的结点的地址
linklist *p = Head->next;
//循环遍历单链表,直到p保存结点的指针域为NULL时循环结束
while(p->next != NULL)
{
//打印数据
printf("%d ", p->data);
//p指向下一个结点(p保存下一个结点的地址)
p = p->next;
}
printf("%d\n", p->data);
}
//尾插法插入数据
void LinklistInsertTail(linklist *Head, DataType value)
{
//申请空间并赋值
linklist *temp = (linklist *)malloc(sizeof(linklist));
temp->data = value;
temp->next = NULL;
//找到最后一个结点
linklist *p = Head;
while(p->next != NULL)
{
p = p->next;
}
//将新插入的结点保存在最后一个结点的后面
//将新插入的结点的地址保存在p指向的结点的指针域里面
p->next = temp;
//将新插入结点的指针域置为NULL
temp->next = NULL;
return ;
}
//判断单链表是否为空
//如果为空返回1,否则返回0
int LinklistIsEmpty(linklist *Head)
{
return Head->next == NULL ? 1 : 0;
}
//头删法删除数据(返回删除的数据)
DataType LinklistDeleteHead(linklist *Head)
{
if(LinklistIsEmpty(Head) == 1)
{
printf("删除失败,链表为空\n");
return (DataType)-1;
}
DataType value = Head->next->data;
linklist *temp = Head->next;
Head->next = Head->next->next;
free(temp);
temp = NULL;
return value;
}
//按照数据修改数据
void LinklistUpdate(linklist *Head, DataType Oldvalue, DataType Newvalue)
{
if(LinklistIsEmpty(Head))
{
printf("链表为空\n");
return ;
}
int flags = 0;
//遍历链表对比每一个数据
while(Head->next != NULL)
{
Head = Head->next;
if(Head->data == Oldvalue)
{
Head->data = Newvalue;
flags = 1;
}
}
if(flags == 0)
{
printf("数据%d不存在,修改失败\n", Oldvalue);
}
}
//按照位置查找数据
DataType LinklistSearchData(linklist *Head, int pos)
{
if(LinklistIsEmpty(Head))
{
printf("链表为空\n");
return (DataType)-1;
}
if(pos < 1)
{
printf("位置有误\n");
return (DataType)-1;
}
linklist *p = Head;
int i;
for(i = 1; i <= pos; i++)
{
if(p->next == NULL)
{
printf("位置有误\n");
return (DataType)-1;
}
p = p->next;
}
return p->data;
}
//按照数据查找位置
int LinklistSearchPos(linklist *Head, DataType value)
{
if(LinklistIsEmpty(Head))
{
printf("链表为空\n");
return -1;
}
linklist *p = Head;
int pos = 0;
while(p->next != NULL)
{
pos++;
p = p->next;
if(p->data == value)
{
return pos;
}
}
printf("数据%d不存在,查找失败\n", value);
return -1;
}
//按照位置插入数据
void LinklistInsertByPos(linklist *Head, int pos, DataType value)
{
if(pos < 1)
{
printf("位置有误\n");
return ;
}
linklist *temp = (linklist *)malloc(sizeof(linklist));
temp->data = value;
temp->next = NULL;
if(LinklistIsEmpty(Head) == 1)
{
temp->next = Head->next;
Head->next = temp;
}
else
{
int i;
for(i = 1; i < pos; i++)
{
if(Head->next == NULL)
{
printf("位置有误\n");
return ;
}
Head = Head->next;
}
temp->next = Head->next;
Head->next = temp;
}
}
//直接插入排序
void LinklistSortInsert(linklist *Head, DataType value)
{
linklist *temp = (linklist *)malloc(sizeof(linklist));
temp->data = value;
temp->next = NULL;
while(Head->next != NULL && Head->next->data < temp->data)
{
Head = Head->next;
}
temp->next = Head->next;
Head->next = temp;
}
main.c
#include "linklist.h"
//分文件编程的意义在于多人协助代码管理
int main(int argc, char const *argv[])
{
linklist *h1 = LinklistCreate();
LinklistInserHead(h1, 100);
LinklistInserHead(h1, 200);
LinklistInserHead(h1, 300);
LinklistInserHead(h1, 400);
LinklistInserHead(h1, 500);
LinklistInserHead(h1, 600);
LinklistPrint(h1);
LinklistInsertTail(h1, 666);
LinklistInsertTail(h1, 777);
LinklistInsertTail(h1, 888);
LinklistPrint(h1);
DataType del_val;
del_val = LinklistDeleteHead(h1);
printf("del_val = %d\n", del_val);
LinklistPrint(h1);
LinklistUpdate(h1, 100, 333);
LinklistPrint(h1);
DataType val;
val = LinklistSearchData(h1, 4);
printf("第4个数据是%d\n", val);
int pos;
pos = LinklistSearchPos(h1, 200);
printf("数据200的位置是%d\n", pos);
LinklistInsertByPos(h1, 4, 1000);
LinklistPrint(h1);
puts("***********************");
linklist *h2 = LinklistCreate();
LinklistInsertTail(h2, 30);
LinklistInsertTail(h2, 60);
LinklistInsertTail(h2, 80);
LinklistPrint(h2);
LinklistSortInsert(h2, 10);
LinklistSortInsert(h2, 40);
LinklistSortInsert(h2, 70);
LinklistSortInsert(h2, 100);
LinklistPrint(h2);
return 0;
}
Makefile
main:main.o linklist.o
main.o:main.c
linklist.o:linklist.c
.PHONY:clean
clean:
$(RM) *.o main a.out
4.扩展用法
实现链表的翻转
h1: 10->20->30->40->NULL
...
h1: 40->30->20->10->NULL
//实现链表的翻转
void LinklistReverse(linklist *Head)
{
if(LinklistIsEmpty(Head))
{
printf("链表为空\n");
return ;
}
//定义两个指针变量来执行链表的翻转
linklist *p, *q;
//让p指针保存头结点之后的第一个有数据的结点的地址
p = Head->next;
//头结点的指针域等于空来初始化头结点
Head->next = NULL;
//循环让q指针保存p指向的结点的首地址并通过头插法将其插法插入到头结点的后面
while(p != NULL)
{
//q指针保存p指向的结点的地址
q = p;
//p指针后移保存下一个结点的地址
p = p->next;
//通过头插法将q指向的结点插入到头结点的后面
q->next = Head->next;
Head->next = q;
}
return ;
}