目录
链表想当于一个功能强大的数组,可随意的对链表中的数据进行插入、删除、添加数据,但是链表的建立运用难度较大,需多构建、处理链表以熟练运用。
链表的构建建立在结构体的基础上,
其结构体的创建形势为
struct Linklist{
int data;
struct Linklist *next;
};
在结构体中又定义一个结构体指针变量指向结构体,多次这样指向地址就构建成立链表,相当于一条 锁链。
如果将链表比作一节列车,车厢就是数据,车厢与车厢之间连接的钩锁就是next指向的地址
链表的构建的关键在与利用结构体在储存数据的同时用指针记录下一个结点的地址,链表构建的主要操作有结构体的建立、头结点的开辟、数据的输入、下一个结点的记录、尾节点的截至。
在开辟结点、删除结点的操作中需要
使用malloc函数开辟结点空间、free函数释放结点空间,其包含在头文件stdlib.h中;
链表建立学习运用从单向链表开始。
链表的学习核心在于要理解链表的建立、结点的插入、删除都是直接对地址进行处理,而不是之前处理数据,例如一条已经建好的链表头结点为head,创建j结构体指针变量flag,flag=head,是直接指向该链表
例如 某建筑公司对一条街区进行拆迁,街区中有三列房屋,flag1、flag2、flag3,老板让你对其中一列房屋进行拆迁,是不是只需要告诉你这列房屋的地址那条街道多少号,你就能找到这列房屋的所在地,而不是说给你一条一模一样的街(只要你乖给你买条gai~)实物让你对照这去寻找
链表也是同样的,假设链表就是计算机存储条这个街区里头的一条小街道,head传递的就是这条街第一个房屋的地址(即头结点),你相应的用这个地址找到的就是这条街,flag=head,就是相当于把老板用微信发给你的地址你复制一遍信息蛮,它实践指向的街(即链表)是没有变化的,flag=head,flag指向的就是这条已经建立好的链表了,而不是你重新开辟头结点。
一、链表建立——头插法
链表的构建法之一的头插法适用于需逆序处理的数据
其主要思想在于每次输入数据进行构建时,新数据成为新的头结点
具体实现如下
#include<stdio.h>
#include<stdlib.h>
#define area sizeof(struct Linklist)//使用宏定义给结构体的空间大小起“别名”方便在开辟结点空间;
struct Linklist{
int data;
struct Linklist *next;
};
int main()
{
int N;
Linklist *head,*end,*node;
head=(Linklist *)malloc(area);//开辟头结点
end=head;//用其他变量记录头结点,在对链表进行处理时尽量不要处理头结点
end->next=NULL;//尾结点的指针指向空结点,建立其尾结点,输入数据时插在尾结点前因此叫头插法
scanf("%d",&N);
for(int i=0;i<N;i++)
{
node=(struct Linklist *)malloc(area);//开辟结点
scanf("%d",&node->data);//结点输入数据
node->next=head->next;//node连接到head->next(即end)
head->next=node;//头结点连接到node结点 /此时相当与由head->end
//变成head->node(1)->end
//再进行循环后变成head->node(2)->node(1)->end
//每次接入结点都是接着原有的链表的头部,因此为逆序
}
return 0;
}
二、链表建立———尾插法
链表的构建法之一的尾插法适用于严格的链表数据结构,数据为顺序接入,每次接入新的结点都接在链表的尾部,因此为严格的顺序接入
尾插法的主要思想在于接入新的结点后将当前结点移动到新结点的位置,以便其进行下一步的输入
具体实现如下
#include<stdio.h>
#include<stdlib.h>
#define area sizeof(struct Linklist)//使用宏定义给结构体的空间大小起“别名”方便在开辟结点空间;
struct Linklist{
int data;
struct Linklist *next;
};
int main()
{
int N;
Linklist *head,*end,*node;
head=(struct Linklist *)malloc(area);//开辟头结点
end=head;//尾结点,在未输入数据时使其指向头结点完成链表的建立此时链表无数据
scanf("%d",&N);
for(int i=0;i<N;i++)
{
node=(struct Linklist *)malloc(area);//开辟新结点
scanf("%d",&node->data);//输入数据至新结点
end->next=node;//将新节点连入链表
end=node;//使尾结点移动到下一个结点
}
end->next=NULL;//完成链表数据的输入后使尾结点指向空结点
return 0;
}
三、链表的输出
链表的输出较简单,主要思想在于输出完当前结点后移动到下一个结点输出
具体实现如下
头插法
#include<stdio.h>
#include<stdlib.h>
#define area sizeof(struct Linklist)//使用宏定义给结构体的空间大小起“别名”方便在开辟结点空间;
struct Linklist{
int data;
struct Linklist *next;
};
int main()
{
int N;
Linklist *head,*end,*node;
head=(Linklist *)malloc(area);//开辟头结点
end=head;//用其他变量记录头结点,在对链表进行处理时尽量不要处理头结点
end->next=NULL;//尾结点的指针指向空结点,建立其尾结点,输入数据时插在尾结点前因此叫头插法
scanf("%d",&N);
for(int i=0;i<N;i++)
{
node=(struct Linklist *)malloc(area);//开辟结点
scanf("%d",&node->data);//结点输入数据
node->next=head->next;//node连接到head->next(即end)
head->next=node;//头结点连接到node结点 /此时相当与由head->end
//变成head->node(1)->end
//再进行循环后变成head->node(2)->node(1)->end
//每次接入结点都是接着原有的链表的头部,因此为逆序
}
head=head->next;//由于在建立链表时为对头结点输入数据因此在输出程序开始时时头结点移动到下一个结点
for(int i=0;i<N;i++)
{
printf("%d ",head->data);
head=head->next;
}
return 0;
}
输出为
尾插法
#include<stdio.h>
#include<stdlib.h>
#define area sizeof(struct Linklist)//使用宏定义给结构体的空间大小起“别名”方便在开辟结点空间;
struct Linklist{
int data;
struct Linklist *next;
};
int main()
{
int N;
Linklist *head,*end,*node;
head=(struct Linklist *)malloc(area);//开辟头结点
end=head;//尾结点,在未输入数据时使其指向头结点完成链表的建立此时链表无数据
scanf("%d",&N);
for(int i=0;i<N;i++)
{
node=(struct Linklist *)malloc(area);//开辟新结点
scanf("%d",&node->data);//输入数据至新结点
end->next=node;//将新节点连入链表
end=node;//使尾结点移动到下一个结点
}
end->next=NULL;//输入完成后将链表指向空
head=head->next;//由于在最开始开辟头结点时未对其进行数据输入因此在输出程序进行前就应将其移动到下一个结点
for(int i=0;i<N;i++)
{
printf("%d ",head->data);
head=head->next;
}
return 0;
}
输出为
四、链表结点的删除
链表结点的删除的主要思想在于将当前要删除的结点的上一个结点指向的地址指向要删除的结点的下一个结点,例如共三个结点node1、node2、node3,将node1->next=node3,同时使用free函数释放head2结点
代码实现为
#include<stdio.h>
#include<stdlib.h>
#define area sizeof(struct Linklist)//使用宏定义给结构体的空间大小起“别名”方便在开辟结点空间;
struct Linklist{
int data;
struct Linklist *next;
};
int main()
{
int N,M;
Linklist *head,*end,*node,*t,*k;
head=(Linklist *)malloc(area);//开辟头结点
end=head;//用其他变量记录头结点,在对链表进行处理时尽量不要处理头结点
end->next=NULL;//尾结点的指针指向空结点,建立其尾结点,输入数据时插在尾结点前因此叫头插法
scanf("%d",&N);
for(int i=0;i<N;i++)
{
node=(struct Linklist *)malloc(area);//开辟结点
scanf("%d",&node->data);//结点输入数据
node->next=head->next;//node连接到head->next(即end)
head->next=node;//头结点连接到node结点 /此时相当与由head->end
//变成head->node(1)->end
//再进行循环后变成head->node(2)->node(1)->end
//每次接入结点都是接着原有的链表的头部,因此为逆序
}
scanf("%d",&M);//输入要删除的结点
k=head->next;//在链表的处理中尽量不要对头结点直接处理,使用其他变量进行间接处理
for(int i=1;i<M;i++)//循环控制在要进行删除的结点的上一个结点
{
t=k;//用变量t记录下当前结点,当k到达要进行删除的结点时,t位于上一个结点;
k=k->next;
}
t->next=k->next;//进行地址存放的跳跃,即“共三个结点node1、node2、node3,将node1->next=node3”
free(k); //释放要删除的结点空间完成删除
head=head->next;
for(int i=0;i<N-1;i++)
{
printf("%d ",head->data);
head=head->next;
}
return 0;
}
输出为
五、链表结点的插入
链表的结点的插入的主要思想于链表结点的思想类似,例如你需要在node1、node3中插入node2,
是不是只需时node1->next=node2;node2->next=node3,就将结点插入到链表中了。
实现为
#include<stdio.h>
#include<stdlib.h>
#define area sizeof(struct Linklist)//使用宏定义给结构体的空间大小起“别名”方便在开辟结点空间;
struct Linklist{
int data;
struct Linklist *next;
};
int main()
{
int N,M;
Linklist *head,*end,*node,*t,*k;
head=(Linklist *)malloc(area);//开辟头结点
end=head;//用其他变量记录头结点,在对链表进行处理时尽量不要处理头结点
end->next=NULL;//尾结点的指针指向空结点,建立其尾结点,输入数据时插在尾结点前因此叫头插法
scanf("%d",&N);
for(int i=0;i<N;i++)
{
node=(struct Linklist *)malloc(area);//开辟结点
scanf("%d",&node->data);//结点输入数据
node->next=head->next;//node连接到head->next(即end)
head->next=node;//头结点连接到node结点 /此时相当与由head->end
//变成head->node(1)->end
//再进行循环后变成head->node(2)->node(1)->end
//每次接入结点都是接着原有的链表的头部,因此为逆序
}
printf("请输入你要在其后添加数据的结点位置\n");
scanf("%d",&M);
t=head;
for(int i=1;i<=M;i++)
{
t=t->next;
k=t;
}//将t移动到要在后面插入数据的结点位置
k=k->next;
node=(struct Linklist *)malloc(area);//开辟新结点用来接收需要输入的数据
printf("请输入你要插入的数据:\n");
scanf("%d",&node->data);//接收数据
t->next=node;//将第M个结点指向新结点
node->next=k;//新结点指向原链表第M+1个结点,完成插入
head=head->next;
for(int i=0;i<N+1;i++)
{
printf("%d ",head->data);
head=head->next;
}
return 0;
}
输出为
六、链表结点数据的修改
链表结点数据修改较简单,咱直接上代码
#include<stdio.h>
#include<stdlib.h>
#define area sizeof(struct Linklist)//使用宏定义给结构体的空间大小起“别名”方便在开辟结点空间;
struct Linklist{
int data;
struct Linklist *next;
};
int main()
{
int N,M,n;
Linklist *head,*end,*node,*t,*k;
head=(Linklist *)malloc(area);//开辟头结点
end=head;//用其他变量记录头结点,在对链表进行处理时尽量不要处理头结点
end->next=NULL;//尾结点的指针指向空结点,建立其尾结点,输入数据时插在尾结点前因此叫头插法
scanf("%d",&N);
for(int i=0;i<N;i++)
{
node=(struct Linklist *)malloc(area);//开辟结点
scanf("%d",&node->data);//结点输入数据
node->next=head->next;//node连接到head->next(即end)
head->next=node;//头结点连接到node结点 /此时相当与由head->end
//变成head->node(1)->end
//再进行循环后变成head->node(2)->node(1)->end
//每次接入结点都是接着原有的链表的头部,因此为逆序
}
printf("请输入修改数据的结点位置\n");
scanf("%d",&M);
t=head;
for(int i=1;i<=M;i++)
{
t=t->next;
}//将t移动到要修改数据的结点位置
printf("请输入你要修改的数据:\n");
scanf("%d",&n);//接收数据
t->data=n;//———————————————————————————数据修改完成!!!
head=head->next;
for(int i=0;i<N+1;i++)
{
printf("%d ",head->data);
head=head->next;
}
return 0;
}
输出为
七、总结
单链表的基础操作有链表构建、结点删除、结点插入、结点数据修改、输出等,在链表的构建与处理时需要注意结点的指针指向,多尝试、调试、思考,并且要注意链表前后结点的衔接,防止链表发生断裂。这是一个熟能生巧的过程,遇到不能解决的问题多使用调试功能找出错误点,有针对的进行处理,链表学习使用初期是坐牢过程,但是当自己独立完成链表的构建、修改、插入、删除等一系列操作后,还是很有成就感的。时刻记住,“我是菜鸡”。