🎈个人主页:豌豆射手^
🎉欢迎 👍点赞✍评论⭐收藏
🤗收录专栏:数据结构
🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步!
引言
一 代码
bool ListInsert(LinkList& L, int i, int e)
{
if (i < 1)
return false;
LNode *p;
int j = 0;
p = L;
while (p != NULL && j < i - 1)
{
p = p->next;
j++;
}
if (p == NULL)//i值不合法
return false;
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
二 代码分析
以下是对ListInsert
函数的每个步骤的详细分析,以及每个步骤对应的代码:
步骤一:检查插入位置是否合法
分析:首先,函数检查传入的插入位置i
是否小于1,因为链表的索引通常从1开始。如果i
小于1,则插入位置不合法,函数直接返回false
。
代码:
if (i < 1)
return false;
步骤二:初始化指针和计数器
分析:然后,函数初始化一个指向链表节点的指针p
,以及一个计数器j
,用于跟踪当前遍历到的位置。p
初始化为链表的头节点L
,j
初始化为0。
代码:
LNode *p;
int j = 0;
p = L;
在单链表的插入操作中,初始化一个指向链表节点的指针p
和一个计数器j
是非常必要的,它们的作用如下:
指向链表节点的指针p
的作用:
-
遍历链表:
p
作为遍历链表的游标,通过不断地更新p
的值(即p = p->next
),我们可以依次访问链表中的每一个节点,直到找到插入位置的前驱节点。 -
定位插入位置:一旦找到插入位置的前驱节点,我们就可以利用
p
来执行插入操作。具体来说,我们将新节点的next
指针指向p
的下一个节点(即p->next
),然后将p
的next
指针指向新节点,完成插入。 -
处理边界情况:通过检查
p
是否为NULL
,我们可以处理插入位置超出链表长度的情况。如果p
为NULL
,则说明链表遍历结束且没有找到合法的插入位置,此时应该返回错误或者做其他相应的处理。
计数器j
的作用:
-
跟踪遍历位置:
j
用于跟踪当前遍历到的链表位置。在遍历链表时,每遍历一个节点,j
就增加1,这样我们就可以知道当前p
指向的是链表的第几个节点。 -
控制遍历长度:通过比较
j
和插入位置i
,我们可以控制遍历的长度。一旦j
达到i-1
(即找到插入位置的前驱节点),我们就可以停止遍历并执行插入操作。这有助于避免不必要的遍历,提高代码的效率。 -
处理非法插入位置:如果
j
在遍历过程中超过了i-1
但链表还未遍历完(即p
不为NULL
),则说明插入位置i
不合法(可能小于1或者大于链表长度加1),此时应该返回错误或做其他相应的处理。
初始化p
为链表的头节点L
,以及j
为0,是为了从链表的起始位置开始遍历,并正确跟踪当前遍历到的位置。这样,在后续的遍历和插入操作中,我们可以根据p
和j
的值来确定插入的位置,并执行相应的操作。
步骤三:遍历链表找到插入位置的前一个节点
分析:接下来,函数进入一个while
循环,用于遍历链表直到找到第i-1
个节点。在循环中,p
指针不断移动到下一个节点,j
计数器递增。如果链表遍历结束或j
达到i-1
,循环结束。
代码:
while (p != NULL && j < i - 1)
{
p = p->next;
j++;
}
该段代码是单链表插入操作中遍历链表以找到插入位置前驱节点的关键部分。
接下来,我会详细分析这段代码。
首先,while
循环的条件是p != NULL && j < i - 1
。这意味着循环会继续执行,直到满足以下任一条件:
p
变为NULL
,即链表遍历结束,没有更多节点可以访问。j
达到i - 1
,即已经遍历到第i-1
个节点,这是我们要插入新节点位置的前驱节点。
在循环体内,执行以下两个操作:
-
p = p->next;
:这行代码将p
指针移动到下一个节点。p->next
是当前p
指针所指向节点的下一个节点的地址,因此通过这行代码,p
会指向链表中的下一个节点。 -
j++;
:这行代码将计数器j
的值增加1。由于j
在循环开始时初始化为0,它现在表示当前已经遍历过的节点数量。每次循环,j
递增,直到达到i - 1
。
循环结束的条件有两种可能性:
- 如果链表长度小于
i - 1
,即链表没有足够多的节点来支持在第i
个位置插入新节点,那么p
会先变为NULL
,循环结束。
在这种情况下,插入操作不能在第
i
个位置进行,因为该位置超出了链表的当前长度。
- 如果链表长度大于或等于
i - 1
,循环将一直执行到j
达到i - 1
。此时,p
将指向第i-1
个节点,即我们要插入新节点位置的前驱节点。
一旦循环结束,p
将指向插入位置的前驱节点(如果i
是合法的),或者为NULL
(如果i
超出了链表的长度)。
接下来的操作将基于p
的当前值来执行,如果p
不为NULL
,则执行插入操作;如果为NULL
,则可能需要返回错误或进行其他适当的处理。
这段代码的作用是确保在插入新节点之前,我们能够准确定位到正确的插入位置,并且为接下来的插入操作做好准备。
通过维护
p
指针和j
计数器,代码能够以一种高效且准确的方式遍历链表,直到找到所需的位置。
步骤四:检查是否找到合法插入位置
分析:如果p
变为NULL
,说明链表长度小于i
,即插入位置超出了链表长度,因此返回false
。
代码:
if (p == NULL)//i值不合法
return false;
为什么p变为NULL,即代表链表遍历结束,没有更多节点可以访问呢?
在单链表中,每个节点都包含一个指向下一个节点的指针。当这个指针为NULL
时,它表示当前节点是链表的最后一个节点,并且没有更多的节点跟随其后。
因此,当我们在遍历单链表时,如果遇到
p
(即当前遍历到的节点指针)变为NULL
,这意味着我们已经到达了链表的末尾,没有更多的节点可以访问了。
在单链表插入操作的上下文中,我们遍历链表是为了找到插入位置的前驱节点。当我们说“链表遍历结束”时,我们是指我们已经检查了链表中的每一个节点,直到找到第i-1
个节点(如果它存在的话),或者直到到达链表的末尾(即没有第i-1
个节点,因为链表长度小于i
)。
在单链表的实现中,链表的末尾是通过将最后一个节点的next
指针设置为NULL
来标识的。因此,当我们的指针p
在遍历过程中遇到NULL
时,我们可以确定已经到达了链表的末尾,没有更多的节点可供检查或访问。
简而言之,p
变为NULL
表示我们已经到达了链表的物理或逻辑终点,即链表的末尾,此时无法继续向后遍历,因为不存在更多的节点。
步骤五:创建新节点并插入到链表中
分析:如果找到了插入位置的前一个节点,函数将分配一个新的节点s
,并设置其数据域为e
。然后,将新节点的next
指针指向p
节点的下一个节点,最后将p
节点的next
指针指向新节点s
,完成插入操作。
代码:
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
首先,在单链表插入操作中,一旦我们找到插入位置的前一个节点(通常称为前驱节点),就可以开始执行插入操作了。
插入操作主要包括三个步骤:创建新节点、调整链接关系、更新前驱节点的next
指针。
代码段中的四个操作就是按照上述步骤执行的:
-
LNode* s = (LNode*)malloc(sizeof(LNode));
这一行代码负责分配一个新的节点,并返回指向该节点的指针s
。malloc
函数用于在堆上动态分配内存,sizeof(LNode)
计算了LNode
结构体的大小,以确保分配足够的内存空间来存储新节点。LNode*
是一个指向LNode
类型的指针,用于存储新节点的地址。 -
s->data = e;
这里,我们将新节点的数据域设置为e
。e
是插入操作的一个参数,它表示要插入到新节点中的数据。s->data
是访问新节点s
中数据域的方式。 -
s->next = p->next;
这行代码将新节点的next
指针指向p
节点的下一个节点。在插入操作中,我们想要新节点成为p
节点的下一个节点,所以我们需要将新节点的next
指针指向p
原本指向的下一个节点。 -
p->next = s;
最后,这行代码将p
节点的next
指针指向新节点s
。这一步是插入操作的关键,它使得新节点s
成为链表中的一部分,并且被正确地插入到p
节点之后。
这四行代码组合在一起,就完成了单链表中的插入操作。新节点s
现在被插入到链表中的p
节点之后,其数据域被设置为e
,并且链表的其余部分保持不变。注意,这里假设p
不为NULL
,也就是说,我们已经找到了插入位置的前驱节点。如果p
为NULL
,则意味着插入位置超出了链表的当前长度,这时应该返回错误或进行其他相应的处理。
还需要注意的是,在实际编程中,我们通常会检查malloc
是否成功分配了内存,因为如果内存分配失败,malloc
会返回NULL
。如果不对此进行检查,尝试访问NULL
指针会导致程序崩溃。因此,在实际代码中,你可能会看到类似if (s == NULL) { /* handle error */ }
的检查。此外,当不再需要链表节点时,应该使用free
函数释放分配给它们的内存,以避免内存泄漏。
步骤六:返回插入成功标志
分析:最后,函数返回true
,表示插入操作成功。
代码:
return true;
整个函数通过上述步骤,实现了在链表的指定位置插入一个新节点的功能。
总结
这篇文章到这里就结束了
谢谢大家的阅读!
如果觉得这篇博客对你有用的话,别忘记三连哦。
我是豌豆射手^,让我们我们下次再见