Java数据结构之常见排序算法(上)

目录
  • 认识排序
  • 常见排序的分类
  • 直接插入排序
  • 希尔排序(缩小增量排序)
  • 选择排序
  • 堆排序

认识排序

在学校中,如果我们要参加运动会,或者军训的时候,会按照身高从矮到高进行站队,比如上课老师手上拿的考勤表,通常是按照学号从低到高进行排序的。再比如编程语言排行榜,也是在排序。

生活中有相当多的排序场景,由此可知,排序还是很重要的, 本章就会介绍常见的一些排序算法。

所谓排序呢,就拿我们上面的举例来说,会按照某个或某些关键字的大小,递增或者递减排列起来的操作,这就是排序,这里面也涉及到排序的稳定性,举个例子:

比如有这样的一组数据:B  D  A  C  A  F,要按照他们的 ascll 码来排序,这里出现了两个 A,我们把第一个出现的 A 称为 A1,第二个出现的 A 称为 A2。

假定排序后结果为:A1  A2  B  C  D  F,那么这个排序算法就是稳定的。

假设排序后结果为:A2  A1  B  C  D  F,那么这个排序算法就是不稳定的。

简而言之,如果待排序的数据中,有两个相同的元素,排序结束后,这两个元素的关系没有发生改变,比如 A1 排序前在 A2 前面,排完序后,A1 还在 A2 前面,这就是稳定的排序算法。

注意:一个不稳定的排序算法,天生就是不稳定的,但是一个稳定的排序算法,你可以把它设计成不稳定的。

常见排序的分类

这张图,概括了我们后续要讲的排序算法,接着正式进入本章的学习吧!(排序算法章节,默认都是升序排序)

注:后续所说的复杂度 log,都是以2为底,特殊的会标注出来。

直接插入排序

现在想请各位小伙伴,想象一下自己在摸扑克牌,摸了第一张牌放在了自己的手中,接着再摸一张,把这张牌跟手上的一张牌进行比较,把它放到合适的位置, 接着再摸一张,把这张牌跟手上的两张牌进行比较,放到合适的位置。

这就是直接插入排序,简单来说,我们每次取的元素,会往一个有序的序列中插入,也就是每次摸牌之前,手上的牌都是排好序的,我们只需要把新摸到的牌,依次与手上有序的牌进行比较,把它放入合适的位置就行!

这里我们用一副静态的图来简单演示下:

public void insertSort(int[] array) {
    // 外循环控制趟数, 第一张牌默认有序, 所以 i 从 1 开始
    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;
    }
}
  • 时间复杂度分析:外循环一共要 n - 1 次,内循环每次最差的情况下要比较 1....n 次,那么去掉 n 前面的小项,也就是 (n - 1) * n 次,即 n^2 - n,去掉最小项,最后的时间复杂度为 O(n^2)
  • 空间复杂度分析:只是开辟了一个 tmp 的变量 i,j,常数,即空间复杂度:O(1)
  • 稳定性:稳定 该排序再数据越接近有序的情况,时间效率越高。

希尔排序(缩小增量排序)

这个排序是直接插入排序的一种优化,你可以想象一下,你面前有并排放好的 8 个爱心号码牌,但是它们是无序的,我们要给号码牌分组,按要求,第一次间隔为 4 个号码牌的为一组,分完组后进行直接插入排序,第二次间隔为 2 个号码牌的为一组,进行直接插入排序,第三次间隔为 1 个号码牌为一组,进行直接插入排序。

听到这有点没理解,没关系,我们就通过画图来把我上述说的内容再次理解下:

由上图我们可以发现,当间隔 > 1 的时候,都是预排序,也就是让我们的数据更接近有序,但是当间隔为 1 的时候,就是直接插入排序了,前面我们说过,直接插入排序,再数据接近有序的时候时间效率是很快的。

由此可见,希尔排序,是直接插入排序的优化版。

如何在代码中实现呢?间隔的值如何取呢?

代码中把这个间隔的值称为 gap,这个 gap 的取值方法有很多,有的人提出 gap 为奇数好,有的提出 gap 为偶数好,我们就采取一种比较简单的方法来取 gap 值,首次取数组长度一半的值为 gap,后续 gap /= 2,即可。当 gap 为 1,也就是直接插入排序了。

代码实现如下:

