【问题标题】:Algorithm: Sort sequence of 0/1 using only comparisons算法:仅使用比较对 0/1 的序列进行排序
【发布时间】:2016-06-10 14:59:45
【问题描述】:

我正在努力提高我的算法知识,并尝试从 Skiena 的算法设计手册中解决以下问题:

4-26 考虑使用比较对包含 n 个 0 和 1 的序列进行排序的问题。对于两个值 x 和 y 的每次比较,算法会了解 x y 中的哪一个。

(a) 给出一个算法,在最坏的情况下进行 n-1 次比较。证明您的算法是最优的。

(b) 给出一个算法,在平均情况下进行 2n/3 次比较排序(假设 n 个输入中的每一个都是 0 或 1 的概率相等)。证明你的算法是最优的。

这是我对 (a) 的解决方案:

void sort(int arr[], int length) {
    int last_zero = -1;
    for (int i = 1; i < length; i++) {
        if (arr[i] < arr[i-1]) {
            int tmp = arr[i];
            arr[i] = arr[++last_zero];
            arr[last_zero] = tmp;
        } else if (arr[i] > arr[i-1]) {
            last_zero = i-1;
        }
    }
    return;
}

有没有更好的方法来做到这一点?

我不确定如何处理 (b) 部分。有一个类似的问题here,但我不明白那里的解释。

编辑:显然这被认为过于模糊,所以让我根据回复进行跟进。

我正在跟进@amit 的回复。这部分我不太明白:

(使得 i_k != i_h for k!= h、i_k != i_h for k!= h 和 i_k != j_h 对于所有 k,h)。

无论如何,我想我大致理解您提出的解决 (b) 部分的想法。然而,当我尝试为它编写代码时,我发现它很难完成。这是我到目前为止所拥有的,根据我的测试,它成功地对所有 (0,1) 和 (1,0) 对进行排序,并将相等的对推到数组的末尾,所以我最终得到 {all zeros, allones , 相等对}。我正在尝试实际移动数组元素,而不是计算 0 和 1 的数量。我被困在如何从我目前所拥有的着手:

int compare(int a, int b);
void shift_right(int arr[], int start, int end);
int prob_4_26_b(int arr[], int length) {
    int last_zero = -1;
    int last_one = -1;
    for (int i = 0; i < length; i += 2) {
        int tmp = compare(arr[i], arr[i+1]);
        int cur_zero, cur_one;
        if (tmp == 0) {
            continue;
        }

        cur_zero = i;
        cur_one = i + 1;
        /* We have a 0 and a 1 */
        /* If this is the first zero we have put it at index 0
         and shift everything from index 0 to cur_zero-1 right by 1.
         last_zero = 0 and if there are ones last_one++ */
        if (last_zero == -1) {
            int start = 0;
            int end = cur_zero - 1;
            shift_right(arr, start, end);
            arr[0]=0;
            last_zero = 0;
            if (last_one != -1) {
                last_one++;
            }
        }
        /* If this is not the first zero we have then put it at
         last_zero+1 and shift everything from last_zero+1 to cur_zero-1
         right by 1. last_zero++ and if we have ones last_one++. */
        else {
            int start = last_zero + 1;
            int end = cur_zero - 1;
            shift_right(arr, start, end);
            arr[last_zero+1] = 0;
            last_zero++;
            if (last_one != -1) {
                last_one++;
            }
        }

        /* If this is the first one we have put it after the last_zero.
         Shift everything from last_zero+1 to cur_one-1 right by 1.
         last_one = last_zero+1. */
        if (last_one == -1) {
            int start = last_zero + 1;
            int end = cur_one-1;
            shift_right(arr, start, end);
            arr[last_zero+1] = 1;
            last_one = last_zero + 1;
        }
        /* If this is not the first one we have put it at last_one+1
         and shift everything from last_one+1 to cur_one-1 right by 1.
         last_one++ */
        else {
            int start = last_one + 1;
            int end = cur_one - 1;
            shift_right(arr, start, end);
            arr[last_one+1] = 1;
            last_one++;
        }
    }
    return 0;
}

