经典排序(一)

声明:该文章为个人学习笔记,非完全原创

总结一下各种经典排序算法,这个也算面试常考的东西了,总结出来方便大家回忆回忆😁,今天还要弄项目,先写到快排,剩下的过几天再写。

一、冒泡排序

1.介绍

基于比较的排序算法,最简单,性能差,即使最好情况时间复杂度也是O(n^2),(可以加一个附加标记改进算法,j),原地排序

2.过程
  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
3.例题

对于一个int数组,请编写一个冒泡排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
测试样例:
[1,2,3,5,2,3],6
[1,2,2,3,3,5]
代码(改良算法,增加了一个标记,当发现序列已经是有序的时直接跳出循环):
import java.util.*;

    public class BubbleSort {
        public int[] bubbleSort(int[] A, int n) {
            // write code here
            int temp;
            int tag=0;
            for(int i =n;i>1;i--){
                
                for(int j = 0;j<i-1;j++){
                    if(A[j]>A[j+1]){
                        tag=1;
                        temp=A[j];
                        A[j]=A[j+1];
                        A[j+1]=temp;
                    }  
                }
                if(tag==0)
                    break;
                tag=0;
            }
            return A;
        }
    }

二、选择排序

1.介绍

常用于数值大键值小的小文件。优点是容易实现,不需要额外空间。

2.过程

寻找序列中最小值-》交换-》对所有元素重复

最好最坏平均时间复杂度都是O(n^2)
由于在CPU中交换比比较所需时间多,选择排序比较多,但和冒泡排序比,交换次数少,所以更快。不稳定性(相同键值的数据可能顺序会变)

3.代码
public class SelectionSort {
public int[] selectionSort(int[] A, int n) {
    for(int i=0;i<n-1;i++)
        for(int j=0;j<n-i-1;j++){
        if(A[j]>A[j+1]){
            int tmp=A[j];
            A[j] = A[j+1];
            A[j+1] =tmp;
        }
    }
    return A;
}  
}

三、插入排序

1.介绍

一种简单有效的比较排序算法。
优点:实现简单,数据量少时效率高,虽然最坏情况时间复杂度也是O(n^2),但实际运行效率优于前两者(平均比较n²/2,交换n²/4次,最坏情况加倍),稳定性,原地(额外空间O(1)),即时。适合数据几乎都已经排序或输入规模较小时使用。最好情况O(n),对于部分有序的输入来说几乎就是线性排序(如果每个元素调整距离最大为k,即可令序列有序,且k相对序列长度很小,此时可认为几乎有序,此时时间复杂度O(kn))。

    public class InsertionSort {
        public int[] insertionSort(int[] A, int n) {
            int i, j, temp;          
            for(i = 1; i < n; i++){
                temp = A[i];
                for(j = i; j > 0 && A[j - 1] > temp; j-- ){
                    A[j] = A[j - 1];
                }
                A[j] = temp;
            }          
            return A;
        }
    }

上述三个算法都是O(n^2)级别算法,下面介绍O(n*logn)级别算法

四、归并排序

时间复杂度最好最坏平均都是O(nlogn),但是空间复杂度O(n),稳定。网上有文章说可以把空间复杂度优化到O(1),但是会牺牲时间效率。其实算法优化也是一种时间和空间的权衡,用空间换时间或用时间换空间。

归并排序使用分治思想,是建立在归并操作(merge)上的一种有效的排序算法。注意下面代码中归并操作的做法,有一些题可能不是归并排序,但是利用归并操作的这个思想可以很好的解决。

大致过程如下:
将待排序序列R[0...n-1]看成是n个长度为1的有序序列,将相邻的有序表成对归并,得到n/2个长度为2的有序表;将这些有序序列再次归并,得到n/4个长度为4的有序序列;如此反复进行下去,最后得到一个长度为n的有序序列。即先把数组分解成n个小序列,再两两组合(二路归并),要求组合后的新序列在序列内有序。

与快排相似,也是通过递归实现。不过是先递归(分解),再归并(组合)。归并过程为:
我们每次从两个列表开头元素选取较小的一个,直到某一个列表到达底部,再将另一个剩下部分顺序取出。看代码更好理解。下面上代码:

import java.util.*;
 
public class MergeSort {
    public int[] mergeSort(int[] A, int n) {
        sort(A,0,n-1);
        return A;
    }
    //分割数组
    public void sort(int[] A,int l,int r){
        if(l<r){
            int mid;
            mid=(l+r)/2;
            sort(A,l,mid);
            sort(A,mid+1,r);
            merge(A,l,mid,r);
        }
    }
    //归并
    public void merge(int[] A,int l, int mid, int r){
        int i=l;
        int j=mid+1;
        int k=l;
        int t;
        int[] temp = new int[A.length];
        while(i<=mid&&j<=r){
            if(A[i]<=A[j])
                temp[k++]=A[i++];
            else
                temp[k++]=A[j++];
        }
        while(i<=mid){
            temp[k++]=A[i++];
        }
        while(j<=r)
            temp[k++]=A[j++];
        for(t=l;t<=r;t++)
            A[t]=temp[t];
         
    }
}

