• 分治法1


    分治法。 无需赘述! 就是把规模较大的问题划分成易于解决的小问题, 这个思路和模块化编程思想较为相似!。 然后把小问题的解组合成所要的最终目的解。 在一般情况下, 分治法都和递归有一腿, 所以如果想用好分治, 请先了解一下递归(当然大神可以秒杀一切, 腾空跳读, 弱渣只有膜拜!)。

            

    分治法模式:

                     

    divide-and-conquer(P)
    {
        if(|P|<=n0) adhoc(P);
        divide P into smaller subinstances P1, P2, ,,, Pk;
        for(i=1; i<=k; i++)
        y1 = divide-and-conquer(Pi);
        return merge(y1, ,,, yk);
    }

             

    分治举例:                             

     《1》二分查找:

     1 int binarySearch(int a[], int x, int n)
     2 {
     3     int left = 0; int right = n-1;
     4     while(left<=right)
     5     {
     6         int middle = (left+right)/2;
     7         if(x==a[middle]) return middle;
     8         if(x>a[middle]) left = middle + 1;
     9         else right = middle - 1;
    10     }
    11     return -1;
    12 }    
    View Code

    《2》棋盘覆盖。

    在一个2^k×2^k个方格组成的棋盘中,若有一个方格与其他方格不同,则称该方格为一特殊方格,且称该棋盘为一个特殊棋盘.显然特殊方格在棋盘上出现的位置有4^k种情形.因而对任何k≥0,有4^k种不同的特殊棋盘.      下图–图(1)中的特殊棋盘是当k=3时16个特殊棋盘中的一个:

                      图(1)

          题目要求在棋盘覆盖问题中,要用下图-图(2)所示的4种不同形态的L型骨牌覆盖一个给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖.

    图(2)

    解题思路:把每个大的棋盘分成4个小棋盘。为了把其中的三个无特殊方格的子棋盘转化成特殊棋盘, 可以用一个L型骨牌覆盖这3个较小棋盘的会合处。 递归的进行分割, 直至棋盘简化成 1 * 1的规模。

     1 int chessBoard(int tr, int tc, int dr, int dc, int size)
     2 {
     3     if(size==1) return;
     4     int t = tile++;          //L型骨牌号 
     5     s = size/2;              //分割棋盘 
     6     //覆盖左上角子棋盘。 
     7     if(dr<tr+s&&dc<tc+s)
     8     //特殊方格在此棋盘中。 
     9     chessBoard(tr, tc, dr, dc, s);
    10     else
    11     {//此棋盘中无特殊方格
    12     // 用t号 L型骨牌覆盖右下角。 
    13         board[tr+s-1][tc+s-1] = t;
    14     //覆盖其余方格 
    15         chessBoard(tr, tc, tr+s-1, tc+s-1, s);
    16     }
    17     //覆盖右上角子棋盘。 
    18     if(dr<tr+s&&dc>=tc+s)
    19         chessBoard(tr, tc+s, dr, dc, s)
    20     else
    21     {
    22         board[tr+s-1][tc+s] = t;
    23         chessBoard(tr, tc+s, tr+s-1, tc+s, s);
    24     }
    25     //覆盖左下角子棋盘。 
    26     if(dr>=tr+s&&dc<tc+s)
    27     chessBoard(tr+s, tc, dr, dc, s);
    28     else
    29     {
    30         board[tr+s][tc+s-1] = t;
    31         chessBoard(tr+s, tc, tr+s, tc+s-1, s);
    32     }
    33     //覆盖右下角子棋盘。 
    34     if(dr>=tr+s&&dc>=tc+s)
    35     chessBoard(tr+s, tc+s, dr, dc, s);
    36     else
    37     {
    38         board[tr+s][tc+s] = t;
    39         chessBoard(tr+s, tc+s, tr+s, tc+s);
    40     }
    41 }
    View Code

     《3》 归并排序。

     1 #include<iostream>
     2 using namespace std;
     3 
     4 const int maxn = 10005;
     5 int A[maxn], T[maxn];
     6 
     7 void merge_sort(int *A, int x, int y, int* T)  //x为要排序数组的下界下标, y为,, 
     8 {
     9     if(y-x>1)
    10     {
    11         int m = x + (y-x)/2;//划分, 其实这里可以优化, 以中位数划分更快。 
    12         int p = x, q = m, i = x;
    13         merge_sort(A, x, m, T);//递归 
    14         merge_sort(A, m, y, T);
    15         while(p<m||q<y)              //合并 
    16         {
    17             if(q>=y||(p<m&&A[p]<=A[q]))
    18             T[i++] = A[p++];               //从左半部分复制 
    19             else T[i++] = A[q++];          //从右半部分复制 
    20         }
    21         for(i=x; i<y; i++) A[i] = T[i];  //从辅助空间复制回A数组。 
    22     }
    23 }
    24 int main()
    25 {
    26     int n;
    27     scanf("%d", &n);
    28     for(int i = 0; i<n; i++)
    29     {
    30         scanf("%d", &A[i]);
    31     }
    32     merge_sort(A, 0, n, T);
    33     /*------*/
    34     return 0; 
    35 } 
    View Code

    《4》快速排序。

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 
     6 int a[10] = {12, 5, 6, 23, 8658,  21, 1, 1, 25};
     7 
     8 void quick_sort(int b, int e, int a[])
     9 {
    10     int i = b, j = e, x = a[(b+e)/2];
    11     do
    12     {
    13         while(a[i]<x) i++;
    14         while(a[j]>x) j--;
    15         if(i<=j) swap(a[i++], a[j--]);
    16     }while(i<j) ;
    17     if(j < e) quick_sort(i, e, a);
    18     if(j > b) qiuck_sort(b, j, a);
    19 } 
    20 
    21 int main()
    22 {
    23     quick_sort(0, 9, a);
    24     for(int i = 0; i<10; i++)
    25     printf("%d ", a[i]);
    26     return 0;
    27 }
    View Code

    《5》 线性时间选择。

    对于给定的n个元素的数组a[0:n—1],要求从中找出第k小的元素。
    输入

    输入有多组测试例。

    对每一个测试例有2行,第一行是整数n和k(1≤k<n≤1000),第二行是n个整数。

    输出

    第k小的元素。

    输入样例

    输出样例

    5 2

    3 9   4 1 6

    7 3

    4   59 7 23 61 55 46

    3

    23

    分析: 对于这个问题, 最简单的方法就是,先排序, 然后输出第K个值, 然而, 这样的算法有点慢。 这里还有更快的算法。 这个算法和快速排序算法的思想差不多。 即现以第一个数为标准, 把数划分成左边的都小于该数, 右边的都大于该数。如果左边的数的个数大于 K 则再把把左边的作为整体划分即可, 否则 在右边, 缩小k变为k-左边的个数, 然后划分右边。 详见代码:

     1 #include<iostream>
     2 #include<cstdio>
     3 using namespace std;
     4 
     5 const int maxn = 1000 + 5;
     6 int a[maxn];
     7 
     8 int select(int left, int right, int k)
     9 {
    10     //找到了第 k 小的元素。 
    11     if(left>=right) return a[left];
    12     int i = left;
    13     int j = right + 1;
    14     int pivot = a[left];
    15     while(true)                //快排 
    16     {
    17         do{
    18             i = i + 1;
    19         }while(a[i]<pivot);
    20         do 
    21         {
    22             j=j-1;
    23         }while(a[j]>pivot);
    24         if(i>=j) break;
    25         swap(a[i], a[j]);
    26     }
    27     if(j-left+1==k) return pivot;
    28     a[left] = a[j]; //存储pivot 
    29     a[j] = pivot;
    30     if(j-left+1<k)
    31     return select(j+1, right, k-j+left-1);
    32     else return select(left, j-1, k);
    33 }
    34 int main()
    35 {
    36     int n, k;
    37     while(scanf("%d%d", &n, &k)!=EOF)
    38     {
    39         for(int i=0; i<n; i++)
    40         scanf("%d", &a[i]);
    41         printf("%d
    ", select(0, n, k+1));//注意数组从 0 开始。 
    42     }
    43     return 0;
    44 }
    View Code

    在最差的形况下, 这个算法的时间复杂度为n^2  . 当然, 这个算法还可以进一步的进行优化。 聪明的你也许已经想到了优化的方法,即和对快排的优化一样, 用产生随机数的方法啦进行划分, 而不是以每次的第一个数作为划分标准。 

    《6》最接近点对问题。

    《7》 循环赛日程表。

    设有n=2^k个运动员要进行网球循环赛。现要设计一个满足以下要求的比赛日程表:        (1)每个选手必须与其他n-1个选手各赛一次;      (2)每个选手一天只能参赛一次;      (3)循环赛在n-1天内结束。

         请按此要求将比赛日程表设计成有n行和n-1列的一个表。在表中的第i行,第j列处填入第i个选手在第j天所遇到的选手。其中1≤i≤n,1≤j≤n-1。8个选手的比赛日程表如下图:

         算法思路按分治策略,我们可以将所有的选手分为两半,则n个选手的比赛日程表可以通过n/2个选手的比赛日程表来决定。递归地用这种一分为二的策略对选手进行划分,直到只剩下两个选手时,比赛日程表的制定就变得很简单。这时只要让这两个选手进行比赛就可以了。如上图,所列出的正方形表是8个选手的比赛日程表。其中左上角与左下角的两小块分别为选手1至选手4和选手5至选手8前3天的比赛日程。据此,将左上角小块中的所有数字按其相对位置抄到右下角,又将左下角小块中的所有数字按其相对位置抄到右上角,这样我们就分别安排好了选手1至选手4和选手5至选手8在后4天的比赛日程。依此思想容易将这个比赛日程表推广到具有任意多个选手的情形。

        算法步骤

     

         (1)用一个for循环输出日程表的第一行 for(int i=1;i<=N;i++) a[1][i] = i

         (2)然后定义一个m值,m初始化为1,m用来控制每一次填充表格时i(i表示行)和j(j表示列)的起始填充位置。

         (3)用一个for循环将问题分成几部分,对于k=3,n=8,将问题分成3大部分,第一部分为,根据已经填充的第一行,填写第二行,第二部分为,根据已经填充好的第一部分,填写第三四行,第三部分为,根据已经填充好的前四行,填写最后四行。for (ints=1;s<=k;s++)  N/=2; 

         (4)用一个for循环对③中提到的每一部分进行划分for(intt=1;t<=N;t++)对于第一部分,将其划分为四个小的单元,即对第二行进行如下划分

         同理,对第二部分(即三四行),划分为两部分,第三部分同理。

         (5)最后,根据以上for循环对整体的划分和分治法的思想,进行每一个单元格的填充。填充原则是:对角线填充

         for(int i=m+1;i<=2*m;i++) //i控制行      

              for(int j=m+1;j<=2*m;j++)  //j控制列       

              { 

                  a[i][j+(t-1)*m*2]= a[i-m][j+(t-1)*m*2-m];/*右下角的值等于左上角的值 */ 

                  a[i][j+(t-1)*m*2-m] =a[i-m][j+(t-1)*m*2];/*左下角的值等于右上角的值 */

             }  

         运行过程

        (1)由初始化的第一行填充第二行

         

         (2)由s控制的第一部分填完。然后是s++,进行第二部分的填充

        

         (3)最后是第三部分的填充

         

     1 #include<iostream>
     2 using namespace std;
     3 
     4 const int maxn = 10005;
     5 int a[maxn][maxn];
     6 
     7 void table(int k, int a[][maxn])
     8 {
     9     int n = 1;
    10     n<<=k; 
    11     for(int i=1; i<=n; i++)
    12     a[1][i] = i;
    13     int m = 1;  //用来每次填充时, 控制填充起始的位置 
    14     for(int s=1; s<=k; s++)//划分(分治)成 k块, 从第一块向下滚。 
    15     {
    16         n/=2;       //每次大长块中, 横分成 n 小块。                       
    17         for(int t=1; t<=n; t++)      //一小块一小块的填充 
    18         for(int i=m+1; i<=2*m; i++)  //控制行数 
    19         for(int j=m+1; j<=2*m; j++)//控制列数 
    20         {
    21             a[i][j+(t-1)*m*2]=a[i-m][j+(t-1)*m*2-m];//右下角的值等于左上角的值 
    22             a[i][j+(t-1)*m*2-m] = a[i-m][j+(t-1)*m*2];//左下角的值等于右上角的值 
    23         }
    24         m*=2; //向下滚, 起始位置翻倍。 
    25     }
    26 }
    27 
    28 int main()
    29 {
    30     int k;
    31     while(scanf("%d", &k)!=EOF)
    32     {
    33         int n=1<<k;
    34         table(k, a);
    35         for(int i=1; i<=n; i++)
    36         {
    37             for(int j=1; j<=n; j++)
    38             printf("%5d", a[i][j]);
    39             printf("
    ");
    40         }
    41     }
    42     return 0;
    43 }
    View Code

    《8》输油管道问题。

    某石油公司计划建造一条由东向西的主输油管道。该管道要穿过一个有n口油井的油田。从每口油井都要有一条输油管道沿最短路经(或南或北)与主管道相连。
    如果给定n口油井的位置,即它们的x坐标(东西向)和y坐标(南北向),应如何确定主管道的最优位置,即使各油井到主管道之间的输油管道长度总和最小的位置?
    给定n口油井的位置,编程计算各油井到主管道之间的输油管道最小长度总和。
    设n口油井的位置分别为 ,i=1~n。由于主输油管道是东西向的,因此可用其主轴线的y坐标唯一确定其位置。主管道的最优位置y应该满足:y是中位数。   
              
    输入

    第1行是一个整数n,表示油井的数量(1≤n≤10 000)。

    接下来n行是油井的位置,每行两个整数x和y

    (﹣10 000≤x,y≤10 000)。

    输出

    各油井到主管道之间的输油管道最小长度总和。

                                                 

    输入样例

    5

    1 2

    2 2

    1 3

    3 -2

    3 3

    输出样例

    6

    分析:很明显当 y 为中位数时最短。 求中位数, 当然可以用排序, 然而更快的方法 goto《5》哈哈!

     1 int select(int left, int right, int k)
     2 {
     3     //ÕÒµ½ÁËµÚ k СµÄÔªËØ¡£ 
     4     if(left>=right) return a[left];
     5     int i = left;
     6     int j = right + 1;
     7     int pivot = a[left];
     8     while(true)                //¿ìÅÅ 
     9     {
    10         do{
    11             i = i + 1;
    12         }while(a[i]<pivot);
    13         do 
    14         {
    15             j=j-1;
    16         }while(a[j]>pivot);
    17         if(i>=j) break;
    18         swap(a[i], a[j]);
    19     }
    20     if(j-left+1==k) return pivot;
    21     a[left] = a[j]; //´æ´¢pivot 
    22     a[j] = pivot;
    23     if(j-left+1<k)
    24     return select(j+1, right, k-j+left-1);
    25     else return select(left, j-1, k);
    26 }
    View Code

    他山之石可以攻玉! 本来对这几个循环理解不清, 结果无意中窥探到某大神博客中的上述图解, 恍然大悟。虽然智商深深受虐! 然, 仍感收益颇丰! 上述分治法参考书籍:

    《算法设计与分析》 愿与“童稚”之人共享!

  • 相关阅读:
    DevExpress控件使用系列--ASPxUploadControl(图片上传及预览)
    DevExpress控件使用系列--ASPxGridView+Popup+Tab
    DevExpress控件使用系列--ASPxTreeList
    "Could not load type 'System.Runtime.CompilerServices.ExtensionAttribute' from assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b7
    Asp.net实现直接在浏览器预览Word、Excel、PDF、Txt文件(附源码)
    ExtJs的事件机制Event(学员总结)
    Ext.Loader
    Ext.ComponentQuery.query()
    Ext.grid.Panel表格分页
    WPF概述
  • 原文地址:https://www.cnblogs.com/acm1314/p/4514371.html
Copyright © 2020-2023  润新知