全排列算法【非递归活动数实现】

求解一个问题,有很多种算法/方法,一旦遇到比较有趣的思想/算法,就忍不住记录下来。

题:求n=4时的全排列(当n=4时,序列为:{1, 2, 3, 4})

算法的思想:
1. 给排列中的每个元素均赋予一个向左或向右的箭头。
2. 如果元素k的箭头指向的是与其相邻但小于k的元素,则称元素k是活动的。
3. 从排列 1 2 3 … n 开始,找其中的最大活动元素k,将该元素k与它所指向的相邻元素交换位置,并改变所有大于k的元素的方向。

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 生成全排列
 *
 * 算法的思想:
 * 1. 给排列中的每个元素均赋予一个向左或向右的箭头。
 * 2. 如果元素k的箭头指向的是与其相邻但小于k的元素,则称元素k是活动的。
 * 3. 从排列 1  2  3  … n 开始,找其中的最大活动元素k,将该元素k与它
 * 所指向的相邻元素交换位置,并改变所有大于k的元素的方向。
 */
public class Perm {
    enum Direction {
        LEFT, RIGHT     //方向有左右
    }

    /**
     * 把每个数看成一个元素,有数值有方向
     */
    static class Element {
        int data;               //数值
        Direction direction;    //方向

        public Element(int data, Direction direction) {
            this.data = data;
            this.direction = direction;
        }
    }

    /**
     * 生成全排列
     * @param list 需要生成全排列的序列集合
     */
    private static void perm(List<Element> list) {
        int count = 1;      //统计全排列的数目
        if (list == null)
            return;
        //首先打印第一种情况
        printAllElement(list, true);

        int index;      //活动数的下标

        while (true) {
            index = findMaxActiveNum(list);     //找到最大活动数下标
            if(index == -1) {
                System.out.println("全排列总数为:" + count);
                return;
            }

            //改变所有大于最大活动数的元素的方向
            changeDirection(list, index);

            //交换最大活动数与它所指向的相邻元素
            if (list.get(index).direction == Direction.LEFT) {
                swapElement(list, index-1, index);
            } else {
                swapElement(list, index, index+1);
            }

            count++;
            printAllElement(list, true);
        }
    }

    /**
     * 找到最大活动数
     * @param list 需要生成全排列的序列集合
     */
    private static int findMaxActiveNum(List<Element> list) {
        if(list == null)
            return -1;

        int length = list.size();
        int index = -1;

        //找出最大活动数的下标
        for (int i = 0; i < length; i++) {
            int data = list.get(i).data;
            boolean isLeft = list.get(i).direction == Direction.LEFT;

            //当不是活动数时,跳出此次循环-
            if (i == 0 && isLeft || i == length-1 && !isLeft ||     //这个数的箭头所指的下一个元素为空
                    isLeft && data < list.get(i-1).data || 
                    !isLeft && data < list.get(i+1).data) {         //这个数比箭头所指的下一个数小
                continue;
            } else {
                if(index == -1) {
                    index = i;
                } else {
                    index = list.get(i).data > list.get(index).data ? i : index;    //记录最大活动数的下标
                }
            }
        }
        return index;
    }

    /**
     * 交换两个元素的值和箭头
     * @param list 需要生成全排列的序列集合
     * @param index1 下标1
     * @param index2 下标2
     */
    private static void swapElement(List<Element> list, int index1, int index2) {
        if(list == null)
            return;
        //交换两个对象的引用,达到交换值的目的
        Element temp = list.get(index1);
        list.set(index1, list.get(index2));
        list.set(index2, temp);
    }

    /**
     * 改变所有大于list.get(index)的元素的方向
     * @param list 需要生成全排列的序列集合
     * @param index 下标
     */
    private static void changeDirection(List<Element> list, int index) {
        if (list == null)
            return;

        int data = list.get(index).data;
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).data > data) {
                list.get(i).direction = list.get(i).direction == Direction.LEFT ?
                        Direction.RIGHT :
                        Direction.LEFT;
            }
        }
    }

    /**
     * 打印全部元素
     * @param list 需要生成全排列的序列集合
     * @param arrowFlag 是否打印箭头
     */
    private static void printAllElement(List<Element> list, boolean arrowFlag) {
        if (list == null)
            return;
        if(arrowFlag) {
            for (Element element: list) {
                switch (element.direction) {
                    case LEFT:
                        System.out.print("← ");
                        break;
                    case RIGHT:
                        System.out.print("→ ");
                        break;
                }
            }
        }

        System.out.println("");

        for (Element element: list) {
            System.out.print(element.data + " ");
        }
        System.out.println("");
    }

    /**
     * 创建一个序列
     * @param n 序列最大的数
     * @return n为最大值的集合
     */
    private static List<Element> createDataList(int n) {
        List<Element> list = new ArrayList<>();

        for (int i = 1; i <= n; i++) {
            list.add(new Element(i, Direction.LEFT));   //初始化一个最大元素为n的序列,并且每个元素拥有一个向左的箭头
        }
        return list;
    }


    public static void main(String[] args) {
        List<Element> elementList;
        Scanner scanner = new Scanner(System.in);
        int n;      //读取n

        System.out.println("【功能】非递归求全排列(活动数实现)");
        System.out.println("请输入n:");
        n = scanner.nextInt();

        elementList = createDataList(n);

        perm1(elementList, 0, elementList.size());
    }
}

运行结果:
这里写图片描述
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值