今天来总结一下常用的内部排序算法。内部排序算法们需要掌握的知识点大概有:算法的原理,算法的编码实现,算法的时空复杂度的计算和记忆,何时出现最差时间复杂度,以及是否稳定,何时不稳定。

首先来总结下常用内部排序算法:

类别 名称 时间复杂度(默认最坏情况) 空间复杂度 稳定性 备注
插入排序 直接插入排序  O(n^2)  O(1) 稳定   
插入排序 希尔排序  最坏O(n^2),平均O(n^1.3)  O(1) 不稳定   
交换排序 冒泡排序  O(n^2)  O(1) 稳定   
交换排序 快速排序  最坏O(n^2),平均O(nlogn) 最坏O(n),平均O(logn) 不稳定  
选择排序 简单选择排序  O(n^2)  O(1) 不稳定  简单方法中唯一一个不稳定 
           

一、基础知识温习:

在进行具体算法的总结前,我们先来温习一下一些基本的概念和方法:

算法的稳定性:如果排序后,两个拥有相等关键字的元素a和b的相对位置没有发生变换,则稳定,否则不稳定。

内部排序是指在排序期间元素全部存放在内存中的排序;外部排序是指在排序期间元素无法全部同时存放在内存中,必须在排序过程中根据要求不断地在内、外存之间移动的操作。

然后再来温习一下时间复杂度的计算:

时间复杂度的计算其实就是计算出算法某条执行语句的具体执行频度,之后取频度的数量级即可。下面用例子温习:

例1:

void fun(int n){
  ;
  ;
}

设语句 i*=2运行t次,则,$t=max_t t, s.t. 2^t<n$,即t是使2^t<n成立最大的t。故$t=log_2 n(向下取整)$。故时间复杂度O(log2 n)。

例2:

count=;
;k<=n;k*=)
 ;j<=n;j++)
  count++;

外循环t次,2^t=n,t=log2 n。内循环n次。故O(nlog2n)。

例3:

y=;
)(y+)<=n)
 y+=;

$t=max_t t , s.t. t^2<=n$t=n^(1/2)。O(n^0.5)。

例4:

;i<=n;i++)
 ;j<=i;j++)
  ;k<=j;k++)
    x++;

内层循环受外循环控制变量大小控制,故不能简单相乘。研究发现,i=某值c时,最内层循环次数为:1+2+…+c。故$O(\sum_i^n i*(n-i-1))$但不会算,不过有个结论可以用:$\sum_{i=1}^n\sum_{j=1}^i\sum_{k=1}^j l=O(\frac{n^3}{6})=O(n^3)$

另外总结一些常用数学公式:

等差数列求和公式:$Sn = \frac{a_1+a_n}{2}n=a_1n+\frac{n(n-1)}{2}d$

等比数列求和公式:$Sn = a_1\frac{1-q^n}{1-q}$

和公式:$1+2+...+n=\frac{n(n+1)}{2}$

平方和公式:$1^2+2^2+...+n^2=\frac{n(n+1)(2n+1)}{6}$

立方和公式:$1^3+2^3+...+n^3=\frac{n^2(n+1)^2}{4}=(\frac{n(n+1)}{2})^2$

二、排序算法们:

2.1直接插入排序:

直接插入排序的思想是,在序列前一部分有序时,将后一部分最前面的元素插入到有序部分,使插入后的序列依然有序。(找位置,找位置过程中移动元素腾出位置,然后插入)具体的做法(文字描述就省略啦,自己看代码吧):

    public static void directInsertSort(int[] a) {
        int len = a.length;
        int temp;
        for(int i=1;i<len;i++) {    //从第二个元素开始,因为一个元素时必然是有序的
            if(a[i]<a[i-1]) {
                temp = a[i];
                int j=i-1;
                for(;j>=0;j--) {
                    if(a[j]>temp) a[j+1]=a[j];    //不用>=是为了保证算法的稳定性,不然后出现的元素就会排在前出现的元素之前了,而且那样也会增加无谓的开销
                }
                a[j+1]=temp;
            }
        }
    }

最差时间复杂度:应该出现在每次内循环中,都要找到最前面的位置插入,即,原数组是倒序的。这时运行频次t=0+1+2+...+(n-1)=n(n-1)/2。故O(n^2)。空间开销为常量们,O(1)。

2.2冒泡排序(基本交换排序):

冒泡排序的思想是,每次将最小的元素交换到队首。(对比左右元素,只要右元素小,就交换位置)。具体算法:

    public static void bubbleSort(int[] a) {
        int len = a.length;
        int temp;
        for(int i=0;i<len;i++) {
            boolean flag=false;    //表示本躺冒泡是否发生交换
            for(int j=len-1;j>i;j--) {
                if(a[j]<a[j-1]) {  //稳定
                    temp=a[j];
                    a[j]=a[j-1];
                    a[j-1]=temp;
                    flag=true;
                }
            }
            if(flag==false) return;    //已经有序了,避免后续无用功
        }
    }

