今天我们来说说常用的三种排序算法:选择排序、插入排序、快速排序

这篇具有很好参考价值的文章主要介绍了今天我们来说说常用的三种排序算法:选择排序、插入排序、快速排序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

原文链接:http://www.ibearzmblog.com/#/technology/info?id=8ac4902f82f525e1456624d5d7a545dc

前言

选择排序、插入排序、快速排序这三种算法算是比较初级的排序算法,对它们的原理和技巧,可以方便我们对后面的算法理解。

温馨提示,因为动图不好弄,所以我在网上下载了AlgorithmMan来进行动图演示。

算法基础模板

后面的算法都是继承于这个基础模板,里面主要是实现一些公用方法,例如比较大小、交换元素等等,所以还是很有必要贴一下代码:

public class SortTemplate {

    /**
     * 排序规则,由子类实现
     * @param a
     */
    public void sort(Comparable[] a){}

    /**
     * 比较v和w的大小
     * @param v
     * @param w
     * @return
     */
    public Boolean less(Comparable v, Comparable w){
        return v.compareTo(w) < 0;
    }

    /**
     * 元素交换
     * @param a
     * @param i
     * @param j
     */
    public void exch(Comparable[] a, int i, int j){
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }

    /**
     * 输出数组里所有的元素
     * @param a
     */
    public void show(Comparable[] a){
        StringBuilder stringBuilder = new StringBuilder();
        for(int i = 0; i < a.length; i++){
            stringBuilder.append(a[i] + "  ");
        }
        return stringBuilder.toString();
    }

    /**
     * 判断数组是否有序的
     * @param a
     * @return
     */
    public boolean isSorted(Comparable[] a){
        for (int i = 1; i < a.length; i++){
            if(less(a[i], a[i + 1]))
                return false;
        }
        return true;
    }

}

选择排序

选择排序是最简单粗暴的,原理如下:第一步,找到这个数组里最小的元素,然后与第一个元素进行交换位置。然后在剩下的元素中找到最小的元素,然后与第二个元素交换位置,如此反复。如下面的动画:
今天我们来说说常用的三种排序算法:选择排序、插入排序、快速排序,排序算法,算法,java

代码实现很简单:

/**
 * 选择排序
 */
public class SelectSort  extends SortTemplate{

    @Override
    public void sort(Comparable[] a) {
        int N = a.length;
        for (int i = 0; i < N; i++){
            int min = i;
            for(int j = i + 1; j < N; j++){
                if(less(a[j], a[min]))
                    min = j;
            }
            exch(a, i, min);

        }
    }
}

接下来我们讨论下它的时间复杂度,从上面的代码可以知道,长度为N的数组,一共需要交换N次,从0到N-1都会进行(N-1)+(N-2)+(N-3)+…+2+1次比较,根据高斯定理,得出N(N-1)/2,则约等于N^2/ 2(最大系数才是直接影响时间复杂度的核心,常量可以忽略不计)。

所以选择排序的时间复杂度为O(N^2 / 2)。

插入排序

插入排序跟选择排序有点不同,插入排序的性能取决于数组是否有序的,就像我们斗地主的时候,都会将每一张牌插入到其它已经有序的牌中,当插入牌后,就需要将后面的牌向后移动一位,这就是插入排序。

今天我们来说说常用的三种排序算法:选择排序、插入排序、快速排序,排序算法,算法,java

具体代码实现如下:

public class InsertSort extends SortTemplate{

    @Override
    public void sort(Comparable[] a) {
        int N = a.length;
        for(int i = 1; i < N; i++){
            for(int j = i; j > 0 && less(a[j], a[j - 1]); j--){
                exch(a, j, j - 1);
            }
        }
    }
}

举个例子,我现在有个无序数组{10,5,3,6,7},那么它的执行过程如下:
今天我们来说说常用的三种排序算法:选择排序、插入排序、快速排序,排序算法,算法,java

接下来我们说下时间复杂度,在最好的情况,不用移动原数组的元素,则只是N-1次比较,同时并不需要比较。不过我们通常都只会考虑最坏情况,跟快速排序一样,假如数组是逆序的(也就是从大到小),那么就需要(N-1)+(N-2)+(N-3)+…+2+1次比较和(N-1)+(N-2)+(N-3)+…+2+1次交换,也就是N^2 / 2次比较和N^2 / 2次交换,也就是在逆序的情况下的时间复杂度为O(n^2),如果在最好的情况下那么时间复杂度就为O(n)

快速排序

快速排序是一种分治的方法,它的核心理念是将一个无序的数组一分为二并分别排序,当两个子数组都有序时则整个数组都为有序。快速排序的核心在于切分,下面是快速排序的大致过程:
今天我们来说说常用的三种排序算法:选择排序、插入排序、快速排序,排序算法,算法,java

主要代码实现如下:

/**
 * 快速排序
 */
public class QuickSort extends SortTemplate{

    @Override
    public void sort(Comparable[] a) {
        sort(a, 0, a.length - 1);
    }

    private void sort(Comparable[] a, int lo, int hi){
        if(hi <= lo){
            return;
        }
        int j = partition(a, lo, hi);
        sort(a, lo, j - 1);
        sort(a, j + 1, hi);
    }

