附上wiki参考链接:https://zh.wikipedia.org/wiki/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95#简要比较
关于排序算法的稳定性:https://zhuanlan.zhihu.com/p/36120420
冒泡排序
描述:它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
时间复杂度:最好=O(n)(本身已经排好了,只要比较O(n-1)次,这是改进版的),最坏=平均=O(n²)
空间复杂度:O(1)
稳定性:可以实现
java实现
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args){
BubbleSort bubbleSort = new BubbleSort();
int[] arr = new int[]{1,2,3,4,5};
bubbleSort.bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
public void bubbleSort(int[] arr){
for(int i = 0;i < arr.length-1;i++){
// 改进,若某轮没有交换,说明已经排序好了,可以不用比较下面的
boolean ischange = false;
// 两个两个元素比较,若前一个元素比后面的元素要大,就交换,这样较小的元素就会浮到前面
// 有i个元素已经排好在最后,所以后面的arr.length-i - arr.length 就不用比较了
for(int j = i+1;j < arr.length-i;j++){
if(arr[i] > arr[j]){
ischange = true;
swap(arr,i,j);
}
}
if(!ischange){
break;
}
}
}
public void swap(int[] arr,int aIndex,int bIndex){
int temp = arr[aIndex];
arr[aIndex] = arr[bIndex];
arr[bIndex] = temp;
}
}
快速排序
描述:(详见描述分治的另一篇文章)
时间复杂度:最好=平均=O(nlogn),最坏=O(n²),与划分的对称性有关。(随机取枢轴元可避免最坏)
空间复杂度:O(logn)
主体函数:partition(),时间复杂度为O(n)
最坏例子:以第一个元素为枢轴元,有12345/54321.
稳定性:不可实现
java实现(随机快排)
import java.util.Arrays;
import java.util.Random;
public class QuickSort {
/**
* 划分主体
* @param a
* @param l
* @param r
* @return
*/
public int partition(int[] a,int l,int r) {
// 生成一个随机下标,随机选择基准元,可期望划分是比较对称的。
int k = getRandomIndex(l,r);
// 这里一定要swap,否则每一次递归生成的k都不一样,会导致结果错误
swap(a,l,k);
int i = l, j = r, x = a[l];
while (i < j) {
while (i < j && a[j] >= x) {
j--;
}
if (i < j) {
a[i] = a[j];
}
while (i < j && a[i] < x) {
i++;
}
if (i < j) {
a[j] = a[i];
}
}
a[i] = x;
return i;
}
/**
* 快排主体
* @param a
* @param l
* @param r
*/
public void quickSort(int[] a,int l,int r){
if(l < r){
int i = partition(a,l,r);
quickSort(a,l,i-1);
quickSort(a,i+1,r);
}
}
public int getRandomIndex(int l,int r){
return new Random().nextInt(r-l+1) + l;
}
public void swap(int[] a,int i,int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
归并排序
描述:将待排序元素分成大小大致相同的两个子集合,分别对两个子集排序,最终将排序好的子集合并成所需要的排好序的集合。
时间复杂度:最好=平均=最坏=O(nlogn)
空间复杂度:O(n)
稳定性:可以实现
java实现
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args){
new MergeSort().MergeSort(new int[]{1,5,4,3,7,6,8,4});
}
/**
* 入口函数
* @param a
*/
public void MergeSort(int[] a){
int[] temp = new int[a.length];
mergeSort(a,0,a.length-1,temp);
System.out.println(Arrays.toString(a));
}
/**
* 主处理函数
* @param a 输入的数组
* @param start 开始index
* @param end 结束index
* @param temp 临时数组temp
*/
public void mergeSort(int[] a,int start,int end,int[] temp){
// 直到start >= end 递归结束
if(start < end){
// if(start < end - 1){
int mid = (end+start) >> 1;
// 递归排序左半部分
// mergeSort(a,start,mid-1,temp);
mergeSort(a,start,mid,temp);
//递归排序由半部分
// mergeSort(a,mid,end,temp);
mergeSort(a,mid+1,end,temp);
//拍好后把temp的结果更改放到原数组中
mergeArr(a,start,mid,end,temp);
}
}
/**
* 把temp数组中的结果放到a中
* @param a
* @param start
* @param mid
* @param end
* @param temp
*/
public void mergeArr(int[] a,int start,int mid,int end,int[] temp){
int i = start;
int j = mid+1;
int k = 0;
// 所有小的元素放到前面,大的元素放到后面
while(i <= mid && j <= end){
if(a[i] <= a[j]){
temp[k++] = a[i++];
}else{
temp[k++] = a[j++];
}
}
while(i <= mid){
temp[k++] = a[i++];
}
while(j <= end){
temp[k++] = a[j++];
}
for(i = 0;i < k;i++){
a[i+start] = temp[i];
}
}
}
堆排序
描述:建立在堆(二叉堆)的基础上,对堆进行排序。
二叉堆的特点:1)二叉堆的节点的值总是>=(或<=)任何一个子节点的值;2)每个节点的左右子树均为二叉堆。
时间复杂度:最好=最坏=平均=O(nlogn)
空间复杂度:O(1)
稳定性:不可实现
关于其复杂度讨论可参考blog:https://chihminh.github.io/2016/08/08/heap-sort/
java实现
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args){
HeapSort heapSort = new HeapSort();
int[] arr = new int[]{1,5,9,8,4,5,2};
heapSort.minHeapSort(arr);
System.out.println(Arrays.toString(arr));
}
public void minHeapSort(int[] arr){
// 1.构造大根堆
for(int i = arr.length-1;i >= 0;i--){
heapInsert(arr,i);
}
// 2.调整堆结构,并且把每次调整得到的最大的元素a[0]与末尾元素交换
int length = arr.length;
swap(arr,0,--length);
while (length > 0){
heapify(arr,0,--length);
swap(arr,0,length);
}
}
/**
* 建立大根堆
*/
public void heapInsert(int[] arr,int i){
int fIndex = (i-1) >> 1;
while (fIndex >= 0 && arr[i] > arr[fIndex]){
swap(arr,i,fIndex);
i = fIndex;
fIndex = (i-1) >> 1;
}
}
public void swap(int[] arr,int aIndex,int bIndex){
int temp = arr[aIndex];
arr[aIndex] = arr[bIndex];
arr[bIndex] = temp;
}
/**
* 就是把index结点调整(下沉)到合适位置的函数
*/
public void heapify(int[] arr,int index,int size){
// 找到index的左孩子坐标
int leftChild = index << 1+1;
while(leftChild < size){
// 把左右孩子比较,取最大值的那个坐标
int largest = leftChild+1 < size && arr[leftChild+1] > arr[leftChild] ? leftChild+1 : leftChild;
// 孩子比父要小,那就不用再往上走了
if(arr[largest] <= arr[index]){
break;
}
// 交换父与孩子位置
swap(arr,largest,index);
// 从父亲的新位置开始继续往上走
index = largest;
leftChild = leftChild << 1+1;
}
}
}
插入排序
描述:它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
时间复杂度:最好=O(n)(升序,只需要比较n-1次),最坏=O(n²)(降序,需要比较1/2n(n-1)),平均=O(n²)
空间复杂度:O(1)(in-place)
稳定性:可实现
java实现
import java.util.Arrays;
public class InsertSort {
public static void main(String[] args){
InsertSort insertSort = new InsertSort();
int[] arr = new int[]{1,3,9,8,4,5,2,7};
insertSort.insertSort(arr);
System.out.println(Arrays.toString(arr));
}
public void insertSort(int[] arr){
// index=0为最小的有序区
for(int i = 1;i < arr.length;i++){
// 把当前元素arr[i]插入到前面的有序区域
for(int j = i-1;j >= 0 && arr[j] > arr[j+1];j--){
// 如果后面的数比前面的数大,那就让它们交换位置,直到插入到正确的位置
swap(arr,j,j+1);
}
}
}
public void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
选择排序
描述:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
时间复杂度:最好=最坏=平均=O(n²)(因为它要跟后面的所有未排序元素比较)
空间复杂度:O(1)
稳定性:不能实现(数组)
java实现
import java.util.Arrays;
public class SelectSort {
public static void main(String[] args){
SelectSort selectSort = new SelectSort();
int[] arr = new int[]{1,3,9,8,4,5,2,7};
selectSort.selectSort(arr);
System.out.println(Arrays.toString(arr));
}
public void selectSort(int[] arr){
for(int i = 0;i < arr.length-1;i++){
int max = i;
// 从后面的未排序区中选取一个最大的值,然后放到当前位置
for(int j = i+1;j < arr.length;j++){
if(arr[j] > arr[i]){
max = j;
}
}
swap(arr,max,i);
}
}
public void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
计数排序
描述:计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于a的元素的个数。然后根据数组来将其中的元素排到正确的位置。
时间复杂度:平均=最坏=最好=O(n)
空间复杂度:O(n)
稳定性:可实现
计数排序为桶排序的落地实现之一。只不过桶里面装的是数字出现的次数。
java实现
import java.util.Arrays;
public class CountSort {
public static void main(String[] args){
CountSort countSort = new CountSort();
int[] arr = new int[]{1,2,3,9,8,4,5,2,7,8};
countSort.countSort(arr);
// Arrays.toString()会把重复元素删掉给,我才发现....
for(int i = 0;i < arr.length;i++){
System.out.print(arr[i] + " ");
}
}
public void countSort(int[] arr){
int max = Integer.MIN_VALUE;
for(int i = 0;i < arr.length;i++){
max = Math.max(arr[i],max);
}
// 创建一个能容纳0-max的桶
int[] bucket = new int[max+1];
// 纪录每一个arr[i]出现的次数
for(int i = 0;i < bucket.length;i++){
bucket[arr[i]]++;
}
int j = 0;
// 根据纪录的次数依次把数据倒出来
for(int i = 0;i < bucket.length;i++){
while(bucket[i] != 0){
arr[j++] = i;
bucket[i]--;
}
}
}
}
希尔排序
描述:直接插入算法的一种改进算法,每一轮按照事先决定的间隔进行插入排序,间隔会依次缩小,最后一次一定要是1。(1的时候就是直接插入排序,直接插入排序在数组基本有序的时候会非常高效)
时间复杂度:平均=O(nlog²n),最坏=O(n²)(步长的选择为关键)
空间复杂度:O(1)
稳定性:不可实现
java实现
public class ShellSort {
public static void main(String[] args){
int[] arr = new int[]{9,2,5,4,3,6,6,8,7,1};
new ShellSort().shellSort(arr);
System.out.println(Arrays.toString(arr));
}
public void shellSort(int[] arr){
// 此处步长的选择为n/2 ... n/4 ... 1
for(int gap = arr.length >> 1 ; gap > 0 ; gap = gap >> 1 ){
// 直接插入排序
for(int i = gap;i < arr.length;i++){
for(int j = i-gap;j >= 0 && arr[j] > arr[j+gap];j-=gap){
swap(arr,j,j+gap);
}
}
}
}
public void swap(int[] arr, int aIndex, int bIndex){
int temp = arr[aIndex];
arr[aIndex] = arr[bIndex];
arr[bIndex] = temp;
}
}