void shift_right(int arr[], int start, int end) {
    for (int i = end; i >=start; i--) {
        arr[i+1] = arr[i];
    }
}

int compare(int a, int b) {
    if (a < b)
        return -1;
    else if (a > b)
        return 1;
    else
        return 0;
}

【问题讨论】:

    标签: c++ c algorithm sorting


    【解决方案1】:

    为了做第二部分,你需要首先实现检查comp(a[i], a[i+1]),和comp(a[i+1], a[i+2])不是无关的。第一个的答案可能会帮助你得到第二个的答案。

    要使用它,首先将序列分成对,(a[i1],a[j1]), (a[i2],a[j2]),..., (a[i_n/2], a[j_n/2])。 (例如 i_k != i_h for k!= h,i_k != i_h for k!= h,以及 i_k != j_h for all k,h)。

    比较每一对。平均而言(假设位是均匀分布的),您将有 n/4 个a[i] &lt; a[j] 或相反的结论性答案,而对于剩余的 n/4,您将有平等。

    所以,首先你可以很容易地对那些有决定性答案的人进行排序(开头越小,结尾越大)。

    接下来,您将递归调用提醒上的算法。但是“诀窍”来了,如果你知道一些i,j:a[i] = a[j],你不需要同时得到答案。其中一个的答案也会告诉你第二个的价值。
    这意味着您可以递归调用只有 n/4 个元素,而不是 n/2(平均而言)。

    当你有一个元素时,停止子句,只需将它与0 比较即可知道它的值。

    这为您提供了以下复杂性函数:

    T(1) = 1
    T(n) = n/2 + T(n/4)
    

    经过一些数学运算,为这个递归公式(或consulting with wolfram alpha)找到了一个近似形式,你会发现T(n) = (2n+1)/3

    【讨论】:

      【解决方案2】:

      我不会为您提供完整的解决方案,但也许足以为您指明正确的方向。当从字面上理解比较的作用时,问题可能会更清楚一些:

      int compare(int a,int b){
          if (a>b) return 1;
          if (b>a) return -1;
          return 0;
      }
      

      下一步是意识到您实际上只需要计算零(或一)来对数组进行排序。但是,只要您比较的数字相等,您就不知道它是零还是一(否则您只需要 n/2 “比较”):

      typedef std::vector<int> T;
      int count(const T& vect) {
          int count = 0;
          int temp_i = -1;
          int temp_count = 0;        
          for (i=0;i<vect.size();i=i+2){
              int x = abs(compare(vect[i],vect[i+1]));  // (1)
              if (x==1) count++;  
              if (x==0) {
                  if (temp==-1) {
                      temp_i = i;
                      temp_count = 2;
                  } else {
                      int x = compare(vect[i],vect[temp_i]));   // (2)
                      if (x==1) {                    // 2 ones and some zeros
                           count += 2;
                           temp_count = 0;
                           temp_i = -1;
                      } else if (x==-1) {            // 2 zeros and some ones
                           count += temp_count;
                           temp_count = 0;
                           temp_i = -1;
                      } else {                       // all the same
                           temp_count += 2;
                      }
              }
          }
      }
      

      我基本上成对比较数字,当它们相同时,我不知道它是零还是一(否则问题是微不足道的),我记得索引将它与我的下一对相同数字进行比较遇到。当它们再次相同时,我只需要记住我遇到了多少对,直到我有一对不等于另一对。 我什至没有尝试编译代码,而且它不是最理想的。它只适用于偶数大小的数组,我刚刚意识到我忘了在循环完成时添加temp_count。一旦我有更多的时间,我会修复它。但是,看看如何降低复杂性就足够了:

      第一次比较 (1) 执行 n/2 次,对于平均输入,在 50% 的情况下必须进行第二次比较。不是真正要求的 2/3 n,但它朝着正确的方向发展;)。

      【讨论】:

        猜你喜欢
        • 2010-10-29
        • 1970-01-01
        • 2013-02-13
        • 2018-09-10
        • 2014-05-30
        • 1970-01-01
        • 1970-01-01
        • 2013-05-21
        相关资源
        最近更新 更多