可以直接通过代码和代码注释的结合来学习并理解链表的基本逻辑,比单纯的看文字理解代码更有乐趣,不过笔者建议在看代码之前可以先去本链接下的视频学习下链表会事半功倍哦~
视频来源B站大佬:@孤烟creater发布的视频
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
/****************************************单链表********************************/
typedef struct List_Node //定义一个链表属性的结构体(节点)
{
int list_val; //存储链表内节点的值(数据域)
struct List_Node* next_node; //名为next_node的指针指向该节点的下一个节点的地址(指针域)
}List_Node_t; //链表属性结构体的别名
/*创建一个有n和节点的单链表*/
List_Node_t* Single_Creat_List(List_Node_t* head,int n)
{
//创建头节点
//head = (List_Node_t*)malloc(sizeof(List_Node_t));//为头节点申请内存空间
head->list_val = 1000; //头节点值初始化为0
head->next_node = NULL; //头节点指向下一个节点的地址为空(不指向)
//开始添加节点构成一个单链表
List_Node_t* current = head; //设置当前正在操作的节点为头节点,从头节点开始
for (int i = 0; i < n; i++)
{
List_Node_t* new_node = (List_Node_t*)malloc(sizeof(List_Node_t));//创造一个新的节点(还没有进入链表中),为节点申请内存空间
new_node->list_val = i * 20; //为新节点的数据域赋值
new_node->next_node = NULL; //指针置空,因为还没有成为链表中的一员
current->next_node = new_node; //将当前正在进行操作的节点的指针指向下一个节点的地址(比如现在正在操作的是头节点,那我就把头节点的指针域赋上它正要指向的节点的地址的值(如:它指向的那个节点的地址是:0x0025)
current = current->next_node; //让当前链表的节点的地址后移一个到它的下一节点去(就是说:我让指向当前的正在操作的节点的地址的指针*current等于当前的节点的指针域中的指针变量所指向的下一个节点的地址),这样,我就可以开始对头节点之后的节点进行操作的,
//也就是说当前节点原本是头节点,经过这个之后,当前节点就变成了头节点的下一个节点了
}
return head;
}
/*遍历并打印每个节点的值*/
void Ergodic_List(List_Node_t* head)
{
int j=0;
List_Node_t* current = head; //设置当前正在操作的节点为头节点,从头节点开始
//遍历链表并输出节点值
printf("开始输出节点\n");
current = head;//从头节点开始
while (current->next_node != NULL)//判断是否遍历结束
{
printf("第%d个节点的值为:%d\n", j++, current->list_val);
current = current->next_node;
}
}
/*释放整个链表内存空间*/
void Free_List(List_Node_t* head)
{
//释放内存,因为这只是个测试代码,创建成功后打印出来就不会再用了,所以需要把真个链表内存都释放了
List_Node_t* current = head; //设置当前正在操作的节点为头节点,从头节点开始
while (current->next_node != NULL)//判断是否遍历完成
{
List_Node_t* temp = current;//创建一个中间变量来传递指针,这样就能保证current的指针能一直传递下去,相当于让*current作为一个遍历的工具
current = current->next_node;//移向下一个节点
free(temp);//释放已经遍历过的节点指针
}
}
/*将要添加的元素添加到指定的链表处*/
List_Node_t* List_Node_add(List_Node_t* head, int val,int pos)
{
int i = 0;
List_Node_t* new_node = (List_Node_t*)malloc(sizeof(List_Node_t));//创建一个新节点
new_node -> list_val = val; //为新节点数据域赋值
new_node -> next_node= NULL; //将新节点指针域置空
if (head == NULL) //如果链表的头节点不存在,即链表为空
{
head = new_node; //让新建的节点成为该链表的头节点
}
else //如果是添加到链表中间
{
List_Node_t* prev = NULL; //用于存放要插入的节点的地址的指针
List_Node_t* cur = head; //开始从头节点遍历
while (cur != NULL&&i<pos) //遍历到尾节点或者到达要插入的节点处时,就不再进行遍历
{
prev = cur; //获取要插入的节点的属性(前一个)
i++; //遍历每个节点的序号
cur = cur->next_node; //向后移动一个节点的位置(后一个)
}//这个while的目的是为了得到两个连续的节点的地址
if (prev == NULL) //如果是插入到头节点之前的话,比如pos小于
{
new_node->next_node = head; //新节点的的指针域就会指向原头节点的地址成为新的头节点
head = new_node; //新的节点正式变为了头节点,而头节点没被释放同时还是被新的头节点指针域指向,所以.老的头节点变为了第二个节点
}
else //插入到链表中间
{
prev->next_node = new_node; //让前一个节点指向新节点
new_node->next_node = cur; //让新节点指向后一个节点
}
}
return head;
}
/*删除指定的元素及其所在的节点(常用):会删除该链表上的所有的数据域为val的节点*/
List_Node_t* List_Node_remove_data(List_Node_t* head, int val)
{
bool val_flag = true;
while (val_flag)
{
if (head == NULL) //如果链表不存在
{
return head; //直接退出函数
}
if (head->list_val == val) //如果要删除的是头节点
{
List_Node_t* temp = head;
head = head->next_node; //将头节点之后的那个节点改为现在的链表的头节点,目的是如果直接删除头节点,而在这之后还有相同的值的话,会继续下去,防止卡死
temp->next_node = NULL; //头节点指针域直接指向NULL,和链表相断开(非必要)
free(temp);
return head;
}
List_Node_t* current = head; //从头节点开始遍历,当前节点为头节点,一般定义了当前节点之后都会选择从头节点开始,即current=head
while (current->next_node != NULL && current->next_node->list_val != val)//找到要想删除的节点的前一个节点(这个while里面是对要删除的那个节点的判断判断)
{
current = current->next_node; //一直后移遍历
if (current->next_node == NULL)
{
val_flag = false;
}
}
if (val_flag == false)
{
break;
}
List_Node_t* del_node = current->next_node; //此时的current就是要删除的那个节点的前一个节点,这里的操作是保存要删除的节点的地址,为了让这个节点在离开链表后能被做其他的利用
current->next_node = current->next_node->next_node; //这里的操作是直接让已经被删除了的节点的前一个节点的指针域直接指向那个已经被删除了的节点的后一个节点的地址,由此让它们连接起来
delete del_node; //彻底删除这个节点
del_node = NULL;
}
return head;
}
/*删除指定的位置及其所在的节点(不常用)*/
List_Node_t* List_Node_remove_pos(List_Node_t* head, int pos)
{
int i = 0;
if (head == NULL) //如果链表不存在
{
return head;
}
if (pos == 0) //如果要删除头节点
{
List_Node_t* temp = head; //将头节点的地址暂存,因为下面会对头节点进行操作
head = head->next_node; //新的头节点设置为被头节点指向的那个节点,即第二个节点
temp->next_node = NULL; //将老头节点指向NULL,和链表断开
free(temp); //释放老头节点内存空间
return head;
}
List_Node_t* current=head; //当前节点为头节点开始遍历
List_Node_t* perv =NULL; //当前节点的上一个节点
while (current->next_node != NULL && i < pos) //如果当前节点为尾节点且找到了要删除的节点(这样做的目的可以防止在不知道链表长度的时候给出的删除的节点的范围超出)
{
perv = current; //当前节点的前一个节点
i++; //当前节点的序号
current = current->next_node; //当前节点
};
perv->next_node = current->next_node; //被删除节点的上一个节点指向被删除节点的下一个节点
current->next_node = NULL; //被删除的节点置空
delete current; //彻底删除
return head;
}
/*修改指定的节点的元素*/
List_Node_t* List_Node_modfiy(List_Node_t* head, int val, int pos)
{
int i = 0;
if (head == NULL)
{
return head;
}
List_Node_t* current = head;
while (current->next_node != NULL && i < pos) //遍历直到尾节点或者要修改的节点结束
{
i++;
current = current->next_node;
}
current->list_val = val; //直接修改当前节点的值即可
return head;
}
/*修改指定的所有元素为其他元素*/
List_Node_t* List_Data_modfiy(List_Node_t* head, int origin_val, int define_val)
{
if (head == NULL)
{
return head;
}
List_Node_t* current = head;
while (current->next_node != NULL) //直接遍历整个链表直到尾节点结束
{
if (current->list_val == origin_val) //替换所有要替换的元素
{
current->list_val = define_val;
}
current = current->next_node;
}
return head;
}
/*以函数的形式*/
int main(void)
{
printf("Author:新照不萱\n");
printf("Publication Date :2023-06-05\n");
List_Node_t* list_head=(List_Node_t*)malloc(sizeof(List_Node_t));//为头节点分配内存
printf("Creat a new linked_list:\n");
List_Node_t* list_handled=Single_Creat_List(list_head,10); //创建一个单链表
Ergodic_List(list_handled); //遍历并打印链表
printf("Add a new Node:\n");
list_handled=List_Node_add(list_handled,20,6); //在指定位置添加一个新节点
Ergodic_List(list_handled); //遍历并打印链表
printf("Delect a Node by data:\n");
list_handled = List_Node_remove_data(list_handled, 1000); //删除指定元素的节点
Ergodic_List(list_handled); //遍历并打印链表
printf("Delect a Node by posation:\n");
list_handled=List_Node_remove_pos(list_handled, 3); //删除指定位置的节点
Ergodic_List(list_handled); //遍历并打印链表
printf("Modfiy a Node by posation:\n");
list_handled = List_Node_modfiy(list_handled,600,5); //修改指定位置的节点
Ergodic_List(list_handled); //遍历并打印链表
printf("Modfiy a Node by data:\n");
list_handled = List_Data_modfiy(list_handled,0,900); //修改所有的元素为自定义元素
Ergodic_List(list_handled); //遍历并打印链表
printf("Free Linklist\n");
Free_List(list_handled); //释放链表(没有这个的话,软件运行会直接卡死)
printf("Free Finlish\n");
}