最差时间复杂度:倒序。同上O(n^2),O(1)。

2.3简单选择排序:

简单选择排序的思想是,每轮找到一个最小的数,和无序部分的第一个元素交换位置。(遍历后交换位置,不存在略过的元素的移动)。具体代码:

    public static void selectionSort(int[] a) {
        int len=a.length;
        int temp;
        for(int i=0;i<len;i++) {
            int min = i;
            for(int j=i+1;j<len;j++) {
                if(a[j]<a[min]) min=j;
            }
            if(min!=i) {
                temp=a[i];
                a[i]=a[min];
                a[min]=temp;
            }
        }
    }

首先由于交换位置的原因,那么第一个最小元素(出现在靠后的位置)就会跑到前面,故不稳定。

时间复杂度方面相同,O(n^2),O(1)。

2.4希尔排序:

希尔排序是升级版的直接插入排序,它引入了步长的概念,先对等步长的每个子序列进行直接插入排序,然后缩短步长,最后步长=1,进行最后一次排序。

    public static void shellSort(int[] a) {
        int len = a.length;
        int temp;
        for(int dk=len/2;dk>=1;dk/=2) {    //步长变化
            for(int i=dk;i<=len;i++) {
                if(a[i]<a[i-dk]) {//如果需要插入
                    temp=a[i];
                    int j=i-dk;
                    for(;j>=0;j-=dk) {
                        if(a[j]>temp) {a[j+dk]=a[j];}
                    }
                    a[j+dk]=temp;
                }
            }
        }
    }

希尔排序的时间复杂度不好计算,直接记结论,最坏O(n^2),平均O(n^1.3)。当相同关键字的元素被分到不同的子表时,顺序可能会发生变化,故不稳定。

2.5快速排序:

著名的快排,是交换排序的一种,思想是每轮把一个数放到它该在的位置(左边的元素都比它小,右边都比它大),即左右各一个指针,先从右边往左找,找到第一个比pivot(基准)小的值,停下,左指针往右找第一个比pivot大的元素,停下,交换两个元素,之后继续,直到两个指针相遇,右指针的位置的元素应小于pivot的值(右指针先走的原因),即为pivot应在的位置,右指针元素与pivot交换即可。还不明白可以参考http://wiki.jikexueyuan.com/project/easy-learn-algorithm/fast-sort.html的介绍。具体:

    public static void quickSort(int[] a) {
        forQuickSort(a,0,a.length-1);
    }
    public static void forQuickSort(int[] a,int l,int r) {
        if(l==r) return;
        int temp;
        int pr=r,pl=l;    //工作指针,先从右边开始
        while(pr>pl) {
            while(pr>pl&&a[pr]>=a[l]) pr--;
            while(pr>pl&&a[pl]<=a[l]) pl++;
            if(pr==pl) {temp=a[l];a[l]=a[pr];a[pr]=temp;}
            else {temp=a[pr];a[pr]=a[pl];a[pl]=temp;}
        }
        forQuickSort(a,l,pr-1);
        forQuickSort(a,pr+1,pr);
    }

还有一种写法:

    private int Partition(int [] input,int start,int end){
        int p = input[start];
        int left = start+1,right=end;
        while(left<=right){
            while(left<=right&&input[right]>=p) right--;
            while(left<=right&&input[left]<=p) left++;
            if(left<right){int temp=input[right];input[right]=input[left];input[left]=temp;}
            else{input[start]=input[right];input[right]=p;}
        }
        return right;
    }

当元素基本有序或者逆序时,快排会得到最坏时间复杂度,O(n^2)。如果比较顺利,快排两边比较均衡,则运行速度将大大提升,达到O(nlogn),而快排的平均时间复杂度和最佳表现接近,也是O(nlogn)。快排是内部排序算法中平均性能最优的排序算法。

对于空间复杂度,快排是递归的,故需要一个递归工作栈,最坏O(n),平均O(logn)。

因为存在左右指针相互交换的情况,所以快排是不稳定的。

关于对多个有序数组合并排序,看mergesort,时间复杂度O(m+n)

https://www.cnblogs.com/kkun/archive/2011/11/23/merge_sort.html

https://www.cnblogs.com/chengxiao/p/6194356.html

关于快速排序时间复杂度证明:

https://www.cnblogs.com/fengty90/p/3768827.html

