单链表由各个内存结构通过一个指针链接在一起组成,每一个内存结构都存在后继内存结构(链尾除外),内存结构由数据域和指针域组成。
下文介绍了链表的一些基本操作,所有代码均在子函数中编写,文章最下方附带完整代码,Copy即可食用。
一、链表的创建
说到链表,肯定少不了结构体,首先第一步我们要创建结构体(头文件不算数嗷)。接下来就要创建头结点,所谓头结点,其实就是创建一个指针来指向一块内存空间 如下文所示:
typedef struct Node{
int data; //数据域(你可以理解为存放数据的位置)
struct Node *next; //指针域(存放下一个结点的地址)
}Node;
LinkList create(){
//定义一个指针,为指针 申请一块空间
Node *head = (Node *)malloc(sizeof(Node));
//判断空间是否开辟成功
if(!head){
printf("空间申请失败!\n");
return NULL;
}
//头结点,指针域值为 NULL
head->next = NULL;
//返回 头结点 指针
return head;
}
二、链表的输入
链表的数据(输入/插入)与数组思想一样,利用scanf录入数据值,不同的是数组是直接放入下标位空间,而链表是将所录入的数据放入到了结构体中的数据域中,然后令移动至指针域所指向的下一个结点空间,继续输入,注意 最后一位结点记得将指针域赋空值 即NULL,如下文所示:
void input(Node *head){
//用来判断头结点是否开辟成功 若不成功则结束程序
if(!head){
return;
}
//这里需要注意 链表是通过头结点一步一步移动寻找数据的,所以需要再创建一个指针指向头结点,利用这个指针进行操作,否则会造成数据丢失的情况。
//创建指针 指向头结点
Node *q = head;
//这里就以录入5个数据为例
for(int i=0;i<5;i++){
Node *p = (Node *)malloc(sizeof(Node)); //开辟新的结点
printf("请输入数据:");
scanf("%d",&p->data); //这里就是输入数据的位置,注意需要放地址
//这里利用的是 尾插法 连接的链表,(尾插法即:将结点连接至链表尾部)
q->next = p; //q指向的是头结点,头结点的指针域存放p的地址
//令q结点移动至它本身指针域存放地址的位置 即p的位置,循环结束下一次循环再次创建新的p结点
q = q->next;
}
//这里因为循环录入已经结束,所以将最后一个结点的指针域赋空值
q->next = NULL;
}
三、链表的查找
查找数据即遍历链表并一步步的进行比较,这段比较简答,上代码:
void find(Node *head){
if(!head){
return;
}
//头结点所指向的 next 是 "首元结点",即:链表中存储第一个数据元素的节点。
Node *q = head->next;
int value;
printf("请输入待查找值:");
scanf("%d",&value);
//循环条件 q 等价于 q != NULL ,即:q存在则为真 返回1;不存在则 为假 返回0。
while(q) {
if(q->data == value){ //若遍历至 数据域数据与待查找值 相同则输出
printf("%d存在数组中!",value);
return;
}
q = q->next; //若没有找到 结点移动至下一位 继续循环查找
}
printf("%d不存在数组中!",value); //循环结束完毕若没有执行 return 结束程序,则输出未找到。
}
四、链表的删除
链表中删除数据 即待删除数据的直接前驱结点指向待删除数据的后一位,即直接前驱结点的指针域存放待删除数据后一位结点的地址。
例:
删除前:A->B->C->D
删除结点B 后:A->C->D
需要注意的是:若要删除结点,必须要知道这个结点的直接前驱结点
直接前驱结点:即 结点 的左边第一个结点,如上例,结点C 就是 结点D 的直接前驱结点。
// 根据 元素值 去删除 这个结点
void delete(LinkList head, int value){
if(!head){
return;
}
Node *p = head; // p 指针 指向 头结点的下一个
Node *q;
while( p->next ){
// 若满足条件,则 p 就是待删除元素结点的直接前驱
if( p->next->data == value ){
q = p->next; // 令 q 指向 待删除结点
p->next = q->next; // 直接前驱结点 指向 待删除结点的 后一位结点
free(q); // 此时 释放结点
} else{
//这里写 else 是为了防止两个相邻的元素 都与 待删除元素的值 相同,若只想删除一个则不需要写 else 直接在 if外、while内 写即可
p = p->next;
}
}
}
五、链表的销毁
销毁链表,无非就是 删除结点并将其释放掉 最后再将头结点赋空值,在C语言中将头结点赋空值即会回收空间,但是 严格的来讲 C语言并没有空值这个概念,一般习惯上,指针若是指向地址0便是空值,其他数据若内容是0便是空值。
void destory(Node *head){
if(!head){
return;
}
Node *p = head->next; //创建首元结点指针
//这里与上面同理,判断结点存在则执行
while(p){
// 创建一个新指针 指向 该结点,用来记录结点
Node *q = p->next;
// 将 p 释放掉即可
free(p);
p = q;
}
head->next = NULL; //使头结点的指针域不在存储地址
free(head); //然后将其释放掉
head = NULL; //最后将 head 指针赋空值,系统会回收空间
}
六、链表的排序
链表排序思想和数组排序类似,区别就是数组遍历容易,数据交换也容易,但链表不同链表 (单项链表)只能一个方向遍历,不可以逆序遍历,且不能随机访问,所以排序相对而言比较麻烦,同时链表的数据交换也很麻烦,如果交换两个节点,需要共涉及3个节点,无形中增加了时间复杂度,也可以直接交换节点中的数据,这种方式相对与交换结点而已比较简单,如下文所示:
void bubbleSort(Node *head){
//这里判断头结点是否存在,若不存在则结程序
if(!head){
return;
}
Node *q = head; //创建指针 指向头结点
//这里创建两个同类型指针,用来接收结点的指针域。因为链表中没有下标这一说,所以我们需要替代品进行辅助排序,你们明白就好。
Node *p;
Node *p1;
//这里就以最简单的冒泡排序来讲,利用外层循环来控制比较次数。
for(int i=0; i<5-1 ;i++){
q = head->next;
//内层循环来控制需要比较的数据
for(int j=0; j< 5-i-1 ; j++){
//这里就用到了上面所创建的两个指针,分别指向q 与 q的后一位。
p = q;
p1 = q->next;
//利用所接收到的指针来比较
if(p->data < p1->data){
//这里利用空杯交换来交换数据域的值,简单来说,就是创建一个变量来接收A的值,然后再将B的值赋于A,最后将最开始创建的变量中的值重新赋给B。
int temp = p->data;
p->data = p1->data;
p1->data = temp;
}
//指针后移。令其指向下一个结点的位置
q = q->next;
}
}
}
七、完整代码块
这里为了检测大家对代码的理解,刻意将所有注释删除掉了,如果需要注释的话看上面的代码块即可,话不多说 上代码:
#include<stdio.h>
#include<stdlib.h>
typedef struct Node{
int data;
struct Node *next;
}Node, *LinkeList;
//这里是函数的声明区
LinkList create(Node *head);
void foreach(Node *head);
void insert(Node *head);
void delete_(Node *head);
void find(Node *head);
void bubbleSort(Node *head);
void foreach(Node *head);
void destory(Node *head);
//主函数
int main(){
Node *head = (Node *)malloc(sizeof(Node));
head->next = NULL;
create(head);
foreach(head);
insert(head);
delete_(head);
find(head);
destory(head);
}
//**************************************
LinkList create(Node *head){
LinkList head = (Node *)malloc(sizeof(Node));
if(!head){
printf("空间申请失败!\n");
return NULL;
}
head->next = NULL;
return head;
}
//**************************************
void foreach(Node *head){
if(!head){
return;
}
LinkList p = head->next;
while(p){
printf("%d ",p->data);
p = p->next;
}
}
//**************************************
void insert(Node *head){
if(!head){
return;
}
int num;
printf("请输入要插入的结点数量:");
scanf("%d",&num);
Node *q = head;
for(int i=0; i<num; i++){
LinkList p = (Node *)malloc(sizeof(Node));
printf("请输入数据域的值:");
scanf("%d",&p->data);
q->next = p;
q = q->next;
}
q->next = NULL;
}
//**************************************
void delete_(Node *head){
if(!head){
return;
}
int value;
printf("请输入带删除元素值:");
scanf("%d",value);
Node *p = head;
Node *q;
while( p->next ){
if( p->next->data == value ){
q = p->next;
p->next = q->next;
free(q);
} else{
p = p->next;
}
}
}
//**************************************
void find(Node *head){
if(!head){
return;
}
Node *q = head->next;
int value;
printf("请输入待查找值:");
scanf("%d",&value);
while(q) {
if(q->data == value){
printf("%d存在数组中!",value);
return;
}
q = q->next;
}
printf("%d不存在数组中!",value);
}
//**************************************
void bubbleSort(Node *head){
if(!head){
return;
}
Node *q = head;
Node *p;
Node *p1;
for(int i=0; i<5-1 ;i++){
q = head->next;
for(int j=0; j< 5-i-1 ; j++){
p = q;
p1 = q->next;
if(p->data < p1->data){
int temp = p->data;
p->data = p1->data;
p1->data = temp;
}
q = q->next;
}
}
}
//**************************************
void destory(Node *head){
if(!head){
return;
}
Node *p = head->next;
while(p){
Node *q = p->next;
free(p);
p = q;
}
head->next = NULL;
free(head);
head = NULL;
}
最后,在这里祝大家的代码永远不报错 ^v^ (其实是在对我说……)