欢迎讨论:在阅读过程中有任何疑问,欢迎在评论区留言,我们一起交流学习!
点赞、收藏与分享:如果你觉得这篇文章对你有帮助,记得点赞、收藏,并分享给更多对数据结构感兴趣的朋友
哨兵位是链表中的一个特殊节点,它并不保存有效数据,只是作为标识存在。使用哨兵位有以下几个好处:
下面我们来实战演练。
合并两个有序链表 - 力扣(LeetCode)
这道题的解法很多,这里我们使用最优的方法——双指针遍历
通过两个指针同时遍历两个链表,逐个比较链表中的节点,将较小的节点接到合并后的链表上,直到一个链表遍历完成,再将另一个链表中剩余的部分直接接到结果链表后。
if(list1 == NULL)
return list2;
if(list2 == NULL)
return list1;
如果其中一个链表为空,直接返回另一个链表。这样可以避免后续操作中对空链表的额外处理。
struct ListNode* tail = NULL, *guard = NULL;
guard = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
tail->next = NULL;
guard
,该节点不会保存实际的数据。它的作用是简化链表的操作,特别是头节点的处理。因为如果直接操作链表头节点,可能需要单独判断头节点是否为空或其他边界情况,使用哨兵节点后,我们就可以统一处理所有节点的插入。tail
指针用于指向合并链表的尾部。通过这个指针,我们可以方便地将新的节点插入到合并后的链表中。struct ListNode* cur1 = list1;
struct ListNode* cur2 = list2;
cur1
和cur2
分别指向两个输入链表list1
和list2
的头节点。我们将使用这两个指针遍历两个链表,进行比较并合并。while(cur1 && cur2) {
if(cur1->val < cur2->val) {
tail->next = cur1;
cur1 = cur1->next;
tail = tail->next;
} else {
tail->next = cur2;
cur2 = cur2->next;
tail = tail->next;
}
}
while
循环,循环条件是cur1
和cur2
都不为空。也就是说,只有当两个链表中都有节点时,才会进入循环。cur1
和cur2
所指向节点的值,选择较小的节点加入到合并后的链表中:
cur1
的值小于cur2
的值,就将cur1
节点连接到tail
的next
上,并将cur1
向后移动一个节点。cur2
节点连接到tail
的next
上,并将cur2
向后移动一个节点。tail
之后,更新tail
指针,指向合并链表的最后一个节点。if(cur1)
tail->next = cur1;
if(cur2)
tail->next = cur2;
while
循环结束后,可能会有一个链表的节点已经全部被处理完,另一个链表中还有节点没有被合并。cur1
指向的链表中还有节点未处理(即cur1
不为空),将剩余的cur1
节点直接接到合并链表的尾部。cur2
指向的链表中还有节点未处理(即cur2
不为空),将剩余的cur2
节点直接接到合并链表的尾部。struct ListNode* head = guard->next;
free(guard);
return head;
guard->next
,因为guard
本身是一个哨兵节点,不保存实际的数据。返回guard->next
就是合并后的链表的头节点。guard
节点占用的内存,因为guard
节点已经不再需要。struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
// 如果list1为空,返回list2
if(list1 == NULL)
return list2;
// 如果list2为空,返回list1
if(list2 == NULL)
return list1;
struct ListNode* tail = NULL, *guard = NULL;
struct ListNode* cur1 = list1;
struct ListNode* cur2 = list2;
// 创建一个哨兵节点guard,方便操作链表头
guard = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
tail->next = NULL;
// 合并两个链表
while(cur1 && cur2) {
// 判断当前cur1的值与cur2的值,选择较小的节点
if(cur1->val < cur2->val) {
tail->next = cur1;
cur1 = cur1->next; // 移动cur1指针
tail = tail->next; // 更新tail指针
} else {
tail->next = cur2;
cur2 = cur2->next; // 移动cur2指针
tail = tail->next; // 更新tail指针
}
}
// 如果cur1还有剩余,连接到tail
if(cur1)
tail->next = cur1;
// 如果cur2还有剩余,连接到tail
if(cur2)
tail->next = cur2;
// 头指针是guard的下一个节点
struct ListNode* head = guard->next;
// 释放哨兵节点
free(guard);
return head;
}
如果这道题不用哨兵位,你将会被一个错误搞的焦头烂额——对空指针解引用,不相信的话你可以去试试,你一定会影响深刻的。
通过这道题,是不是觉得哨兵位真的很香?非常省事,特别是这种多链表的联合问题,我们一定要留个心眼。