一文搞定十大经典排序算法,附代码详解

排序算法日常使用的最基本的算法之一,大体可以分为外部排序内部排序

  • 内部排序指的是待排序列完全存放在内存中所进行的排序过程,只适合较短的元素序列,不适合大数据排序。
  • 外部排序指的是大文件排序,待排序列存储在外存储器上,体积过大无法一次装入内存,需要在内存和外存储器之间进行多次数据交换。

一、内部排序的比较

排序算法 平均时间复杂度 最好情况 最坏情况 空间复杂度 稳定性
冒泡排序 O(n^2) O(n) O(n^2) O(1) 稳定
选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定
插入排序 O(n^2) O(n) O(n^2) O(1) 稳定
希尔排序 O(nlogn) O(n) O(n ^ s) 1<s<2 O(1) 不稳定
归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定
快速排序 O(nlogn) O(nlogn) O(n ^ 2) O(nlogn) 不稳定
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定
计数排序 O(n + k) O(n + k) O(n + k) O(k) 稳定
桶排序 O(n + k) O(n + k) O(n^2) O(n + k) 稳定
基数排序 O(n * k) O(n * k) O(n * k) O(n + k) 稳定

基本有序情况下,高效地算法是:直接插入排序,冒泡排序
无序情况下:高效的算法是:堆排序,快速排序,归并排序。

关于时间复杂度:

  1. 平方阶排序(On^2):插入、选择和冒泡排序
  2. 线性对数阶(O(nlog2n))排序:快速排序、堆排序、归并排序
  3. O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序
  4. 线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。

关于稳定性:

  1. 稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序
  2. 不稳定的排序算法:选择排序、快速排序、希尔排序、堆排序

1.1 冒泡排序

1.1.1 算法步骤:

依次比较相邻的两个数,把小数放在前面,大数放在后面。首先第一趟,比较第一个和第二个数,小数在前,大数在后,然后比较第二和第三两个数,小数在前,大数在后,如此依次比较,直至到数组末尾第一趟结束。针对除了最后一个元素的所有元素重复以上步骤,直至没有任何一对数字需要比较。
规律:N个数字排序,总共进行N-1趟排序,每趟将一个最大值移至末尾。

示例:以3,1,0,2,4,9,8,6,7,5为例:

  • 第1趟:[1, 0, 2, 3, 4, 8, 6, 7, 5, 9]
  • 第2趟:[0, 1, 2, 3, 4, 6, 7, 5, 8, 9]
  • 第3趟:[0, 1, 2, 3, 4, 6, 5, 7, 8, 9]
  • 第4趟:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 第5趟:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

结果 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

1.1.2 参考代码:

/**
 * 冒泡排序
 * */
public void bubbleSort(int[] nums) {
    int n = nums.length;
    for (int i = 0;i < n-1; i ++) {
        boolean flag = true;
        for (int j = 0;j < n - i-1; j++) {
            if (nums[j] > nums[j + 1]) {
                int tmp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = tmp;
                flag = false;
            }
        }
        if (flag) break;
    }
}

1.2 选择排序

1.2.1 算法步骤:

首先在待排序列中选择最小(大)元素,存放到待排序列的起始(结束)位置。然后从剩余待排序列中继续选择最小(大元素),存放到已排序列的末尾。重复以上步骤,直至整个序列排序完成。

