问题描述
我正在分析 C
编程中的归并排序,我不明白算法的 basic operation
是什么以及我如何设置和 recurrence relation
用于该基本步骤(最好案例)!?
我认为基本操作将是已注释为基本操作的comparison step
。
如果是,那么我该如何设置和recurrence relation
?
#include <stdio.h>
// lets take a[5] = { 32,45,67,2,7 } as the array to be sorted.
// merge sort function
void mergeSort(int a[],int p,int r)
{
int q;
if (p < r)
{
q = (p + r) / 2;
mergeSort(a,p,q);
mergeSort(a,q + 1,r);
merge(a,q,r);
}
}
// function to merge the subarrays
void merge(int a[],int q,int r)
{
int b[5]; //same size of a[]
int i,j,k;
k = 0;
i = p;
j = q + 1;
while (i <= q && j <= r)
{
if (a[i] < a[j]) //basic operation
{
b[k++] = a[i++]; // same as b[k]=a[i]; k++; i++;
}
else
{
b[k++] = a[j++];
}
}
while (i <= q)
{
b[k++] = a[i++];
}
while (j <= r)
{
b[k++] = a[j++];
}
for (i = r; i >= p; i--)
{
a[i] = b[--k]; // copying back the sorted list to a[]
}
}
解决方法
一些提示:
- 递归关系(不存在于您的片段中)是:
- 对左半部分进行排序
- 对右半部分进行排序
- 合并两个部分。
- 决定传递哪些参数,中点和总大小,或者左边大小和右边大小。
- 决定尺寸是否包含最后一个元素
- 首选无符号数据类型的大小和索引;这些永远不会是负面的。
- merge() 函数只需要三个参数;递归调用不需要传递指向数组第一个元素的指针,但可以在数组内的某处给出一个指针。
- 您不需要数组的完整副本,您只需要其中的一半。
void merge(int array[],unsigned lsiz,unsigned rsiz)
{
unsigned lpos,rpos,totpos,totsiz;
int *spare;
if (lsiz > 1) { // recurse: sort the left side
unsigned half;
half = lsiz /2;
merge(array,half,lsiz - half);
}
if (rsiz > 1) { // recurse: sort the right side
unsigned half;
half = rsiz /2;
merge(array+lsiz,rsiz - half);
}
/*
** allocate a copy of the left side
**,so that we can merge *in place*
*/
spare = malloc(sizeof *array * lsiz);
if (!spare) return;
memcpy(spare,array,sizeof *spare *lsiz);
totsiz = lsiz + rsiz;
lpos = 0;
rpos = lsiz;
#if 0
for (totpos = 0; lpos < lsiz && rpos < totsiz; totpos++) {
if (spare[lpos] <= array[rpos]) { array[totpos] = spare[lpos++];}
else { array[totpos] = array[rpos++]; }
}
while (lpos < lsiz) {array[totpos++] = spare[lpos++]; }
while (rpos < totsiz) {array[totpos++] = array[rpos++]; }
#else
for (totpos = 0; totpos < totsiz; totpos++) {
if (lpos >= lsiz) goto right;
if (rpos >= totsiz) goto left;
if (spare[lpos] > array[rpos]) goto right;
left:
array[totpos] = spare[lpos++];
continue;
right:
array[totpos] = array[rpos++];
continue;
}
#endif
free(spare);
}
注意:我保留了 goto
版本,因为我认为它实际上更具可读性。
由于上述答案提供了该过程的代码,因此我只是针对一般类别的问题解决相同的思考过程。这是 Divide and Conquer 问题的经典示例。此类问题的复杂性分析依赖于识别这3个操作的复杂性
- 划分:这是将问题划分为子问题所需的工作量。在归并排序的情况下,这是将数组划分为 2 个等长子数组的步骤。此步骤的复杂性将是 O(1),因为它涉及找到数组的中点。
- 征服:此步骤将涉及解决由上述步骤创建的子问题。所以这里的子问题是对 2 个大小为原始大小一半的数组进行排序。
- Combine:这一步涉及合并子问题的结果以找到原始问题的结果,在这种情况下,这将简化为从已排序的 2 个子数组创建排序数组的问题,这就是由合并功能完成。此步骤的复杂性与段的大小呈线性关系。
所以如果 T(n) 是对大小为 n 的数组进行排序的复杂度, T(n/2) 是对 n/2 大小的片段进行排序的复杂度。现在,如果我用上述 3 个步骤来表达复杂性,如果看起来像
T(n) = O(1) + 2 * T(n/2) + O(n)
可以使用Master's Theorem解决,复杂度为O(nlog(n))