002–链表的查找,插入,删除操作
第一节我们实现了简单的有头链表的创建,基于这个,进一步学习三种常用的链表操作。
2.1 查找操作
首先是查找操作,链表的特点在于其结点是随机分布在内存中的,不用像数组一样分出一大块连续空间给他,提高了内存的利用率。但同时,因为它不是连续内存,也就无法支持像数组一样可以随机访问的特点,即不能利用下标直接找到其中的某个元素。因为下一个结点在哪只有上一个结点知道(有点像谍战片里的线人),所以查找操作也就没有数组那么方便,需要我们构造一个专门的函数来实现。简单来说,查找操作是支持之后一系列对链表操作的基础,这一点我们在之后就会看到。
Node *research(Pnode head, int k) //查找第k个结点
{
Pnode temp;
temp=head;
int i=1; //这里结点我们是按正常数数的,所以从1开始
printf("now we start to look for it !\n");
while((i<k)&&temp) //当temp不为空,且i<k时,让i自增、指针后移直到需要的第k个
{
temp=temp->next;
i++;
}
if((i==k)&&temp) //进一步确认我们找到了需要的第K个,
{ //确保不是因为遍历完链表或因为原本输入的k值就不符合要求使上一个while停止
printf("find it !\n");
printf("%d\n",temp->data);
return temp;
}
else //若正确则返回该结点的位置指针,否则返回空。
{
printf("error !\n");
return NULL;
}
}
- 指针不同于一般的参数,如果直接对head指针操作,就会改变head指针的指向,那么下一次使用这个链表时就没有一个头指针能把整个链表牵引出来了。所以这里需要一个temp临时工来进行各项操作。
- 要求查找第k个结点,所以这里声明一个i变量来遍历各结点,作为计数器使用。同时temp不断向后移,直到第k个结点。循环条件中的temp不为空既保证了前面temp赋值成功又作为若链表不存在第k个结点时的停止条件。
- if语句来判断循环停止是因为查找失败还是找到了第k个结点,查找成功则返回指向该结点的一个指针,否则返回空指针。这里的i==k条件是必要的,否则当用户输入一个小于1的k时,前面的while循环会直接停止,后面的if条件也会为真,但实际上程序并没有找到第k个结点。
这样我们就构建了一个查找第k个结点并返回指向该结点的指针的函数。之后的插入、删除结点操作将会用到它。
2.2 插入操作
链表较数组优秀的一个特点就在于插入数据简便省时,因为它没有数组连续空间和随机访问的特点,插入新的结点时不需要让整个整体进行移动。但同时也因此,要找到插入点的位置就需要动用之前写的research函数了。
int insertNode(Pnode head,Pnode newp,int k) //在第k个结点前插入newp结点,成功返回1,失败返回0
{
Pnode temp=NULL;
printf("now we insert it !\n");
if(k==1) //如果插入点就是第一个结点,就直接把头结点给temp,不用再查找位置
{
temp=head;
}
else
{
temp=research(head,k-1); //如果插入点不是头,就查找k-1的位置,返回给temp
}
if(temp) //这里确保research确实查找到了位置,否则temp为空,插入失败
{
newp->next=temp->next;
temp->next=newp;
return 1;
}
return 0;
}
- 与查找同理,我们用temp临时工来工作。
- 当k=1时,说明插入点是头部,那就不需要麻烦research,直接将头部给temp进行操作即可。
- 如果插入点不是头部,则查找k-1的位置。即找到需要插入点的前一个(因为要靠前一个的next指针与新结点相连才能重新构成链表),赋给temp;
- 找到插入位置后,正式进行插入操作。将新结点的next指针指向k-1个结点的next指向——即原第k个结点,之后在讲k-1个结点的next指向新结点,结点就成功插入链表了。操作比较类似排序算法时两个数交换时的操作。
ps:之前对指针不太懂,一直不能理解最后这个操作的原因。实际上p->next就是在p上存储了下一个结点的地址,newp->next=temp->next就是把原版temp里存储的下一个位置的地址给newp;同理,temp->next=newp就相当于temp存入了newp的地址。
2.3 删除操作
删除的基本原理和插入一样,没什么大变化。
int deleteNode(Pnode head,int k) //在删除第k个结点
{
Pnode temp1=NULL; //一个储存k-1,一个储存k
Pnode temp2=NULL;
printf("now we delete it !\n");
if(k==1) //同上insertNode
{
temp1=head;
}
else
{
temp1=research(head,k-1);
}
temp2=temp1->next;
if(temp1&&temp2) //保证k-1个和k个都不为空
{
temp1->next=temp2->next; //把k-1和k+1相连,释放k的空间
free(temp2);
return 1;
}
else
{
printf("error");
return 0;
}
}
- 链表中各种对结点操作最重要的是不能丢了下一个结点的地址,就像谍战片里那些单线的线人总是会出这样那样的意外,因为只有上一个结点知道下一个结点的地址,删除、插入操作都需要注意记录好下一个结点的地址
- 所以这里如果用两个temp来分别记录k-1和k结点。将k的next赋给k-1的next,即将k+1的地址给了k-1,让链表跳过原第k个结点。这样第k个结点就与链表失去联系了(用于和他联系的k-1结点不再记录他的地址),如果没有temp2,它就将永远遗失在内存的长河中。
- 但这里我们要尽量养成好习惯,对于malloc申请的动态内存,要记得在不用的时候还给系统,这也是用temp2储存原本应该遗失的原第k个结点的原因之一。free(temp2)将结点的内存释放,这样它就是真正的烟消云散了。