我正在努力提高我的算法知识,并试图通过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;
}
答案 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,j
:a[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,但是它朝着正确的方向前进;)。