前言
记录一下单链表的各种操作思路
,方便后面复习回顾。
一、单链表介绍
链表是链式存储
结构,用于存储逻辑关系为线性
,也就是“一对一”关系的数据。
- 有头链表:存在一个头节点,头节点的
指针域有效
,数据域看个人
。 - 无头链表:每一个数据域和指针域都有效。
- 特点:内存中
不连续
,通过指针
链接。 - 解决问题:
长度固定
的问题和插入删除
麻烦的问题。 - 逻辑结构:线性结构
- 存储结构:链式存储结构
- 操作:增删改查
二、单链表创键
为什么要用指针来操作链表呢?
在我看来是每一个节点都有指针来指向
,所以自然想到用指针来操作了,而且我们开辟堆空间放节点时,也是用指针接收的。在对结构体进行操作时,我们通过移动头节点的指针来到达想要的位置,然后进行相应操作
。
1.初始化
- 开辟空间,创建节点
- 头节点
数据域置0
(可选) - 头节点
指针域指向NULL
// 初始化链表
Node* initList()
{
// 开辟空间,创建节点
Node* list = (Node*)malloc(sizeof(Node));
list -> data = 0; //头节点数据域一般存放节点个数
list -> next = NULL;
return list;
}
Node* list = initList();//头节点指针
2.头插法
从头节点的后面插入
,头节点用来保存链表的信息
。
开辟空间,创建节点
数据域保存传进来的数据
调整新建节点的指向
(上图1
)调整头节点的指向
(上图2
)头节点数据+1
,表示新增了一个节点 (上图3
, 可选)
// 头插法:从头节点的后面插入。头节点用来保存链表的信息
void headInsert(Node* list, int data)
{
// 开辟空间
Node* node = (Node*)malloc(sizeof(Node));
node -> data = data; //节点数据域保存传进来的数据
node -> next = list -> next; //将头节点next指向的节点地址赋值给新建节点next
list -> next = node; //头节点指向新建节点
list -> data++; //头节点数据+1,表示新增了一个节点
}
3.尾插法
// 尾插法:遍历到最后一个节点,在该节点后面插入新节点
void tailInsert(Node* list, int data)
{
Node* head = list;
// 开辟空间
Node* node = (Node*)malloc(sizeof(Node));
node -> data = data;
node -> next = NULL; //最后一个,没有可指向的节点
//移动到最后一个节点
while (list -> next)
{
list = list -> next;
}
//最后节点指向新节点
list -> next = node;
head -> data++;//节点数量++
}
4.打印
// 从第一个节点开始遍历输出每一个节点
void printList(Node* list)
{
list = list -> next; //从第一个节点开始遍历
while (list)
{
printf("%d->", list -> data);
list = list -> next;
}
printf("\n");
}
5.删除
遍历所有节点, 将要删除的节点内容和每一个节点的内容进行比对,找到目标节点后,把它指向的节点传给前一个节点的next
(如图1
)
需要两个指针,初始时,一个(pre
)指向头节点,一个(current
)指向头节点后面,每次循环时,它们都向后移动一次
操作完要释放该空间
(如图2
)
头节点的data要-1
(如图3
)
void delete(Node* list, int data)
{
Node* pre = list;
Node* current = list->next;
while (current)//遍历节点
{
if (current->data == data)//根据data找到指定位置
{
pre->next = current->next;//调整前一节点指向
free(current);//释放空间
current = NULL;
printf("已删除!\n");
list->data--;//节点数量-1
break;
}
//移动到下一个节点
pre = current;
current = current->next;
}
}
6.任意位置插入
// 任意位置插入:用户输入位置和数据
void insertNode(Node* list)
{
int post;//插入位置
int temp_data;//存用户的插入数
printf("请输入要插入的位置:\n");
scanf("%d", &post);
getchar();
// 进行容错判断
if (post < 0 || post > list->data+1)//
{
printf("位置不在有效范围!\n");
return;
}
printf("请输入要插入的数:\n");
scanf("%d", &temp_data);
getchar();
// 开辟空间,创建节点
Node* node = (Node*)malloc(sizeof(Node));
node->data = temp_data;
Node* current = list;//要移动的指针
// 移动到要插入的前一个节点上
for (int i = 1; i < post; i++)//插入到位1,不移动指针,插入到位2,移动1次,插入到位3,移动2次
{
current = current->next;
}
// 插入操作
node->next = current->next;//调整插入节点的next
current->next = node; //调整前一节点的next
list->data++;
}
7.判断空表
// 判断是否空链表:看头节点的next是否为null
void isNullList(Node* list)
{
if (list->next == NULL)
{
printf("NULL\n");
return;
}
printf("节点数:%d\n", list->data);
}
8.清空节点
// 清空节点:从第一个节点开始遍历,将每一个节点进行删除,最后让头节点指向NULL
void noneNode(Node* list)
{
Node* current = list->next;
while (current != NULL)
{
Node* current_next = current->next;//提前保存要删除节点的下一个节点位置
//删除操作
free(current);
current = NULL;
list->data--;
//当头节点data为0时,停止删除
if (list->data == 0)
{
list->next = NULL;
printf("已清空!\n");
return;
}
current = current_next;//删除完,指针向后移动
}
}
9.查找数据
// 查找:用户输入数据,将数据去和每一个节点进行匹配,如果成功,返回位置
void searchNode(Node* list)
{
int temp_data;//要查找数据
int num = 0;//记录数据所在位置
printf("请输入要查找的数据:");
scanf("%d", &temp_data);
Node* current = list->next;//移动到第一个节点
while (current != NULL)//遍历
{
if (current->data == temp_data)//匹配
{
printf("%d在位置%d!\n", temp_data, num+1);
}
num++;//不匹配位置++
current = current->next;
}
}
10.修改数据
//修改:让头节点指针移动到指点位置,修改节点的数据
void modifyNode(Node* list)
{
int num;//位置
int temp_data;//要修改的数据
printf("请输入要修改的位置:");
scanf("%d", &num);
// 容错判断
if (num < 0 || num > list->data)
{
printf("位置超范围!\n");
}
printf("请输入要修改的值:");
scanf("%d", &temp_data);
for (int i = 0; i < num; i++)//让头节点指针移动到修改位置
{
list = list->next;
}
list->data = temp_data;//修改
printf("修改成功!\n");
}
11.数据转置
// 链表转置:将原表从头节点处拆开,遍历头节点后每一个节点,每次将要遍历的节点'头插入'头节点中
void linkListReverse(Node* list)
{
Node* current = list->next;//原表用来移动的指针,从第一个节点开始
list->next = NULL;
while (current)//遍历原表
{
//将原表每一个节点头插入空的头节点
Node* next_node = current->next;//在插入之前保存current下一个结点,不然current指向改变后,其后续节点找不到了
current->next = list->next;//调整当前遍历节点的next
list->next = current;//头节点指向新插入节点
current = next_node;//移动到下一个节点
}
printf("转置完成!\n");
}
12.数据排序
// 排序
// 思路:
// 根据平均分降序排序
// 思路:将头节点和后续节点分开,对后续无头节点进行遍历,
// 将遍历节点重新插入头节点后面,每次插入时要考虑顺序
// 这样要插入的链表就是一个已经排序好的链表
void sortLinkList(Node* list)
{
// 将头节点和后续节点分开前,要保存后续第一个节点位置,否找找不到后面
Node* current = list->next;
//将头节点和后续节点进行分开
list->next = NULL;
//对后续无头节点进行遍历,将每个节点重新插入头节点后面,插入时考虑顺序
while (current)
{
//每个节点插入前,都要提前保存下一个节点,因为后面current的next发生变化
Node* current_next = current->next;
//头节点指针复制品,用来移动的,指向头节点
Node* sorted_ptr = list;
//节点插入时,要考虑插入位置,当第一个位置不合适就去下一个位置,所以要对已排序链表进行遍历
while (sorted_ptr)
{
// 什么时候进行插入?
//1.当已排序链表为空时直接插入(条件1)
//2.当遍历已排序链表最后位置时,直接插入(条件1)
//3.移到合适位置插入(条件2)
if (sorted_ptr->next == NULL || current->data < sorted_ptr->data)
{
//头插操作
current->next = sorted_ptr->next;
sorted_ptr->next = current;
break;
}
else
{
//不符合插入条件时指针后移
sorted_ptr = sorted_ptr->next;
}
}
//发生插入后,current指针后移,移到下一个待插入节点
current = current_next;
}
printf("排序完成!\n");
}
三 、全部代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct Node
{
int data;
struct Node* next;
}Node;
// 初始化
Node* initList()
{
Node* list = (Node*)malloc(sizeof(Node));
if (list == NULL)
{
perror("malloc error");
}
list->data = 0;
list->next = NULL;
return list;
}
// 头插法:从头节点的后面插入。头节点用来保存链表的信息
// void headInsert(Node* list, int data)
// {
// // 开辟空间
// Node* node = (Node*)malloc(sizeof(Node));
// node -> data = data; //节点数据域保存传进来的数据
// node -> next = list -> next; //将头节点next指向的节点地址赋值给新建节点next
// list -> next = node; //头节点指向新建节点
// list -> data++; //头节点数据+1,表示新增了一个节点
// }
// 尾插法
// void tailInsert(Node* list, int data)
// {
// Node* head = list;
// // 开辟空间
// Node* node = (Node*)malloc(sizeof(Node));
// node -> data = data;
// node -> next = NULL; //最后一个,没有可指向的节点
// while (list -> next)
// {
// list = list -> next;
// //list已经是尾巴了,怎么让list->data加1呢?
// }
// list -> next = node;
// head -> data++;
// }
// 任意位置插入:用户输入位置和数据
void insertNode(Node* list)
{
int post;//插入位置
int temp_data;//存用户的插入数
printf("请输入要插入的位置:\n");
scanf("%d", &post);
getchar();
// 进行容错判断
if (post < 0 || post > list->data+1)//
{
printf("位置不在有效范围!\n");
return;
}
printf("请输入要插入的数:\n");
scanf("%d", &temp_data);
getchar();
// 开辟空间,创建节点
Node* node = (Node*)malloc(sizeof(Node));
node->data = temp_data;
Node* current = list;//要移动的指针
// 移动到要插入的前一个节点上
for (int i = 1; i < post; i++)//插入到1不移动指针,插入到2,移动1次,插入到3,移动2次
{
current = current->next;
}
// 插入操作
node->next = current->next;//调整插入节点的next
current->next = node; //调整前一节点的next
list->data++;
}
// 删除
void delete(Node* list)
{
int temp_data;
printf("请输入要删除的数据:");
scanf("%d", &temp_data);
Node* pre = list;
Node* current = list->next;
while(current != NULL)
{
if (current->data == temp_data)
{
pre->next = current->next;
free(current);
current = NULL;
list->data--;
printf("删除成功!\n");
return;
}
pre = current;
current = current->next;
}
printf("没找到!\n");
}
// 判断是否空链表
void isNullList(Node* list)
{
if (list->next == NULL)
{
printf("NULL\n");
return;
}
printf("节点数:%d\n", list->data);
}
// 清空节点
void noneNode(Node* list)
{
Node* current = list->next;
while (current != NULL)
{
Node* hou = current->next;//用来保存移动指针的下一个节点位置
free(current);
current = NULL;
list->data--;
if (list->data == 0)
{
list->next = NULL;
printf("已清空!\n");
return;
}
current = hou;//当前指针向后移动
}
}
// 查找:用户输入数据,如果存在,返回位置
void searchNode(Node* list)
{
int temp_data;
int num = 0;
printf("请输入要查找的数据:");
scanf("%d", &temp_data);
Node* current = list->next;
while (current != NULL)
{
if (current->data == temp_data)
{
printf("%d在位置%d!\n", temp_data, num+1);
}
num++;
current = current->next;
}
}
//修改数据
void modifyNode(Node* list)
{
int num;
int temp_data;
printf("请输入要修改的位置:");
scanf("%d", &num);
// 容错判断
if (num < 0 || num > list->data)
{
printf("位置超范围!\n");
}
printf("请输入要修改的值:");
scanf("%d", &temp_data);
for (int i = 0; i < num; i++)
{
list = list->next;
}
list->data = temp_data;
printf("修改成功!\n");
}
// 链表转置:将原表从头节点处拆开,遍历头节点后每一个节点,每次将要遍历的节点'头插入'头节点中
void linkListReverse(Node* list)
{
Node* current = list->next;//在断开前,保存后面第一个节点
list->next = NULL;
while (current)//遍历原表
{
//将原表每一个节点头插入空的头节点
Node* next_node = current->next;//在插入之前保存current下一个结点,不然current指向改变后,其后续节点找不到了
current->next = list->next;//调整当前遍历节点的next
list->next = current;//头节点指向新插入节点
current = next_node;//移动到下一个节点
}
printf("转置完成!\n");
}
// 排序
// 思路:
// 根据平均分降序排序
// 思路:将头节点和后续节点分开,对后续无头节点进行遍历,
// 将遍历节点重新插入头节点后面,每次插入时要考虑顺序
// 这样要插入的链表就是一个已经排序好的链表
void sortLinkList(Node* list)
{
// 将头节点和后续节点分开前,要保存后续第一个节点位置,否找找不到后面
Node* current = list->next;
//将头节点和后续节点进行分开
list->next = NULL;
//对后续无头节点进行遍历,将每个节点重新插入头节点后面,插入时考虑顺序
while (current)
{
//每个节点插入前,都要提前保存下一个节点,因为后面current的next发生变化
Node* current_next = current->next;
//头节点指针复制品,用来移动的,指向头节点
Node* sorted_ptr = list;
//节点插入时,要考虑插入位置,当第一个位置不合适就去下一个位置,所以要对已排序链表进行遍历
while (sorted_ptr)
{
// 什么时候进行插入?
//1.当已排序链表为空时直接插入(条件1)
//2.当遍历已排序链表最后位置时,直接插入(条件1)
//3.移到合适位置插入(条件2)
if (sorted_ptr->next == NULL || current->data < sorted_ptr->data)
{
//头插操作
current->next = sorted_ptr->next;
sorted_ptr->next = current;
break;
}
else
{
//不符合插入条件时指针后移
sorted_ptr = sorted_ptr->next;
}
}
//发生插入后,current指针后移,移到下一个待插入节点
current = current_next;
}
printf("排序完成!\n");
}
// 遍历
void printList(Node* list)
{
list = list -> next; //list++
while (list)
{
printf("%d->", list -> data);
list = list -> next; //list++
}
printf("NULL\n");
}
int main(int argc, char const *argv[])
{
Node* list = initList();
isNullList(list);
insertNode(list);
insertNode(list);
insertNode(list);
insertNode(list);
insertNode(list);
// delete(list);
printList(list);
isNullList(list);
// searchNode(list);
modifyNode(list);
printList(list);
isNullList(list);
// printf("节点数:%d\n", list->data);
linkListReverse(list);
printList(list);
sortLinkList(list);
printList(list);
noneNode(list);
isNullList(list);
return 0;
}