使用三种方法求解前N个正整数的排列

       本篇博文给大家介绍前N个正整数的排列求解的三种方式。第一种是暴力求解法;第二种则另外声明了一个长度为N的数组,并且将已经排列过的数字保存其中;第三种方式则采用了另外一种思路,即首先获取N个整数的升序排列,然后对其位置进行随机交换以达到前N个整数的随机排列的目的。首先我们来看看第一种方式,具体代码如下:

import java.util.Random;

import org.junit.Test;

public class App {
  private int n = 100000;
  @Test
  public void testFirst() {
    int[] arr = new int[n];
    long start = System.currentTimeMillis();
    for (int i = 0; i < n; i++) {
      arr[i] = readInt(1, n + 1);
      while (isExist(arr, i)) {
        arr[i] = readInt(1, n + 1);
      }
    }
    long end = System.currentTimeMillis();
    System.out.println("方法一消耗时长为:" + (end - start));
  }

  private boolean isExist(int[] arr, int index) {
    for (int i = 0; i < index; i++) {
      if (arr[i] == arr[index]) {
        return true;
      }
    }
    return false;
  }

  private int readInt(int i, int j) {
    Random random = new Random();
    int result = random.nextInt(j);
    while (result < i) {
      result = random.nextInt(j);
    }
    return result;
  }
}

       这里输出结果如下

方法一消耗时长为:38580

        即当n为100000的时候,计算结果需要38.580s。该算法通过readInt(int i, int j)方法获取一个大于等于i并且小于等于j的整数,通过isExist(int[] arr, int index)方法判断数组中在索引为index之前的序列里是否已经存在了索引为index对应的值。该算法求解的思路即为从0开始对数组arr赋值,并且在每次获取一个随机数之后判断数组之前的部分中是否已经存在了该值,若存在了该值则继续获取,直至数组之前的部分中不存在该值。这种算法将产生大量的重复计算,主要来源在于判断数组之前的部分中是否存在该值和对于新的数组元素的获取两个部分。为了解决第一个问题,即判断数组之前部分中是否存在该值,我们采用了第二种算法,具体代码如下:

import java.util.Random;

import org.junit.Test;

public class App {
  private int n = 100000;

  @Test
  public void testSecond() {
    int[] arr = new int[n];
    long start = System.currentTimeMillis();
    boolean[] used = new boolean[n];
    for (int i = 0; i < n; i++) {
      arr[i] = readInt(1, n + 1);
      while (used[arr[i] - 1]) {
        arr[i] = readInt(1, n + 1);
      }
      used[arr[i] - 1] = true;
    }
    long end = System.currentTimeMillis();
    System.out.println("方法二消耗时长为:" + (end - start));
  }

  private int readInt(int i, int j) {
    Random random = new Random();
    int result = random.nextInt(j);
    while (result < i) {
      result = random.nextInt(j);
    }
    return result;
  }
}

        计算结果如下:

方法二消耗时长为:148

        若将N改为1000000,计算结果为:1.648s,若将N改为10000000,计算结果为33.679s,可以看出,该算法相对于第一种算法有所改进,但是对于千万级数据量的处理消耗时长较长。第二种算法另外声明了一个数组used,该数组长度为N,这里将要求解的数据(长度为N的正整数的排列)与used数组的下标形成一种一一对应的关系,当要判断某个获取到的正整数是否已经存在时,只需要判断数组中对应下标的数据值是否为true,为true则表示已经存在。但是这里经过我们的分析也会发现,对于采用readInt(int i, int j)方法获取新数据时,若该数据非常靠后,那么获取的难度(循环的次数)将会大大增加,这里也会影响该算法的运行效率。为了避免这个问题,我们则可以采用第三种方法,该方法则使用了一个已知的事实,即结果数组中的数值我们是完全确定的,变化的只是该数值的顺序,因而,我们如果采用随机算法改变各个数值的位置,即可达到排列各个数值的目的。具体的算法如下:

import java.util.Random;

import org.junit.Test;

public class App {
  private int n = 10000000;

  @Test
  public void testThird() {
    int[] arr = new int[n];
    long start = System.currentTimeMillis();
    for (int i = 0; i < n; i++) {
      arr[i] = i + 1;
    }
    for (int i = 1; i < n; i++) {
      swapReferences(arr, i, readInt(0, i));
    }
    long end = System.currentTimeMillis();
    System.out.println("方法三消耗时长为:" + (end - start));
  }

  private void swapReferences(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
  }

  private int readInt(int i, int j) {
    Random random = new Random();
    int result = random.nextInt(j);
    while (result < i) {
      result = random.nextInt(j);
    }
    return result;
  }
}

       运行结果如下:

方法三消耗时长为:2105

       该方法通过readInt(int i, int j)方法每次获取数组中索引为0到索引为i的随机索引,将并且将数组中索引为i的数据与获取到的索引对应的数据位置替换,这样就可以达到全排列的目的。

原文地址:https://www.cnblogs.com/zhangxufeng/p/8284064.html