2016-02-27 79 views
3

我想提高我的算法知识的0/1排序顺序,我试图解决从Skiena的算法设计手册以下问题:算法:只使用比较

4-26考虑的问题使用比较对n 0和1的序列进行排序。对于x和y两个值的每个比较,算法会学习哪个x保持不变。 (a)给出一个算法,在最坏情况下对n - 1个比较进行排序。证明你的算法是最优的。

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

这是我(一)解决方案:

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为K = H,I_K =对于k = i_h小时,I_K = j_h 对所有K,H) 。

无论如何,我想我通常理解你提出的解决方案(b)的想法。然而,当我试图为它编写代码时,我发现很难完成。这是我迄今为止,并根据我的测试它成功地对所有(0,1)和(1,0)对进行排序并将相等的对推到数组的末尾,所以我最终得到{全零,全1 ,等于对}。我试图实际移动数组元素,而不是计数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; 
} 

回答

2

为了做第二部分,你需要首先实现检查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])。 (使得对于k,h,i_k!= i_h,对于k!= h,i_k!= i_h,并且对于所有的k,h,i_k!= j_h)。

比较每个这样的对。平均而言(假设这些比特是均匀分布的),您将得到n/4的结论性答案a[i] < a[j]或其他方式,而对于剩下的n/4,您将具有平等性。因此,首先你可以很容易地对那些具有决定性答案的人进行排序(开始时较小,结束时较大)。

接下来,您将递归地调用提醒中的算法。但是,如果你知道对于某些i,ja[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

0

我不会给你一个完整的解决方案,但也许这足以让您指向正确的方向。该问题可能变得有点更加清晰,同时陈述问题时,从字面上什么比较呢:

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

下一步是要认识到,你实际上只需要算零(或1)进行排序阵列。然而,只要你比较的数字是平等的,你不知道,如果它是零或一的(否则你就只需要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,但它在正确的方向;)。