• 减一技术应用:生成排列与幂集


         摘要: 使用减一技术生成N个自然数的排列及N个数的集合的幂集。

         难度: 初级

         减一技术,与二分搜索一样,是一种通用算法设计技术。它是分治法的一种特殊形式,通过建立问题实例P(n) 与问题实例P(n-1)的递推求解关系式而实现;最经典的例子莫过于插入排序了。这里,给出减一技术在生成排列组合方面的应用。

     

       (一)  排列问题: 生成自然数 1,2,,,,,n 的所有排列。

     

         算法描述:

           使用减一技术,建立自然数12...n的排列与12...n-1的递推关系。假设 P(n-1) 是 自然数 12...n-1的所有排列 p1, p2,..., p(m)的集合,则P(n)通过如下方式得到: 对所有排列p1, p2, ... , p(m) , 将 n 插入到 这些排列的 n 个位置上,得到的所有排列。 例如 12的排列为 12, 21, 则123的排列通过如下方式得到: 1. 将3插入到12中,得到 312,132,123;2. 将3 插入到21中,得到 321, 231, 213. 通过小例子往往能够为问题的求解指明道路。

         “最小变化”要求: 有时,要求生成的所有排列中,相邻排列只有两个相邻位置不同。比如1234和1324是满足的,而1234和1432则不满足。上述方法生成的排列 312, 132,123, 321, 231,213 , 123和321就不满足这个要求。解决方案是,当对排列pi采用从左往右插入完成时,对相邻排列pi+1采用从右往左插入。比如1中将3插入排列12是从左往右插入;则2中应该将3从右往左插入,得到213,231,321,这样,与前面的312,132,123就满足最小变化要求了。

     

        详细设计:

        1. 输入: 自然数 n

        2. 输出: 所有排列的集合,每个排列用一个链表来表示(考虑到插入操作); 所有排列用链表的可变列表(ArrayList)来表示。之所以采用这样的方式,考虑到之后可能要取出排列进行求解,比如分配问题。代价是空间效率很低。

        3. 数据结构: 使用队列来存储 n-1 的所有排列; 然后取出队列中的每个元素, 将 n 插入到其中,得到 n 的一个排列。

        

    package algorithm.permutation;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.LinkedList;
    /**
     *  Permutation:
     *  Generate all the permutations of a given number.
     *  
     *  生成 n 个数 的全排列。
     *
     */
    public class Permutation {
        
        /** 
         * 每一个排列使用一个 LinkedList<Integer> 来存储,
         * 使用 LinkedList<Integer> 的 列表来存储所有的排列
         *  
         */
        
        private ArrayList<LinkedList<Integer>> perms;
        
        /**
         *  使用 flag 作为交替 从左往右 和 从右往左 扫描的 标志 
         *  这样,可以实现排列“最小变化”的要求。
         *  即:每相邻的两个排列,只有两个相邻位置的不同。
         * 
         */
        
        private int flag = 1;
        
        
        /** 构造器  */
        
        public Permutation() {
            
            if (perms == null)
                perms = new ArrayList<LinkedList<Integer>>();
            
        }
        
        /** 生成 n 个数的全排列,并存储在 perms 中 */
        
        private void createPerm(int n) {
            
            if (n <= 0)
                throw new IllegalArgumentException();
            
            if (n == 1) {
                LinkedList<Integer> init = new LinkedList<Integer>();
                init.add(1);
                perms.add(init);
            }
            else {
                    createPerm(n-1);
                    
                    // 对每一个 n-1 的排列P(n-1), 将 n 插入到该排列 P(n-1) 的 n 个可能位置上,
                    // 即可得到 n 个 相应的 n 元素排列 P(n)
                    
                    int length = perms.size();
                    for (int i=0; i < length; i++) {
                       LinkedList<Integer> p = perms.get(i);
                       if (flag == 0) {
                           
                           // flag = 0: 从左向右扫描插入
                           
                           for (int j=0; j <= p.size(); j++) {
                              LinkedList<Integer> pcopy = copylist(p);
                              pcopy.add(j, n);
                              perms.add(pcopy);
                              flag = 1;
                           }
                           
                       }   
                        else {
                            
                            // flag = 1: 从右向左扫描插入
                            
                            for (int j=p.size(); j >=0; j--) {
                               LinkedList<Integer> pcopy = copylist(p);
                               pcopy.add(j, n);
                               perms.add(pcopy);
                               flag = 0;
                            }
                            
                        }
                    } 
                    
                    // 删除所有的 P(n-1) 排列
                    
                    for (int i=0; i < length; i++) {
                         perms.remove(0);
                    }
            
            }    
            
        }
        
        /** 获取 n 个元素的全排列  */
        
        public ArrayList<LinkedList<Integer>> getPerm(int n) {
            
            createPerm(n);    
            return perms;
        }
        
        /** 复制 list 的元素到另一个列表, 并返回该列表  */
        
        private LinkedList<Integer> copylist(LinkedList<Integer> list) {
            
            LinkedList<Integer> copylist = new LinkedList<Integer>();
            Iterator iter = list.iterator();
            while (iter.hasNext()) {
                Integer i = (Integer) iter.next();
                copylist.add(i);
            }
            return copylist;
        }
    }

     

        (二) 生成给定集合 {1,2,...,n} 的幂集,即给定自然数3,要生成其幂集: { {0}, {1}, {2}, {1,2}, {3}, {1,3},{2,3},{1,2,3} }

          算法描述:

          使用减一技术,建立问题实例P(n)与问题实例P(n-1)的递推求解关系。若P(n-1)是问题实例n-1的幂集,则问题实例n的幂集通过如下方式得到: powerset(n) = powerset(n-1) + powerset(n-1)∪ {n} ,即,对于n-1的幂集的每一个集合与{n}求并集,然后将得到的集合与n-1的幂集求并集。例如 {1} 的幂集为 {{0}, {1}} ,则 {1,2} 的幂集为 { {2} {1,2}, {0}, {1} } ,其中 {0} 表示空集, {0} ∪ {n} = {n}

     

          详细设计:

          1. 输入: 自然数 n

          2. 输出: {1,2,..,n}的幂集,每一个子集使用一个LinkedList来表示,幂集使用LinkedList的可变列表ArrayList来表示和存储。

     

           Java代码实现:

    package algorithm.permutation;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.LinkedList;
    /**
     *  PowerSet
     *  Generate all the subsets of a given set.
     *  
     *  生成给定集合的所有子集。
     * 
     */
    public class PowerSet {
        
        /** 
         * 每一个子集使用一个 LinkedList<Integer> 来存储,
         * 使用 LinkedList<Integer> 的 列表来存储所有的子集
         *  
         */
        
        private ArrayList<LinkedList<Integer>> powerset;
        
        /** 构造器  */
        
        public PowerSet() {
            
            if (powerset == null)
                powerset = new ArrayList<LinkedList<Integer>>();
            
        }
        
        /** 生成  {1,2,3,……, n} 的 幂集 */
        
        private void createPowerset(int n) {
            
            if (n < 0)
                throw new IllegalArgumentException();
            
            if (n == 0) {
                LinkedList<Integer> empty = new LinkedList<Integer>();
                powerset.add(empty);
            }
            
            if (n == 1) {
                LinkedList<Integer> empty = new LinkedList<Integer>();
                LinkedList<Integer> init = new LinkedList<Integer>();
                init.add(1);
                powerset.add(empty);
                powerset.add(init);
            }
            else {
                createPowerset(n-1);
                
                // powerset(n) = powerset(n-1) + powerset(n-1)∪ {n}
                
                int length = powerset.size();
                for (int i=0; i < length; i++) {
                       LinkedList<Integer> p = powerset.get(i);
                       LinkedList<Integer> pcopy = copylist(p);
                       pcopy.add(p.size(), n);
                       powerset.add(pcopy);
                       
                 }   
        
             }    
        
         }
            /** 获取 n 个元素集合的幂集  */
            
            public ArrayList<LinkedList<Integer>> getPowerset(int n) {
                
                createPowerset(n);    
                return powerset;
            }
            /** 复制 list 的元素到另一个列表, 并返回该列表  */
            
            private LinkedList<Integer> copylist(LinkedList<Integer> list) {
                
                LinkedList<Integer> copylist = new LinkedList<Integer>();
                Iterator iter = list.iterator();
                while (iter.hasNext()) {
                    Integer i = (Integer) iter.next();
                    copylist.add(i);
                }
                return copylist;
            }
            
        }

          算法分析:

           减一技术的时间复杂度通常是: T(n) = T(n-1) + G(n) .

           ①  若 G(n) 为常数,则 T(n) = O(n) ;    (连续子数组的最大和)

           ②  若 G(n) 为 O(logn), 则 T(n) = O(nlogn) ;  (堆排序)

           ③   若 G(n) = an+b(a!=0) ,则 T(n) = O(n^2) .     (插入排序)

           因此, 当 G(n) 为常数或对数时, 减一技术是比较高效的。  

  • 相关阅读:
    关于 省赛模拟赛(迪迦桑专场)
    ZOJ3878: Convert QWERTY to Dvorak(浙江省赛2015)
    Is It A Tree?
    Escape
    关于细节
    [UE4]AnimDynamics简介
    [UE4]武器碰撞
    [UE4]CustomAnimationBlueprintNode 自定义动画蓝图节点
    百钱买白鸡
    asp.net 标准控件的重要属性
  • 原文地址:https://www.cnblogs.com/lovesqcc/p/4038374.html
Copyright © 2020-2023  润新知