Ryen的学习笔记

成长有多少新奇的美,就有多少撕裂的痛;离去有多么辽阔的自由,就有多么无边的孤寂。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

《算法竞赛入门经典》读书笔记二 之 枚举

Posted on 2010-02-04 17:23  Ryen_lee  阅读(565)  评论(0)    收藏  举报

第五章

  • C++中结构体的使用:类似与类,可以定义构造函数,重载运算符。
  • qsort的使用: 其默认是增续排列,自定义比较函数,该函数使用时不拘一格。

第六章

  • ACM题目中尽量使用数组来实现链表。
  • 指针访问比用“数组+下标”方式略快。

第七章

  • 生成1~n排列的方法
  1. 基本思想是用递归,先输出所有以1开头的排列,然后输出所有以2开头的排列,最后输出以n开头的。伪代码如下:
    void print_permutation(序列A, 集合S)
    {
    if(S 为空) 输出序列A;
    else 按照从小到大的顺序依次考虑S的每个元素v
    {
    print_permutation(在A的末尾加上v得新序列,s
    -{v})
    }
    }

     

    对集合S的元素进行排序,然后统计各个元素出现的个数,可以得到生成可重集的程序:
    代码
    #include<stdio.h>
    int P[100], A[100];

    // 输出数组P中元素的全排列。数组P中可能有重复元素, 数组P中的元素需要已经按顺序排好了
    void print_permutation(int n, int* P, int* A, int cur) {
    int i, j;
    if(cur == n) {
    for(i = 0; i < n; i++) printf("%d ", A[i]);
    printf(
    "\n");
    }
    else for(i = 0; i < n; i++) //因为是全排列,所以每次大循环是必要的。从中选去最小元素
    if(!i || P[i] != P[i-1])
    {
    int c1 = 0, c2 = 0;
    for(j = 0; j < cur; j++) if(A[j] == P[i]) c1++; //判断该元素是否还能使用
    for(j = 0; j < n; j++) if(P[i] == P[j]) c2++;
    if(c1 < c2) {
    A[cur]
    = P[i];
    print_permutation(n, P, A, cur
    +1);
    }
    }
    }

    int main() {
    int i, n;
    scanf(
    "%d", &n);
    for(i = 0; i < n; i++)
    scanf(
    "%d", &P[i]);
    //sort(P, P+n);
    print_permutation(n, P, A, 0);
    return 0;
    }

     

     

    STL提供了 next_permutation函数,可以按照字典顺序生成排列:
    代码
    #include<cstdio>
    #include
    <algorithm>
    using namespace std;
    int main() {
    int n, p[10];
    scanf(
    "%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &p[i]);
    sort(p, p
    +n); // 排序,得到p的最小排列
    do {
    for(int i = 0; i < n; i++) printf("%d ", p[i]); // 输出排列p
    printf("\n");
    }
    while(next_permutation(p, p+n)); // 求下一个排列
    return 0;
    }

     

  • 枚举子集的三种方法:
  1. 增量构造法:每次首先打印当前子集合,然后尝试按照定序选择一个元素放入当前集合中。然后递归调用下去。该方法解答树节点为2^n.
    代码
    #include<stdio.h>

    int B[5]={1,3,5,7,9};
    int A[10];
    int C[10] = {0,0,0,1,0,2,0,3,0,4}; // 该数组记录数组B中个元素的下标

    void print_subset(int n, int cur)
    {

    for(int i = 0; i < cur; i++)
    printf(
    "%d ", A[i]); // 打印当前集合
    printf("\n");

    int s = cur ? C[A[cur-1]] +1 : 0 ;// 确定当前元素的最小可能值 这里使用了定序技巧
    for(int i = s; i < n; i++)
    {
    A[cur]
    = B[i];
    print_subset(n,cur
    +1); // 递归构造子集
    }
    }

    int main() {
    print_subset(
    5, 0);
    return 0;
    }

     

  2. 位向量法:构造位向量B[i], 仍然使用递归,每步生成位向量一位,直至N位时,根据B打印结果。该方法解答树节点为2^(n+1),因为包含了中间结果,所以比上一种方法多了一倍节点。
    代码
    #include<stdio.h>

    void print_subset(int n, int* B, int cur) {
    if(cur == n) {
    for(int i = 0; i < cur; i++)
    if(B[i]) printf("%d ", i); // 打印当前集合
    printf("\n");
    return;
    }
    B[cur]
    = 1; // 选第cur个元素
    print_subset(n, B, cur+1);
    B[cur]
    = 0; // 不选第cur个元素
    print_subset(n, B, cur+1);
    }

    int B[10];
    int main() {
    print_subset(
    5, B, 0);
    return 0;
    }

     

  3. 二进制法: 思想和第二种方法一样,不过使用32位INT 即内置类型作为位向量使用。利用计算机位运算以及内部为二进制的优势,效率也为 2^n的效率。
    代码
    #include<stdio.h>

    void print_subset(int n, int s) { // 打印{0, 1, 2, ..., n-1}的子集S
    for(int i = 0; i < n; i++)
    if(s&(1<<i)) printf("%d ", i); // 这里利用了C语言“非0值都为真”的规定
    printf("\n");
    }

    int main() {
    int n = 5;
    for(int i = 0; i < (1<<n); i++) // 枚举各子集所对应的编码 0, 1, 2, ..., 2^n-1
    print_subset(n, i);
    return 0;
    }

     

  • 回溯法: 使用时注意一定要在递归调用后将递归调用前设置的全局辅助(VIS)变量重置。
  • 隐式图遍历: BFS。 迭代加深搜索:每次决定搜索的深度(递增),然后使用DFS进行搜索。

第八章

  • 二分查找:
    while(x < y)
    {
    m
    = x + (y - x) / 2;
    if(F(m)) y = m;
    else x = m + 1;
    }

    由于 / 对整型取整是朝零取整而不是向下取整, 即-5/2是2 所以使用x + (y - x) / 2 则可以保证分界点总是靠近区间的起点。如果使用(x + y) / 2则不行(当x+y小于零时, 所求的中点会靠近终点)

  • 最大值最小化: 通过二分和贪心,将最优化问题转换为二分查找问题, 应该也是数值方法的思想。