十大经典排序算法
文章目录
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。
O(1) < O(logn) < O(n) < O(nlongn) < O(n^2) < O(2^n) < O(n!) < O(n^n)
冒泡排序
最简单的排序,类似于单词书的abandon!! 必须熟练写出! 时间复杂度: O(n²)
1. 算法步骤
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
2.算法实现
public class MaoPao {
/**
* 主函数
*/
public static void main(String[] args) {
Integer[] array={5,6,7,8,9,1,2,3};
System.out.println("==========冒泡=======");
for (int i : maopao(array)) {
System.out.println(i);
}
}
/**
* 冒泡排序方法
*/
static Integer[] maopao(Integer[] arr){
//外层循环控制比较轮数=============
for (int i = 0; i < arr.length; i++) {
//设计一个标记,如果是true,则表示此次循环没有进行交换,也就是排序已经有序
boolean flag = true;
//内层循环控制相邻两个的比较-----------
for (int j = 0; j < arr.length-1-i; j++) {
if(arr[j] > arr[j+1]){
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
//只要交换就设置flag为false
flag = false;
}
}
//标志位true证明排序完成了。-----------
if(flag){break;}
}
//外层循环结束===================
return arr;
}
}
选择排序
选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。
1. 算法步骤
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。
2.算法实现
package com.sort;
import java.util.Arrays;
/**
* @author 结构化思维wz
* 选择排序
*/
public class XuanZe {
/**
*主函数
*/
public static void main(String[] args) {
int[] array={5,6,7,8,9,1,2,3};
System.out.println("==========选择排序=======");
System.out.println(Arrays.toString(sort(array)));
}
/*选择排序*/
public static int[] sort(int[] arr) {
// 总共要经过 N-1 轮比较,相当于给arr[i]位置找最合适的值。
for (int i = 0; i < arr.length - 1; i++) {
//先把最小下标给min,之后比较如果不是最小(大)的就换位
int min = i;
// 每轮需要比较的次数 N-i
for (int j = i + 1; j < arr.length; j++) { //从i的后一位开始循环比较
if (arr[j] < arr[min]) {
// 记录目前能找到的最小值元素的下标
min = j;
}
}
// 将找到的最小值和i位置所在的值进行交换
if (i != min) {
int tmp = arr[i];
arr[i] = arr[min];
arr[min] = tmp;
}
}
return arr;
}
}
插入排序
插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。O(n²) 的时间复杂度。
1. 算法步骤
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
2. 算法实现
public class ChaRu {
public static void main(String[] args) {
int[] array={5,6,7,8,9,1,2,3};
System.out.println("==========插入排序=======");
System.out.println(Arrays.toString(sort(array)));
}
public static int[] sort(int[] arr){
// 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的
for (int i = 1; i < arr.length; i++) {
// 记录要插入的数据
int tmp = arr[i];
// 从已经排序的序列最右边的开始比较,找到比其小的数
int j = i;
while (j > 0 && tmp < arr[j - 1]) {
arr[j] = arr[j - 1];
j--;
}
// 存在比其小的数,插入
if (j != i) {
arr[j] = tmp;
}
}
return arr;
}
}
希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
动图演示:
实例演示:
用一个例子来展示一个完整的希尔排序过程,首先数据的初始状态如图所示,这里为了更好地体现希尔排序的优点,我特地把值较大的元素放到了靠左的位置,把值较小的元素放到了靠右的位置
该数组长度为8,因此我们设置初始的增量为 8 / 2 = 4,那么该数组的分组情况如下图所示:
图中颜色相同的元素为一组,每组内的各个元素间隔都为4,现在对每个组内进行从小到大排序,排序结果如下图所示:
此时我们将增量缩小一半,即 4 / 2 = 2,同样的,现在将所有元素重新组合,把所有间隔为2的元素视作一组,分组结果如下图所示:
图中颜色相同的元素为一组,每组内的各个元素间隔都为2,现在对每个组内进行从小到大排序,排序结果如下图所示:
我们继续将增量缩小一半,即 2 / 2 = 1,同样的,现在将所有元素重新组合,把所有间隔为1的元素视作一组,此时所有的元素都为同一组了,就相当于对所有的数据进行普通的插入排序,我们可以看到,对比最开始的数据,总得来说,小的值都比较靠左了,大的值也都比较靠右了,这样排序起来效率就很高了。结果如下图所示:
1.算法步骤
选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
按增量序列个数 k,对序列进行 k 趟排序;
每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
2.算法实现
package com.sort;
import java.util.Arrays;
/**
* @author 结构化思维wz
*/
public class XiEr {
public static void main(String[] args) {
int[] array={5,6,7,8,9,1,2,3};
System.out.println("==========希尔排序=======");
System.out.println(Arrays.toString(shellSort(array)));
}
public static int[] shellSort(int[] arr){
int length = arr.length;
int temp;
for (int step = length/2; step >= 1; step=step/2){ //控制分组
/*组内遍历排序*/
for (int i = step; i <length ; i++) { //相当于中间位置的元素,从此往后遍历与组内靠前的比较
temp =arr[i]; //把当前i下标的值给 temp;
int j = i - step; //得到组中前一个的元素下标
while (j>=0 && arr[j]>temp){ //交换条件
arr[j+step] = arr[j]; //靠后的值变为前一个较小的。
j = j - step; //前面的元素继续往前移动
}
arr[j+step] = temp;
}
}
return arr;
}
}
归并排序
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
- 尽可能的将一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是1为止。
- 将相邻的两个子组进行合并成一个有序的大组;
- 不断的重复步骤2,直到最终只有一个组为止。
1.算法步骤
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
- 重复步骤 3 直到某一指针达到序列尾;
- 将另一序列剩下的所有元素直接复制到合并序列尾。
2.算法实现
package com.sort;
import java.util.Arrays;
public class GuiBing {
private static int[] arr;
public static void sort(int[] array){
arr = new int[array.length];
sort(array,0,array.length-1);
}
private static void sort(int[] arr,int lo, int hi){
if(lo>=hi) {
return;
}
int mid = lo + (hi - lo)/2;
sort(arr,lo,mid);
sort(arr,mid+1,hi);
//合并两个数组
merge(arr,lo,mid,hi);
}
private static void merge(int[] a, int lo,int mid ,int hi ){
//指向辅助数组的指针。
int i = lo;
//定义第一个数组的指针,初始指向第一个元素;
int p1 = lo;
//定义第二个数组的指针,初始指向第一个元素
int p2 = mid+1;
//比较两个数组指针指向的元素的大小,哪个小,就放入arr数组
while(p1<=mid&&p2<=hi){
if(a[p1]<a[p2]){
arr[i++]=a[p1++];
}else{
arr[i++]=a[p2++];
}
}
//如果数组二遍历完,数组一没完
while(p1<=mid){
arr[i++] = a[p1++];
}
//如果数组一遍历完,数组二没完
while(p2<=hi){
arr[i++] = a[p2++];
}
//拷贝数组
for (int i1 = lo; i1 <= hi; i1++) {
a[i1] = arr[i1];
}
}
public static void main(String[] args) {
int[] array = {8, 4, 5, 7, 1, 3, 6, 2};
sort(array);
System.out.println(Arrays.toString(array));
}
}
快速排序
快速排序是对冒泡排序的一种改进。
它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
1.算法步骤
分治思想
- 以数组第一个数为分界值,通过该分界值将数组分成左右两部分;
- 将大于或等于分界值的数据放到到数组右边,小于分界值的数据放到数组的左边。此时左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值;
- 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
- 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左侧和右侧两个部分的数据排完序后,整个数组的排序也就完成了。
切分原理:
把一个数组切分成两个子数组的基本思想:
- 找一个基准值,用两个指针分别指向数组的头部和尾部;
- 先从尾部向头部开始搜索一个比基准值小的元素,搜索到即停止,并记录指针的位置;
- 再从头部向尾部开始搜索一个比基准值大的元素,搜索到即停止,并记录指针的位置;
- 交换当前左边指针位置和右边指针位置的元素;
- 重复2,3,4步骤,直到左边指针的值大于右边指针的值停止。
2.算法实现
package com.sort;
import java.util.Arrays;
/**
* @author 结构化思维wz
* 快速排序
*/
public class KuaiSort {
public static void main(String[] args) {
int[] array={5,6,7,8,9,1,2,3};
System.out.println("==========插入排序=======");
sort(array);
System.out.println(Arrays.toString(array));
}
public static void sort(int[] arr){
int l = 0;
int r = arr.length-1;
sort(arr,l,r);
}
public static void sort(int[] arr,int l, int r){
if (l >= r){
return;
}
//一趟快排,并返回交换后基准点的下标
int pivot = pivot(arr,l,r);
//递归排序基准点左边的数组
sort(arr,l,pivot-1);
//递归排序基准点右边的
sort(arr,pivot+1,r);
}
/**
* 让左右指针同时向中间移动,左指针碰到第一个比p大的,右指针碰到第一个比p小的,停止并交换。
* 重复此动作,直到左右指针相遇,相遇后交换相遇的值和基准点,此时基准点已经归位
*/
public static int pivot(int[] arr, int l ,int r){
//p 为基准点,待排序的第一个数
int p = arr[l];
while(true){
//右指针找比p小的值(p小就一直走,直到碰到大的)
while(l < r && p < arr[r]){--r;}
//左指针找比p大的值
while(l < r && p > arr[l]){++l;}
//指针相遇时,交换基准值位置,并返回新的基准点(分割点)
if(r == l){
arr[l] = arr[r];
arr[r] = p;
return l;
}else { //找到值后,左右指针值交换。
int temp = arr[r];
arr[r] = arr[l];
arr[l] = temp;
}
}
}
}
堆排序
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。
堆排序的平均时间复杂度为 Ο(nlogn)。
堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序,下面先来看看什么是大根堆和小根堆
性质:每个结点的值都大于其左孩子和右孩子结点的值,称之为大根堆;每个结点的值都小于其左孩子和右孩子结点的值,称之为小根堆。如下图
我们对上面的图中每个数都进行了标记,上面的结构映射成数组就变成了下面这个样子
还有一个基本概念:查找数组中某个数的父结点和左右孩子结点,比如已知索引为i的数,那么
1.父结点索引:(i-1)/2(这里计算机中的除以2,省略掉小数)
2.左孩子索引:2*i+1
3.右孩子索引:2*i+2
所以上面两个数组可以脑补成堆结构,因为他们满足堆的定义性质:
大根堆:arr(i)>arr(2i+1) && arr(i)>arr(2i+2)
小根堆:arr(i)<arr(2i+1) && arr(i)<arr(2i+2)
动画演示:
1.算法步骤
1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
2.算法实现
1.无序的怎么构建成一个堆?
2.输出堆顶后如何构建新的堆?
未完待续… 1024 快乐