编程珠玑第二章读书笔记 第二章 Aha!算法 (手摇法)

给定一个包含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?

先选定一个数,再二分查找另一个数。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值