八大排序[超级详细](动图+代码优化)这一篇文章就够了(中)

简介: 八大排序[超级详细](动图+代码优化)这一篇文章就够了(中)

一、直接选择排序🍭

1、基本思想🍉

第一次从R[0]~R[n-1]中选取最小值,与R[0]交换,第二次从R[1]~R[n-1]中选取最小值,与R[1]交换,....,第i次从R[i-1]~R[n-1]中选取最小值,与R[i-1]交换,.....,第n-1次从R[n-2]~R[n-1]中选取最小值,与R[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。

 可以通过看下面GIF来简单了解直接选择排序的过程:

网络异常,图片无法展示
|

2、代码实现🍉

import java.util.Arrays;
public class Main {
    public static void selectSort(int[] arr){
        //防止arr数组为空或者arr只有一个数,不用进行排序
        if (arr == null || arr.length < 2) {
            return;
        }
        /*每次要进行比较的两个数,的前面那个数的下标*/
        for (int i = 0; i < arr.length - 1; i++) {
            //min变量保存该趟比较过程中,最小元素所对应的索引,
            //先假设前面的元素为最小元素
            int min = i;
            /*每趟比较,将前面的元素与其后的元素逐个比较*/
            for (int j = i + 1; j < arr.length; j++) {
                //如果后面的元素小,将后面元素的索引极为最小值的索引
                if(arr[j] < arr[min]) {
                    min = j;
                }
            }
            //然后交换此次查找到的最小值和原始的最小值
            swap(arr, i, min);
        }
    }
    public static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
    public static void main(String[] args) {
        int[] arr={9, 8, 6, 29, 10, 7, 37, 48};
        selectSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

3、代码优化🍉

选择排序的优化引入的二分的思想,前面是找到最小的值往前面放,现在在一趟循环中同时找到最大最小值,将最小值放入头,最大值放入尾。

import java.util.Arrays;
public class Main {
    public static void SelectSort(int[] arr){
        // find the max and min num in an iteration
        int n = arr.length;
        for(int i = 0;i<n;i++){
            int min = i;
            int max = n-1;
            for(int j = i;j<n;j++){
                if (arr[j]<arr[min]){
                    min = j;
                }
                if (arr[j]>arr[max]){
                    max = j;
                }
            }
            swap(arr,i,min);
            // 防止i的位置为最大值,然后被最小值换了,所以检查一下
            if (max == i){
                max = min;
            }
            swap(arr,n-1,max);
            n = n-1;
        }
    }
    public static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
    public static void main(String[] args) {
        int[] arr={9, 8, 6, 29, 10, 7, 37, 48};
        SelectSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

4、优缺点🍉

优点:

1. 实现简单,易于理解和编写。

2. 内存占用小,不需要额外的存储空间。

3. 适用于小规模的数据排序。

缺点:

1. 时间复杂度较高,平均时间复杂度为O(n^2),在数据规模较大时效率低下。

2. 排序过程中不稳定,可能导致相同元素的相对位置发生变化。

3. 交换次数较多,对于大量的数据交换次数会增加,导致效率下降。

4. 不适用于链式结构,因为链式结构不支持随机访问。

5、算法分析🍉

1、时间复杂度

在插入排序中,当待排序序列是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较n- 1次,时间复杂度为O(n)。最坏的情况是待排序数组是逆序的,此时需要比较次数最多,总次数记为:1+2+3+…+N-1,所以,插入排序最坏情况下的时间复杂度为O(n^2)。平均来说,array[1…j-1]中的一半元素小于array[j],一半元素大于array[j]。插入排序在平均情况运行时间与最坏情况运行时间一样,是O(n^2)。


2、空间复杂度


不需要额外的空间进行排序。因此空间复杂度为O(1)。


3、稳定性:


不稳定,因为排序序列为(5,5,1),第一趟排序之后(1,5,5),这可以很清楚的看到两个5的相对位置发生了变化。

6、适应场景🍉

1. 数据规模较小,不超过几千个元素。

2. 数据分布比较均匀,不存在大量相同的元素。

3. 内存空间有限,不能使用其他高级排序算法。

4. 数据元素之间的比较操作比较简单,可以快速进行比较。

5. 对于稳定性要求不高的场合,不需要保持相同元素的相对位置不变。

二、堆排序 🍭

1、堆🍉

堆一般指的是二叉堆通常是一个可以被看做一棵完全二叉树的数组对象。

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。堆总是满足下列性质

  • 堆中某个结点的值总是不大于或不小于其父结点的值;(即k ᵢ <=k₂ᵢ 且kᵢ<=k₂ᵢ₊₁为小根堆,kᵢ>k₂ᵢ且kᵢ>=k₂ᵢ₊₁为大根堆)
  • 堆总是一棵完全二叉树。

在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话堆中的最小值位于根节点)。堆中定义以下几种操作:

  • 最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
  • 创建最大堆(Build Max Heap):将堆中的所有数据重新排序
  • 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算

可以看下面小根堆建立的GIF简单了解堆排序的的过程


网络异常,图片无法展示
|


2、基本思想🍉

堆排序是简单选择排序的改进,利用二叉树代替简单选择方法来找最大或者最小值,属于一种树形选择排序方法。

利用大根堆(小根堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大值(最小值)变得简单。

  1. 将待排序的序列构造成一个大根堆,此时序列的最大值为根节点
  1. 依次将根节点与待排序序列的最后一个元素交换
  2. 再维护从根节点到该元素的前一个节点为大根堆,如此往复,最终得到一个递增序列

3、堆的存储方式🍉

从堆的概念可知,堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储。


image.png

注意:对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节

点,就会导致空间利用率比较低

将元素存储到数组中后,可以根据二叉树章节的性质5对树进行还原。假设i为节点在数组中的下标,则有:


如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2

如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子

如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子

4、堆的shift up和shift down🍉

Ⅰ、shift up:向一个最大堆中添加元素🍓

如下面向堆中加入一个新元素52,这时需要重新调整为大根堆,52比它的父节点28大,需要交换,然后和28的父节点(41)比较,还是更大也需要交换



image.png


image.png

Ⅱ、shift down:从一个最大堆中取出一个元素只能取出最大优先级的元素,也就是根节点🍓

如果上面那个堆的62变成了12,这时候为了维持大根堆,只能将12这个节点进行shift down操作以维持大根堆


65bde4e239ac453597ee460020ee43e6.png


image.png



5、堆的插入与删除🍉

Ⅰ、插入🍓

堆的插入总共需要两个步骤:

  1. 先将元素放入到底层空间中(注意:空间不够时需要扩容)
  2. 将最后新插入的节点向上调整,直到满足堆的性质
  3. 其实这就是shift up操作


image.png

Ⅱ、删除🍓

注意:堆的删除一定删除的是堆顶元素(因为二叉堆不支持查找元素位置,因此删除一个你完全不知道内容的元素毫无意义)。具体如下:

1. 将堆顶元素对堆中最后一个元素交换

2. 将堆中有效数据个数减少一个

3. 对堆顶元素进行向下调整(小根堆为例)


image.png

调整之后






image.png

6、建堆的时间复杂度🍉

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):


image.png


第1层,2^0个节点,需要向下移动h-1层

第2层,2^1个节点,需要向下移动h-2层

第3层,2^2个节点,需要向下移动h-3层

第4层,2^3个节点,需要向下移动h-4层

........................

第h-1层,h-2个节点,需要向下移动1层



网络异常,图片无法展示
|


7、代码(建大根堆小根堆,入队出队)🍉

import java.util.Arrays;
public class Main {
    public static int[] elem;
    public static int usedSize;//当前堆当中的有效的元素的数据个数
    /**
     * 建堆:【大根堆】
     * 时间复杂度:O(n)
     */
    public static void createHeap() {
        for (int parent = (usedSize-1-1) / 2; parent >= 0 ; parent--) {
            shiftDown(parent,usedSize);
        }
    }
    /**
     * 实现 向下调整
     * @param parent 每棵子树的根节点的下标
     * @param len 每棵子树的结束位置
     */
    private static void shiftDown(int parent, int len) {
        int child = 2 * parent + 1;
        //最起码是有左孩子
        while (child < len) {
            //判断 左孩子 和 右孩子  谁最大,前提是  必须有  右孩子
            if(child+1 < len && elem[child] < elem[child+1]) {
                child++;//此时 保存了最大值的下标
            }
            if(elem[child] > elem[parent]) {
                swap(elem,child,parent);
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }
    private static void swap(int[] array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
    /**
     * 入队
     * @param x
     */
    public void offer(int x) {
        if(isFull()) {
            elem = Arrays.copyOf(elem,elem.length*2);
        }
        this.elem[usedSize] = x;
        usedSize++;
        shiftUp(usedSize-1);
    }
    /**
     * 实现 向上调整
     * @param child 需要向上调整的子树的下标
     */
    private void shiftUp(int child) {
        int parent = (child-1) / 2;
        while (child > 0) {
            if(elem[child] > elem[parent]) {
                swap(elem,child,parent);
                child = parent;
                parent = (child-1) / 2;
            }else {
                break;
            }
        }
    }
    public boolean isFull() {
        return usedSize == elem.length;
    }
    /**
     * 出队
     */
    public int poll() {
        if(isEmpty()) {
            return -1;
        }
        int old = elem[0];
        swap(elem,0,usedSize-1);
        usedSize--;
        shiftDown(0,usedSize);
        return old;
    }
    /*
    判断是否为空
     */
    public boolean isEmpty() {
        return usedSize == 0;
    }
    /**
     *建立小根堆
     */
    public static void heapSort() {
        int end = usedSize - 1;
        while (end > 0) {
            swap(elem,0,end);
            shiftDown(0,end);
            end--;
        }
    }
    public static void main(String[] args) {
        elem = new int[]{5,4,10,16,1,8,9,48,18,17};
        usedSize = elem.length;
        createHeap();//建立小根堆,先建立大根堆,然后将最大值放入堆尾,建立小根堆
        heapSort();
        System.out.println(Arrays.toString(elem));
        //[1, 4, 5, 8, 9, 10, 16, 17, 18, 48]
        createHeap();//建立大根堆
        System.out.println(Arrays.toString(elem));
        //[48, 18, 16, 17, 9, 10, 5, 1, 8, 4]
    }
}


8、总结🍉

  1. 堆排序使用堆来选数,相比直接选择排序效率就高了很多。堆排序中每一趟都有元素归位了
  2. 时间复杂度:最好/最环/平均时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定
  5. 适用场景:元素较多的情况,因为建初始堆的所需的比较次数比较多,属于堆排序不适


目录
相关文章
|
3天前
|
搜索推荐 算法 C语言
【排序算法】八大排序(上)(c语言实现)(附源码)
本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
29 8
|
3天前
|
搜索推荐 算法 C语言
【排序算法】八大排序(下)(c语言实现)(附源码)
本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
21 7
|
5月前
|
存储 算法 Java
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
|
6月前
|
存储 算法 搜索推荐
【算法】七大经典排序(插入,选择,冒泡,希尔,堆,快速,归并)(含可视化算法动图,清晰易懂,零基础入门)
【算法】七大经典排序(插入,选择,冒泡,希尔,堆,快速,归并)(含可视化算法动图,清晰易懂,零基础入门)
176 1
|
算法 索引
二分查找算法&最靠左索引&最靠右索引详解与优化:图文全解+代码详注+思路分析(一)
二分查找算法&最靠左索引&最靠右索引详解与优化:图文全解+代码详注+思路分析
272 0
|
算法 索引
二分查找算法&最靠左索引&最靠右索引详解与优化:图文全解+代码详注+思路分析(二)
二分查找算法&最靠左索引&最靠右索引详解与优化:图文全解+代码详注+思路分析
116 0
|
6月前
|
算法 搜索推荐 C语言
数据结构排序——详解快排及其优化和冒泡排序(c语言实现、附有图片与动图示意)
数据结构排序——详解快排及其优化和冒泡排序(c语言实现、附有图片与动图示意)
64 0
【动态规划入门修炼手册】——泰波那契数(滚动数组空间优化)|三步问题
【动态规划入门修炼手册】——泰波那契数(滚动数组空间优化)|三步问题
61 0
|
机器学习/深度学习 算法
带你读《图解算法小抄》十四、排序(8)
带你读《图解算法小抄》十四、排序(8)
|
算法 搜索推荐
带你读《图解算法小抄》十四、排序(1)
带你读《图解算法小抄》十四、排序(1)