前言
今天学习到数据结构里的尾插法(郝斌老师的数据结构),发现很多初学者对于视频所讲的尾插法的代码有些疑惑(尽管我也是个小白),下面我们来对其分析一下 下附代码二、代码示例
这里我把老师在课堂上讲的代码搬过来
没看过视频的可以先看这个较为完整的代码
看过视频的同学可以直接跳过看下一个代码:
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
typedef struct Node
{
int data; //数据域
struct Node *next; //指针域
} NODE,*PNODE;
PNODE create_list(void);
void traverse_list(PNODE pHead);
int main(void)
{
PNODE pHead=NULL; //
pHead=create_list(); //创建一个非循环单链表,并将改链表的头结点的地址赋值给pHead
traverse_list(pHead);
return 0;
}
PNODE create_list(void)
{
int len; //用来存放有效结点的个数
int i;
int val; //用来临时存放用户输入的结点的值
PNODE pHead=(PNODE)malloc(sizeof(NODE)); //此处地方的PHead与下面的PHead无关系,这里的是一个局部变量
if(pHead==NULL)
{
printf("分配失败,程序终止!\n");
exit(-1);
}
PNODE pTail=pHead;
pTail->next=NULL;
printf("请输入你需要生成链表结点的个数:len=");
scanf("%d",&len);
for(i=0;i<len;i++)
{
printf("请输入%d个结点的值:",i+1);
scanf("%d",&val);
//需要一个接受专门接受数据的新节点
PNODE pNew=(PNODE)malloc(sizeof(NODE));
if(NULL==pNew)
{
printf("分配失败,程序终止!\n");
exit(-1);
}
pNew->data =val; //这里如何理解?
pTail->next=pNew; //这里如何理解?
pNew->next=NULL; //这里如何理解?
pTail=pNew; //这里如何理解?
}
return pHead;
}
void traverse_list(PNODE pHead)
{
.....
}
上面的代码有些多
所以我们将最核心的代码单独拿出来
我们可以仅看下面的代码:
首先我们来对n,m两行(暂且就这么叫)以及最下面的四行代码进行分析
PNODE pTail=pHead; //n行
pTail->next=NULL; //m行
n行
这里定义了一个PNODE(struct Node *)类型的指针,变量名为pTail,并且将pHead中保存的地址赋给pTail
m行
将pTail所指向的结构体中的指针域清空
然后我们来分析最后的四行代码
pNew->data =val;
pTail->next=pNew; //关键操作
pNew->next=NULL;
pTail=pNew; //这里如何理解?
我们来逐行分析
第一行 :pNew->data =val;
将val 赋给 pNew所指向的结构体中的数据域,这个pNew是什么呢?
没错,它也是个指针,需要注意的是我们在这里说的指针和内存的关系
第二行:pTail->next=pNew;
将pNew的地址赋给pTail所指向的结构体中的指针域
第三行:pNew->next=NULL;
将pNew所指向的所指向的结构体中的指针域清空
第四行:pTail=pNew;
将pNew的地址赋给pTail
到了这里,相信大家有一些朦胧感觉了
不要着急,我们继续深入一下
需要注意的是,我们在这个过程中从未直接操作内存,我们一直是通过指针来操作的,
我们知道,如果链表只有一个元素时头结点和尾节点是相同的,
在n行代码哪里我们已经知道头结点,并且将头结点的地址发送给了pTail,这就使pTail也指向了头结点,但是此时的链表里是没有元素的,所以pTail也指向了尾节点,而m行将头结点的指针域清空也就使它变成了一个尾节点,我们的目的也就是将它变成一个尾节点。
如果你理解了上面几句的含义,那么最后的四行代码就没有什么难度了,
创建一个新的动态内存,并把内存的地址传给pNew,然后将val 赋给 pNew所指向的结构体中的数据域,
然后将pNew的地址赋给pTail所指向的结构体中的指针域,这里就是灵魂操作了哦,这一句代码就将pNew所指向的内存挂到了pHead所指向的内存(也就是头结点)的后面,
所以。。。。为什么?
我们不要忘了,此时的pTail里保存的东西是什么,就是头结点的地址啊,但是pTail是指向尾节点的啊
所以我们要把pNew所指向的指针域清空,让它变成一个尾节点,
然后,我们要做的仅仅是更新pTail中保存的地址就可以了
总结
我觉得这个代码让我印象最深的地方是在哪里呢,是在pTail->next=pNew;这一行,当时我一直想不明白pTail->next接收pNew的地址有什么用,我在看视频时的弹幕上也没有找到我想要的答案,有的在说画个图就理解了,但是我画了半天也没画明白,后来我就没去管它,去吃饭了,吃饭的时候我就觉得我可能想偏了,我把内存和指针给搞混了(因为弹幕上有人在刷pTail是个替身什么的,我就把它给想成是内存了),我觉得替身这个词不好,应该换一个,叫烤肠比较好,pTail就相当于在原来pHead这根签子已经插到烤肠(内存)里的基础上又扎了根签儿。。。
就到这里吧,如果这篇文章对你有帮助当然是最好的了,大家一起努力,能看到我这个文的估计和我水平相差无几,我也只是一个普通的大学生而已,一起努力呗(这其实是我的第一个案例分析,上面有什么不对的地方希望各位大神能指点我一下)