public void shellSort(int[] array) {
    // gap初始值设置成数组长度的一半
    int gap = array.length >> 1;
    // gap 为 1 的时候直接插入排序
    while (gap >= 1) {
        shell(array, gap);
        gap >>= 1; // 更新 gap 值 等价于 -> gap /= 2;
    }
}
private void shell(int[] array, int gap) {
    for (int i = gap; 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^1.3),感兴趣的话,可以查阅一下相关书籍。
  • 空间复杂度分析:仍然开辟的是常数个变量,空间复杂度为 O(1)
  • 稳定性:不稳定

选择排序

这个排序是个很简单的排序,你想象一下,有个小屁孩,喜欢玩小球,我给他安排了个任务,把这一排小球从小到大排列起来,摆给我看,于是小屁孩就找,每次从一排小球中找出最大的,放到最后,固定不动,那是不是也就是说,每次能确定一个最大的石子的最终位置了。我们来看图:

通过图片我们也能看出来,每次找到最大值于最后一个值交换,所以每趟都能把最大的放到最后固定不动,每趟能排序一个元素出来,那这样用代码来实现就很简单了:

public void selectSort(int[] array) {
    int end = array.length - 1;
    // 剩最后一个元素的时候, 不用比较了, 已经有序了
    // 所以 i < array.length - 1
    for (int i = 0; i < array.length - 1; i++) {
        int max = 0;
        int j = 0;
        while (j <= end) {
            if (array[j] > array[max]) {
                max = j;
            }
            j++;
        }
        //找到了最大值的下标, 把最大值与最后一个值交换
        swap(array, max, end--); // end-- 最后一个元素固定了, 不用参与比较
    }
}

这个算法有没有可以优化的空间呢?

有!那么既然小屁孩能一次找出最大的球,那能不能让小屁孩一次找出两个球出来呢?

分别是这些球中,最大的和最小的,最大的放在最右边,最小的放在最左边,那么我们每次就能确定两个球的最终位置,也就是我们一次能排序两个元素。

图解:

代码实现如下:

public void selectSort(int[] array) {
    int left = 0;
    int right = array.length - 1;
    while (left < right) {
        int maxIndex = left;
        int minIndex = left;
        // i = left + 1 -> 每次找最大最小值下标的时候, 可以不用算默认给的最大值和最小值下标
        for (int i = left + 1; i <= right; i++) {
            if (array[i] > array[maxIndex]) {
                maxIndex = i;
            }
            if (array[i] < array[minIndex]) {
                minIndex = i;
            }
        }
        swap(array, minIndex, left);
        // 如果最大值为 left 的位置情况的话, 走到这, 最大值已经被交换到 min 位置上了
        if (maxIndex == left) {
            // 更新最大值的位置
            maxIndex = minIndex;
        }
        swap(array, maxIndex, right);
        left++;
        right--;
    }
}
  • 时间复杂度分析:虽然是优化了,但去小项之后,还是 O(n^2)
  • 空间复杂度分析:O(1)
  • 稳定性:不稳定实际开发中用的不多

堆排序

如果你有学习过优先级队列,或者看过博主优先级队列的文章,那么这个排序对于你来说还是很轻松的,当然在堆排序的讲解中,不会过多的去介绍堆的概念,如果对这部分概念还不理解,可以移至博主的上一篇文章进行学习。

堆排序,简单来说,就是把一组数据,看成一个完全二叉树,再把这棵树,建大堆或者建小堆,接着进行排序的一种思路。至于如何建大堆或小堆,和向上调整算法以及向下调整算法。

这里我们来分析一下,排升序应该建什么堆?大堆!排降序建小堆!

这里我们来排升序,建大堆,因为大堆堆顶元素一定是堆中最大的,所以我们可以把堆顶元素和最后一个元素进行交换,这样我们就确认了最大值的位置,接着将交换后的堆顶元素进行向下调整,仍然使得该数组满足大堆的特性!

图解如下:

如上图步骤也很简单,先是将数组建成大堆,然后利用大堆来进行堆排序,首先将堆顶元素和最后一个元素交换,由此最大的元素就有序了,接着将该堆进行向下调整,使继续满足大堆性质,依次进行下去即可。

代码实现:

public void heapSort(int[] array) {
    // 建大堆 从最后一个非叶子节点开始向下调整
    // 非叶子节点下标 = (孩子节点下标 - 1) / 2
    for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
        shiftDown(array, parent, array.length);
    }
    // 建大堆完成后, 每次堆顶元素与最后一个元素交换, 锁定最大元素的位置
    for (int len = array.length - 1; len > 0; len--) {
        swap(array, 0, len); //根节点与最后一个元素交换
        shiftDown(array, 0, len); //根节点位置向下调整
    }
}
private void shiftDown(int[] array, int parent, int len) {
    int child = parent * 2 + 1;
    while (child < len) {
        if (child + 1 < len && array[child + 1] > array[child]) {
            child++;
        }
        // 判断父节点是否大于较大的孩子节点
        if (array[parent] < array[child]) {
            swap(array, parent, child);
            // 更新下标的位置
            parent = child;
            child = parent * 2 + 1;
        } else {
            return;
        }
    }
}
  • 时间复杂度分析:建堆的时间复杂度优先级队列那期有说过为 O(n),排序调整堆的时候,一共要调整 n-1 次,每次向下调整的时间复杂度是 logn,所以即 logn(n - 1),即 O(n*logn),加上面建堆的时间复杂度:O(n) + O(n*logn),最终时间复杂度也就是:O(n*logn)。
  • 空间复杂度分析:O(1)
  • 稳定性:不稳定