示例:以3,1,0,2,4,9,8,6,7,5为例:

  • 第1趟:[0, 1, 3, 2, 4, 9, 8, 6, 7, 5]
  • 第2趟:[0, 1, 3, 2, 4, 9, 8, 6, 7, 5]
  • 第3趟:[0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
  • 第4趟:[0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
  • 第5趟:[0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
  • 第6趟:[0, 1, 2, 3, 4, 5, 8, 6, 7, 9]
  • 第7趟:[0, 1, 2, 3, 4, 5, 6, 8, 7, 9]
  • 第8趟:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 第9趟:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 第10趟:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

结果 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

1.2.2 参考代码:

/**
 * 简单选择排序
 * */
public void selectionSort(int[] nums) {
    for (int i = 0;i < nums.length; i++) {
        int small_index = i;
        for (int j = i + 1; j < nums.length; j++) {
            if (nums[j] < nums[small_index]) {
                small_index = j;
            }
        }
        if (i < small_index) {
            int tmp = nums[i];
            nums[i] = nums[small_index];
            nums[small_index] = tmp;
        }
        System.out.println("* 第" + (i+1) + "趟:" + Arrays.toString(nums));
    }
}

1.3 插入排序

1.3.1 算法步骤:

把初始n个待排序的元素,看成有序表+无序表。初始状态,有序表只有待排序序列的第一个元素,无序表中有n-1个元素。每次排序,从无序表中取出第i个原则,插入都有序表中对应合适的位置。重复这个步骤,直至所有元素都插入有序表,代表整个序列排序完成。

示例:以3,1,0,2,4,9,8,6,7,5为例:

  • 第1趟:[1, 3, 0, 2, 4, 9, 8, 6, 7, 5]
  • 第2趟:[0, 1, 3, 2, 4, 9, 8, 6, 7, 5]
  • 第3趟:[0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
  • 第4趟:[0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
  • 第5趟:[0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
  • 第6趟:[0, 1, 2, 3, 4, 8, 9, 6, 7, 5]
  • 第7趟:[0, 1, 2, 3, 4, 6, 8, 9, 7, 5]
  • 第8趟:[0, 1, 2, 3, 4, 6, 7, 8, 9, 5]
  • 第9趟:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

结果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

1.3.2 参考代码:

/**
 * 直接插入排序
 * */
public void insertSort(int[] nums) {
    if(nums==null||nums.length==0)
        return;
    for (int i = 1;i < nums.length; i++) {
        int tmp = nums[i] , j = i;
        if (nums[j - 1] > tmp) {
            while (j >= 1 && nums[j - 1] > tmp) {
                nums[j] = nums[j - 1];
                j--;
            }
        }
        nums[j] = tmp;
        System.out.println("* 第" + (i) + "趟:" + Arrays.toString(nums));
    }
}

1.4 希尔排序

1.4.1 算法步骤:

首先选择一个增量序列 t1 , t2, … , tk , 其中ti > tj , tk = 1; 然后按照增量分别将待排数组分成多个子序列,使每个子序列元素个数相对较少,然后对各个子序列分别进行直接插入排序,代整个排序序列基本有序后,对所有元组再进行一次直接插入排序。

示例:以3,1,0,2,4,9,8,6,7,5为例:

  • 增量为5的时:[3, 1, 0, 2, 4, 9, 8, 6, 7, 5]
  • 增量为2的时:[0, 1, 3, 2, 4, 5, 7, 6, 8, 9]
  • 增量为1的时:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

1.4.2 参考代码:

/**
 * 希尔排序
 * */
public static void hillSort(int[] nums){
    if(nums == null || nums.length <= 1){
        return;
    }
    int i,j;
    int increment;
    int temp;
    for(increment=nums.length/2;increment>0;increment/=2) {
        for(i=increment;i<nums.length;i++) {
            temp=nums[i];
            for(j=i-increment;j>=0;j-=increment) {
                if(temp<nums[j]) {
                    nums[j+increment]=nums[j];
                }else
                    break;
            }
            nums[j+increment]=temp;
        }
    }
}

1.5 归并排序

1.5.1 算法步骤

归并排序是利用归并的思想实现的排序方法,该方法采用的经典的分治策略(把问题分解成若干个小问题然后递归求解)。首先我们需要申请空间,使其大小为两个已排序序列之和,该空间用来存放合并后的序列。然后我们设定两个指针,最初位置分别是两个已经排序序列的起始位置。比较两个比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;重复这个步骤 直到某一指针达到序列尾;然后将将另一序列剩下的所有元素直接复制到合并序列尾。

示例:以3,1,0,2,4,9,8,6,7,5为例:

  • [3, 1, 0, 2, 4, 9, 8, 6, 7, 5]
  • [3, 1, 0, 2, 4, 9, 8, 6, 7, 5]
  • [3, 1, 0, 2, 4, 9, 8, 6, 7, 5]
  • [3, 1, 0, 2, 4, 9, 8, 6, 7, 5]
  • [0, 1, 3, 2, 4, 9, 8, 6, 7, 5]
  • [0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
  • [0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
  • [0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
  • [0, 1, 2, 3, 4, 6, 8, 9, 7, 5]

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

1.5.2 参考代码

/**
 * 归并排序
 * */
public static void mergeSort(int []arr){
    int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
    sort(arr,0,arr.length-1,temp);
}
private static void merge(int[] arr,int left,int mid,int right,int[] temp){
    int i = left;//左序列指针
    int j = mid+1;//右序列指针
    int t = 0;//临时数组指针
    while (i<=mid && j<=right){
        if(arr[i]<=arr[j]){
            temp[t++] = arr[i++];
        }else {
            temp[t++] = arr[j++];
        }
    }
    while(i<=mid){//将左边剩余元素填充进temp中
        temp[t++] = arr[i++];
    }
    while(j<=right){//将右序列剩余元素填充进temp中
        temp[t++] = arr[j++];
    }
    t = 0;//因为需要拷贝所以要把临时数组的指针置0(即指向第一个元素)
    //将temp中的元素全部拷贝到原数组中
    while(left <= right){
        arr[left++] = temp[t++];
    }
}
private static void sort(int[] arr,int left,int right,int []temp){
    if(left<right){
        int mid = (left+right)/2;
        sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
        sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
        merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
    }
}

1.6 快速排序

1.6.1 算法步骤

每次挑选出一个元素为基准,设两个指针,一个left一个right,分别指向序列的头和尾。用两个指针扫描数组,把所有比基准大的,都摆放在基准的后面,所有比基准小的,都摆放在基准的后面。这一次扫描之后,基准就位于它的最终位置。然后分别在基准左侧和基准右侧再次执行这个操作。知道整个序列排列完成。

示例:以3,1,0,2,4,9,8,6,7,5为例:

  • [3, 1, 0, 2, 4, 9, 8, 6, 7, 5]
  • [2, 1, 0, 3, 4, 9, 8, 6, 7, 5]
  • [0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
  • [0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
  • [0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
  • [0, 1, 2, 3, 4, 5, 8, 6, 7, 9]
  • [0, 1, 2, 3, 4, 5, 8, 6, 7, 9]
  • [0, 1, 2, 3, 4, 5, 7, 6, 8, 9]

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

1.6.2 参考代码

/**
 * 快速排序
 * */
private void quickSort(int nums[], int left , int right) {
    if (left < right) {
        int i = left , j = right,temp = nums[left];
        System.out.println(" * "+Arrays.toString(nums));
        while (i < j) {
            while (nums[j] > temp && i < j) j--;
            if (i < j){
                nums[i] = nums[j]; i++;
            }
            while (nums[i] < temp && i < j) i++;
            if (i < j) {
                nums[j] = nums[i]; j--;
            }
        }
        nums[i] = temp;
        quickSort(nums , left , i -1);
        quickSort(nums , i+1 , right);
    }
}

1.7 堆排序(重要!!!)

1.7.1 算法步骤

首先,将待排序列构造成一个大顶堆。我们用数组+限定规则构造大顶堆,下标为i的数组元素是下标为2*i+12*i+2元素的父节点。且i的数组元素的父节点下标为(i-1)/2

此时堆顶元素就是序列的最大值。然后我们把最后一个位置的数和堆顶位置的数做交换,把最大值放到数组最后的位置。此时让堆大小减1,最大值就被固定在了末尾。
把除最后一个元素的序列重新调整成大顶堆,然后依次执行上面的步骤,输出倒数第二大的数。依次类推,把所有元素都输出之后,整个序列就变成了有序序列。

示例:以3,1,0,2,4,9,8,6,7,5为例:

  • [2, 7, 8, 6, 5, 0, 4, 1, 3, 9]
  • [3, 7, 4, 6, 5, 0, 2, 1, 8, 9]
  • [1, 6, 4, 3, 5, 0, 2, 7, 8, 9]
  • [2, 5, 4, 3, 1, 0, 6, 7, 8, 9]
  • [0, 3, 4, 2, 1, 5, 6, 7, 8, 9]
  • [1, 3, 0, 2, 4, 5, 6, 7, 8, 9]
  • [1, 2, 0, 3, 4, 5, 6, 7, 8, 9]
  • [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

1.7.2 参考代码

/**
 * 堆排序
 * */
public void heapSort(int [] arr) {
    if(arr==null||arr.length<2) {
        return;
    }
    for(int i=0;i<arr.length;i++) {
        heapInsert(arr,i);//依次将数组中i位置上的数加进来,让它0~i之间的数形成大顶堆
    }
    int heapSize=arr.length;//堆大小heapSize一开始等于数组的全部
    swap(arr,0,--heapSize);//最后一个位置上的数与第一个位置上的数(堆顶元素)交换,堆大小减1,即最后一个位置上的数不动了
    while(heapSize>0) {
        heapify(arr,0,heapSize);//从0位置开始,将当前形成的堆继续调整为一个大顶堆
        swap(arr,0,--heapSize);//最后一个位置上的数与第一个位置上的数(堆顶元素)交换,堆大小减1,即最后一个位置上的数不动了
    }
}

//建立大顶堆的过程
public void heapInsert(int[] arr,int index) {
    while(arr[index] > arr[(index-1)/2]) {//当前index位置上的数若比其父结点上的数大,则交换他俩的位置
        swap(arr,index,(index-1)/2);
        index=(index-1)/2;//然后index来到了它的父节点位置,继续上面的while
    }
}

//若有一个节点的值变小了,则需要往下沉(与其左右孩子中较大的数进行交换位置的)的操作
public void heapify(int[] arr,int index,int heapSize) {
    int left=index*2+1;//左孩子下标
    while(left<heapSize) {//未越界,即该节点并非叶子节点,存在左孩子
        //该节点有右孩子,让largest作为左右孩子较大值的下标
        int largest=(left+1 < heapSize) && arr[left+1] > arr[left] ? left+1 : left;
        //让largest成为该节点与该节点左右孩子中较大值的下标
        largest=arr[largest] > arr[index] ? largest : index;
        if(largest==index) {
            break;
        }
        swap(arr,largest,index);//largest!=index
        index=largest;//该节点下标变成了较大孩子的下标
        left=index*2+1;//继续往下走,重复上面的while
    }
}

public void swap(int[] arr,int i,int j) {
    int temp=arr[i];
    arr[i]=arr[j];
    arr[j]=temp;
}


代码已上传至本人Github仓库 , 欢迎大家star,follow

发布了35 篇原创文章 · 获赞 87 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_36008321/article/details/105216817