    private int partition(Comparable[] a, int lo, int hi){
        int i = lo, j = hi + 1;
        Comparable first = a[i];
        while (true){
            while (less(a[++i], first)){
                if(i == hi){
                    break;
                }
            }
            while (less(first, a[--j])){
                if(j == lo){
                    break;
                }
            }
            if(i >= j){
                break;
            }
            exch(a, i, j);
        }
        exch(a, lo, j);
        return j;
    }
}

快速排序的核心是切分,主要需要满足下面三个条件:

  1. a[lo]到a[j - 1]中的所有元素都不大于a[j]。
  2. a[j + 1]到a[hi]的所有元素都不小于a[j]。
  3. a[j]是已经锁定位置。

其中,回看上面代码,partition()是切分方法,我们重点来分析下:

    private int partition(Comparable[] a, int lo, int hi){
        // 默认第一次切分的元素是数组第一个元素,也就是a[0]
        int i = lo, j = hi + 1;
        Comparable first = a[i];
        while (true){
            // 从左往右找出比切分元素大的元素下标
            while (less(a[++i], first)){
                if(i == hi){
                    break;
                }
            }
            // 从右往左找出比切分元素小的元素下标
            while (less(first, a[--j])){
                if(j == lo){
                    break;
                }
            }
            if(i >= j){
                break;
            }
            // 交换i和j的位置,保证左边的元素都是小于等于切分元素,右边的都是大于等于切分元素
            exch(a, i, j);
        }
        // 一共有三种情况会跳出上面的循环,1.切分元素是数组里最大的元素,2.切分元素是数组里最小的元素,3.当指针相遇时则会跳出。最后的一部j指针的位置与切分元素进行交换
        exch(a, lo, j);
        return j;
    }

从sort()方法可以看到,快速排序就是通过不断的切分、递归的方式进行排序,我们接下来看看它的实际排序过程:

输入: 12, 8, 1, 17, 9, 18, 1, 11, 10, 8, 19, 17, 20
-----------------------
切分前数组: 12  8  1  17  9  18  1  11  10  8  19  17  20  
切分前i: 0, 切分前j: 13, 切分元素为a[i]: 12, lo: 0
比切分元素大的位置i: 3, 比切分元素小的位置j: 9
将i和j的元素进行交换
交换后的数组: 12  8  1  8  9  18  1  11  10  17  19  17  20  
比切分元素大的位置i: 5, 比切分元素小的位置j: 8
将i和j的元素进行交换
交换后的数组: 12  8  1  8  9  10  1  11  18  17  19  17  20  
最后交换lo和j的位置
最后的出数组: 11  8  1  8  9  10  1  12  18  17  19  17  20  
-----------------------
切分前数组: 11  8  1  8  9  10  1  12  18  17  19  17  20  
切分前i: 0, 切分前j: 7, 切分元素为a[i]: 11, lo: 0
最后交换lo和j的位置
最后的出数组: 1  8  1  8  9  10  11  12  18  17  19  17  20  
-----------------------
切分前数组: 1  8  1  8  9  10  11  12  18  17  19  17  20  
切分前i: 0, 切分前j: 6, 切分元素为a[i]: 1, lo: 0
比切分元素大的位置i: 1, 比切分元素小的位置j: 2
将i和j的元素进行交换
交换后的数组: 1  1  8  8  9  10  11  12  18  17  19  17  20  
最后交换lo和j的位置
最后的出数组: 1  1  8  8  9  10  11  12  18  17  19  17  20  
-----------------------
切分前数组: 1  1  8  8  9  10  11  12  18  17  19  17  20  
切分前i: 2, 切分前j: 6, 切分元素为a[i]: 8, lo: 2
最后交换lo和j的位置
最后的出数组: 1  1  8  8  9  10  11  12  18  17  19  17  20  
-----------------------
切分前数组: 1  1  8  8  9  10  11  12  18  17  19  17  20  
切分前i: 4, 切分前j: 6, 切分元素为a[i]: 9, lo: 4
最后交换lo和j的位置
最后的出数组: 1  1  8  8  9  10  11  12  18  17  19  17  20  
-----------------------
切分前数组: 1  1  8  8  9  10  11  12  18  17  19  17  20  
切分前i: 8, 切分前j: 13, 切分元素为a[i]: 18, lo: 8
比切分元素大的位置i: 10, 比切分元素小的位置j: 11
将i和j的元素进行交换
交换后的数组: 1  1  8  8  9  10  11  12  18  17  17  19  20  
最后交换lo和j的位置
最后的出数组: 1  1  8  8  9  10  11  12  17  17  18  19  20  
-----------------------
切分前数组: 1  1  8  8  9  10  11  12  17  17  18  19  20  
切分前i: 8, 切分前j: 10, 切分元素为a[i]: 17, lo: 8
最后交换lo和j的位置
最后的出数组: 1  1  8  8  9  10  11  12  17  17  18  19  20  
-----------------------
切分前数组: 1  1  8  8  9  10  11  12  17  17  18  19  20  
切分前i: 11, 切分前j: 13, 切分元素为a[i]: 19, lo: 11
最后交换lo和j的位置
最后的出数组: 1  1  8  8  9  10  11  12  17  17  18  19  20  
---------最后输出-----------
1  1  8  8  9  10  11  12  17  17  18  19  20  

