大计算场景下 N个自然数的全排列问题

欢迎大家这么多年的捧场,为了更好地更舒服的写东西,思考东西,我决定从今天开始(20161017)把自己的所有文章和创作都逐步转移到自媒体上,请大家扫下面的二维码关注,接着收看更多有价值的干货

    

          最近看到庞果网上有个编程竞赛,题目是求N个自然数的全排列

               题目看起来很简单,但是要关注两个问题:

               1. 避免使用递归算法,因为这样会使计算复杂度成指数级别上升

               2. 巨大的计算量下,如何把计算工作切分成若干模块,并行。 现代计算机的特点是多核心,家用PC双核,四核,server上18路,36路核心。把大数据运算切分到各个核心上会事半功倍。

              

                因此我花了周六写了一个代码, 用非递归算法求全排列, 用特定的算法的把计算量分割成若干单元,用java线程池运算。 运算结果写本地文件。

               我的本地机器 2.93HZ双核CPU, 4G内存, win7系统,最高作了16个自然数的全排列,进行了一共4亿六千万次的运算,2分钟内完成。这个成绩还是可以的。

              

                更进一步,如果还想提高运算能力, 我会考虑用x86集群, 每个机器上启动一个JVM, 各个JVM之间tcp通信协同作业, 能将运算速度再提高一个数量级。

                有兴趣研究更深入算法或者技术实现的可以联系我,以下是我的代码:

               

                 #代码开源共享,但是转载的时候请注明出处,谢谢合作!!

                

import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 非递归算法 + 分割成若干计算单位并发运算 
 * 在高计算量下会有明显优势,但是在计算量较小,线程切换会有开销
 * 本地机器 2.93HZ双核CPU, 4G内存, win7系统, 16个数的全排列,一共4亿7千万次运算,2分钟内完成
 * 如果更进一步,可以考虑用x86机器做集群运算,每个机器上启动一个JVM, JVM之间用tcp通信协调,一起跑
 * 有兴趣想深入研究可以联系我
 * 
 * @author 蒋彪 版权所有 转载请注明出处
 * @param <E>
 * @param <T>
 * @mail nanjingjiangb@hotmail.com
 * @data 2013/3/4
 */
public class CombinationTest<E> {

	// 计算模块的线程执行框架,线程数保持在CPU核数 + 1, 可以最大榨取机器性能
	// private final static ExecutorService exec = Executors
	// .newFixedThreadPool(1);
	private final static ExecutorService exec = Executors.newCachedThreadPool();

	public static void main(String[] args) {

		if (args.length < 1) {
			return;
		}
		// 先将这串数字正排序,预先处理
		Arrays.sort(args);
		ArrayList<Integer> min = Util.changeStringsToArray(args);

		Arrays.sort(args, Collections.reverseOrder());
		ArrayList<Integer> max = Util.changeStringsToArray(args);

		ArrayList diffBlock = getStepData(min, max);

		for (int k = 0; k < diffBlock.size() - 1; k++) {
			CombinationArg task = new CombinationArgNoRecursion();
			task.setPn((ArrayList<Integer>) diffBlock.get(k));
			task.setMax((ArrayList<Integer>) diffBlock.get(k + 1));
			Future future = exec.submit(task);
			// future.get();
		}

		// exec.shutdown();
	}

	/**
	 * 分割待排序数据,分割成若干个起点的小块计算量 算法待优化,可考虑CPU,内存等限制计算快的大小
	 * 
	 * @param pn
	 * @return
	 */
	public static ArrayList getStepData(ArrayList<Integer> min,
			ArrayList<Integer> max) {

		ArrayList result = new ArrayList();
		// 循环把第二位和第一位交换
		// 比如,原始数据是1,2,3 ,4, 5
		// 那么可以分割成 12345 ~ 21345 ~ 31245~ 41235~ 51234 几个近似区间来计算
		result.add((ArrayList) min.clone());
		for (int j = 1; j < max.size(); j++) {
			Collections.swap(min, j, 0);
			result.add((ArrayList) min.clone());
		}
		result.add(max);
		return result;
	}

}

/**
 * 全排列组合算法接口 继承自Callable,方便在多线程环境下扩展
 * 
 * @author 蒋彪 版权所有 转载请注明出处
 * @param <E>
 * @param <T>
 * @mail nanjingjiangb@hotmail.com
 * @data 2013/3/4
 */
interface CombinationArg<E, T> extends Callable<T> {

	/**
	 * 输出全排列的方法 在多JVM并行环境考虑打印到分散式文档,或者内存数据库中
	 * 
	 * @param pn
	 *            inputData
	 */
	void printPermutation(ArrayList<E> pn);

	void setMax(ArrayList max);

	/**
	 * 得到全排列的具体算法 有递归和非递归等多种算法实现
	 * 
	 * @param pn
	 *            inputData
	 */
	boolean nextPermutation(ArrayList<E> pn);

	/**
	 * 全排列API
	 * 
	 * @param pn
	 *            inputData
	 */
	void sortInt();

	void setPn(ArrayList pn);
}

/**
 * 非递归全排列组合算法 并且将该算法平均分割成若干任务,充分利用多核CPU并行运算
 * 
 * @author 蒋彪 版权所有 转载请注明出处
 * @param <E>
 * @mail nanjingjiangb@hotmail.com
 * @data 2013/3/4
 */
