PZK学数据结构之链表(史上最详细思路和代码注释)(完整代码我放在最后面了,可以直接跑,方便大家cv编程)
文章目录
前言
在增删的指定节点操作就用到了查,大家可以看完增删的前两个就去看看查是怎么操作的,然后方便对指定节点操作。
本篇文章是交大家如何用结构体指针实现增删查改,
一、链表是什么?
百度百科链表:链表是一种物理存储单元上非连续、非顺序的存储结构,
数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
我的理解:多个有指针域的结构体的通过指针顺序链接在一起, 在内存中的存储是散落的
2.1图解数组和链表的存储
在内存如下存储:
---------------------------------------------------------------------------------------------------------------------------------------------------------------
2.2简单实现链表的两种方法,第一种是用结构体,第二种是用结构体指针
代码如下(示例):
2.2.1实现链表1
#include <stdio.h>
#include <stdlib.h>
struct Test//结构体定义
{
int data;
struct Test *next;
};
int main()
{
struct Test t1={1,NULL};
struct Test t2={2,NULL};
struct Test t3={3,NULL};
t1.next=&t2;//链表精髓在这,每个节点的next存放的是下一个节点的地址。这样就连接起来了。
t2.next=&t3;
}
2.2.1实现链表2
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct node
{
int data;
struct node * next;
}node,*pnode;//node是取别名,pnode是类型,结构体指针类型
//struct node * pnode
//创建头节点,初始化函数。返回头节点的指针。
pnode init()
{
pnode head = (pnode )malloc(sizeof(node)); //用malloc开辟空间,此时pnode == struct node *
if(head == NULL)//此时做一个容错判断,如果没有开辟成功,打印错误
{
perror("head fault");
}
head->data = 0;//开辟空间之后里面是没有值的,最好给数据域和指针域分别赋值
head->next = NULL;//指针没有指向,指向NULL就ok,防止其乱指向
return head;//返回头节点指针,方便我们以后调用
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------
二、本文实现的几个功能
链表的增加
链表的删除
链表的查找
链表的改动
---------------------------------------------------------------------------------------------------------------------------------------------------------------
三、链表的增加
在内存如下演示:
实现思路:(博主比较懒,三种插法只详细写了一个思路,思路都大同小异,我在每个插法代码的前面都画了内存操作图,里面有简单思路,每一行代码里面都给了注释,不理解的可以评论区留言呀)
第一步,创建新节点,然后把传递的值赋值给新节点
pnode pnew = (pnode )malloc(sizeof(node));//用malloc开辟空间,类型是pnode,大小是一个结构体的大小
第二步,写一个容错判断,如果pnew==NULL,说明创建不成功,直接退出
第三步:链接新节点到链表头下一个节点,何为链接,无非就是把下个节点的地址赋值给头节点的next;
pnew->next = head->next;//head->next原本存的是第二个节点的地址,现在把它赋值给新节点,那么是不是新节点的下一个就是原先的第二个节点了,就是实现图中1
第四步:把头结点和新节点连接起来
head->next = pnew;//此时,我们在把新节点和头节点链接起来,这样就是头节点指向新节点了,pnew本身就是一个指针
3.1头插法:每次都在头结点的后面插入
//每次都在头结点的后面增加,简称头插法
/****************************************************************
* 函数名: insertHead
* 参 数: head 链表的头节点指针
* data 需要插入节点数据
* 返回值: 链表中节点个数
* 节点的个数保存在head的data中。
****************************************************************/
int insertHead(node* head,int data)
{
//第一步:创建新节点,存入数据
pnode pnew = (pnode )malloc(sizeof(node));//用malloc开辟空间,类型是pnode,大小是一个结构体的大小
if(NULL == pnew)//容错判断新创建的节点是否成功
{
perror("head NULL");
exit(0);//不成功就输出错误,然后退出
}
pnew->data = data;//把传进来的值赋值给新节点
//第二步:链接新节点到链表头下一个节点,何为链接,无非就是把下个节点的地址赋值给头节点的next;
//head的next为空的情况
pnew->next = head->next;//head->next原本存的是第二个节点的地址,现在把它赋值给新节点,那么是不是新节点的下一个就是原先的第二个节点了
//第三步:head不为空,把头节点的next指针指向新的节点
head->next = pnew;//此时,我们在把新节点和头节点链接起来,这样就是头节点指向新节点了,pnew本身就是一个指针
//当第二步赋值的时候,头节点与原先的第二个节点链接自动断开
//第四步:节点个数加一
(head->data)++;//因为我们头节点的数据域存的是节点个数
//第五步:返回头节点中节点数据;
return head->data;
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------
3.2尾插法:每次都在链表的最后面插入
在内存如下演示:
/****************************************************************
* 函数名: insertEnd
* 参 数: head 链表的头节点指针
* data 需要插入节点数据
* 返回值: 链表中节点个数
* 节点的个数保存在head的data中。
****************************************************************/
int insertEnd(pnode head,int data)
{
//第一步:定义临时指针
node* ptmep = head;
//第二部:遍历链表,找最后一个节点
while( ptmep->next != NULL)
{
ptmep = ptmep->next;//指针偏移
}
//循环完之后,ptemp的next已经为NULL了
//第三步:创建新节点
pnode pnew = (pnode)malloc(sizeof(node));//malloc开辟空间
//第四步:给节点赋值
pnew->data = data;
//第五步:新节点插入到链表末尾
pnew->next = NULL;//尾插法,新节点需放在最后,所以新节点后面没有节点了,指向null
ptmep->next = pnew;//原最后一个节点指向新节点,那么原最后一个节点就成为了倒数第二个节点
//第六步:返回链表长度
(head->data)++;
return head->data;//也可以直接return ++(head->data);一样的效果
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------
3.3指定位置插法:在指定位置插入新节点
在内存如下演示:
/*********************************************
* 函数名:insert
* 参数: head头节点指针
* pos 需要插入的位置
* data 需要插入的数据
* 返回值: 链表的节点个数
* 作用: 在pos的位置插入一个节点
*****************************************************/
int insert(pnode head,int pos,int data)
{
// 第0步:判断pos是否超过节点个数
if(pos > head->data)
{
printf("无法插入指定位置,程序退出\n");
exit(0);
}
// 第一步:定义临时变量,用来遍历到每个节点
pnode ptemp = head;//赋值后,ptemp就相当与头节点,不过只是一个临时变量,程序结束就会释放
// 第二步: 创建节点,赋值
pnode pnew = (pnode)malloc(sizeof(node));
pnew->data = data;
//第三步:找到pos的上一个节点
int i = 0;
for ( i = 0; i < pos-1; i++)//for循环的第二个条件,小于pos,就循环到pos
{
ptemp = ptemp->next;
}
// 第四步:新节点连接到链表中
//4.1 新节点连接到第pos的节点上
pnew->next = ptemp->next;//p->next->next指向pos的下一个节点
//4.2 pos前一个节点(ptemp)的next指向新节点
ptemp->next = pnew;
//第五步:返回节点数
(head->data)++;
return head->data;
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------
四、链表的删除
4.1头删法:删除头结点的最后一个节点
在内存如下演示:
/*********************************************
* 函数名:deleteEnd
* 参数: head头节点指针
* 返回值: 删除后的节点个数
* 作用: 在链表中删除最后一个节点
*****************************************************/
int deleteEnd(pnode head)
{
//第一步:定义临时变量,遍历,判断空
if(IsEmpty(head))
{
printf("链表为空,无法删除\n");
return head->data;
}
pnode ptemp = head;
//第二步:找倒数第二个节点 ptemp->next->next == NULL
while(ptemp->next->next != NULL)
{
ptemp = ptemp->next;
}
//第三步:释放最后一个节点
free(ptemp->next);
//第四步:把删除后剩下的最后一个节点的next=NULL;
ptemp->next = NULL;
//第五步:数据元素个数减1
return --(head->data);
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------
4.2尾删法:删除链表的最后一个节点
在内存如下演示:
/*********************************************
* 函数名:deleteEnd
* 参数: head头节点指针
* 返回值: 删除后的节点个数
* 作用: 在链表中删除最后一个节点
*****************************************************/
int deleteEnd(pnode head)
{
//第一步:定义临时变量,遍历,判断空
if(IsEmpty(head))
{
printf("链表为空没法删除\n");
return head->data;
}
pnode ptemp = head;
//第二步:找倒数第二个节点 ptemp->next->next == NULL
while(ptemp->next->next != NULL)
{
ptemp = ptemp->next;
}
//第三步:释放最后一个节点
free(ptemp->next);
//第四步:把删除后剩下的最后一个节点的next=NULL;
ptemp->next = NULL;
//第五步:数据元素个数减1
return --(head->data);
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------
4.3指定删法:删除链表的指定节点
在内存如下演示:
/*********************************************
* 函数名: deletePos
* 作 用: 删除pos位置的节点
* 参 数: head 链表的头指针
* pos 需要删除的节点位置
* 返回值: 返回链表中的节点个数
**********************************************/
int deletePos(pnode head,int pos)
{
//判断链表是否为NULL
if(IsEmpty(head))
{
printf("链表为null没法删除\n");
return head->data;
}
//第一步:定义临时变量
pnode ptemp = head;
pnode pPos = NULL;
//第二步:找到pos位置的前一个节点(保存pos位置)
for (int i = 0; i < pos -1 ; i++)
{
ptemp =ptemp->next;
if (!ptemp)
{
printf("\n位置不存在\n");
return head->data;
}
}
pPos = ptemp->next; //保存pos位置指针
//第三步:把pos的前一个位置的next指针指向pos下一个位置。
ptemp->next = pPos->next;
//第四步:释放pos位置节点
free(pPos);
pPos = NULL;
//第五步:节点个数减1(返回节点个数)
return --(head->data);
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------
五、链表的查找,可以查找指定值的节点,也可以找指定节点的值
在内存如下演示:
/*********************************************
* 函数名:findNode
* 参数: head头节点指针
* data 需要超找的数据
* 返回值: 如果成功返回找到的data位置的指针
* 如果失败返回NULL
* 作用: 在链表中查找data的所在位置。
*****************************************************/
pnode findNode(pnode head,int data)
{
// 第一步:定义临时变量,用来遍历到每个节点
pnode ptemp = head;
int num = 0; //记录data的位置
//第二步:遍历整改链表
while (NULL != ptemp->next )
{
ptemp = ptemp->next;
num++;
//第三步: 比对是否找到
if(ptemp->data == data)
{
printf("data=%d在%d的位置\n",data,num);
return ptemp;
}
}
printf("%d在链表中没找到\n",data);
return NULL;
}
六、链表的改动,思路图我就没画了本人比较懒能偷一点懒是一点,大致思路就是在五查找的基础上去替换值,可以替换指定位置的值(知道位置用for循环),也可以替换指定值的值(指定值,用while循环,用值对比)
/**********************************************************
* 函数名:changeData
* 参数: head头节点指针
* pos 需要修改的数据的位置
* data 需要修改的数据
* 返回值: 如果成功返回ture,失败返回false
* 作 用:把链表中pos位置的数据修改成data
***********************************************************/
int changeData(pnode head,int pos,int data)
{
// 第0步:判断pos是否超过节点个数,节点5,要是head只有4,就没有5节点来改变
if(pos > head->data)
{
printf("pos超出节点,无法修改\n");
}
// 第一步:定义临时变量,用来遍历到每个节点
pnode ptemp = head;
int i = 0;
//第三步:找到pos节点
for(i=0;i<pos;i++)
{
ptemp = ptemp->next;
}
//上面一步一步偏移完之后,ptemp已经遍历到pos位置了就等与pos
//第四步:替换数据
ptemp->data = data;
//这里最好是弄一个返回值,来返回是否成功,也可以打印出来
return head->data;
}
总结,大家不会可以评论也可以私信我一起讨论,欢迎大家评论指导。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
/*作 者:pzk
功 能:实现链表的增删查改
*第一个功能:头插法,每次都在头节点的后一个位置插入(链接到头节点上)
*第二个功能:尾插法,每次插入都在链表的最后一个节点,使原本指向NULL的最后一个节点指向新节点
*第三个功能:指定位置插入,用for循环,找到指定节点的上一个节点,把新节点插入到指定节点的上一个节点的后面
*第四个功能:改变指定位置的值(也可以用值去查找,把旧值用新值代替,例如我想吧原本存2的指针改为存3)
*第五个功能:查找,查找我们想要的值,并返回指定值的位置的指针
*/
typedef struct node
{
int data;
struct node * next;
}node,*pnode;//node是取别名,pnode是类型,结构体指针类型
//struct node * pnode
//创建火车头,初始化函数。返回火车头的指针。
pnode init()
{
pnode head = (pnode )malloc(sizeof(node)); //用malloc开辟空间,此时pnode == struct node *
if(head == NULL)//此时做一个容错判断,如果没有开辟成功,打印错误
{
perror("head fault");
}
head->data = 0;//开辟空间之后里面是没有值的,最好给数据域和指针域分别赋值
head->next = NULL;//指针没有指向,指向NULL就ok,防止其乱指向
return head;//返回头节点指针,方便我们以后调用
}
//第二步:头插法,在头节点的后面插入一个元素
/****************************************************************
* 函数名: insertHead
* 参 数: head 链表的头节点指针
* data 需要插入节点数据
* 返回值: 链表中节点个数
* 节点的个数保存在head的data中。
****************************************************************/
int insertHead(node* head,int data)
{
//第一步:创建新节点,存入数据
pnode pnew = (pnode )malloc(sizeof(node));//用malloc开辟空间,类型是pnode,大小是一个结构体的大小
if(NULL == pnew)//容错判断新创建的节点是否成功
{
perror("head NULL");
exit(0);//不成功就输出错误,然后退出
}
pnew->data = data;//把传进来的值赋值给新节点
//第二步:链接新节点到链表头下一个节点,何为链接,无非就是把下个节点的地址赋值给头节点的next;
//head的next为空的情况
pnew->next = head->next;//head->next原本存的是第二个节点的地址,现在把它赋值给新节点,那么是不是新节点的下一个就是原先的第二个节点了
//第三步:head不为空,把头节点的next指针指向新的节点
head->next = pnew;//此时,我们在把新节点和头节点链接起来,这样就是头节点指向新节点了,pnew本身就是一个指针
//当第二步赋值的时候,头节点与原先的第二个节点链接自动断开
//第四步:节点个数加一
(head->data)++;//因为我们头节点的数据域存的是节点个数
//第五步:返回头节点中节点数据;a/* */
return head->data;
}
// 写尾部插入。
int insertEnd(pnode head,int data)
{
//第一步:定义临时指针
node* ptmep = head;
//第二部:遍历链表,找最后一个节点
while( ptmep->next != NULL)
{
ptmep = ptmep->next;//指针偏移
}
//循环完之后,ptemp的next已经为NULL了
//第三步:创建新节点
pnode pnew = (pnode)malloc(sizeof(node));//malloc开辟空间
//第四步:给节点赋值
pnew->data = data;
//第五步:新节点插入到链表末尾
pnew->next = NULL;//尾插法,新节点需放在最后,所以新节点后面没有节点了,指向null
ptmep->next = pnew;//原最后一个节点指向新节点,那么原最后一个节点就成为了倒数第二个节点
//第六步:返回链表长度
(head->data)++;
return head->data;//也可以直接return ++(head->data);一样的效果
}
/**********************************************************
* 函数名:changeData
* 参数: head头节点指针
* pos 需要修改的数据的位置
* data 需要修改的数据
* 返回值: 如果成功返回ture,失败返回false
* 作 用:把链表中pos位置的数据修改成data
***********************************************************/
int changeData(pnode head,int pos,int data)
{
// 第0步:判断pos是否超过节点个数,节点5,要是head只有4,就没有5节点来改变
if(pos > head->data)
{
printf("pos超出节点,无法修改\n");
}
// 第一步:定义临时变量,用来遍历到每个节点
pnode ptemp = head;
int i = 0;
//第三步:找到pos节点
for(i=0;i<pos;i++)
{
ptemp = ptemp->next;
}
//上面一步一步偏移完之后,ptemp已经遍历到pos位置了就等与pos
//第四步:替换数据
ptemp->data = data;
//这里最好是弄一个返回值,来返回是否成功,也可以打印出来
return head->data;
}
/*********************************************
* 函数名:insert
* 参数: head头节点指针
* pos 需要插入的位置
* data 需要插入的数据
* 返回值: 链表的节点个数
* 作用: 在pos的位置插入一个节点
*****************************************************/
int insert(pnode head,int pos,int data)
{
// 第0步:判断pos是否超过节点个数
if(pos > head->data)
{
printf("无法插入指定位置,程序退出\n");
exit(0);
}
// 第一步:定义临时变量,用来遍历到每个节点
pnode ptemp = head;//赋值后,ptemp就相当与头节点,不过只是一个临时变量,程序结束就会释放
// 第二步: 创建节点,赋值
pnode pnew = (pnode)malloc(sizeof(node));
pnew->data = data;
//第三步:找到pos的上一个节点
int i = 0;
for ( i = 0; i < pos-1; i++)//for循环的第二个条件,小于pos,就循环到pos
{
ptemp = ptemp->next;
}
// 第四步:新节点连接到链表中
//4.1 新节点连接到第pos的节点上
pnew->next = ptemp->next;//p->next->next指向pos的下一个节点
//4.2 pos前一个节点(ptemp)的next指向新节点
ptemp->next = pnew;
//第五步:返回节点数
(head->data)++;
return head->data;
}
/*********************************************
* 函数名:findNode
* 参数: head头节点指针
* data 需要超找的数据
* 返回值: 如果成功返回找到的data位置的指针
* 如果失败返回NULL
* 作用: 在链表中查找data的所在位置。
*****************************************************/
pnode findNode(pnode head,int data)
{
// 第一步:定义临时变量,用来遍历到每个节点
pnode ptemp = head;
//记录data的位置
int n=0;
//第二步:遍历整改链表
//第三步: 比对是否找到
while(ptemp->next != NULL)//当循环到倒数第二个节点时,会先在里面偏移,偏移后对比值
{
ptemp = ptemp->next;
n = n+1;//记录节点的位置,每偏移一次就加1
if(ptemp->data == data)//如果链表此时位置的值与传进来的指定值一样,就打印出来
{
printf("找到了!%d在%d的位置",ptemp->data,n);
return ptemp;//返回此时节点的指针,并退出这个函数
}
}
printf("没找到");
return NULL;
}
// 判断链表是否为空,如果为空则返回真,否则返回假
bool IsEmpty(pnode head)
{
if(head->next == NULL)//也可以用头节点的值判断,其值为0说明没有其他节点,判断head是不是指向空也行
{
return true;
}
return false;
}
/*********************************************
* 函数名:deleteEnd
* 参数: head头节点指针
* 返回值: 删除后的节点个数
* 作用: 在链表中删除最后一个节点
*****************************************************/
int deleteEnd(pnode head)
{
//第一步:定义临时变量,遍历,判断空
if(IsEmpty(head))
{
printf("链表为空没法删除\n");
return head->data;
}
pnode ptemp = head;
//第二步:找倒数第二个节点 ptemp->next->next == NULL
while(ptemp->next->next != NULL)
{
ptemp = ptemp->next;
}
//第三步:释放最后一个节点
free(ptemp->next);
//第四步:把删除后剩下的最后一个节点的next=NULL;
ptemp->next = NULL;
//第五步:数据元素个数减1
return --(head->data);
}
/*********************************************
* 函数名: deleteHead
* 作 用: 删除第一个数据节点
* 参 数: head 链表的头指针
* 返回值: 返回链表中的节点个数
**********************************************/
int deleteHead(pnode head)
{
//第0步:先判断是否链表为空
if(IsEmpty(head))
{
printf("链表为空,无法删除\n");
return head->data;
}
//第一步:定义临时变量,保存头的下一个节点
pnode ptemp = head->next;
//第二步:头节点的next连接到第二个节点
head->next = head->next->next;//
//第三步:释放第一个数据节点
free(ptemp);
ptemp->next = NULL;//把ptemp指针指向空,防止其乱指
//第四步:链表节点个数减1
(head->data)--;
return head->data;
}
/*********************************************
* 函数名: deletePos
* 作 用: 删除pos位置的节点
* 参 数: head 链表的头指针
* pos 需要删除的节点位置
* 返回值: 返回链表中的节点个数
**********************************************/
int deletePos(pnode head,int pos)
{
//判断链表是否为NULL
IsEmpty(head);
//第一步:定义临时变量
pnode ptemp = head;
pnode pnew;
//第二步:找到pos位置的前一个节点(保存pos位置)
for(int i=0;i<pos-1;i++)
{
ptemp = ptemp->next;
if (!ptemp)
{
printf("\n位置不存在\n");
return head->data;
}
}
//保存pos位置指针
pnew = ptemp->next;
//第三步:把pos的前一个位置的next指针指向pos下一个位置。
ptemp->next = ptemp->next->next;//把下下个指针赋给自己用pnew操作就是ptemp->next=pnew->next
//第四步:释放pos位置节点
free(pnew);//注意pnew->next和pnew是有区别的
pnew->next = NULL;
//第五步:节点个数减1(返回节点个数)
return --(head->data);
}
/************************************************
* 函数名: deletePos
* 作 用: 对链表进行排序,使用选择排序算法
* 参 数: head 链表的头指针
*
* 返回值: 无
************************************************/
//这个不建议初学者去玩,学好增删查改就很ok了,一定要自己多敲代码
void LinkSort(pnode head)
{
//第一步:定义临时变量
pnode ptemp = head;
pnode p = head;
int temp = 0;//用于交换两个变量
while(ptemp->next != NULL)
{
ptemp = ptemp->next;
p = ptemp;
while(p->next)
{
p = p->next;
if (p->data < ptemp->data)
{
temp = ptemp->data;
ptemp->data = p->data;
p->data = temp;
}
}
}
}
void show(pnode head)
{
printf("链表有%d个节点\n",head->data);//因为我们的头节点的data存的是节点个数
// 第一步:定义临时变量,用来遍历到每个节点
pnode ptemp = head;
//第二步:判断是否为最后一个节点
while(ptemp->next != NULL)//如果头节点->next为空,那么此时就是一个空链表,后面没有节点
{
//第三步:寻找下一个节点
ptemp = ptemp->next;//这个地方可以自己思考理解一下,是怎么实现的
//原理很简单,ptemp->next原本是ptemp的下一个节点,这么一赋值,ptemp自动往右边移了一个节点
printf("%d-->",ptemp->data);//打印的时候,指针需要用->来操作,结构体的话可以不是指针类型可以用点操作,ptemp.data,结构体指针不能这么用
}
printf("NULL\n");
}
int main()
{
pnode head = init();
//头部插入5个元素
int i = 0;
for(i=0;i<3;i++)
{
insertHead(head,i*10);
}
//尾部插入5个元素
for(i=0;i<3;i++)
{
insertEnd(head,i*100);
}
show(head);
//changeData(head,5,6666);
//show(head);
//insert(head,6,9999);
//show(head);
//findNode(head,6666);
//deleteHead(head);
//deleteEnd(head);
deletePos(head,2);
show(head);
return 0;
}