wordpress能做交互类网站seo入门黑帽培训教程
目录
第一步:问题本质分析
第二步:指针的角色定义
第三步:核心操作逻辑(滑动指针)
第四步:循环直到一个链表为空
第五步:代码实现
给定两个有序的单链表(升序),我们要把它们合并成一个新的有序链表,并且不新建节点,只复用原有节点。
数据结构:数组:合并数组(Merging Arrays)-CSDN博客
举个例子(升序合并):
List A: 1 → 3 → 5 → NULL
List B: 2 → 4 → 6 → NULL
合并后:
Merged: 1 → 2 → 3 → 4 → 5 → 6 → NULL
第一步:问题本质分析
我们面对的是两个“有序序列”,本质是:
你要像“拉拉链”一样,从两个链表中选择当前最小的节点,接到结果链表中,然后往前推进那个节点
这其实就像是两个队列合并排序(归并排序 merge 步骤),但数据结构是链表。
❓问题1:我们能做的“最小动作”是什么?
答:我们能做的最小动作是:
比较
a1->data
和b1->data
,谁小,谁应该排在合并链表最前面。
这是第一性操作单位,我们只能通过一次比较和一次 ->next
访问,来获取有限信息。
❓问题2:为什么我们需要一个“结果链表”?
你想最终有一个“新链表”,连接所有节点。但这些节点已经存在了,我们不用重新创建节点(否则浪费空间)。
所以我们只需要做一件事:
“连接指针”,让所有节点按顺序接起来。
第二步:指针的角色定义
❓问题3:我们能否只用一个变量维护合并链表?
设想我们一开始选定了合并链表的“第一个节点”。
比如第一次比较 a1
和 b1
,发现 a1->data < b1->data
,我们就让:
merged_head = a1;
但这还不够,我们还需要维护一个指针:
→ 当前合并链表的最后一个节点(即尾指针)
否则我们就无法追加下一个节点。
推导到此,我们需要两个“操作级”指针:
-
merged_head
:记录整个合并链表的开头 -
merged_tail
:指向合并链表的最后一个节点,方便追加
几个基本指针变量来进行这项操作:
名称 | 用途 |
---|---|
first | 指向第一个链表当前节点 |
second | 指向第二个链表当前节点 |
merged_tail | 当前合并链表最后一个节点(尾指针) |
merged_head | 指向合并后链表的头节点(第一次选中谁就赋值) |
第三步:核心操作逻辑(滑动指针)
每一步我们都比较 first->data
和 second->data
:
-
取小的那个节点
-
把它接到
merged_tail->next
上 -
然后移动那个指针前进一步,否则下一轮还是同一个元素,这叫“消费当前节点”。
if (A->data < B->data) {merged_tail->next = A;A = A->next;
} else {merged_tail->next = B;B = B->next;
}
merged_tail = merged_tail->next;
图解过程
A → [1] → [3] → [5]
B → [2] → [4] → [6]
执行顺序如下:
步骤 | 比较 | 选择 | Merged 链表 |
---|---|---|---|
1 | 1 vs 2 | 1 | [1] |
2 | 3 vs 2 | 2 | [1] → [2] |
3 | 3 vs 4 | 3 | [1] → [2] → [3] |
4 | 5 vs 4 | 4 | [1] → [2] → [3] → [4] |
5 | 5 vs 6 | 5 | ... |
6 | NULL vs 6 | 6 | ... |
第四步:循环直到一个链表为空
❓问题4:谁是第一个节点?我们要不要特殊处理第一次?
第一次没有任何节点被加入,所以我们不能这样:
merged_tail->next = ...
因为 merged_tail
还没有定义,所以第一次我们要单独处理:
-
第一次比较 A 和 B,谁小,谁就成为
merged_head
-
也让
merged_tail
指向它,开始构建过程
这个操作写成:
if (A->data < B->data) {merged_head = A;merged_tail = A;A = A->next;
} else {merged_head = B;merged_tail = B;B = B->next;
}
为什么后面不需要再判断 merged_head?
因为我们一旦初始化了 merged_head
和 merged_tail
,剩下的逻辑就完全一样了。
当任意一个链表先结束(指针变成 NULL),另一个链表的剩余部分就是升序链表,可以直接接在合并链表尾部
if (A == NULL)merged_tail->next = B;
elsemerged_tail->next = A;
操作 | 本质解释 |
---|---|
合并操作 | 从两个有序链表中每次选最小的那个 |
指针处理 | 用 tail 指针维护“已合并链表的结尾” |
尾部接上 | 最后只剩一个非空链表,直接接上 |
内存节省 | 所有节点都是原链表复用,无需新建节点 |
第五步:代码实现
struct Node* merge(struct Node* A, struct Node* B) {struct Node* merged_head = NULL;struct Node* merged_tail = NULL;// Step 1: 处理第一个节点,决定 merged_headif (A == NULL) return B;if (B == NULL) return A;if (A->data < B->data) {merged_head = A;merged_tail = A;A = A->next;} else {merged_head = B;merged_tail = B;B = B->next;}// Step 2: 一边比较一边构建合并链表while (A != NULL && B != NULL) {if (A->data < B->data) {merged_tail->next = A;merged_tail = A;A = A->next;} else {merged_tail->next = B;merged_tail = B;B = B->next;}}// Step 3: 谁没处理完就接上谁if (A != NULL)merged_tail->next = A;elsemerged_tail->next = B;return merged_head;
}
时间与空间复杂度分析
方面 | 分析 |
---|---|
时间复杂度 | O(n + m),n 和 m 分别是两个链表的长度 |
空间复杂度 | O(1),因为不新建节点,只操作指针 |