选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,
冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。
那什么是稳定的排序算法呢?
就是在排序的过程中,相等的两个数并不会在排列后中为位置发生次序发生颠倒(一般而言,交换相隔很远的元素的算法是不稳定的)
时间效率分析汇总
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
冒泡 | O(n2) | O(n2) | 稳定 | O(1) | n小时较好 |
选择 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
插入 | O(n2) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(dn) | O(dn) | 稳定 | O(n) | d为整数最高为的位数 |
希尔 | O(nlogn) | O(ns) 1<s<2 | 不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n2) | 不稳定 | O(log2n) | n大时较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(n) | n大时较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大时较好 |
关于快速排序,归并排序的空间复杂度,这里解释一下
归并排序每次递归都要用到一个辅助表,长度与待排序的表长度相同,虽然递归次数是O(log2n),但每次递归都会释放掉所占的辅助空间,所以下次递归的栈空间和辅助空间与这部分释放的空间就不相关了,因而空间复杂度还是O(n)。
快速排序空间复杂度只是在通常情况下才为O(log2n),如果是最坏情况的话,很显然就要O(n)的空间了。当然,可以通过随机化选择pivot来将空间复杂度降低到O(log2n)。但下面的快速排序实现就是一种O(n)的实现了
适用场景
冒泡排序: 在实际运用中它是效率最低的算法;但在待排序的元素序列基本有序的前提下,效率最高
插入排序: 一般不适合数据量比较大的场合或数据重复比较多的场合;但在每个元素距其最终位置不远时效率高
选择排序: 在实际应用中和冒泡排序基本差不多
希尔排序: 希尔排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起快速排序,归并排序,堆排序慢很多。
归并排序: 合并排序比堆排序稍微快一点,由于它需要一个额外的数组,在数据量大是比较适用
快速排序: 快速排序比大部分排序算法都要快。尽管在某些特殊的情况(有序,逆序)下有比快速排序快的算法,但通一般情况下,没有比它更快的。
堆排序: 适合数据量大的处理,对随机数的处理比快速排序慢,但和归并排序还是有比较有竞争的
代码实现
一、冒泡排序
/**
* 冒泡排序 <br/>
* 时间复杂度O(n2) 空间复杂度O(1)
*
* @author Joeson
*
*/
public class BubbleSort
{
public static void main(String[] args)
{
int[] data = new int[] { 12, 3, 34, 13, 42, 56, 62, 35, 8 };
bubbleSort(data);
System.out.print("排序结果: " + Arrays.toString(data));
}
/**
* 排序核心部分
*
* @param a
* @return
*/
private static void bubbleSort(int[] a)
{
int temp;
for (int i = 0; i < a.length; i++)
for (int j = 0; j < a.length - i - 1; j++)
{
if (a[j] > a[j + 1])
{
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
二、选择排序
/**
* 选择排序<br/>
* 时间复杂度O(n2) 空间复杂度O(1)
*
* @author Joeson
*
*/
public class SelectionSort
{
public static void main(String[] args)
{
int[] data = new int[] { 12, 3, 34, 13, 42, 56, 62, 35, 8 };
selectionSort(data);
System.out.print("排序结果: " + Arrays.toString(data));
}
/**
* 选择排序核心
*
* @param data
* @return
*/
private static void selectionSort(int[] data)
{
int min;
int temp;
for (int i = 0; i < data.length; i++)
{
min = i;
for (int j = i + 1; j < data.length; j++)
{
if (data[j] < data[min])
{
min = j;
}
}
temp = data[i];
data[i] = data[min];
data[min] = temp;
}
}
}
三、插入排序
/**
* 插入排序<br/>
* 时间复杂度O(n2) 空间复杂度O(1)
*
* @author Joeson
*
*/
public class InsertSort
{
public static void main(String[] args)
{
int[] data = new int[] { 12, 3, 34, 13, 42, 56, 62, 35, 8 };
insertSort(data);
System.out.print("排列顺序: " + Arrays.toString(data));
}
/**
* 插入排序
*
* @param data
*/
private static void insertSort(int[] data){
for (int i = 1; i < data.length; i++) {
int tmp = data[i];
int j = i - 1;
while(j >= 0 && data[j] > tmp){
data[j + 1] = data[j];
j--;
}
data[j + 1] = tmp;
}
return data;
}
}
四、基数排序
/**
* 基数排序(桶排序) <br/>
* 平均时间复杂度:O(dn)(d即表示整形的最高位数) <br/>
* 空间复杂度:O(10n) (10表示0~9,用于存储临时的序列)<br/>
* 稳定性:稳定
*
* @author Joeson
*
*/
public class RadixSort
{
/**
* 最大数的位数
*/
static int max_Pos = 2;
public static void main(String[] args)
{
int[] data = new int[] { 23, 54, 63, 61, 46, 27 };
radixSort(data, 10, 2);
System.out.print("排列结果: " + Arrays.toString(data));
}
/**
*
* @param data
* @param radix
* 多少进制数,就代表多少个桶
* @param d
* 将关键字拆分为多少个子关键字
* @return
*/
public static void radixSort(int[] data, int radix, int d)
{
// 需要一个临时数组
int[] tmp = new int[data.length];
// 桶
int[] buckets = new int[radix];
for (int i = 0, rate = 1; i < d; i++)
{
Arrays.fill(buckets, 0);
// 拷贝数组到tmp中
System.arraycopy(data, 0, tmp, 0, data.length);
// 计算数组关键位置的子关键字,将其位置记录在bucktes中
for (int j = 0; j < data.length; j++)
{
int subKey = (tmp[j] / rate) % radix;
buckets[subKey]++;
}
for (int j = 1; j < radix; j++)
{
buckets[j] = buckets[j] + buckets[j - 1];
}
for (int m = data.length - 1; m >= 0; m--)
{
int subKey = (tmp[m] / rate) % radix;
data[--buckets[subKey]] = tmp[m];
}
System.out
.println("对" + rate + "位上子关键字排序:" + Arrays.toString(data));
rate *= radix;
}
}
}
五、希尔排序
/**
* 希尔排序
*
* <br/>
* 时间复杂度O(n1.25)
*
* @author Joeson
*
*/
public class ShellSort
{
public static void main(String[] args)
{
int[] a = new int[] { 12, 3, 34, 13, 42, 56, 62, 35, 8 };
shellSort(a);
System.out.print("排列顺序 : ");
System.out.println(Arrays.toString(a));
}
/**
* 希尔排序核心1
*
* @param args
* @return
*/
private static void shellSort(int[] data)
{
for (int step = data.length / 2; step > 0; step /= 2)
{
shellInsert(data, step);
}
}
/**
* 增序排列
*
* @param data
* @param step
* @return
*/
private static void shellInsert(int[] data, int step)
{
int j;
int min;
for (int i = step; i < data.length; i++)
{
if (data[i - step] > data[i])
{
min = data[i];
for (j = i - step; j >= 0 && data[j] > data[j + step]; j -= step)
{
data[j + step] = data[j];
}
data[j + step] = min;
}
}
}
}
六、快速排序
/**
* 快速排序 时间复杂度 O(nlogn)
* 空间复杂度O(n)
*
* @author Joeson
*
*/
public class QuickSort
{
public static void main(String[] args)
{
int[] data = new int[] { 12, 45, 12, 42, 42, 46, 63, 28 };
quickSort(data, 0, data.length - 1);
System.out.print("排序顺序: ");
System.out.println(Arrays.toString(data));
}
/**
* 增序排序
*
* @param a
* @param from
* @param to
* 最大下标
*/
public static void quickSort(int[] data, int begin, int end)
{
if (end <= begin)
return;
int mid = partition(data, begin, end);
quickSort(data, begin, mid - 1);
quickSort(data, mid + 1, end);
}
/**
* 快速排序核心部分
*
* @param a
* @param low
* @param high
* @return
*/
public static int partition(int[] data, int begin, int end)
{
int low = begin + 1;
int high = end;
do
{
while (low < end && data[low] <= data[begin])
low++;
while (high > begin && data[high] >= data[begin])
high--;
swap(data, low, high);
} while (low < high);
// 返回最后一次交换
swap(data, low, high);
swap(data, begin, high);
return high;
}
public static void swap(int[] a, int i, int j)
{
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
七、合并排序
/**
* 合并排序
*
* @author Joeson
*/
public class MergeSort {
public static void main(String[] args) {
int[] list = new int[]{12, 45, 12, 42, 42, 46, 63, 28};
mergeSort(list, 0, list.length - 1);
System.out.print("排列顺序: ");
for (int i = 0; i < list.length; i++) {
System.out.print(list[i] + " ");
}
}
private static void mergeSort(int[] list, int from, int to) {
if (from < to) {
int mid = (from + to) >> 1;
mergeSort(list, from, mid);
mergeSort(list, mid + 1, to);
merge(list, from, to);
}
}
private static void merge(int[] list, int from, int to) {
int i = from;
int mid = (from + to) >> 1;
int j = mid + 1;
int[] a = new int[to - from + 1];
int count = 0;
while (i <= mid && j <= to) {
if (list[i] < list[j]) {
a[count++] = list[i++];
} else {
a[count++] = list[j++];
}
}
// 把剩余部分都加进去
while (i <= mid) {
a[count++] = list[i++];
}
while (j <= to) {
a[count++] = list[j++];
}
for (int k = from; k <= to; k++) {
list[k] = a[k - from];
}
}
}
八、堆排序
/**
* 建堆 O(n) 删除O(logn) <br/>
* 堆排序包括两个步骤<br/>
* (1)初始堆(堆的定义:1)堆是一个完全二叉树 2)根结点的值或者大于左右子树的值或者小于左右子树的值 3)左右子树也是一个堆) <br/>
* (2)调整堆(当初始小顶堆之后,堆顶元素是最小的元素,取出最小的元素与最后一个元素相交换,再把剩下n-1个元素调整成堆,依次调整直到1为止)
* 非终端节点调整 初始堆时从n/2向上调整成堆 把第一个元素与最后一个元素交换之后从第1个元素开始调整成新堆
*
*
* 时间复杂度为O(nlogn) 辅助空间为O(1)
*
* @author Joeson
*
*/
public class HeapSort
{
public static void main(String[] args)
{
int[] data = new int[] { 2, 23, 24, 43, 52, 24, 51, 26 };
heapSort(data);
System.out.print("排列顺序: ");
System.out.println(Arrays.toString(data));
}
/**
* 堆排序核心部分
*
* @param num
*/
private static void heapSort(int[] a)
{
if (a.length <= 0)
return;
// 调整整个堆堆过程
for (int i = a.length / 2 - 1; i >= 0; i--)
{
adjust(a, i, a.length);
}
int tmp;
for (int i = a.length - 1; i > 0; i--)
{
tmp = a[i];
a[i] = a[0];
a[0] = tmp;
// 从a[0]到num[i-1]调整成新堆
adjust(a, 0, i);
}
}
/**
* 调整堆
*
* @param num
* @param s
* 调整开始父节点
* @param t
* 调整结尾的界限,不包括
*/
private static void adjust(int[] a, int begin, int end)
{
int tmp = a[begin];
// 从最下父节点 与子节点比较,一直向上进行调整
for (int i = 2 * begin + 1; i < end; i = 2 * i + 1)
{
// 找出较大者把较大者给num[i]
if (i < end - 1 && a[i] < a[i + 1])
{
i++;
}
if (a[i] > tmp)
{
a[begin] = a[i];
begin = i;
}
else
break;
}
a[begin] = tmp;
}
}