内部排序八大算法原理及JAVA实现

这篇博客详细介绍了内部排序的八大算法:插入排序、冒泡排序、选择排序、希尔排序、堆排序、归并排序、快速排序和基数排序。讲解了每个算法的基本思想、具体步骤、时间复杂度和空间复杂度,并分析了它们的稳定性。对于数据量较小、较有序的情况,插入排序和冒泡排序表现良好;而快速排序通常情况下速度最快,但不适合内存有限的场景;归并排序虽稍快于堆排序,但需要更多内存;堆排序适用于大规模数据;希尔排序和选择排序效率较低;基数排序则只适用于整数排序。
摘要由CSDN通过智能技术生成

一、概述

  排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。具体分类如图1-1(源自网上)
  
  

图1-1

  
  某些必要的定义:

  • 时间复杂度:
      时间复杂度是一个函数,它定量描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。
  • 空间复杂度:
      一个程序的空间复杂度是指运行完一个程序所需内存的大小。一个程序执行时除了需要存储空间和存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些为现实计算所需信息的辅助空间。程序执行时所需存储空间包括以下两部分。  
      (1)固定部分。这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。
      (2)可变空间,这部分空间的主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。
  • 稳定性:
      假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种排序算法是稳定的;否则称为不稳定的。
    对于不稳定的排序算法,只要举出一个实例,即可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。
    需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。

二、具体算法

1、插入排序

1.基本思想:
  插入排序由N-1趟排序组成。对于p=1到N-1趟,保证位置从0到p的元素处于已排序状态。即:第p次排序时,将位置p上的元素向前移动到合适的位置。
2.具体步骤及图解:

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5

下图详细描述了插入排序的排序过程(源自网上)。

图2-1

3.代码实现

    /**
     * 插入排序(由小到大)
     *
     * @param a 排序数组
     */
    public static <T extends Comparable<? super T>> void insertionSort(T[] a) {
        int i;
        for (int p = 1; p < a.length; p++) {
            T temp = a[p];
            for (i = p; i > 0 && temp.compareTo(a[i - 1]) < 0; i--) {
                a[i] = a[i - 1];
            }
            a[i] = temp;
        }
    }

4.分析

  • 稳定性:相同元素不互换次序,是稳定的。
  • 时间复杂度:
    • 最好情况:数组本就有序,则每趟排序只需进行一次比较,共需N-1趟,故为O(n)
    • 最差情况:数组倒序,第p趟插入需要比较p-1次,故1+2+……N-1=N*(N-1)/2,故为O(N^2)
    • 平均情况为O(N^2)
  • 空间复杂度:所需空间为常数,O(1)
  • 结论:数据量较小,较有序的情况下性能较好

2、冒泡排序

1.基本思想:
  重复地走访过要排序的元素,一次比较相邻两个元素,如果他们的顺序错误就把他们调换过来,直到没有元素再需要交换,排序完成。
2.具体步骤及图解:

  1. 比较相邻的元素,如果前一个比后一个大,就把它们两个调换位置。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

下图详细描述了冒泡排序的排序过程(源自网上)。
这里写图片描述

图2-2

3.代码实现

     /**
     * @param a
     * @param <T>
     */
    public static <T extends Comparable<? super T>> void bubbleSort(T[] a) {
        for (int i = 0; i < a.length; i++) {
            for (int j = 0; j < a.length - i - 1; j++) {
                if (a[j].compareTo(a[j + 1]) > 0) {
                    T temp = a[j + 1];
                    a[j + 1] = a[j];
                    a[j] = temp;
                }
            }
        }
    }

4.分析

  • 稳定性:相同元素不互换次序,是稳定的。
  • 时间复杂度:此种排序方式,数据顺序对算法影响不大,为O(N^2)
  • 空间复杂度:所需空间为常数,O(1)

3、选择排序

1.基本思想:
  在要排序的一组数中,选出最小的一个数与第1个位置的数交换;然后在剩下的数当中再找最小的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
2.具体步骤及图解:

  1. 第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;
  2. 第二趟,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;
  3. 以此类推…..
  4. 第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,直到整个序列按关键码有序。

下图详细描述了选择排序的排序过程(源自网上)。

图2-3

