【问题标题】:What is the basic operation of merge sort and how do I find a recurrence relation for the basic step in the best case什么是归并排序的基本操作以及如何在最好的情况下找到基本步骤的递归关系
【发布时间】:2021-05-21 08:36:45
【问题描述】:

我正在分析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, p, q, r);
    }
}

// function to merge the subarrays
void merge(int a[], int p, 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[]
    } 
}

【问题讨论】:

  • [style] 我将从使用更具描述性的标识符开始。
  • 循环关系(不存在于您的片段中)是 1) 对左半部分进行排序 2) 对右半部分进行排序 3) 合并两半。您的代码仅包含 (3) [而且我认为其中有一些错误]

标签: c algorithm mergesort recurrence


【解决方案1】:

一些提示:

  • 重复关系(您的片段中不存在)是:
    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, half, 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 版本,因为我认为它实际上更具可读性。

【讨论】:

  • 真正使用请重新考虑你对malloc的使用,我们真的需要那么多malloc吗?
  • 不,您可以使用单个(半尺寸)存储。但这需要更多的簿记。 [因此:使演示程序复杂化]
【解决方案2】:

由于上述答案提供了该过程的代码,因此我只是针对一般问题类别解决其背后的思考过程。这是Divide and Conquer 问题的经典示例。此类问题的复杂性分析依赖于识别这 3 个操作的复杂性

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-13
    • 2014-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多