单向链表
基本概括
链表的常用结构主要成分有有空头链表与无空头链表
有空头链表
创建一个节点专门作为链表头。该头不装数据,所有的数据节点都在都在该头的后面。
操作起来简单
操作方式大致也就五步:创建,增删改查
请看下集
无空头链表
第一个数据节点作为链表头
操作起来稍微麻烦
这两种链表的形式大同小异,写的格式是固定的,可以将形式记在心里,用的时候就没必要再想,可以直接写。
所有数据结构的操作方式大致也就五步:创建,增删改查
无空头链表-尾添加
创建链表步骤如下:
1)定义链表结构体–本节点的数据和指向下个节点的指针
struct Chain{
int iData;
struct Chain *pNext;
};
2)主函数中定义头指针和尾指针
//定义头指针 //定义尾指针--增加效率
struct Chain *pHead = NULL,
*pTail = NULL;
3)定义函数,增加节点
注意:
函数本身就要修改指针的指向,其次这两个指针都要传进去,要修该指向,所以要传二级指针。
void AddTail(struct Chain **pHead, struct Chain **pTail,int iData){}
因为这个pTail
、pHead
是指向NULL的 ,所以我们要将其传到函数中去指向创建出来的链表或节点的地址上,iData
即是要增加的节点值
4)创建节点
//创建节点 和伪链表相似
struct Chain *pTemp = (struct Chain*)malloc(sizeof(struct Chain));
5)赋值
//节点成员赋值--每一个成员都要赋值
pTemp->iData = iData;
pTemp->pNext = NULL;
//一定要赋值空--否则对后面的操作有影响
5)将分开的节点连在一起
注意:链表中无节点和有节点连是不一样的,因为无节点时头指针和尾指针都要指向新增的节点,而有节点时只要尾部指向新结点就可。
所以要分两种情况判断
当
pHead
为空的时候说明一个数据也没有,新创建的节点既是头也是尾,头指向它,尾也指向它—为空和不为空,代码逻辑不一样,所以要分类判断
if (NULL == *pHead || NULL == *pTail) {}//空链表
else //有节点 -- 一个以上
当为空链表时,头尾都指向新节点
*pHead = pTemp;
*pTail = pTemp;
当不为空链表时
//需要两个操作 第一:将新节点连到尾部
(*pTail)->pNext = pTemp;
//尾指针指向新节点--相当于尾向后移一个
*pTail = pTemp;
又可以看到,if
和else
中有公共的部分,则可以写在外面,而判断条件的前提是pTemp
不为NULL,连接总代码如下:
//pTemp申请空间可能会失败,所以要加个判断
if (NULL != pTemp)
{
//节点成员赋值--每一个成员都要赋值
pTemp->iData = iData;
pTemp->pNext = NULL; //一定要赋值空--对操作有影响
//节点创建后 连再一起
//当pHead为空的时候说明一个数据也没有,新创建的节点既是头也是尾
//头指向它,尾也指向它-为空和不为空,代码逻辑不一样,所以要分类判断
if (NULL == *pHead || NULL == *pTail) //空链表
{
//新链表既是头也是尾
*pHead = pTemp;
//*pTail = pTemp;
}
else //有节点 -- 一个以上
{
//需要两个操作 第一:将新节点连到尾部
(*pTail)->pNext = pTemp;
//尾指针指向新节点--相当于尾向后移一个
//*pTail = pTemp;
}
//if和else都有公共部分,放外面即可
*pTail = pTemp;
}
6)主函数调用
AddTail(&pHead, &pTail, 3);
AddTail(&pHead, &pTail, 4);
AddTail(&pHead, &pTail, 5);
for (int i = 0;i<3;i++)//输出打印
{
printf("%d ", pHead->iData);
pHead = pHead->pNext;
}
链表建立的总代码如下:
#include<stdio.h>
#include<stdlib.h>
void AddTail(struct Chain **pHead, struct Chain **pTail,int iData);
struct Chain
{
int iData;
struct Chain *pNext;
};
int main(void)
{
struct Chain *pHead = NULL,
*pTail = NULL;
AddTail(&pHead, &pTail, 3);
AddTail(&pHead, &pTail, 4);
AddTail(&pHead, &pTail, 5);
for (int i = 0;i<3;i++)
{
printf("%d ", pHead->iData);
pHead = pHead->pNext;
}
system("pause");
return 0;
}
void AddTail(struct Chain **pHead, struct Chain **pTail,int iData)
{
struct Chain *pTemp = (struct Chain*)malloc(sizeof(struct Chain));
if (NULL != pTemp)
{
pTemp->iData = iData;
pTemp->pNext = NULL;
if (NULL == *pHead || NULL == *pTail)
*pHead = pTemp;
else
(*pTail)->pNext = pTemp;
*pTail = pTemp;
}
}
如果熟练的记在脑子里,那以后用的时候一定比德芙还顺滑!
pTail尾指针的作用
如果没有尾指针:
步骤与有尾指针相似,在连接节点时有差别。
首先:我们无法通过尾指针来直接指向尾步,添加节点,这就意味着要通过循环先找到尾部,然后再添加,如下:
if (NULL == *pHead)
*pHead = pTemp;
else
{
//定义临时中间变量指向头
struct Chain *MidChange = *pHead;
//找尾
while (MidChange->pNext != NULL)
MidChange = MidChange->pNext;
//此刻MidChange指向尾,MidChange就装在尾的上面
MidChange->pNext = pTemp;
}
注意:要通过定义中间变量来遍历,若要用头指针遍历,最终头指针就不指向头了,这会导致节点的丢失
与有尾指针相比:显然无尾指针要从开始到结束遍历一遍,若有成百上千个节点,都遍历效率显然会很低,而有尾指针就直接指向尾节点,不用遍历,效率显然提高了
释放链表
只要释放链表的地址,所以形参部分传一级指针就可以,如果说在释放后还要给pHead
赋值为NULL
,那就要传二级指针了。
1)定义释放链表的函数
void FreeChain(struct Chain*pHead){}
2)遍历链表
void FreeChain(struct Chain*pHead)
{
while (pHead != NULL)
{
//定义一个中间变量,记录pHead
struct Chain*Temp = pHead;
pHead = pHead->pNext;
free(Temp);
}
}
注意:定义中间变量,记录pHead
是定义一个Temp
也指向第一个节点,然后,执行pHead = pHead->pNext;
即让pHead
向下走一个节点,此时Temp
仍然指向着第一个节点,所以释放Temp
,既是释放第一个节点
释放后,将指针指向NULL
将指针指向空,即是操作指针,所以参数要传二级指针
可以在释放的基础上加以修改
void FreeChain(struct Chain**pHead, struct Chain**pTail)
{
while (*pHead != NULL)
{
//定义一个中间变量,记录pHead
struct Chain*Temp = *pHead;
*pHead = (*pHead)->pNext;
free(Temp);
}
*pHead = NULL;
*pTail = NULL;
}
总体代码如下:
#include<stdio.h>
#include<stdlib.h>
void AddTail(struct Chain **pHead, struct Chain ** pTail,int iData);
void print(struct Chain * pHead);
void FreeChain(struct Chain **pHead, struct Chain **pTail);
struct Chain
{
int iData;
struct Chain *pNext;
};
int main(void)
{
struct Chain *pHead = NULL,
*pTail = NULL;
AddTail(&pHead, &pTail, 3);
AddTail(&pHead, &pTail, 2);
AddTail(&pHead, &pTail, 1);
print(pHead);
FreeChain(&pHead,&pTail);
system("pause");
return 0;
}
void AddTail(struct Chain ** pHead, struct Chain ** pTail,int iData)
{
struct Chain *pTemp = (struct Chain*)malloc(sizeof(struct Chain));
if (NULL != pTemp)
{
pTemp->iData = iData;
pTemp->pNext = NULL;
if (NULL == *pHead)
*pHead = pTemp;
else
(*pTail)->pNext = pTemp;
*pTail = pTemp;
}
}
void print(struct Chain * pHead)
{
while (pHead != NULL)
{
printf("%d ",pHead->iData );
pHead = pHead->pNext;
}
}
void FreeChain(struct Chain**pHead, struct Chain**pTail)
{
//struct Chain *pT = *pHead;
while (*pHead != NULL)
{
//定义一个中间变量,记录pHead
struct Chain*Temp = *pHead;
*pHead = (*pHead)->pNext;
free(Temp);
}
*pHead = NULL;
*pTail = NULL;
}
无空头链表-头添加
尾添加是将节点不断往尾巴上连,每次连完后,将尾指针不断指向最新的节点,变成新的尾,也就是说,尾不停往后移;头添加是尾不变,不断往头上去连,也就是说新节点指向头,然后将头往前移,即头每次指向新节点,逻辑相同。
在其他条件不变的情况下:
void AddHead(struct Chain **pHead,struct Chain **pTail,int iData){
struct Chain *pTemp = (struct Chain*)malloc(sizeof(struct Chain));
if (pTemp != NULL){
pTemp->iData = iData;
pTemp->pNext = NULL;
if (NULL == *pHead)
*pTail = pTemp;
else
pTemp->pNext = *pHead;
*pHead = pTemp;
}
}
这里的更改,既是将pTemp
的结构体指针指向头指针,此时即在头部添加了,然后再将头部移到新节点头部即可。
无空头链表-中间添加
逻辑
采用在第n个下标位置添加的逻辑,分两步:
一、根据下标查找节点
返回值:返回节点地址
参数:链表头,下标 --不修改指针,仅仅是遍历
函数体:参数合法性检测、循环遍历链表、未找到的处理
二、接入节点
参数:头指针、尾指针、下标位置、数据
函数体:参数合法性检测、头添加、
根据下表查找节点
1)定义查找节点的函数
struct Chain *FindMid(struct Chain *Head,int Index){}
注意:
返回值,返回的是节点地址
由于不需要修改链表,仅仅是遍历,所以传一级指针就可以
2)合法性检测
struct Chain* Find_Index(struct Chain *pHead,int Index){
if(NULL == pHead || Index < 0){
printf("链表为空或下标无效\n");
return NULL;
}
//循环链表
int flag = 1;
while(NULL != pHead && flag < Index){
pHead = pHead->pNext;
flag ++; //记录下标
}
return pHead; //找到一定从上面返回,没找到从这里返回
}
参数合法性检测 保证执行不会出错
3)可以通过输出来检查是否正确
printf("%d ",*(Find_Index(pHead,1)));
下面的是下标的地址,用地址操作符*,就是数值了
Find_Index(pHead,1)
接入节点
接入节点就是修改链表了,那就要传头和尾的二级指针
1)定义函数
void AddMid(struct Chain **pHead, struct Chain **pTail, int Index, int iData){}
Index
和iData
分别是要插入的下标和要插入的值
2)合法性检测
void AddMid(struct Chain **pHead, struct Chain **pTail, int Index, int iData){
//合法性检测
if (Index < 0){
printf("输入下标小于0,错误\n");
return;//结束所在函数的功能
}
}
return的作用:
1.返回值
2.结束所在函数
3)若下标为0,直接在头添加,不必再寻找下标
//下标为0,头添加
if (0 == Index)
AddHead(pHead,pTail,iData);
4) 寻找下标
寻找下标我们可以调用上面的FindMid
函数,不过我们要添加一个节点。就要让新节点指向要添加的下标上的节点,将要添加的下标上的节点的前一个位置的结构体指针指向新节点即可。所以总的来说,就是需要前一个节点的下标,所以我们传参的时候,要传要添加的下标的前一个节点即可。
如下:
在下标为3的位置上添加新节点
那就要将下标为2的节点的指针指向新结点,新结点的指针指向下标为4的节点上,所以主要就是知道下标为3的前一个下标就好嘞
else{
//找位置
struct Chain *pTemp = Find_Index(&pHead,iData-1);
}
5)判断找的位置是否为空、申请空间、节点成员赋值、连接节点(参考上面)
//判断pTemp是否为空
if (NULL != pLocal)
{
//申请节点空间
struct Chain *Temp = (struct Chain*)malloc(sizeof(struct Chain));
//节点成员赋值
Temp->iData = iData;
Temp->pNext = NULL;
//连接节点 先连后断 先将新结点连接到后一位置,再将前节点连到新节点
Temp->pNext = pLocal->pNext; //连
pLocal->pNext = Temp;//断
}
else
printf("查无此节点\n");
}
注意:连接节点时,要先连后断,即先与后面的节点连上,再用前节点的指针连接新节点。
一次性添加数据为x的n个节点
1.可以循环n次调用我们写好的尾添加、头添加、中间添加,接入固定数据x(对代码进行重复使用)
1)封装函数
void AddMore(struct Chain **pHead, struct Chain **pTail, int iCount, int iData){}
2)添加节点
对链表指针进行修改,要传二级指针,另外还需要传递添加的个数和添加的数。
void AddMore(struct Chain **pHead, struct Chain **pTail, int iCount, int iData)
{
for (int i = 0;i<iCount;i++)
AddMid(pHead, pTail, 4, iData);
}
这里直接调用添加函数,放在for
循环中,会进行多次循环,即实现添加多个。
为了更加灵活,可以将AddMid
的下标位置参数,在AddMore
中传入,这样就可以更加灵活了!
void AddMore(struct Chain **pHead, struct Chain **pTail, int iCount, int iData,int Index)
{
for (int i = 0;i<iCount;i++)
AddMid(pHead, pTail, Index, iData);
}
无空头链表-添加的总代码
#include<stdio.h>
#include<stdlib.h>
struct Chain
{
int Data;
struct Chain *pNext;
};
void Add_To_Tail(struct Chain **pHead,struct Chain **pTail,int Data){
struct Chain *pTemp = (struct Chain*)malloc(sizeof(struct Chain)); //pTemp指向这个空间,先在栈区开辟空间
if(pTemp != NULL){ //这个指针指向的空间地址不是空
pTemp->Data = Data;
pTemp->pNext = NULL;
//无节点时头指针和尾指针都要指向新增的节点
if(*pHead == NULL && *pTail == NULL) *pHead = pTemp;
else (*pTail)->pNext = pTemp;
*pTail = pTemp;
}
}
void Add_To_Head(struct Chain **pHead,struct Chain **pTail,int Data){
struct Chain *pTemp = (struct Chain*)malloc(sizeof(struct Chain));
if(pTemp != NULL){
pTemp->Data = Data;
pTemp->pNext = NULL;
}
if(*pHead == NULL) *pTail = pTemp;
else pTemp->pNext = *pHead;
*pHead = pTemp;
}
struct Chain* Find_Index(struct Chain *pHead,int Index){
if(NULL == pHead || Index < 0){
printf("链表为空或下标无效\n");
return NULL;
}
//循环链表
int flag = 1;
while(NULL != pHead && flag < Index){
pHead = pHead->pNext;
flag ++; //记录下标
}
return pHead; //找到一定从上面返回,没找到从这里返回
}
void Add_To_Mid(struct Chain **pHead,struct Chain **pTail,int Index,int Data){
if(Index < 0 || NULL == *pHead) return;
if(0 == Index) Add_To_Head(pHead,pTail,Data);
else{
struct Chain *Local = Find_Index(*pHead,Index);
if(NULL != Local){
struct Chain *pTemp = (struct Chain*)malloc(sizeof(struct Chain));
pTemp->Data = Data;
pTemp->pNext = NULL;
pTemp->pNext = Local->pNext;
Local->pNext = pTemp;
}else printf("节点为空\n");
}
}
void print(struct Chain *pHead){
while(pHead != NULL){
printf("%d ",pHead->Data);
pHead = pHead->pNext;
}
}
void Add_More(struct Chain *pHead,struct Chain *pTail,int Count,int Data,int Index){
for(int i = 0;i < Count;i ++) Add_To_Mid(&pHead,&pTail,Index,Data);
}
void FreeChain(struct Chain **pHead,struct Chain **pTail){
while(pHead != NULL){
struct Chain *pTemp = *pHead;
*pHead = (*pHead)->pNext;
free(pTemp);
}
*pHead = NULL,*pTail = NULL;
}
int main(){
struct Chain *pHead = NULL,*pTail = NULL;
Add_To_Tail(&pHead,&pTail,2);
Add_To_Tail(&pHead,&pTail,3);
Add_To_Tail(&pHead,&pTail,4);
Add_To_Head(&pHead,&pTail,1);
Add_To_Mid(&pHead,&pTail,2,7);
Add_More(pHead,pTail,3,9,3);
print(pHead);
FreeChain(&pHead,&pTail);
return 0;
}