常见排序算法总结:插入排序,希尔排序,冒泡排序,快速排序,简单选择排序以及java实现的更多相关文章

  1. 八大排序算法的python实现(八)简单选择排序

    代码: #coding:utf-8 #author:徐卜灵 # L = [6, 3, 2, 32, 5, 4] def Select_sort(L): for i in range(0,len(L)) ...

  2. Python实现八大排序(基数排序、归并排序、堆排序、简单选择排序、直接插入排序、希尔排序、快速排序、冒泡排序)

    目录 八大排序 基数排序 归并排序 堆排序 简单选择排序 直接插入排序 希尔排序 快速排序 冒泡排序 时间测试 八大排序 大概了解了一下八大排序,发现排序方法的难易程度相差很多,相应的,他们计算同一列 ...

  3. 冒泡排序与简单选择排序——Java实现

    1.冒泡排序 1)原理说明:反复遍历要排序的数列,一次比較两个元素,假设他们的顺序错误就把他们交换过来.走訪数列的工作是反复地进行直到没有再须要交换,也就是说该数列已经排序完毕. 2)代码实现: pa ...

  4. Java学习笔记day07_琐碎知识_水仙花数_ASCII码_冒泡排序_简单选择排序_折半查找

    琐碎知识: 水仙花数, ASCII码, 冒泡排序, 简单选择排序, 折半查找 1.水仙花数 每位数的平方的和等于本身. 如100到999之间的水仙花数满足: 个位的平方+十位的平方+百位的平方 = 本 ...

  5. 冒泡排序Vs直接选择排序

    什么是排序?为什么要使用排序?事实上我们生活中处处都用到了排序.拿字典来说,如今,我们要在字典中查找某个字(已经知道这个字的读音),首先.我们须要依据这个字的读音,找到它所所在文件夹中的位置,然后依据 ...

  6. python实现排序算法 时间复杂度、稳定性分析 冒泡排序、选择排序、插入排序、希尔排序

    说到排序算法,就不得不提时间复杂度和稳定性! 其实一直对稳定性不是很理解,今天研究python实现排序算法的时候突然有了新的体会,一定要记录下来 稳定性: 稳定性指的是 当排序碰到两个相等数的时候,他 ...

  7. C数据结构排序算法——直接插入排序法用法总结(转http://blog.csdn.net/lg1259156776/)

    声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 排序相关的的基本概念 排序:将一组杂乱无章的数据按一定的规律顺次排列起来. 数据表( data list): ...

  8. java排序算法(四):冒泡排序

    java排序算法(四):冒泡排序 冒泡排序是计算机的一种排序方法,它的时间复杂度是o(n^2),虽然不及堆排序.快速排序o(nlogn,底数为2).但是有两个优点 1.编程复杂度很低.很容易写出代码 ...

  9. 冒泡排序算法和简单选择排序算法的js实现

    之前已经介绍过冒泡排序算法和简单选择排序算法和原理,现在有Js实现. 冒泡排序算法 let dat=[5, 8, 10, 3, 2, 18, 17, 9]; function bubbleSort(d ...

随机推荐

  1. js 实现 复制 功能 (zeroclipboard)

    #复制功能因访问权限和安全问题, 被浏览器禁了# 我要实现的功能:点击复制按钮,复制浏览器的当前页面地址,加上用户选择的参数(用户查找过滤),直接将该链接发给别人,点击打开就是对应的查找结果而不是默认 ...

  2. Redis原理及使用

    一:原理介绍 1:什么是redis?  Redis 是一个基于内存的高性能key-value数据库. 2:Reids的特点Redis本质上是一个Key-Value类型的内存数据库,很像memcache ...

  3. java爬虫系列第四讲-采集"极客时间"专栏文章、视频专辑

    1.概述 极客时间(https://time.geekbang.org/),想必大家都知道的,上面有很多值得大家学习的课程,如下图: 本文主要内容 使用webmagic采集极客时间中某个专栏课程生成h ...

  4. ConcurrentHashMap1.8源码分析

    文章简介 想必大家对HashMap数据结构并不陌生,JDK1.7采用的是数组+链表的方式,JDK1.8采用的是数组+链表+红黑树的方式.虽然JDK1.8对于HashMap有了很大的改进,提高了存取效率 ...

  5. 使用 MSIX 打包 DotNetCore 3.0 客户端程序

    如何你希望你的 WPF 程序能够以 Windows 的保护机制保护起来,不被轻易反编译的话,那么这篇文章应该能帮到你. 介绍 MSIX 是微软于去年的 Windows 开发者日峰会 上推出的全新应用打 ...

  6. 逐个使用C++11新特性

    C++11 auto & decltype auto:根据变量初始值来推导变量类型,并用右值初始化变量. decltype:从表达式推导出类型,并将变量定义为该类型,但不用表达式的值初始化该变 ...

  7. Socket 通讯原理

    Socket是什么呢? Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后 ...

  8. C#枚举(Enum)小结

    枚举概念 枚举类型(也称为枚举)提供了一种有效的方式来定义可能分配给变量的一组已命名整数常量.该类型使用enum关键字声明. 示例代码1 enum Day { Sunday, Monday, Tues ...

  9. SQL Server 增加链接服务器

    exec sp_addlinkedserver '名称' , '' , 'SQLOLEDB' , '10.102.29.xxx' exec sp_addlinkedsrvlogin '名称' , 'f ...

  10. easyui实现分页

    主要参考官方的文档,欢迎评论 1.集成easyui,下面是我的引入方式,我引入到了head.html 每次只要引入该页面就可以了. <!-- easyui样式支持 --><link ...