引入:对比单链表和数组
单链表:
优点:改变数据很方便(因为只用将next指向下一个的开头即可下面会有所提及)
缺点:访问慢(因为是要逐个逐个地访问)
数组:
优点:访问数据快(因为只需找到下标即可)
缺点:改变数据即删除插入麻烦
准备活动:
结构体创还能, 头结点,尾结点,链表的工作原理
1.对结构体的声明和重命名
结构体声明大体没什么变化,
主要区别是“struct Node* next”
这里定义了一个结构体指针用来链接个个结构体
2. 头结点,尾结点同理
3. 工作原理:通过指针域将结构体一一串联
1.单链表的创建
ListNode* Creat(int n)
{
ListNode* head, * tail, * p;
head = (ListNode*)malloc(sizeof(struct Node));//分配头结点空间
tail = head;//意思是尾指针指向表头
head->data = NULL;//head data空值
head->next = NULL;//同理
//int a = 0;
int i = 0;
while (i != n)//while和for都没影响,主要是main函数中的n忘了赋值
{
int a = 0;
scanf("%d", &a);
p = (ListNode*)malloc(sizeof(ListNode));//给每个分配空间,来一个分配一个
p->data = a;//赋值
p->next = NULL;//未赋值的令为空指针
tail->next = p;//(第一次)指向头结点的尾指针指向下一个的data值,使其连接起来,后面以此类推
tail = p;//设置新的表尾
i++;
}
tail->next = NULL;//可有可无,前面的p->next = NULL,然后把p的赋给tail,已经完成
return head;//返回一个结构体指针
}
1).定义结构体指针 :头结点head,尾结点tail,和p
2)头结点处理:给头结点分配空间,用malloc函数,此时注意将尾结点tail指向head,并把head的data和next令为空指针, (因为head指针不用来赋值,赋值用p)此后head就可以不动了,
3)对p的赋值:接着用for或者是while循环对结构体赋值,注意的是,每次在赋值之前,要给p重新分配空间,这样才可以使用,将数据域赋值以后,将指针域赋值为NULL,
4)对tail的处理:此时的tail指向的是head,把tail的指针域next指向p的数据域,即是将此时head的next的指针域的地址赋值以p的数据域,然后把p赋值给tail,即将tail令为新的表尾
(重点:如果要想好好理解,则应该把tail->next理解为head的指针域,因为此时tail等同于head)
5)返回head操作:其实tail->next= NULL
可以忽略不计,因为,将p赋给tail前,p的指针域已经为NULL
okok,这下我们就先完成了链表的创建喽
下一站:链表的遍历
2.单链表的遍历
void ListTravel(ListNode* L)//遍历链表
{
ListNode* p = L->next;
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
遍历的话,就相较于其他的会加单一点
1)首先,要得到我们创建的链表的头结点,在main函数中建一个即可
2)其次,我们要新建一个结构体指针,命名啥都可以,为方便还是名为p吧
加入while循环,只要p!=NULL的话,就打印即可,然后p指向p->next,进入下一个节点
3)注意的事,要打印\n,因为每做完一次,都要打印一遍
遍历就完啦,简单吧
下一站:删除
3.单链表的删除
void DelectNode(ListNode* head, int index)//删除链表,要传目标的index,思路,找到目标前一个的next,把目标free掉,再把前一个的next和目标后一个的连接起来,同时要判断是否会出界之类的
{
int i = 1;
ListNode* p = head;
while (p->next != NULL && i < index)
{
p = p->next;
i++;
}
if (p->next == NULL || i > index||p == NULL)//判断无效的情况
{
printf("删除失败");
return NULL;
}
ListNode* q = p->next;
p->next = q->next;
free(q);
}
1)要删除链表中的一部分,首先我们要找到要删除的结点,先定义一个结构体指针p指向head,这里我们可以使用while循环,来找到要删结点的前一个结点
比如:输入数据为1 2 3 4 5
删除目标数据:3
结果呈现:1 2 4 5
据分析得知,3是在第三个位置上的,所以我们要找的点为2
运用while循环,如果循环没有终止,p指向下一个节点,i++
2)while循环结束后,我们就找到了第二个节点,再删除之前要判断是否会出现越界或者其他之类的情况,否则删除失败,return NULL返回空指针,本质上使得函数终止,同时也什么也返回不了
3)删除实例,咱们定义一个新的结构体指针q,指向下一个节点即要删除的点,然后把q给free掉
,也就是删除操作,然后把p的next指向q的next,也就是把要删除节点的下一个结点的指针赋给p的指针域,(这里是指针传指针,不同于之前的地址传指针)
4)最后在main函数中使用我们的遍历就可以看到结果啦!
最后呢,我们会讲到单链表的插入
目前也就学到这里,见谅见谅
4.单链表的插入
void InsertList(ListNode*head,int index2)//插入链表,自己思考:在创建一个链表,把头结点的next和结点next结合,尾结点和后一个结合
{
ListNode* p = head;
int i = 0;
while (p != NULL && i < index2 - 1)
{
p = p->next;
i++;
}
if (p == NULL || p->next == NULL || i > index2 - 1)
{
printf("插入失败");
return NULL;
}
ListNode* new = (ListNode*)malloc(sizeof(ListNode));
new->data = 3;
new->next = p->next;
p->next = new;
}
1)跟单链表的删除一样,要想实现链表的插入,首先要先找到插入点,同理用while循环即可
2)伴随的也还是要判断是否越界等问题,同理上述
3)比如说
在删除链表后,原表表现为:1 2 4 5
那我们想插入3的话
就恢复成1 2 3 4 5
4)首先,建立一个新的结构体指针new,并为其分配好空间,赋值后
然后我们就可以开始操作了,首先我们把new的指针域赋值以p的指针域,使new的指针域指向4的位置,然后再将p的指针与赋值以new,使p的指针指向new的data位置,通俗的来讲嘛就是,
p->next原来指向4,用new->next取代后,new->next指向4,然后呢,p->next也不可以在指向4,所以将矛头调转,指向new的数据域,也就是3
下面引用B站上一位up的图片(kkghcty),方便大家理解
5.总代码,软件为VS2022,所以会有#define _CRT_SECURE_NO_WARNINGS这个
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct Node {
int data;
struct Node* next;
}ListNode;
ListNode* Creat(int n);//创建来链表
void ListTravel(ListNode* L);//遍历链表
void DelectNode(ListNode* head, int index);//删除链表
void InsertList(ListNode*head, int index2);//插入链表
int main()
{
int n = 0;
scanf("%d", &n);
ListNode* head = Creat(n);//创建链表
ListTravel(head);//遍历链表
int index1 = 3;//表示删除结点为第三个
DelectNode(head,3);//删除链表
ListTravel(head);//遍历链表打印
int index2 = 3;//表示插入结点为第三个
InsertList(head, index2);
ListTravel(head);//遍历链表打印
}
ListNode* Creat(int n)
{
ListNode* head, * tail, * p;
head = (ListNode*)malloc(sizeof(struct Node));//分配头结点空间
tail = head;//意思是尾指针指向表头
head->data = NULL;//head data空值
head->next = NULL;//同理
//int a = 0;
int i = 0;
while (i != n)//while和for都没影响,主要是main函数中的n忘了赋值
{
int a = 0;
scanf("%d", &a);
p = (ListNode*)malloc(sizeof(ListNode));//给每个分配空间,来一个分配一个
p->data = a;//赋值
p->next = NULL;//未赋值的令为空指针
tail->next = p;//(第一次)指向头结点的尾指针指向下一个的data值,使其连接起来,后面以此类推
tail = p;//设置新的表尾
i++;
}
tail->next = NULL;//可有可无,前面的p->next = NULL,然后把p的赋给tail,已经完成
return head;//返回一个结构体指针
}
void ListTravel(ListNode* L)//遍历链表
{
ListNode* p = L->next;
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
void DelectNode(ListNode* head, int index)//删除链表,要传目标的index,思路,找到目标前一个的next,把目标free掉,再把前一个的next和目标后一个的连接起来,同时要判断是否会出界之类的
{
int i = 1;
ListNode* p = head;
while (p->next != NULL && i < index)
{
p = p->next;
i++;
}
if (p->next == NULL || i > index||p == NULL)//判断无效的情况
{
printf("删除失败");
return NULL;
}
ListNode* q = p->next;
p->next = q->next;
free(q);
}
void InsertList(ListNode*head,int index2)//插入链表,自己思考:在创建一个链表,把头结点的next和结点next结合,尾结点和后一个结合
{
ListNode* p = head;
int i = 0;
while (p != NULL && i < index2 - 1)
{
p = p->next;
i++;
}
if (p == NULL || p->next == NULL || i > index2 - 1)
{
printf("插入失败");
return NULL;
}
ListNode* new = (ListNode*)malloc(sizeof(ListNode));
new->data = 3;
new->next = p->next;
p->next = new;
}
目前学习就到这里,感谢大家观看!
求花花!