工作中经常会有一些场景,需要对集合排序,所以对常用的几种排序算法做了一次测评,mark下
1、冒泡排序:
冒泡排序的原理比较简单,依次比较两个相邻元素,如果与期望顺序不符,则交换。如此循环,就可以将最大或最小元素按期望排到最前或最后。
public static void bubbleSort(int[] array) {
int n = array.length;
for (int i=0;i<n;i++) {
for (int j=0;j<n-i-1;j++) {
if (array[j] > array[j+1]) {
int temp = array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
}
}
2、选择排序
选择排序跟冒泡排序原理相似,选择排序是循环集合,找出最大元素,排到最后,再从未排序集合中找最大,排到未排序集合里的最后。
public static void selectSort(int[] array) {
for (int i=0;i<array.length-1;i++) {
int pos = i;
int min = array[i];
for (int j=i+1;j< array.length;j++) {
if (array[j] < min) {
pos = j;
min = array[j];
}
}
if (i != pos) {
int temp = array[i];
array[i] = array[pos];
array[pos] = temp;
}
}
}
3、插入排序
插入排序类似于玩扑克牌的时候,我们会把抓到的牌插入到手里已经排好顺序的牌中。插入排序就是以集合第一个元素为有序集合开始,以第二个元素作为循环开始,扫描排好序的集合,按大小插入到有序集合中。
public static void insertSort(int[] array) {
for (int i=0;i<array.length-1;i++) {
for (int j=i+1;j > 0;j--) {
if (array[j] < array[j-1]) {
swap(array, j, j-1);
}
}
}
}
4、快速排序
快速排序是二分排序。开始以第一个元素为基准,小于此元素的插入到之前,大于此元素的插入到之后。这样就形成了一个以初始第一个元素为中心,左右两边各是一个子集合,且左边元素都小于此元素,右边元素都大于此元素。然后再对左右两边集合做相同操作,这样集合越来越小,直到排序完成。
public static void quikSort(int[] list) {
quik(list, 0, list.length-1);
}
private static void quik (int[] list, int left, int right) {
if (left >= right)
return;
int middle = sort(list, left, right);
quik(list, left, middle - 1);
quik(list,middle + 1 , right);
}
private static int sort (int[] list, int start, int end) {
int startValue = list[start];
int position = start;
while (start < end) {
while (start < end && list[end] >= startValue) {
end--;
}
while (start < end && list[start] <= startValue) {
start++;
}
swap(list, start, end);
}
swap(list, start, position);
return start;
}
5、归并排序
归并排序是将集合一直二分,直到二分的集合是有序的,然后再跟其他集合组合,组合是遍历两个集合,按大小插入集合
private static int[] mergeSort(int[] array, int begin, int end) {
if (begin < end) {
int middle = (begin + end)/2;
array = mergeSort(array, begin, middle);
array = mergeSort(array, middle +1, end);
merge(array, begin, middle, end);
}
return array;
}
private static void merge(int[] array, int begin, int midddle, int end) {
int[] tempArray = new int[end - begin + 1];
int minleft = begin;
int maxleft = midddle + 1;
int temppos = 0;
while (minleft <= midddle && maxleft <= end) {
if (array[minleft] <= array[maxleft]) {
tempArray[temppos] = array[minleft];
minleft++;
} else {
tempArray[temppos] = array[maxleft];
maxleft++;
}
temppos++;
}
if (minleft <= midddle)
tempArray[temppos] = array[minleft];
if (maxleft <= end)
tempArray[temppos] = array[maxleft];
for (int i=0;i< tempArray.length;i++) {
array[begin+i] = tempArray[i];
}
}
6、堆排序
堆排序与前边几个都不相同。堆排序是将一个集合当做二叉树来排序。这个二叉树按集合中的顺序,对应从树跟节点到叶节点。如集合:[3,2,1,4,5,6,9,7,8] 这样的一个集合,那么对应的二叉树:
开始排序,则从最后一个非根节点开始,与子节点比较,与最大子节点交换位置
最终排序如下:
这就是大根树,即父节点大于两个子节点,此时对应集合为:[9, 8, 6, 7, 5, 3, 1, 2, 4]
集合中的最大值在根节点处。
这时把根节点的移动到最末,集合则为:[4, 8, 6, 7, 5, 3, 1, 2, 9],然后对集合长度-1的集合重复上面操作。最终到集合完全排序。
public static void heapSort(int[] array) {
//构造大根堆
for (int i = array.length/2-1 ; i >= 0; i--) {
buildMaxHeap(array, i, array.length);
}
System.out.println("maxHeap=" + Arrays.toString(array));
for (int i = array.length-1; i > 0; i--) {
int temp = array[i];
array[i] = array[0];
array[0] = temp;
buildMaxHeap(array, 0, i);
}
System.out.println("2maxHeap=" + Arrays.toString(array));
}
/*
* 构造大根堆
* 从最后一个非叶子节点开始
* 需处理的集合范围,end值
*/
private static void buildMaxHeap (int[] array, int i, int length) {
int temp = array[i];
//步长为j*2+1,即指向节点的左子节点
for (int j = i * 2 + 1; j < length; j = j * 2 + 1) {
if (j + 1 < length && array[j] < array[j+1]) {
j++; // 左子节点小于右子节点,则直接去比较右子节点与根节点大小
}
if (array[j] > temp) {
array[i] = array[j];
// array[j] = temp; // temp 还是要与子节点做比较的,所以不用换
i = j;
} else
break;
}
array[i] = temp;
}
代码完毕,开始测试
在数据量较小时,1000以下,几种算法耗时相差不大
当数量级大于10000时,快排,归并,堆排依然很稳,耗时很小,但冒泡、选择、插入性能下降
数据量到100000时,冒泡、选择、插入直接弃用。快排,插入,堆排依然很强。
测评结果就不贴了,动动手感受更深刻。
其他像计数排序,基数排序,桶排序这些适用于特定集合的算法,对于元素值在有限区间内的集合,性能还是不错的,但对于元素值比较杂乱的就不太适用了。可以了解借鉴下