我们常说,“没有最好,只有更好。”归并排序大量引用了递归,尽管在代码上比较清晰,容易理解,但这会造成时间和空间上的性能损耗。我们排序追求的就是效率,有没有可能将递归转化成迭代呢?结论当然是可以的,而且改动之后,性能上进一步提高了,来看代码。
/* 对顺序表L作归并非递归排序 */
void MergeSort2(SqList *L)
{
/* 申请额外空间 */
int * TR = (int *)malloc(L->length * sizeof(int));
int k = 1;
while (k < L->length)
{
MergePass(L->r, TR, k, L->length);
/*子序列长度加倍 */
k = 2 * k;
MergePass(TR, L->r, k, L->length);
/* 子序列长度加倍 */
k = 2 * k;
}}
这段代码是归并排序算法的非递归实现。
首先,代码中通过malloc函数申请了一个大小为L->length的int类型数组TR,用来存放归并排序过程中的中间结果。
然后,通过一个while循环,不断进行归并排序的迭代。循环的条件是k小于L->length,其中k表示子序列的长度。
在循环内部,首先调用MergePass函数,将顺序表L中的元素归并排序到数组TR中,子序列的长度为k。MergePass函数的作用是将数组L->r中的元素按照长度为k的子序列进行归并排序,并将结果存放在数组TR中。
然后,将TR中的有序序列再次归并排序到顺序表L的r数组中,子序列的长度再次加倍。
通过不断迭代,每次将序列的子序列长度加倍,并在顺序表和临时数组之间进行归并排序,最终得到了完整的有序序列。
总结来说,这段代码实现了归并排序算法的非递归版本,通过迭代地将子序列的长度加倍,并在顺序表和临时数组之间进行归并排序,最终得到了完整的有序序列。
从代码中,我们能够感受到,非递归的迭代做法更加直截了当,从最小的序列开始归并直至完成。不需要像归并的递归算法一样,需要先拆分递归,再归并退出递归。
现在我们来看MergePass代码是如何实现的。
/* 将SR[]中相邻长度为s的子序列两两归并到TR[] */
void MergePass(int SR[], int TR[], int s, int n)
{
int i = 1;
int j;
while (i <= n - 2 * s + 1)
{
/* 两两归并 */
Merge(SR, TR, i, i + s - 1, i + 2 * s - 1);
i = i + 2 * s;
}
/* 归并最后两个序列 */
if (i < n - s + 1)
Merge(SR, TR, i, i + s - 1, n);
/* 若最后只剩下单个子序列 */
else
for (j = i; j <= n; j++)
TR[j] = SR[j];
}
这段代码是归并排序算法中的归并段落操作。
函数接收一个原始序列SR,一个用于存放结果的序列TR,以及两个参数s和n,表示对SR的相邻长度为s的子序列进行归并,归并结果存放在TR中,n表示SR中的元素个数。
首先,通过一个while循环,迭代地将SR中相邻长度为s的子序列两两归并到TR中。循环的条件是i小于等于n-2*s+1,其中i表示SR中子序列的起始位置。
在循环内部,调用Merge函数,将SR中的两个相邻子序列(起始位置分别为i和i+s-1,结束位置为i+2*s-1)归并到TR中。这样,每次循环都将两个相邻子序列归并到TR中。
每次循环后,更新i的值为i+2*s,继续处理下一对相邻子序列。
当循环结束时,如果i小于n-s+1,说明剩下的子序列长度大于s,即还有一对子序列需要归并。此时,再次调用Merge函数,将剩下的两个子序列归并到TR中。
如果i大于等于n-s+1,说明剩下的子序列长度小于等于s,即只有单个子序列未被归并。此时,通过一个循环将剩下的单个子序列复制到TR中。
通过这个归并段落操作,将SR中相邻长度为s的子序列逐步归并到TR中,生成更长的有序子序列。
综上所述,这段代码实现了归并排序算法的归并段落操作,通过迭代地将SR中相邻长度为s的子序列归并到TR中,最终得到更长的有序子序列。
非递归的迭代方法,避免了递归时深度为log2n的栈空间,空间只是用到申请归并临时用的TR数组,因此空间复杂度为O(n),并且避免递归也在时间性能上有一定的提升,应该说,使用归并排序时,尽量考虑用非递归方法。