给定一个无序的int数组 R{13, 22, 19, 8, 1, 6},其长度为6,假使我们要需要使用冒泡法排序从小到大对数组进行排序
第一趟:1 13 22 19 8 6
第二趟:1 6 13 22 19 8
第三趟:1 6 8 13 22 19
第四趟:1 6 8 13 19 22
第五趟:1 6 8 13 19 22
过程分析:
初始状态下整个数组无序
第一趟:扫描范围[j-1,n-1]即[0,5]范围的数据,依次比较(R[n],R[n-1]),(R[n-1],R[n-2]),…,(R[2],R[1]),若每对中R[j] < R[j-1],交换两数的位置,即保证了数组中的第 1(下标为0) 个元素为最小的
第二趟:扫描范围[j-1,n-1]即[1,5]范围的数据,做相同的操作即可保证数组中R[j-1]在[j-1,n-1]的范围是最小的
….
第n-1趟时,可以保证数组中为R[n-1-1]在[n-1-1,n-1]中的数字是最小的
此时数组中只剩下了R[n-1],且一定是最大的,同时也说明长度为n的数组,至少要经过(n-1)趟排序才能使数组有序
给出一个用于检验数组是否有序及随机生成[rangeL,rangeR]范围内的任意长度的数组的工具类
package com.lancelot.csdn;
/**
* 工具类用于判断数组是否有序及随机生成[n,m]范围内的任意长度的数组
* @author Lancelot
*
*/
public class SortTestHelper {
//生成有 n个元素的随机数组,每个元素的随机范围为[rangeL,rangeR]
public static int[] generateRandomArray(int n, int rangeL, int rangeR){
assert rangeL <= rangeR;
int[] arr = new int[n];
for(int i = 0; i < n; i++){
arr[i] = (int)(Math.random() * (rangeR - rangeL + 1) + rangeL);
}
return arr;
}
/**
* 验证数组是否有序
* @param arr
* @return
*/
public static boolean isSort(int[] arr){
for (int i = 0; i < arr.length - 1; i++) {
if(arr[i] > arr[i + 1])
return false;
}
return true;
}
/**
* @param arr
*/
public static void printArray(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
排序代码
package com.lancelot.csdn;
/**
* 冒泡法排序
* @author Lancelot
*
*/
public class BubbleSort {
public static void sort(int[] arr, int length){
for(int i = 0; i < length -1; i++){
for(int j = length - 1; j > i; j--){
if(arr[j] < arr[j-1]){
swap(arr, j, j-1);
}
}
}
}
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = SortTestHelper.generateRandomArray(10,1, 1000);
sort(arr, arr.length);
if(SortTestHelper.isSort(arr)){
SortTestHelper.printArray(arr);
}
}
}
当我们在进行第 j趟排序时,此时(R[0]~R[j-1])一定是有序的,如果此时(R[j]~R[n-1])是有序的,那么在第j趟排序时,数组中不会发生交换行为,即如果(R[j]~R[n-1])是无序的,则一定会发生交换行为。我们可以设置flag来记录第j趟排序时数组是否发生数据交换行为,如果第j趟未发生交换,整个数组有序,提前结束程序
public static void sort2(int[] arr, int length){
for(int i = 0; i < length -1; i++){
//设置遍历是否发生交换的标志位
boolean flag = true;
for(int j = length - 1; j > i; j--){
if(arr[j] < arr[j-1]){
swap(arr, j, j-1);
flag = false; //发生交换
}
}
if(flag)
return;
}
}
当我们完成了第j趟排序时,数组中的(R[0]~R[j-1])一定是有序的,如果此时在(R[j]~R[n-1])中发现最后一次发生数据交换的位置为m(j < m < n-1),即(R[j]~R[m])时有序的,那么在第j+1趟时我们可以只对(R[m + 1]~R[n-1])进行排序,使用变量pos记录在每趟排序中最后发生数据交换的位置
/**
* 优化内层循环
* @param arr
* @param length
*/
public static void sort3(int[] arr, int length){
int k = 0; //内层循环终结点
int pos = 0; //记录最后一趟的循环位置
for(int i = 0; i < length -1; i++){
for(int j = length - 1; j > k; j--){
if(arr[j] < arr[j-1]){
swap(arr, j, j-1);
pos = j;
}
}
k = pos;
}
}
读者可以在阅读完后自己写程序测试算法的性能,
1.在使用未优化算法时,当数量为1k、10k、100k…耗时的情况(很容易得出时间呈现n^2递增)
2.在相同的数据量下,各种算法的时间复杂度耗时比较(笔者这里发现其实并没有随机生成的数据下,耗时并没有明显减少,反而耗时增加)
3.读者可自行写个随机生成比较有序的数组的工具类,相同数据量下不同算法的耗时(在近乎有序的数组中,优化的算法耗时明显减少),在不同数据量下,优化后的算法性能得到明显提示(不再呈现n^2递增)