Divide and conquer algorithm and greedy algorithm and a classic example

how are you

basic concept

The so-called greedy algorithm means, when the problem solving, always made in the current appears to be the best choice . In other words, not be considered as a whole the best, he made only in the sense of local optimal solutions.

There is no fixed algorithm greedy algorithm framework, the key is to select the greedy algorithm design strategy. It must be noted that the greedy algorithm is not able to get the best overall solution to all problems, select the greedy strategy must have no after-effect, that is, after a certain state of the process will not affect the previous state, only with the current status.

 So the greedy strategy adopted must carefully analyze whether it meets no after-effect.
 

The basic idea of ​​the greedy algorithm

  1. A mathematical model to describe the problem.

    2. Solving the problem into several sub-problems.

    3. For each sub-problem solving, get local optimal solutions to subproblems.

    Synthesis of a solution of the original solution to the problem solution handle 4. The local optimal solutions.

 

Greedy algorithm implementation framework

Greedy algorithm is applicable on the premise that: local optimization strategy can lead to global optimal solution

In fact, the greedy algorithm is applicable rarely the case. Generally, for a problem analysis is appropriate for the greedy algorithm, you can select several actual data in the analysis of the problem, you can make judgments.

 

Greedy algorithm implementation framework

    From the initial solution to a problem departure;
    while (for a given energy towards the target before further Total)
    { 
          The use of feasible decisions, find a solution elements feasible solution;
    }
    All solutions of a combination of elements of a feasible solution to the problem;
 

Select greedy strategy

     Because with the greedy algorithm can only be achieved through global optimum solution locally optimal solution strategy, therefore, must pay attention to determine whether the problem is suitable for greedy algorithm strategies, solutions must be found if the optimal solution to the problem.
 

Analysis examples

    Here is a topic can try greedy algorithm solution, greed really good solution, but not optimal solution.
 
    [Knapsack problem] has a backpack, the backpack capacity is M = 150. There are seven items, the article may be divided into an arbitrary size.
    Requirements to get as the total value of the items into the backpack maximum, but can not exceed the total capacity.
 
    Item A B C D E F G
    Wt 35306050401025
    Worth 10,403,050,354,030
    analysis:
    The objective function: Σpi maximum (the sum of the maximum value)
    Constraint is loaded on the total weight of the article does not exceed the capacity of the knapsack: Σwi <= M (M = 150)
    (1) According to the greedy strategy, each selected to the maximum value of the items into the backpack, whether the result of the best?
    (2) each selected to share the weight of the smallest items into whether the optimal solution?
    (3) each item selected maximum value per unit weight, become a strategic solution to this question.
 
    It is noteworthy that, not entirely greedy algorithm can not be used after the greedy strategy once it has been proved that it is an efficient algorithm.
 For example, Prim algorithm and Kruskal minimum spanning tree algorithm is pretty greedy algorithm.
    Greedy algorithm is still one very common algorithm, due to its simple, construction greedy strategy is not very difficult.
    Unfortunately, it is the proof of the need to really use the title of the algorithm.
    In general, it proved greedy algorithm around: the optimal solution of the whole problem to some problems in the sub-greedy policy optimal solution comes from.
 
    For example in three kinds of greedy strategy, are not set up (not proved), explains as follows:
    (1) greedy strategy: Choose the value of the greatest. Counter-example:
    W=30
    Item: A B C
    Weight: 281,212
    Value: 302 020
    According to the policy, first select the items A, the next you can not also, but select B, C is better.
    (2) greedy strategy: Select the minimum weight. It is counter-examples and counterexamples first strategy is similar.
    (3) greedy strategy: maximum weight value of items selected units. Counter-example:
    W=30
    Item: A B C
    Weight: 282,010
    Value: 282 010
    According to the policy, all three items, like the weight of the unit value, the program can not make judgments based on existing policies, if you select A, then the answer wrong.
 

In fact, this case is in line with greedy strategy , because the overall situation no matter which of the two will be the first election backpack filled, because the problem can be split into articles of any size, so, even if the air under it, and the last item can be split, into them, their value per unit weight is the same, therefore, the same as last backpacks final weight, the same weight, then the value is the same. )

So the use of third strategy, as follows:

package cn.itcast.recursion;

import java.util.Arrays;

public class GreedyPackage {
    private int MAX_WEIGHT = 150;
    private int[] weights = new int[]{35, 30, 60, 50, 40, 10, 25};
    private int[] values = new int[]{10, 40, 30, 50, 35, 40, 30};

