`
yiding_he
  • 浏览: 446140 次
  • 性别: Icon_minigender_1
  • 来自: 湖南
社区版块
存档分类
最新评论

可遍历的全排列

SUN 
阅读更多
全排列是一种比较烦人的东西。本文讨论如何在不使用递归的情况下,让全排列结果能够被遍历(也就是实现 Iterator 接口),使得全排列的提供者能随时提供“下一个”全排列结果,直到给出所有的排列。

本文发表于:http://yiding-he.iteye.com

通常全排列是不可排序的,因为不同的遍历方式会按不同的顺序得到结果(如交换法、滚动法等)。我们需要选择一种方式,每次枚举都能得到唯一的结果,绝不重复。因为只有这样的方式才能用来遍历。那就是:

全排列树。想象一下,树中的每个节点就是一个全排列结果,其子节点是将其交换变化而来的。举个例子,
根节点为 (1, 2, 3) 的树,其一级子节点就是 (1, 2, 3) (2, 1, 3) (3, 1, 2) 三个,分别是从排列中的 [0]-[0] 交换、[0]-[1] 交换和 [0]-[2]交换得来的。而二级子节点,排列中的位置 [0] 不变,从 [1] 开始和其他位置交换。(2, 1, 3) 的子节点为 (2, 1, 3) 和 (2, 3, 1) 两个。以此类推到下一级,直到无法交换位置为止。实际上,(1, 2, 3) 的全排列树一共就是三层。

你可能看到了:这里不是有重复的节点了吗?没关系。虽然枝节点有重复,但叶子节点是绝无重复的。我们只需要遍历这棵树的最底层,就能得到所有全排列。

遍历的时候,如何确定“上一个”和“下一个”叶子节点的位置呢?用一个很直观的表示法:每个节点的子节点都从 0 开始标上号,根节点就不用标号了,然后根据叶子节点的路径将标号组合成一个数组,就是每个节点的位置。例如上面那棵树中的叶子节点(2, 1, 3),位置就是 {1, 0}。它的下一个位置就是 {1, 1},再下一个位置是 {2, 0},不信把整个树画出来自己对对看。

实际上,因为叶子节点的位置都是绝对的,我就不一定非要从第一个结果来排起。给出任意一个排列结果,我都可以找到它在这棵树中的位置,然后直接求得下一个位置和下一个位置的排列结果。

基本的思路就是这些,然后就是用代码来实现。至于怎么思考实现的就略去了,下面是代码:
java 代码
 
  1. import sun.reflect.generics.reflectiveObjects.NotImplementedException;  
  2. import java.util.Iterator;  
  3.   
  4. /** 
  5.  * 全排列树 -- 可遍历的全排列 
  6.  */  
  7. class SortTree implements Iterator {  
  8.   
  9.     private int level;  
  10.   
  11.     private int[] defaultArr; // {1, 2, 3, 4, 5, ...} 这样一个数组  
  12.   
  13.     private int[] currentPosition;  
  14.   
  15.     public SortTree(int level) {  
  16.         this.level = level;  
  17.         init();  
  18.     }  
  19.   
  20.     private void init() {  
  21.         defaultArr = new int[level];  
  22.         for (int i = 0; i < level; i++) {  
  23.             defaultArr[i] = i;  
  24.         }  
  25.         currentPosition = getBeforeStartPosition();  
  26.     }  
  27.   
  28.     /** 
  29.      * 获得指定位置的全排列 
  30.      * 
  31.      * @param position 全排列在树中的位置 
  32.      * 
  33.      * @return 处于指定位置的全排列 
  34.      */  
  35.     public int[] getValue(int[] position) {  
  36.         int[] cloned = defaultArr.clone();  
  37.   
  38.         if (position.length != level - 1) {  
  39.             System.out.println("invalid position level");  
  40.             return new int[0];  
  41.         }  
  42.   
  43.         for (int i = 0; i < position.length; i++) {  
  44.             swap(cloned, i, i + position[i]);  
  45.         }  
  46.   
  47.         return cloned;  
  48.     }  
  49.   
  50.     /** 
  51.      * 获得指定的全排列的位置 
  52.      * 
  53.      * @param value 全排列中的一项 
  54.      * 
  55.      * @return 指定的项在全排列树中的位置 
  56.      */  
  57.     public int[] getPosition(int[] value) {  
  58.         int[] cloned = defaultArr.clone();  
  59.   
  60.         int[] position = new int[value.length - 1];  
  61.         for (int i = 0; i < value.length - 1; i++) {  
  62.             int pointer = 0;  
  63.             if (value[i] == cloned[i]) {  
  64.                 position[i] = 0;  
  65.             } else {  
  66.                 pointer++;  
  67.                 while (pointer < value.length) {  
  68.                     if (value[i] == cloned[pointer]) {  
  69.                         swap(cloned, i, pointer);  
  70.                         position[i] = pointer - i;  
  71.                     }  
  72.                     pointer++;  
  73.                 }  
  74.             }  
  75.         }  
  76.   
  77.         return position;  
  78.     }  
  79.   
  80.     /** 
  81.      * 获得下一个位置 
  82.      * 
  83.      * @param position 当前位置 
  84.      * 
  85.      * @return 下一个位置 
  86.      */  
  87.     public int[] getNextPosition(int[] position) {  
  88.         int[] result = position.clone();  
  89.         for (int i = result.length - 1; i >= 0; i--) {  
  90.             int upper = result.length - i;  
  91.             if (result[i] + 1 <= upper) {  
  92.                 result[i] += 1;  
  93.                 break;  
  94.             } else {  
  95.                 result[i] = 0;  
  96.             }  
  97.         }  
  98.         return result;  
  99.     }  
  100.   
  101.     /** 
  102.      * 是否还有下一个位置 
  103.      * 
  104.      * @param position 当前位置 
  105.      * 
  106.      * @return 如果还有则返回 true。 
  107.      */  
  108.     public boolean hasNextPosition(int[] position) {  
  109.         for (int i = 0; i < position.length; i++) {  
  110.             if (position[i] != defaultArr.length - i - 1) {  
  111.                 return true;  
  112.             }  
  113.         }  
  114.         return false;  
  115.     }  
  116.   
  117.     /** 
  118.      * 获得第一个位置之前的位置 
  119.      * 
  120.      * @return 第一个位置之前的位置 
  121.      */  
  122.     public int[] getBeforeStartPosition() {  
  123.         int[] position = new int[defaultArr.length - 1];  
  124.         position[position.length - 1] = -1;  
  125.         return position;  
  126.     }  
  127.   
  128.     private void swap(int[] ints, int a, int b) {  
  129.         int t = ints[a];  
  130.         ints[a] = ints[b];  
  131.         ints[b] = t;  
  132.     }  
  133.   
  134.     public boolean hasNext() {  
  135.         return hasNextPosition(currentPosition);  
  136.     }  
  137.   
  138.     public Object next() {  
  139.         currentPosition = getNextPosition(currentPosition);  
  140.         return getValue(currentPosition);  
  141.     }  
  142.   
  143.     public void remove() {  
  144.         throw new NotImplementedException();  
  145.     }  
  146. }  

使用方法:
java 代码
 
  1. SortTree tree = new SortTree(6);  
  2. int counter = 0;  
  3. while (tree.hasNext()) {  
  4.     int[] sort = (int[]) tree.next();  
  5.     printArray(sort);  
  6.     counter++;  
  7. }  
  8.   
  9. System.out.println("一共 " + counter + " 个结果");

代码仅用于检验思路,写法不甚严格。若要实用,最好能对参数加一些判断。
分享到:
评论

相关推荐

    C#深度优先遍历实现全排列

    C#深度优先遍历实现全排列 C#深度优先遍历实现全排列是指通过C#语言使用深度优先遍历算法来实现全排列的过程。全排列是指对一个集合中的元素进行重新排列,以产生所有可能的排列组合。例如,对于数字1、2、3的...

    生成全排列矩阵.zip

    否则,遍历剩余的元素,将每个元素放到首位,并对剩下的元素进行递归调用。 在MATLAB中,`perms`函数的使用更为简便: ```matlab % 使用内置函数perms perms_vec = perms([1 2 3]); for i = 1:size(perms_vec, 1) ...

    DFS搜索 全排列 next_permutation.cpp

    DFS搜索案例——寻找全排列。 深度优先遍历图的方法是,从图中某顶点v出发: (1)访问顶点v; (2)依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问; (3)若此时图...

    C#实现解决全排列重复问题

    3. **遍历待处理元素**:对于每个未处理的元素(即索引小于数组长度),执行以下操作: - **检查重复**:如果元素已存在于目标排列中,跳过此元素,避免重复排列。 - **插入元素**:将元素添加到目标排列中,并...

    五个数的全排列

    在编程领域,全排列是一个经典的算法问题,它涉及到如何生成一组特定数量的元素的所有可能排列。在这个场景中,我们关注的是使用C语言来实现对5个数的全排列。C语言是一种底层、高效的编程语言,适合处理这种计算...

    c++编写的全排列源代码

    它通过遍历已排序的前k-1个元素,并检查它们是否与当前尝试添加的元素`l`相同来实现。如果所有这些元素都不等于`l`,则返回`true`,表明`l`可以添加到排列中;否则返回`false`。 - `voidfind(intk)`:这是递归函数...

    c# n的全排列

    否则,对于当前索引位置,遍历所有未使用的元素,并通过`Swap`函数交换它们的位置,然后递归处理下一个位置,最后再回溯(恢复原始状态)。 这个C#程序会输出所有可能的3个数字的排列,例如:123, 132, 213, 231, ...

    递归练习 数据结构实验全排列

    如果不是,我们就遍历所有可能的选择,将当前元素与起始位置元素交换,然后对剩下的元素进行递归调用。在每次递归返回后,我们还需要通过回溯操作恢复数组的原始状态,以便后续的选择。 全排列问题的递归解决方案...

    易语言数字文本的全排列.rar

    - 在函数内部,遍历剩余未排列的数字,每次选择一个数字添加到已排列的部分,然后对剩下的数字进行递归调用。 - 当所有数字都已排列时,输出当前排列,然后回溯到上一步,继续尝试其他可能性。 2. **非递归实现**...

    全排列-非递归算法

    - 遍历当前容器中的所有排列,寻找尚未使用的位置。 - 将当前数字放入找到的空位,生成一个新的排列。 - 将新排列添加到结果集中。 - 回溯:如果所有位置都尝试过了,返回上一层;否则,继续尝试下一个未使用的...

    易语言源码易语言数字文本的全排列.rar

    3. 如果`当前位索引`未超过数组长度,遍历数组中从`当前位索引`开始的所有未使用过的数字,将每个数字放在`当前位索引`的位置,然后递归调用`全排列`函数处理下一位。 4. 在递归调用返回后,恢复原数组状态,即回溯...

    全排列(多种算法实现)

    全排列是计算机科学中一种常见的算法问题,主要涉及组合数学和递归思想。在编程中,全排列通常指的是给定一个包含n个不同元素的序列,找出所有可能的n!种排列方式。这个问题广泛应用于数据处理、数据分析以及各种...

    五个字母全排列 .sql 数据库

    总结来说,这个压缩包提供的SQL数据库是一个存储五个字母全排列的工具,可能用于各种用途,比如密码生成、字符集分析或是其他需要遍历所有可能组合的场景。理解并操作这个数据库需要基本的SQL知识,以及对全排列概念...

    python练习fibonacci全排列

    在Python中,可以使用内置的`itertools`模块来简化全排列问题的解决,`itertools.permutations()`函数可以直接生成一个可迭代对象,包含了输入序列的所有排列。 总结起来,"python练习fibonacci全排列"这个主题涵盖...

    java递归实现N个数全排列输出

    否则,它会遍历所有未处理的元素,将第一个未处理的元素与当前位置的元素交换,然后递归地处理剩余的元素。递归结束后,通过调用`swap`函数恢复原数组状态,实现回溯。 这个算法的时间复杂度是O(N!),因为它需要...

    求字符串的全排列

    然后,它遍历未处理的部分,对每个字符进行交换,然后递归地处理剩余部分(l+1到r)。交换后的字符在下一次递归调用之前被回溯,以恢复原始字符串,这是回溯算法的关键步骤。 这段代码展示了如何使用递归和回溯算法...

    c++实现全排列

    但是,由于可选元素个数越来越少,我们的这个 N 位数不能象 10 进制数那样使用一个常数的 n-1次方来做基。因此,我们定义第 n 位的基为 (n-1)!,和 10 进制数将是类似的。 根据这个策略,我们可以得到每次选择的...

    随机全排列生成程序及其应用开发(有程序代码)

    通过循环遍历数组,使用`swap`函数将数组元素随机交换,确保生成的序列中包含0。最后,输出生成的随机数序列。 **实验要求**可能包括理解随机全排列的概念,掌握两种生成方法的原理,能够编写出正确的C程序,并进行...

Global site tag (gtag.js) - Google Analytics