目录
一、原题重现
描述:
输入两个升序排列的序列,将两个序列合并为一个有序序列并输出。
输入描述:
输入包含三行,
第一行包含两个正整数n, m,用空格分隔。n表示第二行第一个升序序列中数字的个数,m表示第三行第二个升序序列中数字的个数。第二行包含n个整数,用空格分隔。
第三行包含m个整数,用空格分隔。
输出描述:
输出为一行,输出长度为n+m的升序序列,即长度为n的升序序列和长度为m的升序序列中的元素重新进行升序序列排列合并。
二、解题思路
首先我们看到两个传入数组均为有序递增数组,所以整体思路很简单,就是同时遍历两个数组,设定两个index分别记录两数组已经遍历到的下标,将两者记录下标对应元素进行比较,取出较小者放入数组,同时将其对应的index向后移动一位,接着重复上面的比较工作,直到其中一个数组遍历结束。若另外一个数组没有遍历到最后一位,只需要将index到结束的所有元素直接插入数组即可。
上面方法相当于创建了两个初始数组,还有一个用于容纳合并两初始数组元素的数组。于是可不可以采用只创建两个数组的方式完成所需要的功能,我们想到利用一个数组正常接收其中一串输入,接着在每次输入值时和初始数组中未被选择的元素进行比较,取输入值和初始数组元素中较小者传入结果数组。当然,如果初始数组中index指向的元素比输入值小,那么我们接着循环接收下次输入就会覆盖掉这次输入值,所以需要注意的逻辑是由index向后遍历初始数组,直到本次输入值小于index指向的元素,将初始数组元素传入结果数组的循环才终止。
下面就将着手此方法实现题目需求......
三、实现源代码
初始代码
int n, m;
printf("请分别输入两个数组的长度:");
scanf_s("%d %d", &n, &m);
int arr1[1000];
int arr[2000]; // 输出序列
for (int i = 0; i < n; ++i)
{
scanf("%d", &arr1[i]);
}
int index_n = 0;
int i = 0;
for (i = 0; i < m; ++i) // 接收m次输入同时选择性传入数组
{
int temp_num = 0;
scanf("%d", &temp_num);
while (arr1[index_n] < temp_num && index_n < n)
{
arr[index_n + i] = arr1[index_n];
index_n++;
}
if (arr1[index_n] >= temp_num)
{
arr[index_n + i] = temp_num;
}
}
if (index_n < n) // 后续元素插入
{
while (index_n != n)
{
arr[index_n + i] = arr1[index_n];
index_n++;
}
}
for (int index = 0; index < m + n; index++) // 打印结果数组
{
printf("%d ", arr[index]);
}
在上面代码中,使用arr作为输出序列即结果数组、arr1作为接收第一组数的初始数组,上面代码逻辑按照思路设定可以说挑不出问题了,试着运行:
不出人意料的又出现了bug,为什么会输出乱码呢,明明代码逻辑没问题呀,为了找出问题,我们换输入用例试试:
我们观察规律,好像发现乱码开始出现的位置好像都是当初始数组遍历结束后才出现,所以问题大致可以确定在将输入数插入结果数组的代码块了,那有什么问题呢,接着看:
调试查找问题:
当初始数组遍历结束,轮到执行将每轮输入值插入结果数组:
接着按下F11执行下一步:
可以看到未进入if语句,直接跳出了赋值语句块,分析具体什么原因会导致 arr1[index_n] >= temp_num 为假命题呢,明明只要跳出上面循环就必然代表不满足循环条件:arr1[index_n] < temp_num && index_n < n ,两者正反命题必然有一个成立,为什么这里两条件都不满足呢?
我们发现上面最后一次循环结束后,进入判断条件 arr1[index_n] < temp_num && index_n < n 发现不符合,会不会是不满足 index_n < n 才跳出循环。此时醍醐灌顶,当时设定这个条件的时候不久代表初始数组已经遍历结束了吗,那上轮循环中对 index_n++ 这里用它对应下标访问初始数组不本就是一种不厚道不正确的方式,未被设定初始化值的元素默认值都很小很小,所以它能通过 arr1[index_n] < temp_num 的筛选,但是卡在了 index_n < n 条件处,当然不仅如此,后面的if语句判断条件 arr1[index_n] >= temp_num 也同样存在越界访问(这里指访问未定义初始值未初始化的内存),如果我们想要成功执行赋值语句块:arr[index_n + i] = temp_num; 就需要去掉这个if判断语句,因为正如所设定的一旦不满足初始数组中元素小于temp_num或初始数组被遍历结束后就跳出循环,接着执行 arr[index_n + i] = temp_num; 赋值语句将输入值直接传入结果数组即可。
这里我们试试按照上面解决思路去掉if判断是否可以正常执行:
终于,获得了理想的结果,这就结束了吗? 不然,经过上面判断,我们在while循环部分同样存在越界访问,只不过刚好被 index_n < n 所捕获,这里的越界访问未设定值部分也是不安全、我们不愿意看到的,所以选择交换 arr1[index_n] < temp_num && index_n < n 两判断条件顺序,利用逻辑与的判断逻辑,当要发生访问数组未设定元素时,可以及时退出避免访问。
最终代码
int n, m;
printf("请分别输入两个数组的长度:");
scanf_s("%d %d", &n, &m);
int arr1[1000];
int arr[2000]; // 输出序列
for (int i = 0; i < n; ++i)
{
scanf("%d", &arr1[i]);
}
int index_n = 0;
int i = 0;
for (i = 0; i < m; ++i) // 接收m次输入同时选择性传入数组
{
int temp_num = 0;
scanf("%d", &temp_num);
while (index_n < n && arr1[index_n] < temp_num)
{
arr[index_n + i] = arr1[index_n];
index_n++;
}
//if (arr1[index_n] >= temp_num) // 去掉这个臭臭的if 该判断需要越界访问
//{
arr[index_n + i] = temp_num;
//}
}
if (index_n < n) // 后续元素插入
{
while (index_n != n)
{
arr[index_n + i] = arr1[index_n];
index_n++;
}
}
for (int index = 0; index < m + n; index++) // 打印结果数组
{
printf("%d ", arr[index]);
}
总结
当发生意想不到的结果时,经验要求我们利用调试跟着计算机的执行思路去发现问题、解决问题。探索问题所在的这个过程可以是痛苦的,也可以是给人惊喜的,每次调试解决问题都可以助长我们的调试技巧和发现问题的能力,当成功解决卡壳半天甚至几天的问题时,内心无疑是无比欣喜的,享受这个学习的过程,并乐在其中是最重要的。