    private void packageGreedy(int capacity, int weights[], int[] values) {

        int n = weights.length;//物品的数量
        double[] = R & lt new new  Double [n-]; // price array 
        int [] = index new new  int [n-]; // cost index sorting items 
        for ( int I = 0; I <n-; I ++ ) { 
            R & lt [I ] = ( Double ) values [I] / weights [I]; 
            index [I] = I; // default sort 
        }
         Double TEMP = 0; // for effective sort 
        for ( int I = 0; I <n--. 1 ; I ++ ) {
             for ( int J = I +. 1; J <n-; J ++) {
                 // descending order of price and a corresponding sorting index 
                IF (R & lt [I] < R & lt [J]) { 
                    TEMP = R & lt [I]; 
                    R & lt [I] = R & lt [J]; 
                    R & lt [J] = TEMP ;
                     int X = index [I]; 
                    index [I] = index [J]; 
                    index [J] = X; 
                } 
            } 
        } 
        // sorted by weight and value are stored in the array 
        int [] = W1 new new  int [n- ];
         int [] V1 = new new int [n-];
         // sorted by weight and value are stored in the array 
        for ( int I = 0; I <n-; I ++ ) { 
            W1 [I] = weights [index [I]]; 
            V1 [I] = values [index [I]]; 
        } 
        // used to hold an array of articles 
        int [] = X new new  int [n-];
         // Add items maximum value 
        int maxValue = 0 ;
         // total weight of the article put 
        int totalweights 0 = ;
         for ( int I = 0; I <n-; I ++ ) {
             // Item weight smaller than the total capacity of the package, that can also be installed under
            IF (W1 [I] < Capacity) { 
                X [I] =. 1; // indicates that the article is installed 
                maxValue = + [I] V1; 
                System.out.println (W1 [I] + "articles are placed kg into the bag, the value: "+ V1 [I]); 
                totalweights + = [I] W1; 
                Capacity = Capacity - W1 [I]; 
            } 
        } 
        System.out.println ( " total number of articles loaded: "+ Arrays, .toString (the X-)); 
        System.out.println ( "the total weight of the items into a total" + totalweights); 
        System.out.println ( "goods into the greatest value:" + maxValue);
    }

    public static void main(String[] args) {
        GreedyPackage greedyPackage = new GreedyPackage();
        greedyPackage.packageGreedy(greedyPackage.MAX_WEIGHT, greedyPackage.weights, greedyPackage.values);
    }
}

 

 

Divide and Conquer Algorithm

definition

Original problem into n smaller, and the structure of the original problem and similar sub-problems recursively solve these sub-problems, then recombined as a result, to obtain a solution of the original problem.

 

Divide and Conquer

  "Divide and rule", can be a big problem down into smaller problems like, keep in mind these minor problems need to have similarities. And each solution small problem after synthesis solution to a big problem. So how big a problem demolition, how to merge small problem this algorithm is the main one thought . In fact, many algorithms such as greedy algorithms, dynamic programming and so are the requirements of the big problems down into small problems. The divide and conquer algorithm important thing is to be able to re-apply to the solution of minor problems combined solution is a big problem.

 

Prerequisites for Use divide and conquer algorithm

  • Original problem is decomposed into small problems with the same pattern;
  • The original problem into sub-problems can be solved independently, there is no correlation between the sub-problems, and this is a clear distinction divide and conquer algorithm with dynamic programming ;
  • With decomposition termination conditions, i.e., when the question is sufficiently small, it can be solved directly;
  • Sub-problems can be combined into the original problem, but the complexity of the merge operation can not be too high, or would not achieve the effect of reducing the overall complexity of the algorithm

 

Each recursive operation will involve three

  • Decomposition : the original problem into a series of sub-problems;
  • 解决:递归地求解各个子问题,若子问题足够小,则直接求解;
  • 合并:将子问题的结果合并成原问题;

分治法适用条件

 

  1、该问题的规模缩小到一定程度就可以很容易解决;

  2、该问题可以分解为若干个规模较小的相同问题,这里注意是最优子结构性质;

  3、利用该问题分解出的子问题的解可以合并为该问题的解;

  4、该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共子问题;

  对于很多算法而言,第一条往往是必要的,因为数据量一旦大起来,问题往往复杂度上升的特别快。这里就需要将这个大问题分解为小问题。小问题处理起来更加方便。第二、三条的才是分治思想的核心,因为很多时候我们会采用递归的方式进行解决,所以在大问题分解为小问题的时候需要保证小问题之间的相同性。单单分解为小问题之后还不能算完成,必须要能够将小问题的解合并为这个问题的最终解才能算真正用到了分治的思想。最后一条也是最关键的,各个子问题之间必须要保证独立性,即不互相影响。如果相互之间有影响,这时候我们采用的是动态规划就更加好一点。

 

