给定一个包含43000,0000,0000,00个32位整数的
顺序文件。如何可以找到一个至少出现了两次的整数?
整数数量比32位整数能够表示的数量要多很多。
二分查找一个包含一半以上整数的子区间,递归查找出现两次的单词。不过这种方法不能保证每次都将整数数目减半。如果使用k个通道。每个通道有n/k个数, 如果每次都能二分需要进行log(2)(n/k)次查找,最坏情况要进行n/k次查找。最差情况下运行时间和n^2成正比。
如何能保证至少能找到一个数,却把运行时间降为线性?如果这一段整数范围为[a,b],则我们最多存储b-a +1个整数,这样最多只会有一个重复元素,更多的元素我们就忽略掉。这样这段中就有且只有一个重复元素。
———————————————————————————————————————————————
给一个未排序的含[a,b]区间的整数的文件。找出文件中没有写出的却在此区间的一个整数。
每一遍测试所有整数的一个位。从最高位开始。把0和1的分开输出到两个文件中去。输出完成后,查看生成的两个文件的行数。对其中行数有缺少的文件再进行探测。
———————————————————————————————————————————————
手摇法原地旋转向量或字符串。这种算法省内存,但比直接开一块内存然后交换的方法要慢。
#include "macro.h"
void reverse(char* s, int start, int end){
int i, j, tmp;
for(i = start, j = end; i < j; i++, j--){
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
int
main(int argc, char* argv[]){
int len = strlen(argv[1]);
int i = atoi(argv[2]);
reverse(argv[1], 0, i - 1);
reverse(argv[1], i, len - 1);
reverse(argv[1], 0, len - 1);
printf("%s\n", argv[1]);
}
更具技巧性的实现:
用到的最大公因子计算函数:
1、旋转整数数组:
int gcd(int i, int j){
while(i != j){
if(i > j)
i -= j;
else
j -= i;
}
return i;
}
void bitswap(int *x, int n, int rotdist){
int i, t, j, k, ii;
if(rotdist == 0 || rotdist == n)
return;
for(i = 0; i < gcd(rotdist, n); i++){
t = x[i];
j = i;
while(1){
k = j + rotdist;
if(k >= n)
k -= n;
if(k == i)
break;
x[j] = x[k];
j = k;
}
x[j] = t;
}
}
运行swap(x, 10, 3),观察每一次交换后数组的情况。这个只有一趟。下划线是最终结果。红色是这次被修改的元素。
j:0, k:3
3, 1, 2, 3, 4, 5, 6, 7, 8, 9
j:3, k:6
3, 1, 2, 6, 4, 5, 6, 7, 8, 9
j:6, k:9
3, 1, 2, 6, 4, 5, 9, 7, 8, 9
j:9, k:2
3, 1, 2, 6, 4, 5, 9, 7, 8, 2
j:2, k:5
3, 1, 5, 6, 4, 5, 9, 7, 8, 2
j:5, k:8
3, 1, 5, 6, 4, 8, 9, 7, 8, 2
j:8, k:1
3, 1, 5, 6, 4, 8, 9, 7, 1, 2
j:1, k:4
3, 4, 5, 6, 4, 8, 9, 7, 1, 2
j:4, k:7
3, 4, 5, 6, 7, 8, 9, 7, 1, 2
3, 4, 5, 6, 7, 8, 9, 0, 1, 2
运行swap(x, 10, 2),观察每一趟的输出。这个需要2趟。
j:0, k:2
2, 1, 2, 3, 4, 5, 6, 7, 8, 9
j:2, k:4
2, 1, 4, 3, 4, 5, 6, 7, 8, 9
j:4, k:6
2, 1, 4, 3, 6, 5, 6, 7, 8, 9
j:6, k:8
2, 1, 4, 3, 6, 5, 8, 7, 8, 9
2, 1, 4, 3, 6, 5, 8, 7, 0, 9
*****************
j:1, k:3
2, 3, 4, 3, 6, 5, 8, 7, 0, 9
j:3, k:5
2, 3, 4, 5, 6, 5, 8, 7, 0, 9
j:5, k:7
2, 3, 4, 5, 6, 7, 8, 7, 0, 9
j:7, k:9
2, 3, 4, 5, 6, 7, 8, 9, 0, 9
2, 3, 4, 5, 6, 7, 8, 9, 0, 1
*****************
这个方法的好处是每一个元素一步到位,大大减少了交换次数。
为什么只需要gcd(n, rotdist)趟呢?
循环替代元素,每一步前进rotdist长度,相当于把所有元素分成了gcd个部分。
2、另一种方法:不过这个每个元素不能一步到位,不如上一个效率高。不是很明白这个算法的原理。
void swap(int *x, int a, int b, int m){
int tmp;
int i;
for(i = 0; i < m; i ++){
tmp = x[a + i];
x[a + i] = x[b + i];
x[b + i] = tmp;
}
}
void swapm(int* x, int n, int rotdist){
int i, j, p, ii;
if(rotdist == 0 || rotdist == n)
return;
i = p = rotdist;
j = n - p;
while(i != j){
if(i > j){
swap(x, p - i, p, j);
i -= j;
}else{
swap(x, p - i, p + j - i, i);
j -= i;
}
}
swap(x, p - i, p, i);
}
swapm(x, 10, 4);的结果。
a:0, b:6, m:4
6, 7, 8, 9, 4, 5, 0, 1, 2, 3
a:0, b:4, m:2
4, 5, 8, 9, 6, 7, 0, 1, 2, 3
a:2, b:4, m:2
4, 5, 6, 7, 8, 9, 0, 1, 2, 3
———————————————————————————————————————————————
二分查找的缺点:整个表必须是已知的,并且还必须预先进行排序。
签名:等价关系定义了各个类的时候,定义各个签名是很有帮助的。先生成签名,再搜集所有具有相同签名的项目。探测法例子:Soundex探测法。
———————————————————————————————————————————————
给定一个具有n个元素的实数集,一个实数t以及一个整数k,请问如何快速确定该实数集是否存在两个元素,它们的总和为t?
先选定一个数,再二分查找另一个数。