归并排序的基本操作是什么,在最佳情况下如何找到基本步骤的递推关系

问题描述

我正在分析 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[]
    } 
}

解决方法

一些提示:

  • 递归关系(不存在于您的片段中)是:
    1. 对左半部分进行排序
    2. 对右半部分进行排序
    3. 合并两个部分。
  • 决定传递哪些参数,中点和总大小,或者左边大小和右边大小。
  • 决定尺寸是否包含最后一个元素
  • 首选无符号数据类型的大小和索引;这些永远不会是负面的。
  • 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个操作的复杂性

  1. 划分:这是将问题划分为子问题所需的工作量。在归并排序的情况下,这是将数组划分为 2 个等长子数组的步骤。此步骤的复杂性将是 O(1),因为它涉及找到数组的中点。
  2. 征服:此步骤将涉及解决由上述步骤创建的子问题。所以这里的子问题是对 2 个大小为原始大小一半的数组进行排序。
  3. 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))