例题

  其实算法的思想不用讲太多,能够化为几句话是最好的,下面就举几个例子来看看分治算法:

  例题一:二分查找,给定一个按照升序排好的数组array,要在这个数组中找出一个特定的元素x;

  当我们遇到一个问题,完全可以在心里问自己下面四个问题:

  1、当前问题能不能切分?

  答:能切分,因为数组按照升序来排列。所以当x大于某个元素array[mid]时,x一定在array[mid]的右边。以此再来切分。每次切一半

  2、分解出来的子问题相同吗?

  答:相同,每个子问题的数据集都是父问题的1/2倍。并且每次只比较子问题的中间的数据

  3、子问题的解能合并为父问题的解吗?

  答:不需要合并,子问题的解即为父问题的解。

  4、子问题之间相互独立吗?

  答:独立,子问题只是判断,不需要和父问题有很强的关联性(这里可以参考一下动态规划算法,就能理解子问题之间怎么判断是独立的)

 

 例题二:归并排序,给定一个无序数组array[7]={49,38,65,97,76,13,27},使其变的有序

  同样在自己心里问问4个问题

  1、当前问题能切分吗?

  答:能,最简单的就是两个数之间的比较,这个数组可以看成多个两个数来比较

  2、分解出来的子问题是否相同?

  答:相同,都是两个数比较大小。

  3、子问题的解能够合成父问题的解吗?

   答:每两个有序数组再按照一定顺序合起来就是最终的题解。这里就是有个合并的过程

  4、子问题之间相互独立吗?

  答:独立,分到最小的时候子问题之间互不影响。

  下面是归并排序代码:

 

总结

  分治算法只是一种思想,不是一个具体的套路,只能说在碰见具体问题时我们能够从这个思路去思考,切分问题?合并问题?子问题之间影响关联大不大?这些都是具体问题具体考虑。还有很多很多题目是用了分治算法。也可以多刷刷题

循环赛日常表:   

设有n=2^k个运动员,要进行网球循环赛。现在要设计一个满足以下要求的比赛日程表

    (1)每个选手必须与其他n-1个选手各赛一场

    (2)每个选手一天只能赛一次

    (3)循环赛一共进行n-1天

将比赛日程表设计成n行n列,表中除了第一列,其他n-1列才是我们要的,数组下标行列都从0开始,第i行j列代表第(i+1)位选手在第j天的对手:

 

 以8个选手为例子,下面是填表的步骤:

 

①我们先初始化第一行各个数为1~8(2~8为:第1天 — 第7天);

②因为是递归,那么要填8x8的左下角和右下角,分别需要知道它的右上角和左上角

③而8x8的盒子它的左上角是一个4x4的盒子,要填4x4的左下角和右下角,也分别需要知道它的右上角和左上角

④现在递归到4x4的盒子的左上角,是一个2x2的盒子,它不需要递归了,直接沿对角线填左下角和右下角的数字,也就是上面的图②

⑤可以看到,经过上面的②③步,我们左上角4x4的盒子,它的·右上角和左上角已经知道了,那就可以沿对角线填它的左下角和右下角了,所以出现了图④

⑥其他的依次类推

 

通俗易懂地讲,就是如果你想填一个大的,你得先得出它左上角和右上角两个盒子 , 再沿对角线分别抄到右下角和左下角而为了得出它左上角和右上角,就需要递归了。

 

package cn.itcast.recursion;

public class SportsSchedule {
    public void scheduleTable(int[][] table, int n) {
        if (n == 1) {
            table[0][0] = 1;
        } else {
/*          填充左上区域矩阵
            n值的变化:8  4  2  1
            m值的变化:4  2  1  1  */
            int m = n / 2;
            scheduleTable(table, m);
            //填充右上区域矩阵
            for (int i = 0; i < m; i++) {
                for (int j = m; j < n; j++) {
                    table[i][j] = table[i][j - m] + m;
                }
            }
            //填充左下区域矩阵
            for (int i = m; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    table[i][j] = table[i - m][j] + m;
                }
            }
            //填充右下区域矩阵
            for (int i = m; i < n; i++) {
                for (int j = m; j < n; j++) {
                    table[i][j] = table[i - m][j - m];
                }
            }
        }
    }

    public static void main(String[] args) {
        int[][] table = new int[8][8];
        int n = 8;
        SportsSchedule schedule = new SportsSchedule();
        schedule.scheduleTable(table, n);
        int c = 0;
        //打印二维数组
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                System.out.print(table[i][j] + " ");
                c++;//每打印一个数,c++
                if (c % n == 0) {//说明打印一行了
                    System.out.println();//换行
                }
            }
        }
    }
}

 

L型骨牌棋盘覆盖

问题描述

在一个2^k×2^k 个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为一特殊方格(特殊点),且称该棋盘为一特殊棋盘。在棋盘覆盖问题中,要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。

 

解题思路

分析:

