算法:仅使用比较对0/1的序列进行排序

时间:2016-02-27 09:16:45

标签: c++ c algorithm sorting

我正在努力提高我的算法知识,并试图通过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代表k!= h,i_k!= i_h代表k!= h,而i_k!= j_h   对于所有k,h)。

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

答案 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])。 (这样i_k!= i_h代表k!= h,i_k!= i_h代表k!= h,而i_k!= j_h代表所有k,h)。

比较每一对。平均而言(假设比特均匀分布),你将得到a[i] < a[j]或其他方面的n / 4结论性答案,对于剩余的n / 4,你将有平等。

所以,首先你可以轻松地对那些有确定答案的人进行排序(开头越小,最后越大)。

接下来,您将在提醒中递归调用算法。但是这里出现了&#34;技巧&#34;如果你知道某些i,ja[i] = a[j],你就不需要为两者做出回答。对其中一个人的回答也会告诉你第二个的价值。
这意味着你可以只用n / 4个元素递归调用,而不是n / 2(平均)。

当你有一个单独的元素时,stop子句将会与0进行比较,以了解它的值。

这为您提供了复杂功能:

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

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

答案 1 :(得分:0)

我不会给你一个完整的解决方案,但也许足以指出你正确的方向。当问题陈述从字面上理解比较时,问题可能会更清楚:

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

下一步是要意识到你实际上只需要计算零(或一些)来对数组进行排序。但是,每当你比较的数字相等时,你就不知道它是零还是一个(否则你只需要n / 2&#34;比较&#34;):

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;
                }
        }
    }
}

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

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