常用的排序算法及其适用场景
1.介绍
常用的排序算法主要有冒泡排序,选择排序,插入排序,希尔排序,堆排序,归并排序,快速排序,桶排序等。
2.稳定性
其中冒泡排序,插入排序,是稳定的排序算法;选择排序,希尔排序,堆排序,归并排序,快速排序是不稳定的排序算法。
3.排序算法的实现
3.1冒泡排序
冒泡排序的时间复杂度为O(n*n),空间复杂度为O(1),在数据有序的时候时间复杂度可以达到O(n)。适用的情景为数据量量不大,对稳定性有要求,且数据基本有序的情况下。这里列举了冒泡排序的3种实现方式,只有第三种实现方式在最好的情况下可以达到O(n)的时间复杂度,其常见实现方式如下:
// 冒泡排序1
public int[] sort_bubble1(int[] array) {
int len = array.length;
for (int i = 0; i < len - 1; i++) {
for (int j = i + 1; j < len; j++) {
if (array[i] > array[j]) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
return array;
}
// 冒泡排序2
public int[] sort_bubble2(int[] array) {
int len = array.length;
for (int i = 0; i < len - 1; i++) {
for (int j = len - 1; j > i; j--) {
if (array[j] < array[j - 1]) {
int temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
}
}
}
return array;
}
// 冒泡排序3
public int[] sort_bubble3(int[] array) {
int len = array.length;
boolean flag = true;
for (int i = 0; i < len - 1 && flag; i++) {
flag = false;
for (int j = len - 1; j > i; j--) {
if (array[j] < array[j - 1]) {
int temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
flag = true;
}
}
if (!flag)
break;
}
return array;
}
3.2选择排序
选择排序的时间复杂度为O(n*n),空间复杂度为O(1),由于每次选出待排序数据中的最小值(增序)或最大值(降序)插入到当前的有序队列中,相对于冒泡排序减少了交换的次数。当数据量不大,且对稳定性没有要求的时候,适用于选择排序。其代码如下:
// 简单选择排序
public int[] sort_simple(int[] array) {
int len = array.length;
for (int i = 0; i < len - 1; i++) {
int min = i;
for (int j = i + 1; j < len; j++) {
if (array[min] > array[j])
min = j;
}
if (min != i) {
int temp = array[i];
array[i] = array[min];
array[min] = temp;
}
}
return array;
}
3.3插入排序
插入排序的时间复杂度为O(n*n),空间复杂度为O(1),最好的情况下即当数据有序时可以达到O(n)的时间复杂度,其排序的思想非常类似于我们打扑克牌的时对手里的牌进行排序的过程,选出一个值,将其插入在合适的排序位置。适用于数据量不大,对算法的稳定性有要求,且数据局部或者整体有序的情况。其代码如下:
// 插入排序
public int[] sort_insert(int[] array) {
int len = array.length;
for (int i = 1; i < len; i++) {
if (array[i] < array[i - 1]) {
int temp = array[i];
int j = i - 1;
for (; j >= 0 && array[j] > temp; j--) { // 这里逻辑与判断的顺序一定不可以颠倒
array[j + 1] = array[j];
}
array[j + 1] = temp;
}
}
return array;
}
3.4希尔排序
希尔排序时间复杂度为O(nlogn)到O(n*n)之间,空间复杂度为O(1),其排序的效率受到比较距离大小的影响。和插入排序的过程相似,相对于插入排序而言,比较较远距离的数据,使得数据移动跨过多个元素,进行一次比较可能会消除多个元素的交换。其实现代码如下:
// 希尔排序
public int[] sort_hill(int[] array) {
int len = array.length;
int increment = len;
do {
increment = increment / 3 + 1;
for (int i = increment; i < len; i += increment) {
if (array[i] < array[i - increment]) {
int temp = array[i];
int j = i - increment;
for (; j >= 0 && array[j] > temp; j -= increment) { // 这里逻辑与判断的顺序一定不可以颠倒
array[j + increment] = array[j];
}
array[j + increment] = temp;
}
}
} while (increment > 1);
return array;
}
3.5堆排序
堆排序的时间复杂度为O(nlog),空间复杂度为O(1)。实现原理是根据大顶堆或小顶堆得数据结构原理,对待排序数据进行调整,最终达到整体有序的效果,其代码如下:
// 堆排序
private void heap_adjust(int[] array, int s, int len) {
int temp = array[s];
for (int i = 2 * s + 1; i < len; i = i * 2 + 1) {
if (i < len - 1 && array[i] < array[i + 1])
i++;
if (array[i] <= temp)
break;
array[s] = array[i];
s = i;
}
array[s] = temp;
}
public int[] sort_heap(int[] array) {
int len = array.length;
for (int i = (len - 1 - 1) / 2; i >= 0; i--) {
heap_adjust(array, i, len);
}
for (int j = len - 1; j > 0; j--) {
int temp = array[j];
array[j] = array[0];
array[0] = temp;
heap_adjust(array, 0, j);
}
return array;
}
这里需要注意的是搞清楚完全二叉树的父节点和子节点的关系。
对于角标从0开始的元素,其节点关系如下:
双亲:(i-1)/2
左孩子:2*i+1
右孩子:2*i+2
对于角标从1开始的元素,其节点关系如下:
双亲:i/2
左孩子:2*i
右孩子:2*i+1
3.6归并排序
归并排序的时间复杂度为O(nlog),空间复杂度为O(n),其需要借助额外的存储空间,是高级排序算法中,唯一一个稳定的排序算法,当数据量较大时,要注意考虑内存空间的开销。这里列举了其两种实现方式,其中第一种相对而言更加容易理解,其代码如下:
// 归并排序1
private void merge(int[] array, int start, int m, int end) {
int[] tmp = new int[end - start + 1];
int k = 0, i = start, j = m + 1;
while (i <= m && j <= end) {
if (array[i] < array[j])
tmp[k++] = array[i++];
else
tmp[k++] = array[j++];
}
for (int r = 0; r <= m - i; r++) {
tmp[k + r] = array[i + r];
}
for (int r = 0; r <= end - j; r++) {
tmp[k + r] = array[j + r];
}
System.arraycopy(tmp, 0, array, start, tmp.length);
}
private void m_sort(int[] array, int start, int end) {
if (start == end)
return;
int m = (start + end) / 2;
m_sort(array, start, m);
m_sort(array, m + 1, end);
merge(array, start, m, end);
}
public int[] sort_merger(int[] array) {
int len = array.length;
m_sort(array, 0, len - 1);
return array;
}
// 归并排序2
private void merge(int[] array, int[] copy_array, int start, int m, int end) {
int k = start, j = m + 1;
while (start <= m && j <= end) {
if (array[start] < array[j])
copy_array[k++] = array[start++];
else
copy_array[k++] = array[j++];
}
for (int i = 0; i <= m - start; i++) {
copy_array[k + i] = array[start + i];
}
for (int i = 0; i <= end - j; i++) {
copy_array[k + i] = array[j + i];
}
}
private void m_sort(int[] array, int[] copy_array, int start, int end) {
if (start == end) {
copy_array[start] = array[start];
} else {
int m = (start + end) / 2;
m_sort(copy_array, array, start, m);
m_sort(copy_array, array, m + 1, end);
merge(array, copy_array, start, m, end);
}
}
public int[] sort_merger(int[] array) {
int len = array.length;
int[] copy_array = array.clone();
m_sort(array, copy_array, 0, len - 1);
return copy_array;
}
3.7快速排序
快速排序算发的时间复杂度为O(nlogn),由于快速排序使用递归的方式实现,因此空间复杂度为O(nlogn)到O(n)之间。快速排序的思想是设置一个数据作为分割线,将小于它的数据交换至其左侧,大于它的数据交换到它的右侧(当数据增序排列的时候)。由于快速排序的原理,常用于查找一组中前k大的数据。其代码如下:
// 快速排序
private int partion(int[] array, int low, int high) {
System.out.println("low is:" + low + ";high is:" + high);
int mid = array[low];
while (low < high) {
while (low < high && array[high] >= mid)
high--;
// int temp = array[high];
// array[high] = array[low];
// array[low] = temp;
array[low] = array[high];
while (low < high && array[low] <= mid)
low++;
// temp = array[high];
// array[high] = array[low];
// array[low] = temp;
array[high] = array[low];
}
array[low] = mid;
System.out.println("array is:" + Arrays.toString(array));
System.out.println("low is:" + low + ";high is:" + high + ";index is:" + low);
return low;
}
private void sort(int[] array, int low, int high) {
// System.out.println(low+"---"+high);
if (low < high) {
int p = partion(array, low, high);
// System.out.println(p);
// System.out.println(Arrays.toString(array));
sort(array, low, p - 1);
sort(array, p + 1, high);
}
}
public int[] sort_quick(int[] array) {
sort(array, 0, array.length - 1);
return array;
}
3.8桶排序
桶排序的时间复杂度为O(n),是一种简单快速的排序算法,也有较大的局限性,仅适用于数据的分布相对比较集中的时候,其原理是建立一个包含一定数目桶的表,将待排序的数据通过散列算法散列到桶中,然后再遍历桶的到有序的数据。下面举个例子来说明:
比如待排序的数据集合为[50,52,59,45,51,48,50,55,56,57],这个数组比较明显的特点是数据分布在45~59之间,范围跨度区间为15(59-45+1),因此可以建立一个长度为15的数组array,即长度为15的桶,其初始值为0,然后遍历待排序数组,将待排序元素的值m减45得到对应桶的角标,将其加1,即array[m-45]++,当将待排序数据遍历完成之后,遍历我们创建的数据,将值不为0的角标输出即可,输出的时候需要再将角标的值加上45。