当k>0时,将2^k×2^k 棋盘分割为4个2^k-1×2^k-1 子棋盘(a)所示。特殊方格必位于4个较小子棋盘之一中,其余3个子棋盘中无特殊方格。为了将这3个无特殊方格的子棋盘转化为特殊棋盘,可以用一个L型骨牌覆盖这3个较小棋盘的会合处,如 (b)所示,从而将原问题转化为4个较小规模的棋盘覆盖问题。递归地使用这种分割,直至棋盘简化为棋盘1×1。

 实现:

每次都对分割后的四个小方块进行判断,判断特殊方格是否在里面。这里的判断的方法是每次先记录下整个大方块的左上角方格的行列坐标,然后再与特殊方格坐标进行比较,就可以知道特殊方格是否在该块中。如果特殊方块在里面,这直接递归下去求即可,如果不在,这根据分割的四个方块的不同位置,把右下角、左下角、右上角或者左上角的方格标记为特殊方块,然后继续递归。在递归函数里,还要有一个变量subSize来记录边的方格数,每次对方块进行划分时,边的方格数都会减半,这个变量是为了方便判断特殊方格的位置。

 覆盖步骤如图:

 

 代码实现:

package cn.itcast.recursion;

public class ChessBoradProblem {
    private int[][] board;//棋盘
    private int specialRow;//特殊点行下标
    private int specialCol;//特殊点列下标
    private int size;//矩阵大小
    private int type = 0;//骨牌类型,1,2,3,4  因为是用数字表示的,所以用int

    public ChessBoradProblem(int specialRow, int specialCol, int size) {
        this.specialRow = specialRow;
        this.specialCol = specialCol;
        this.size = size;
        board = new int[size][size];
    }

    /**
     * @param specialRow 特殊点的行下标
     * @param specialCol 特殊点的列下标
     * @param leftRow    分割成4个后每个矩阵的左边的起点行下标
     * @param leftCol    分割成4个后每个矩阵的左边起点列下标
     * @param size       矩阵的宽或者高
     */

    //相对于四个方格中右上的方格,左边起点的leftRow不一定是0了
    private void ChessBoard(int specialRow, int specialCol, int leftRow, int leftCol, int size) {
        if (size == 1) {
            return;
        }

        int subSize = size / 2;
        type = type % 4 + 1;//不断+1,超过4就取模
        int n = type;

        //假设特殊点在左上角,然后行和列都小于一半
        if (specialRow < leftRow + subSize && specialCol < leftCol + subSize) {
            ChessBoard(specialRow, specialCol, leftRow, leftCol, subSize);
        } else {
            //不在左上角,左上角矩阵的右下角就是特殊点
            board[leftRow + subSize - 1][leftCol + subSize - 1] = n;
            ChessBoard(leftRow + subSize - 1, leftRow + subSize - 1, leftRow, leftCol, subSize);
        }

        //特殊点在右上方,行小于一半,列大于一半
        if (specialRow < leftRow + subSize && specialCol >= leftCol + subSize) {
            ChessBoard(specialRow, specialCol, leftRow, leftCol + subSize, subSize);
        } else {
            board[leftRow + subSize - 1][leftCol + subSize] = n;
            ChessBoard(leftRow + subSize - 1, leftCol + subSize, leftRow, leftCol + subSize, subSize);
        }

        //特殊点在左下方
        if (specialRow >= leftRow + subSize && specialCol < leftCol + subSize) {
            ChessBoard(specialRow, specialCol, leftRow + subSize, leftCol, subSize);
        } else {
            board[leftRow + subSize][leftCol + subSize - 1] = n;
            ChessBoard(leftRow + subSize, leftCol + subSize - 1, leftRow + subSize, leftCol, subSize);
        }

        //特殊点在右下方
        if (specialRow >= leftRow + subSize && specialCol >= leftCol + subSize) {
            ChessBoard(specialRow, specialCol, leftRow + subSize, leftCol + subSize, subSize);
        } else {
            board[leftRow + subSize][leftCol + subSize] = n;
            ChessBoard(leftRow + subSize, leftCol + subSize, leftRow + subSize, leftCol + subSize, subSize);
        }
    }


    public void printBoard(int specialRow, int specialCol, int size) {
        ChessBoard(specialRow, specialCol, 0, 0, size);
        printResult();
    }

    private void printResult() {
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                System.out.print(board[i][j] + " ");//注意:print
            }
            System.out.println();
        }
    }


public static void main(String[] args) {
        int N = 4;//矩阵大小
        //选取特殊点
        int specialRow = 0;
        int specialCol = 1;
        ChessBoradProblem boradProblem = new ChessBoradProblem(specialRow, specialCol, N);
        boradProblem.printBoard(specialRow, specialCol, N);
    }
}

 

 

 

Guess you like

Origin www.cnblogs.com/xiaozhongfeixiang/p/11942890.html