class CombinationArgNoRecursion<E> implements CombinationArg {

	private ArrayList pn = new ArrayList();

	// private ThreadLocal<WriteLog> write = new ThreadLocal();

	private WriteLog write = new WriteLogToFile();

	private SimpleDateFormat formatter = new SimpleDateFormat(
			"dd-MMM-yyyy HH:mm:ss:SSS");

	private BigInteger max = new BigInteger("0");

	@Override
	public void setPn(ArrayList pn) {
		this.pn = pn;
	}

	@Override
	public void setMax(ArrayList max) {
		this.max = Util.changeArrayListToBigInteger(max);
	}

	public CombinationArgNoRecursion() {
	}

	@Override
	public Object call() throws Exception {
		sortInt();
		return true;
	}

	private int firstFlg = 0;

	/**
	 * 全排列数字
	 * 
	 * @param num
	 */
	public void sortInt() {
		System.out.println("/*****" + Thread.currentThread().getName()
				+ " 开始求全排列 ...... "
				+ formatter.format(new Date(System.currentTimeMillis()))
				+ "*******/");
		System.out.println("/*****************" + "start num is: "
				+ Util.changeArrayListToBigInteger(pn) + " end num is: " + max
				+ "***********************/");

		write.setWriter(Thread.currentThread().getName() + "打印全排序.txt");
		while (nextPermutation(pn)) {

		}
		write.closeWrite();
		System.out.println("/*****" + Thread.currentThread().getName()
				+ " 结束求全排列 ...... "
				+ formatter.format(new Date(System.currentTimeMillis()))
				+ "*******/");

	}

	/**
	 * 在屏幕上打印出全排列
	 * 
	 * @param str
	 */
	@Override
	public void printPermutation(ArrayList pn) {
		write.printPermutation(pn);
	}

	/**
	 * 
	 * 根据当前的排列p,计算下一个排列。
	 * 
	 * 原则是从1234–>4321,若p已经是最后一个排列,传回false,否则传回true。
	 * 
	 * @param pn
	 *            计算起点
	 * @param max
	 *            计算终点
	 */
	@Override
	public boolean nextPermutation(ArrayList pn) {
		if (firstFlg == 0) {
			printPermutation(pn);
			firstFlg++;
		}

		int last = pn.size() - 1;

		int i, j, k;

		// 从后向前查找,看有没有后面的数大于前面的数的情况,若有则停在后一个数的位置。

		i = last;

		while (i > 0 && ((Integer) pn.get(i) < (Integer) pn.get(i - 1)))

			i--;

		// 若没有后面的数大于前面的数的情况,说明已经到了最后一个排列,返回false。

		if (i == 0)

			return false;

		// 从后查到i,查找大于p[i - 1]的最小的数,记入k

		k = i;

		for (j = last; j >= i; j--)

			if (((Integer) pn.get(j) > (Integer) pn.get(i - 1))
					&& ((Integer) pn.get(j) < (Integer) pn.get(k)))

				k = j;

		// 交换p[k]和p[i - 1]

		Collections.swap(pn, k, i - 1);

		// 倒置p[last]到p[i]
		for (j = last, k = i; j > k; j--, k++)
			Collections.swap(pn, j, k);

		printPermutation(pn);

		// 如果超出限制,则停止计算
		BigInteger now = Util.changeArrayListToBigInteger(pn);
		if (now.compareTo(max) > 0) {
			return false;
		}
		return true;

	}

}

interface WriteLog {
	void writeLog(String log);

	void printPermutation(ArrayList pn);

	void setWriter(String fileName);

	void closeWrite();
}

class WriteLogToFile implements WriteLog {

	ThreadLocal<FileWriter> writer = new ThreadLocal();

	public void setWriter(String fileName) {
		try {
			writer.set(new FileWriter(fileName, true));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void closeWrite() {
		try {
			writer.get().close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * B方法追加文件:使用FileWriter
	 * 
	 * @param fileName
	 * @param content
	 */
	public void appendMethod(String content) {
		try {
			// 打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件
			writer.get().write(content);
			// 避免频繁的openWrite, closeWrite,增加CPU负担
			// writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void writeLog(String log) {
		appendMethod(log);
		appendMethod("\r\n");
	}

	@Override
	public void printPermutation(ArrayList pn) {
		StringBuffer tmp = new StringBuffer();
		for (int i = 0; i < pn.size(); i++) {
			tmp.append(pn.get(i)).append(",");
		}
		writeLog(tmp.toString());
	}

}

class Util {
	public static ArrayList<Integer> changeStringsToArray(String[] s) {
		ArrayList<Integer> px = new ArrayList<Integer>(s.length);
		for (String x : s) {
			px.add(Integer.parseInt(x));
		}
		return px;
	}

	public static BigInteger changeArrayListToBigInteger(ArrayList<Integer> pn) {
		StringBuffer orgData = new StringBuffer();

		for (int j = 0; j < pn.size(); j++) {
			orgData.append(pn.get(j));
		}
		return new BigInteger(orgData.toString());
	}

	public static ArrayList<Integer> changeBigIntegerToArrL(BigInteger tmp) {
		String x = tmp.toString();
		ArrayList<Integer> r = new ArrayList<Integer>();
		for (int j = 0; j < x.length(); j++) {
			r.add(Integer.parseInt(String.valueOf(x.charAt(j))));
		}
		return r;
	}
}


 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值