时间复杂度证明比较复杂,我这里直接输出为平均 O(2NlnN),最多则为O(N^2/2).

结尾

老实说,我现在看《算法》这本书的时候很多地方都不太明白,都是通过不断查阅资料加写博客来加深印象,收获还不错!文章来源地址https://www.toymoban.com/news/detail-569435.html

到了这里,关于今天我们来说说常用的三种排序算法:选择排序、插入排序、快速排序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 常用的排序算法简介:冒泡、选择、插入、归并、快速

    常用的排序算法包括冒泡排序、选择排序、插入排序、归并排序和快速排序。以下是它们的简单介绍: 1. 冒泡排序(Bubble Sort):    冒泡排序是一种经典的基于交换的排序算法。它重复地比较相邻的元素,如果顺序错误,则交换它们,直到整个序列有序。它的时间复杂度为

    2024年02月14日
    浏览(41)
  • 说说你对选择排序的理解?如何实现?应用场景?

    选择排序(Selection sort)是一种简单直观的排序算法,无论什么数据进去都是  O(n²) 的时间复杂度,所以用到它的时候,数据规模越小越好 其基本思想是:首先在未排序的数列中找到最小(or最大)元素,然后将其存放到数列的起始位置 然后再从剩余未排序的元素中继续寻找最

    2024年04月23日
    浏览(43)
  • 快速排序的三种实现方法

    快速排序的单趟排序 快速排序的单趟排序:是以一个数作为基准值,实现将数组中比基准数小的数放在基准值的左侧,比基准值大的数放在基准值的右侧。 方法一:霍尔法 霍尔法的由来:霍尔是一个人的名字,他是最初发现快速排序的人,所以,它使用的单趟排序算法被称为

    2024年01月25日
    浏览(40)
  • C++ vector逆序排序的三种方法

    突然忘了快速逆序的方法,在网上搜索vector逆序发现没有,于是自己写一下,帮助大家快速查找。 假如你有一个vector里面有元素1,2,3,4,5,则逆序方法如下。 方法一: 方法一比方法二方便。 方法二: 方法三: 方法四(推荐): 你也可以通过使用rbegin,rend指针逆序排序

    2024年02月17日
    浏览(44)
  • 说说常见的排序算法有哪些?区别?

    排序是程序开发中非常常见的操作,对一组任意的数据元素经过排序操作后,就可以把他们变成一组一定规则排序的有序序列 排序算法属于算法中的一种,而且是覆盖范围极小的一种,彻底掌握排序算法对程序开发是有很大的帮助的 对与排序算法的好坏衡量,主要是从时间

    2024年04月22日
    浏览(25)
  • rsync常用的三种用法

    用法1:本地用法 类似于cp、dd命令,实现备份文件的复制(备份) # rsync /etc/passwd /home/passwd.bak # rsync -b --suffix=.bak2 --backup-dir=/tmp/ /etc/passwd /home/passwd.bak --suffix=xxx        指定旧备份文件的后缀名 --backup-dir=xxxx   指定将旧备份文件移动到哪个位置下 1 2 3 4 用法2:远程shell 利用

    2024年01月17日
    浏览(37)
  • JavaScript中常用的三种弹窗

    目录 一、alert(警告框) 二、confirm(确认框) 三、prompt (提示框)   JavaScript 中可以创建三种消息框:警告框、确认框、提示框。         alert()方法是显示一条弹出提示消息和确认按钮的警告框。 需要注意的是 :         alert()是一个阻塞的函数,如果我们不点确认

    2024年02月13日
    浏览(44)
  • Docker——常用挂载的三种方式

    在 Docker 中,有三种常见的挂载方式,它们分别是: 绑定挂载(Bind Mounts) :绑定挂载是将主机上的文件或目录挂载到容器中。这种挂载方式允许容器与主机之间共享文件和目录,并且对其中一个的更改会直接影响到另一个。可以通过在运行容器时使用  -v  或  --mount  参数

    2024年02月12日
    浏览(54)
  • mysql常用的三种备份方法

    mysql按照备份恢复方式分为逻辑备份和物理备份 逻辑备份是备份sql语句,在恢复的时候执行备份的sql语句实现数据库数据的重现 物理备份就是备份数据文件了,比较形象点就是cp下数据文件,但真正备份的时候自然不是的cp这么简单 这2种备份各有优劣,一般来说,物理备份恢

    2024年02月12日
    浏览(55)
  • unity常用的三种拖拽方法

    在2d图片与3d场景中使用OnMouseDrag()的方法实现拖拽,而对于ui没有作用。 通过添加Event Tringger组件实现,按下Add New Event Type添加新的事件类型,下拉菜单中显示不同的事件类型,包括鼠标进入离开按下松开点击拖拽等,以及拖拽结束后的EndDrag事件。他看上去和Button组件中的o

    2024年02月04日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包