五、快排

基于比较的著名排序算法,是分治的一个实例,计算复杂度需要用分治法主定理。

1.算法:
    a,如果数组中仅有一个元素或没有元素需要排序,则返回(递归返回条件)
    b,选择一个元素作枢轴(pivot),把数组分为,小于pivot的元素,大于的两部分(划分)
    c,对两部分递归调用该算法。(递归)
2.性能

时间复杂度与上面的相比最好,平均O(nlogn);最坏O(n²)(发生在数组已排序且选最后一个元素作枢轴的情况)。空间复杂度O(logn)~O(n²),因为是递归算法,需要利用栈空间保存。不稳定(可举例证明)。

3.枢轴的选择和优化

a、若选择数组最左边或最右边的元素作枢轴,可能由于非均衡划分导致快排最坏情况发生,所以不好。
b、随机选择枢轴元素可以保证每个元素被选中概率相等,确保划分在平均情况下均衡,防止最坏情况发生。
c、随机选枢轴只是令划分在平均情况下均衡。换句话说就是减小了最坏情况发生的概率,但实际上最坏情况时间复杂度还是O(n²)。我们可以想到如果能每次选所有元素的中位数作为枢轴,就能保证每次划分都是均衡的。但显然这样做是不可能的,因为寻找数组所有元素的中位数的时间开销太大了。一个常用的方法是从数组中随机选择3个元素,取其中位数作为枢轴。

4.代码
    import java.util.*;
    
    public class QuickSort {
        public int[] quickSort(int[] A, int n) {
            // write code here
            sort(A,0,n-1);
            return A;
        }
        void sort(int[] A,int l,int r){
            int pivot;
            if(l<r){
                pivot=partition(A,l,r);
                sort(A,l,pivot-1);
                sort(A,pivot+1,r);
            }
        }
        int partition(int[] A,int l,int r){
            int i=l;
            int j=r;
            int t=l-1;
            Random rand= new Random();
            int p=l+rand.nextInt(r-l+1);
            swap(A,r,p);
            for(;i<r;i++){
                if(A[i]<A[p]){
                    swap(A,i,++t);
                }
            }
            swap(A,r,++t);
            return t;
        }
        void swap(int[] A,int a,int b){
            int temp = A[a];
            A[a] = A[b];
            A[b] = temp;
        }
    }

六、希尔排序

希尔排序,又叫缩小增量排序(看到后面你就知道为什么这么叫了),由Donnald Shell提出而得名。是个不稳定算法(不理解的话可以举个例子)。
该算法本质上就是一个泛化的插入排序,可以视作直接插入排序的改良。因为插入排序在序列几乎有序时效率很高,希尔排序通过先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的。

希尔排序最好情况时间复杂度为O(n),平均和最坏情况复杂度取决于步长序列(增量序列)的选择。希尔排序最重要的也是步长选择这一步。

Donald Shell 最初建议步长选择为n/2,并且对步长取半直到步长达到 1。虽然这样取可以比O(n^2)类的算法(插入排序)更好,但这样仍然有减少平均时间和最差时间的余地。 可能希尔排序最重要的地方在于当用较小步长排序后,以前用的较大步长仍然是有序的。

| 步长序列 | 最坏情况时间复杂度 |
| ------------- |:-------------:| -----:|
| n/(2^i) | O(n²) |
| 2i*3i | O(nlog²n) |
已知的最好步长序列由Marcin Ciura设计(1,4,10,23,57,132,301,701,1750,…) 这项研究也表明“比较在希尔排序中是最主要的操作,而不是交换。”用这样步长序列的希尔排序比插入排序和堆排序都要快,甚至在小数组中比快速排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。

另一个在大数组中表现优异的步长序列是(斐波那契数列除去0和1将剩余的数以黄金分割比的两倍的幂进行运算得到的数列):(1, 9, 34, 182, 836, 4025, 19001, 90358, 428481, 2034035, 9651787, 45806244, 217378076, 1031612713, …)

代码(步长选择n/(2^i)):

import java.util.*;

public class ShellSort {
    public int[] shellSort(int[] A, int n) {
        int h,i,j,temp;
        for(h=n/2;h>=1;h=h/2){
            for(i=h;i<n;i++){
                temp=A[i];
                j=i;
                for(;j>=h&&A[j-h]>temp;){
                    A[j]=A[j-h];
                    j=j-h;
                }
                A[j]=temp;
            }
        }
        return A;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容

  • 本文分析冒泡、选择、插入、希尔、快速、归并和堆排序,为不影响阅读体验,将关于时间、空间复杂度和稳定性的概念放在博文...
    DeppWang阅读 426评论 0 2
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,170评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,729评论 0 15
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,245评论 0 2
  • 说到黑头,就会提到毛孔粗大,可以说是很多朋友的烦恼,到底黑头毛孔粗大怎么办呢?我是这样做的,大家可以试试哦! 第一...
    爱吃糖果的弟弟阅读 224评论 0 0