很多人第一次接触的排序都是冒泡排序。
上代码:
/** * @Title: bubble * @Description: TODO(冒泡排序) * @param @param is 设定文件 * @return void 返回类型 */ private static void bubble(int[] is) { boolean isChange = true; for (int i = 0; i < is.length && isChange; i++) { isChange = false; for (int j = is.length - 1; j > i; j--) { if (is[j] < is[j - 1]) { swap(is, j, j - 1); isChange = true; } } } }
private static void swap(int[] is, int i, int j) { is[i] = is[i] + is[j]; is[j] = is[i] - is[j]; is[i] = is[i] - is[j]; }
冒泡排序1000000个元素的int数组,耗时接近半小时。。。
程序执行了1636590毫秒
已经排好序的数组倒是超快的。。 1000万的数组,毫秒级搞定。
逆序。。。 没试。。 应该比乱序只高不低的。。。 有兴趣的可以试试
总结就是: 别用冒泡了。。
主函数:
/** * @Title: main * @Description: TODO(这里用一句话描述这个方法的作用) * @param @param args 设定文件 * @return void 返回类型 */ public static void main(String[] args) { // TODO Auto-generated method stub int[] is = new int[MAX]; for (int i = 0; i < MAX; i++) { is[i] = new Random().nextInt(MAX); // is[i] = i; // is[i] = MAX - i; } // System.out.println(Arrays.toString(is)); long start = System.currentTimeMillis(); quickSort(is,0,is.length - 1); long end = System.currentTimeMillis(); // System.out.println(Arrays.toString(is)); System.out.println("程序执行了" + (end - start) + "毫秒"); }
接下来是重点:快排。
上代码:
/** * 快排核心,选择基准并进行依次排列,并获取排序后基准所在位置 * 两个指针,left在最左边,right在最右边,left++,right--,当右边有大于基准的数或左边有大于基准的数时,交换 * 直到两个指针相遇,复杂度为O(right - left) */ private static int getMid(int[] is, int left, int right) { int temp = is[left]; // 基准 while (left < right) { while (left < right && is[right] >= temp) { right--; } is[left] = is[right]; while (left < right && is[left] <= temp) { left++; } is[right] = is[left]; } is[left] = temp; return left; }
/** * 快排核心,选择基准并进行依次排列,并获取排序后基准所在位置 * 两个指针,left在最左边,right在最右边,left++,right--,当右边有大于基准的数或左边有大于基准的数时,交换 * 直到两个指针相遇,复杂度为O(right - left) */ private static int getMid(int[] is, int left, int right) { // System.out.println("-----------------开始一次快排的排列---------"); // System.out.println("排序前的数组为:"+Arrays.toString(is)+"\r\n"); // System.out.println("这次排序以第"+(left + 1)+"个数"+is[left]+"为基准和开始,以第"+(right+1)+"个数"+is[right]+"为结束"); // System.out.println("本次排列需要排列的分数组为:"); // int row = left; // int hight = right; // print(is,row,hight); int temp = is[left]; // 基准 while (left < right) { while (left < right && is[right] >= temp) { right--; } is[left] = is[right]; while (left < right && is[left] <= temp) { left++; } is[right] = is[left]; } is[left] = temp; // System.out.println("本次排列结束后分数组为:"); // print(is,row,hight); // System.out.println("本次排列结束后整个数组为:"); // System.out.println(Arrays.toString(is)); // System.out.println("-------------------本次排列结束,本次排列中,比"+temp+"大的数换到它右边,比它小的数到它左边,"+temp+"被换到了第"+(left+1)+"个位置"); return left; }
下面是排列10个随机数,快排的过程:
[5, 3, 2, 2, 0, 5, 0, 1, 4, 5] -----------------开始一次快排的排列--------- 排序前的数组为:[5, 3, 2, 2, 0, 5, 0, 1, 4, 5] 这次排序以第1个数5为基准和开始,以第10个数5为结束 本次排列需要排列的分数组为: 5 3 2 2 0 5 0 1 4 5 本次排列结束后分数组为: 4 3 2 2 0 5 0 1 5 5 本次排列结束后整个数组为: [4, 3, 2, 2, 0, 5, 0, 1, 5, 5] -------------------本次排列结束,本次排列中,比5大的数换到它右边,比它小的数到它左边,5被换到了第9个位置 -----------------开始一次快排的排列--------- 排序前的数组为:[4, 3, 2, 2, 0, 5, 0, 1, 5, 5] 这次排序以第1个数4为基准和开始,以第8个数1为结束 本次排列需要排列的分数组为: 4 3 2 2 0 5 0 1 本次排列结束后分数组为: 1 3 2 2 0 0 4 5 本次排列结束后整个数组为: [1, 3, 2, 2, 0, 0, 4, 5, 5, 5] -------------------本次排列结束,本次排列中,比4大的数换到它右边,比它小的数到它左边,4被换到了第7个位置 -----------------开始一次快排的排列--------- 排序前的数组为:[1, 3, 2, 2, 0, 0, 4, 5, 5, 5] 这次排序以第1个数1为基准和开始,以第6个数0为结束 本次排列需要排列的分数组为: 1 3 2 2 0 0 本次排列结束后分数组为: 0 0 1 2 2 3 本次排列结束后整个数组为: [0, 0, 1, 2, 2, 3, 4, 5, 5, 5] -------------------本次排列结束,本次排列中,比1大的数换到它右边,比它小的数到它左边,1被换到了第3个位置 -----------------开始一次快排的排列--------- 排序前的数组为:[0, 0, 1, 2, 2, 3, 4, 5, 5, 5] 这次排序以第1个数0为基准和开始,以第2个数0为结束 本次排列需要排列的分数组为: 0 0 本次排列结束后分数组为: 0 0 本次排列结束后整个数组为: [0, 0, 1, 2, 2, 3, 4, 5, 5, 5] -------------------本次排列结束,本次排列中,比0大的数换到它右边,比它小的数到它左边,0被换到了第1个位置 -----------------开始一次快排的排列--------- 排序前的数组为:[0, 0, 1, 2, 2, 3, 4, 5, 5, 5] 这次排序以第4个数2为基准和开始,以第6个数3为结束 本次排列需要排列的分数组为: 2 2 3 本次排列结束后分数组为: 2 2 3 本次排列结束后整个数组为: [0, 0, 1, 2, 2, 3, 4, 5, 5, 5] -------------------本次排列结束,本次排列中,比2大的数换到它右边,比它小的数到它左边,2被换到了第4个位置 -----------------开始一次快排的排列--------- 排序前的数组为:[0, 0, 1, 2, 2, 3, 4, 5, 5, 5] 这次排序以第5个数2为基准和开始,以第6个数3为结束 本次排列需要排列的分数组为: 2 3 本次排列结束后分数组为: 2 3 本次排列结束后整个数组为: [0, 0, 1, 2, 2, 3, 4, 5, 5, 5] -------------------本次排列结束,本次排列中,比2大的数换到它右边,比它小的数到它左边,2被换到了第5个位置 [0, 0, 1, 2, 2, 3, 4, 5, 5, 5] 程序执行了2毫秒
接下来,就是看快排效率的时候了。前面实验了希尔排序,1000000个int数组的耗时分别为:
乱序: 209毫秒 顺序: 66毫秒 逆序 82毫秒
快排相同数据,乱序的结果:
程序执行了123毫秒
顺序的时候,栈溢出了。。。
实现一下快排序的非递归:
/** * @Title: quickSort * @Description: TODO(快排的非递归实现) * 类似递归,主要是利用堆栈保存还未排序的数组信息 * @param @param a 设定文件 * @return void 返回类型 */ private static void quickSort(int a[]) { //堆栈,保存排列信息 Stack<Integer> index = new Stack<Integer>(); int start = 0; int end = a.length - 1; int mid; index.push(start); index.push(end); while (!index.isEmpty()) { end = index.pop(); start = index.pop(); mid = getMid(a, start, end); if (start < mid - 1) { index.push(start); index.push(mid - 1); } if (end > mid + 1) { index.push(mid + 1); index.push(end); } } }
结果为
程序执行了268420毫秒
逆序和顺序类似。
可以看到快排特别不适合已经基本有序的对象排序。
记得在刚学排序算法的时候,书上说快排是现有的最好的排序算法。今天的实验也说明快排在乱序中,是优于希尔排序的。
而且,快排有两个特点:
其一是可以排列一个数组中任意两个位置中间的数。比如数组[0, 9, 3, 1, 5, 7, 6, 2, 7, 3] 排列这个数组第3个数到第5个数,结果为:[0, 9, 1, 3, 5, 7, 6, 2, 7, 3]。这种需求很少见,但是如果看见了应该能想到快排。
还有一个特点就是快排在排列任意两个位置之间的数列时,对其他数列是没有影响的。所以快排很适合利用多线程优化它。
比如说,快排一亿个数,可以分成10个线程,第一个线程快排第1个到第一千万个数,第二个线程快排第一千万到两千万之间的数。