常见的排序算法
Posted 自由乐土
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常见的排序算法相关的知识,希望对你有一定的参考价值。
一.排序与稳定性的概念
1.排序:
使一串记录按照其中的某个或某些关键字大小按照递增或递减的排列起来的操作。平时提到的排序如没有特殊说明都是指升序。
2. 稳定性:
对于两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算法。
(如果一个排序是稳定的排序,那么它也可以被实现为一个不稳定的排序。 而如果一个排序本身是不稳地的排序,那么就不能实现为一个稳定的排序。)
二.排序算法种类
三.逐个介绍以上的排序算法
1.插入排序
(1)直接插入排序
直接插入排序将待排序数组分为有序区间和无序区间两个部分。每次选择无序区间的第一个元素在有序区间内选择合适的位置插入。
- 常见的做法是将前面的部分作为有序区间,每次将与此有序区间相邻的无序区间的第一个数取出插入前面有序区间内。
实现如下:
//直接插入排序
public static void insertSort(int[] array)
for (int i = 1; i < array.length; i++)
int tmp = array[i];
int j = i - 1;
for (;j >= 0;j--)
//如果下面取等号则是不稳定排序
if(array[j] > tmp)
array[j+1] = array[j];
else
break;
array[j+1] = tmp;
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
特点:直接插入排序,初始数据越接近有序,时间效率越高
(2)希尔排序
希尔排序又称缩小增量法,先选定一个数,把待排序文件中的所有记录分成个组,所有的距离为gap的记录分在同一组内,并对每一组内数据进行排序,当gap=1时,所有数据在同一组内排好序。
注意:
- ①希尔排序是对直接插入排序的优化
- ②当gap > 1时都是预排序,目的时让数组更接近有序,当gap = 1 时,数据已经接近有序的了,这样就会很快。整体而言,可以达到优化的效果。
- ③一般gap的值取5,3,1,保证最后一个数是1.
实现:
public static void shellSort(int[] array)
int[] arr = 5,3,1;
for (int i = 0; i < arr.length; i++)
insertSortGap(arr[i],array);
public static void insertSortGap(int gap,int[] array)
for (int i = 1; i < array.length; i++)
int tmp = array[i];
int j = i - gap;
for(; j >= 0 ;j -= gap)
if(array[j] > tmp)
array[j+gap] = array[j];
else
break;
array[j+gap] = tmp;
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
2.选择排序
(1)选择排序
每一次从无序区间内选择最小的一个元素存放在无序区间的最前,直到全部待排序数据元素排完。
实现如下:
public static void selectSort(int[] array)
for (int i = 0; i < array.length -1; i++)
for (int j = i+1; j < array.length; j++)
if(array[i] > array[j])
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
(2)堆排序
基本原理也是选择排序。只是不在使用遍历的方式查找无序区间的最大数,而是通过堆来选择无序区间的最大的数。
注意:
排升序要建大堆,排降序要建小堆。 以升序为例,每次操作将堆顶元素与数组最后元素进行交换,然后向下调整时传入的参数长度范围递减。
实现如下:
public static void heapSort(int[] array)
//先创建一个大根堆
for (int i = (array.length-1-1)/2; i >= 0; i--)
adjustDown(i,array.length,array);
//将最大值放到最后一个元素的位置
for (int i = array.length-1; i > 0; i--)
int tmp = array[0];
array[0] = array[i];
array[i] = tmp;
adjustDown(0,i,array);
//维护大根堆的性质
public static void adjustDown(int i,int len,int[] array)
int parent = i;
int child = parent*2 + 1;
while(child < len)
if(child+1 < len && array[child] < array[child+1])
child++;
if(array[parent] < array[child])
int tmp = array[parent];
array[parent] = array[child];
array[child] = tmp;
parent = child;
child = 2 * child + 1;
else
break;
时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定
3.交换排序
(1)冒泡排序
在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个的过程知道数组有序。
实现:
public static void bubbleSort(int[] array)
//i代表要冒泡的趟数
for (int i = 0; i < array.length-1; i++)
boolean sorted = true;
//j代表每一趟要进行冒泡的数字下标
for (int j = 0; j < array.length - i - 1; j++)
//相等时不交换,为保持稳定性。
if(array[j] > array[j+1])
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
sorted = false;
if(sorted)
break;
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
(2)快速排序(重点)
原理: ①从待排序区间选择一个数作为基准。
②一个partition方法:遍历整个待排序区间,将比基准值小的放到基准值左边,将比基准值大的放到基准值右边。
③采用分治思想,对左右两个区间按照同样的方式处理,知道小区间的长度为1代表已有序,或等于0代表没数据。
①递归实现:
public static void swap(int[] array,int i,int j)
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
public static int partition(int[] array,int start,int end)
int i = start;
int j = end;
int ret = array[start];
while(i < j)
while(i < j && array[j] >= ret)
j--;
while(i < j && array[i] <= ret)
i++;
swap(array,i,j);
swap(array,i,start);
return i;
public static void quickSortIntenel(int[] array,int start,int end)
if(start >= end) return;
int pivot = partition(array,start,end);
quickSortIntenel(array,start,pivot);
quickSortIntenel(array,pivot+1,end);
public static void quickSort(int[] array)
quickSortIntenel(array,0,array.length-1);
②循环形式
//快速排序(非递归形式) 使用栈解决
public static void quickSort2(int[] array)
Stack<Integer> stack = new Stack<>();
stack.push(array.length-1);
stack.push(0);
while(!stack.isEmpty())
int left = stack.pop();
int right = stack.pop();
int pivot = partition(array,left,right);
if(pivot > left+1)
stack.push(pivot-1);
stack.push(left);
if(pivot < right-1)
stack.push(right);
stack.push(pivot+1);
时间复杂度:
最好:O(nlogn) 平均:O(nlog) 最坏:O(n^2)
空间复杂度:O(logn) 最坏O(n)
这有一位大佬写的关于快速排序优化的博文,推荐一下:
快速排序的优化
4.归并排序
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
递归思路实现:
public static void mergeSortIntenel(int[] array,int low,int high)
if(low >= high)
return;
int mid = (low+high)/2;
//左边和右边分别进行拆分
mergeSortIntenel(array,low,mid);
mergeSortIntenel(array,mid+1,high);
//拆分到每组只有一个元素后开始合并
merge(array,low,mid,high);
//合并函数,合并左右两组有序数组
public static void merge(int[] array,int start,int mid,int end)
int s1 = start;
int s2 = mid+1;
int[] ret = new int[end - start+1];
int k = 0;
while(s1 <= mid && s2 <= end)
if(array[s1] <= array[s2])
ret[k++] = array[s1++];
else
ret[k++] = array[s2++];
//将左右两组数剩余的部分给ret
while(s1 <= mid)
ret[k++] = array[s1++];
while(s2 <= end)
ret[k++] = array[s2++];
//将ret内元素给array,注意array的下标是i+start
for (int i = 0; i < ret.length; i++)
array[i+start] = ret[i];
public static void mergeSort(int[] array)
mergeSortIntenel(array,0,array.length-1);
时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:稳定
非递归实现:
public static void merSort2Intenel(int[] array,int gap)
//将数据分为两段有序的数据进行合并,每次gap都为原来的两倍。
int s1 = 0;
int e1 = s1 + gap - 1;
int s2 = e1 + 1;
int e2 = s2+gap-1 < array.length ? s2+gap-1:array.length-1;
int[] tmp = new int[array.length];
int k = 0;
//前提条件,s2 < array.length,保证两段都有数据才能进入循环
while(s2 < array.length)
while(s1 <= e1 && s2 <= e2)
if(array[s1] <= array[s2])
tmp[k++] = array[s1++];
else
tmp[k++] = array[s2++];
while(s1 <= e1)
tmp[k++] = array[s1++];
while(s2 <= e2)
tmp[k++] = array[s2++];
s1 = e2+1; //s1不可能越界
e1 = s1+gap-1; //e1可能越界
s2 = e1+1; //s2可能越界
e2 = s2+gap-1 < array.length ? s2+gap-1:array.length-1;
//退出循环后,后一段已经结束,前一段可能还有数据
while(s1 < array.length)
tmp[k++] = array[s1++];
for (int i = 0; i < tmp.length; i++)
array[i] = tmp[i];
//非递归实现
public static void mergeSort2(int[] array)
for (int i = 1; i < array.length; i*=2)
merSort2Intenel(array,i);
总结:
以上是关于常见的排序算法的主要内容,如果未能解决你的问题,请参考以下文章