下期预告:【Java 数据结构】常见排序算法(下)

(0)

相关推荐

  • 排序算法图解之Java冒泡排序及优化

    目录 1.冒泡排序简介 2.图解算法 3.冒泡排序代码实现 4.冒泡排序算法的优化 1.冒泡排序简介 冒泡排序(Bubble Sorting)即:通过对待排序的序列从前往后,依次比较相邻元素的值,若发现逆序则交换位置,使较大的元素逐渐移动到后部,就像水底的气泡一样逐渐从水面冒出来,这就是冒泡名称的由来 2.图解算法 以将序列{3, 9, -1, 10, -20}从小到大排序为例! 基本思想就是,在每一趟排序实现将最大的数移到序列的最后端!这主要通过比较相邻两个元素实现,当相邻的两个元素逆序的时候

  • Java实现常见的排序算法的示例代码

    目录 一.优化后的冒泡排序 二.选择排序 三.插入排序 四.希尔排序 五.快速排序 六.随机化快速排序 七.归并排序 八.可处理负数的基数排序 一.优化后的冒泡排序 package com.yzh.sort; /* 冒泡排序 */ @SuppressWarnings({"all"}) public class BubbleSort { public static void BubbleSort(int[]a){ for (int i = 0; i <a.length-1 ; i+

  • 排序算法图解之Java希尔排序

    目录 1.希尔排序简介 2.希尔排序算法图解 3.希尔排序代码实现 1.希尔排序简介 希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法,其也是一种特殊的插入排序,即将简单的插入排序进行改进后的一个更加高效的版本,也称缩小增量排序. 希尔排序是非稳定排序算法.把记录按下标的一定增量分组,对每组使用直接插入排序算法排序:随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止. 2.希尔排序算法图解 以序列: {8, 9, 1, 7,

  • 排序算法图解之Java选择排序

    目录 1.选择排序简介 2.图解选择排序算法 3.选择排序代码实现 1.选择排序简介 选择排序(Selection sort)是一种简单直观的排序算法.它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾.以此类推,直到全部待排序的数据元素的个数为零.选择排序是不稳定的排序方法. 2.图解选择排序算法 选择排序的基本思想如下: 第一次:从arr[0]~arr[n-1]中选取最小值,

  • 排序算法图解之Java快速排序的分步刨析

    目录 1.快速排序简介 2.思路简介及图解 3.实现代码及运行结果 1.快速排序简介 快速排序是对冒泡排序的一种改进.基本思想为:通过一趟排序将要排序的数据分割为独立的两个部分,其中一部分的所有数据比另外一部分的所有数据要小,然后按照此方法对这两部分分别进行快速排序,整个过程可以递归进行,以此达到整个数据变成有序序列. 2.思路简介及图解 快速排序算法通过多次比较和交换来实现排序,其排序流程如下: (1)首先设定一个分界值,通过该分界值将数组分成左右两部分. (2)将大于或等于分界值的数据集中到

  • 排序算法图解之Java插入排序

    目录 1.插入排序简介 2.插入排序思想及图解 3.插入排序代码实现 1.插入排序简介 插入排序,一般也被称为直接插入排序.对于少量元素的排序,它是一个有效的算法.插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的.记录数增1的有序表.在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动 2.插入排序思想及图解 插入排序的基本思想如下: 把n个待排序的元素看成为一个有序表和一个无

  • Java数据结构之常见排序算法(上)

    目录 认识排序 常见排序的分类 直接插入排序 希尔排序(缩小增量排序) 选择排序 堆排序 认识排序 在学校中,如果我们要参加运动会,或者军训的时候,会按照身高从矮到高进行站队,比如上课老师手上拿的考勤表,通常是按照学号从低到高进行排序的.再比如编程语言排行榜,也是在排序. 生活中有相当多的排序场景,由此可知,排序还是很重要的, 本章就会介绍常见的一些排序算法. 所谓排序呢,就拿我们上面的举例来说,会按照某个或某些关键字的大小,递增或者递减排列起来的操作,这就是排序,这里面也涉及到排序的稳定性,举

  • JS中的算法与数据结构之常见排序(Sort)算法详解

    本文实例讲述了JS中的算法与数据结构之常见排序(Sort)算法.分享给大家供大家参考,具体如下: 排序算法(Sort) 引言 我们平时对计算机中存储的数据执行的两种最常见的操作就是排序和查找,对于计算机的排序和查找的研究,自计算机诞生以来就没有停止过.如今又是大数据,云计算的时代,对数据的排序和查找的速度.效率要求更高,因此要对排序和查找的算法进行专门的数据结构设计,(例如我们上一篇聊到的二叉查找树就是其中一种),以便让我们对数据的操作更加简洁高效. 这一篇我们将会介绍一些数据排序的基本算法和高

  • Java实现常见排序算法的优化

    冒泡排序 冒泡排序的思想: 每次让当前的元素和它的下一个元素比较大小.如果前一个的元素大于后一个元素的话,交换两个元素. 这样的话经历一次扫描之后能确保数组的最后一个元素一定是数组中最大的元素. 那么下次扫描的长度比上次少一个.因为数组的最后一个元素已经是最大的了.即最后一个元素已经有序了. 优化一: 优化的思路就是每一次扫描遍历一次数组.如果某次的扫描之后没有发生数组元素的交换的话.那么说明数组的元素已经是有序的了, 就可以直接跳出循环.没有继续扫描的必要了. 优化二:如果数组的尾部已经局部有

  • Java 常见排序算法代码分享

    目录 1. 冒泡排序 2. 选择排序 3. 插入排序 4. 快速排序 5. 归并排序 6. 希尔排序 6.1 希尔-冒泡排序(慢) 6.2 希尔-插入排序(快) 7. 堆排序 8. 计数排序 9. 桶排序 10. 基数排序 11. 使用集合或 API 11.1 优先队列 11.2 Java API 汇总: 1. 冒泡排序 每轮循环确定最值: public void bubbleSort(int[] nums){     int temp;     boolean isSort = false;

  • Java实现8种排序算法的示例代码

    冒泡排序 O(n2) 两个数比较大小,较大的数下沉,较小的数冒起来. public static void bubbleSort(int[] a) { //临时变量 int temp; //i是循环次数,也是冒泡的结果位置下标,5个数组循环5次 for (int i = 0; i < a.length; i++) { //从最后向前面两两对比,j是比较中下标大的值 for (int j = a.length - 1; j > i; j--) { //让小的数字排在前面 if (a[j] <

  • Java中七种排序算法总结分析

    目录 前言:对文章出现的一些名词进行解释 一.插入排序 1.基本思想 2.直接插入排序 3.希尔排序(缩小增量排序) 二.选择排序 1.基本思想 2.直接选择排序 3.堆排序 三.交换排序 1.基本思想 2.冒泡排序 3.快速排序(递归与非递归) 1.Hoare版 2.挖坑法 3.前后标记法(难理解) 4.快速排序优化 5.快速排序非递归 6.相关特性总结 四.归并排序(递归与非递归) 前言:对文章出现的一些名词进行解释 排序: 使一串记录,按照其中的某个或某些关键字的大小,递增或者递减排列起来

  • java实现的各种排序算法代码示例

    折半插入排序 折半插入排序是对直接插入排序的简单改进.此处介绍的折半插入,其实就是通过不断地折半来快速确定第i个元素的 插入位置,这实际上是一种查找算法:折半查找.Java的Arrays类里的binarySearch()方法,就是折半查找的实现,用 于从指定数组中查找指定元素,前提是该数组已经处于有序状态.与直接插入排序的效果相同,只是更快了一些,因 为折半插入排序可以更快地确定第i个元素的插入位置 代码: package interview; /** * @author Administrat

  • Python八大常见排序算法定义、实现及时间消耗效率分析

    本文实例讲述了Python八大常见排序算法定义.实现及时间消耗效率分析.分享给大家供大家参考,具体如下: 昨晚上开始总结了一下常见的几种排序算法,由于之前我已经写了好几篇排序的算法的相关博文了现在总结一下的话可以说是很方便的,这里的目的是为了更加完整详尽的总结一下这些排序算法,为了复习基础的东西,从冒泡排序.直接插入排序.选择排序.归并排序.希尔排序.桶排序.堆排序.快速排序入手来分析和实现,在最后也给出来了简单的时间统计,重在原理.算法基础,其他的次之,这些东西的熟练掌握不算是对之后的工作或者

  • Java十大经典排序算法图解

    目录 0.算法概述 0.1 算法分类 0.2 算法复杂度 0.3 相关概念 1.冒泡排序(Bubble Sort) 1.1 算法描述 1.2 动图演示 1.3 代码实现 2.选择排序(Selection Sort) 2.1 算法描述 2.2 动图演示 2.3 代码实现 2.4 算法分析 3.插入排序(Insertion Sort) 3.1 算法描述 3.2 动图演示 3.3代码实现 3.4 算法分析 4.希尔排序(Shell Sort) 4.1 算法描述 4.2 动图演示 4.3 代码实现 4.

  • Javascript中的常见排序算法

    具体代码及比较如下所示: 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  <html xmlns="http://www.w3.org/1999/xhtml" lang="gb2312">

随机推荐