3.代码实现

     /**
     * @param a
     * @param <T>
     */
    public static <T extends Comparable<? super T>> void selectSort(T[] a) {
        int minIndex;
        for (int i = 0; i < a.length - 1; i++) {
            minIndex = i;
            for (int j = i + 1; j < a.length; j++) {
                if (a[i].compareTo(a[j]) > 0) {
                    minIndex = j;
                }
            }
            if (minIndex != i) {
                T temp = a[minIndex];
                a[minIndex] = a[i];
                a[i] = temp;
            }
        }
    }

4.分析

  • 稳定性:选择排序是不稳定的排序算法,不稳定发生在最小元素与A[i]交换的时刻。比如序列:{ 5, 8, 5, 2, 9 },一次选择的最小元素是2,然后把2和第一个5进行交换,从而改变了两个元素5的相对次序。
  • 时间复杂度:无论最好情况还是最差情况,都需要进行1+2+……N-1=N*(N-1)/2次比较找出最值,故为O(N^2)
  • 空间复杂度:所需空间为常数,O(1)

4、希尔排序

1.基本思想:
  先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
2.具体步骤及图解:

  1. 选择一个增量序列d[k],其中ti>tj,tk=1;
  2. 按增量序列个数k,对序列进行k 趟排序;
  3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

增量的选择:

 a. 希尔增量 d[k]的选择是 d[t]=N/2,d[k]=d[k+1]/2 此处即选择此种方式 最坏运行时间O(N^2)
 b. Hibbard增量 {
  1, 3, ..., 2^k-1}  最坏运行时间O(N^3/2)
 c. Sedgewick增量:{
  1, 5, 19, 41, 109...}该序列中的项或者是9*4^i - 9*2^i + 1或者是4^i - 3*2^i + 1

下图详细描述了希尔排序的排序过程(源自网上)。
这里写图片描述

图2-4

3.代码实现

     /**
     * @param a
     * @param <T>
     */
      public static <T extends Comparable<? super T>> void shellSort(T[] a) {
        int j;
        for (int gap = a.length / 2; gap > 0; gap /= 2) {
  //b[k]
            for (int i = gap; i < a.length; i++) {
                T temp = a[i];
                for (j = i; j >= gap && temp.compareTo(a[j - gap]) < 0; j -= gap) {
                    a[j] = a[j - gap];
                }
                a[j] = temp;
            }
        }
    }

4.分析

  • 稳定性:希尔排序是不稳定的排序算法,因为相同元素可能被分割至不同的组,导致次序改变。
  • 时间复杂度:与选择的增量有关
    • 希尔增量:此种增量方式,当N=2^k时,会导致前后增量为倍数关系,有一直到步长为1之前都未涉及到的元素,极限情况下甚至还有可能之前所有的排序都在浪费时间,如{1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15, 8, 16}数组,步长为8,4,2时未进行任何处理。故最坏时间复杂度与插入排序相同为O(N^2)
    • Hibbard增量 最坏运行时间O(N^3/2)
    • Sedgewick增量:为O(N^4/3)
  • 空间复杂度:所需空间为常数,O(1)

5、堆排序

1.基本思想:
  堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构(通常堆是通过一维数组来实现的),并同时满足堆的性质:即子结点的键值总是小于(或者大于)它的父节点。利用这种堆性质,可以轻易选出最值。
2.具体步骤及图解:

  1. 将数组构建为建一个堆(最大堆或者最小堆根据排序需求选择)
  2. 把堆顶元素(最大值)和堆尾元素互换
  3. 把堆的尺寸缩小1,并调整使得剩余元素成为一个新的堆。
  4. 重复步骤2,直到堆的尺寸为1

下图详细描述了堆排序的排序过程(源自网上)。
这里写图片描述

图2-5

3.代码实现

     /**
     * 堆排序(第0位有值)
     *
     * @param a
     * @param <T>
     */
    public static <T extends Comparable<? super T>> void heapSort(T[] a) {
        //将数组转化为堆
        for (int i = a.length / 2 - 1; i >= 0; i--) {
            percDown(a, i, a.length);
        }
        //排序
        for (int i = a.length - 1; i >= 0; i--) {
            swap(a, 0, i);
            percDown(a, 0, i);
        }
    }

    /**
     * 最大堆的下滤操作
     *
     * @param a
     * @param hole
     * @param n    二叉堆中现有元素
     * @param <T>
     */
    private static <T extends Comparable<? super T>> void percDown(T[] a, int hole, int n) {
        
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值