数据结构基础(五)排序

简单选择排序

对于长度为n的数组a

  1. 找出后n个数(下标0~n-1)中最小的数,与a[0]交换
  2. 找出后n-1个数(下标1~n-1)中最小的数,与a[1]交换
  3. 找出后n-2个数(下标2~n-1)中最小的数,与a[2]交换
  4. 依次类推



    即每次找到剩余数组中最小的元素放在前面,代码如下:

    /**
     * 简单选择排序,O(n^2)
     * @param a
     */
    public static void selectSort(int[] a) {    
        for (int i = 0; i < a.length-1; i++) {
            int k=i; //剩余数组中最小元素下标
            for (int j = i+1; j < a.length; j++) {
                if(a[k]>a[j])
                    k=j;
            }
            //交换a[k]与a[i]
            swap(a, k, i);
        }
    }

冒泡排序

对于长度为n的数组a

  1. 对前n个数进行冒泡式交换(从0到n-1,如果相邻的两个数,a[i]>a[i+1]则交换),最后使a[n-1]为前n个数中最大数
  2. 对前n-1个数进行冒泡式交换,最后使a[n-2]为前n-1个数中最大数
  3. 对前n-2个数进行冒泡式交换,最后使a[n-3]为前n-2个数中最大数
  4. 依次类推


即每次找出最大的数放在后面,与选择排序相反。代码如下:

    /**
     * 冒泡排序,O(n^2)
     * @param a
     */
    public static void bubbleSort(int[] a) {
        for (int i = 0; i < a.length; i++) {
              //一次排序后使尾部的数为最大的
            for (int j = 0; j < a.length-i-1; j++) {
                if(a[j]>a[j+1]){
                    swap(a, j, j+1);
                }
            }
        }
    }

插入排序

对于无序序列,假设前i个数为有序的,将第i+1个数插入前i个数中,使其成为i+1个有序序列。
如图,对于长度为n的数组a

  1. 前1个数为有序序列,将a[1]插入其中,形成2个数的有序序列
  2. 前2个数为有序序列,将a[2]插入其中,形成3个数的有序序列
  3. 依次类推,最终前n个数都变成有序序列,排序成功


代码如下:

    /**
     * 插入排序,O(n^2)
     * @param a
     */
    public static void insertSort(int[] a) {
        int temp,j;
        //将a[i]插入前面的有序序列中
        for (int i = 1; i < a.length; i++) {
            temp=a[i];
            //从有序序列尾部递减,如果比a[i]大则向后放置
            for (j=i-1; j >=0&&a[j]>temp; j--) {
                a[j+1]=a[j];
            }
            //将a[i]插入有序序列
            a[j+1]=temp;
        }
    }

快速排序

稍微复杂点的算法,运用了“分治”的思想
对于无序序列,随便找出其中一个数作为“基准数”,然后将序列中小于它的数放在其左边,大于它的数放在它右边。再对左右区间重复此过程,直到区间只有一个数,排序成功。具体过程如下图:


过程很好理解,但难点在于如何实现这个左右区间呢?
我们设置两个变量i和j,分别指向序列的头和尾。让i递增,j递减。每次找到比基准数大的a[i]和比基准数小的a[j]后便进行交换。一直到i=j,再最后一次交换基准数与a[j]。此时便实现了一个基准数左边比它小,右边比它大的序列。
需要注意的是,每次必须是j先递减。原因是这样最后一次交换时,保证基准数交换的是比它小的a[j]。
没有画图来讲述整个过程,还是不懂可以看:坐在马桶上看算法:快速排序

下面是代码:

    /**
     * 快速排序,O(nlogn)
     * @param a
     * @param left 
     * @param right
     */
    public static void quickSort(int[] a, int left, int right) {
        if (left > right)
            return;

        int middle = a[left]; // 基准数
        int i = left;
        int j = right;
        while (i != j) {
            // 从最右递减找出小于基准数的数
            while (a[j] >= middle && i < j) {
                j--;
            }
            // 从最左递增找出大于基准数的数
            while (a[i] <= middle && i < j) {
                i++;
            }
            // 交换
            swap(a, i, j);
            
        }
        // 将基准数和最后小于它的数进行交换
        a[left] = a[i];
        a[i] = middle;
        // 以基准数划分左右区间,再对左右区间进行快排
        quickSort2(a, left, i - 1);
        quickSort2(a, i + 1, right);
    }

由于a[left]我们已经用middle保存了,所以也可以利用a[left]来作为中间变量交换a[i]和a[j],优化后的代码如下:

    /**
     * 快速排序,O(nlogn)
     * @param a
     * @param left
     * @param right
     */
    public static void quickSort(int[] a,int left,int right) {
        
        if(left>right)
            return ;
        
        int middle=a[left];  //基准数
        int i=left;
        int j=right;
        while (i != j) {
            //从最右递减找出小于基准数的数
            while (a[j] >= middle && i < j) {
                j--;
            }
            a[i]=a[j];
            //从最左递增找出大于基准数的数
            while (a[i] <= middle && i < j) {
                i++;
            }
            a[j]=a[i];
        }
        //将基准数和最后小于它的数进行交换
        a[i]=middle;
        //以基准数划分左右区间,再对左右区间进行快排
        quickSort(a, left, i-1);
        quickSort(a, i+1, right);
    }   

堆排序

堆其实就是一个完全二叉树,分为最大堆(父结点>=子结点)、最小堆(父结点<=子结点)。一般用数组来存储,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2i + 1和2i + 2。

堆的两个操作

要明白堆排序,就要先明白堆的添加和删除原理。


  • 在堆数组尾部添加元素时,需要不停将该元素与其父结点进行对比交换,类似于元素在“上升”。也就是使新添加的元素插入一个有序的序列中,形成一个新的有序堆序列
  • 堆删除元素时,总是先删除根结点,然后将最后一个元素移到根结点,与子结点对比交换,类似于元素在“下沉”。最终形成新的有序堆序列。

好的,明白堆的添加删除后,我们就可以解决以下问题:

  1. 现在给了一个无序数组,如何将其变成堆数组?
    我们可以对数组反向迭代,从末尾开始每个元素进行“下沉处理”,这样便保证迭代完成后的数组是一个最小堆
  2. 如何利用堆进行排序
    由于最小堆的根结点永远是所有元素中最小的。我们可以进行删除操作。依次取出顶点的结点,这些取出的顶点最终就形成了一个递增序列。

堆排序代码

注意:下面代码为了方便,每次取出堆的顶点放入数组末尾,最终形成了一个递减序列。也可以将取出的元素新放入另外一个数组,形成递增序列。

public class Heap {
    private int[] a; //堆数组
    private int n; //结点个数

    /**
     * 构造一个堆化数组
     * @param a 数组
     * @param n 数组中元素个数
     */
    public Heap(int[] a,int n) {
        this.a = a;
        this.n=n;
        for (int i = (n-2)/2; i >=0; i--) {
            minHeapDown(i,n);
        }
    }
    
    /**
     * 最小堆,对index下标结点进行“上浮”处理
     * @param index
     */
    public void minHeapFixUp(int index) {
        int temp=a[index];
        //index的父结点
        int fIndex=(index-1)/2;
        //未到顶点时继续
        while(fIndex>=0&&index>0){
            //如果该结点大于父结点,直接退出循环
            if(a[fIndex]<temp)
                break;
            //如果该结点小于父节点,则交换结点
            a[index]=a[fIndex];
            index=fIndex;
            fIndex=(index-1)/2;
        }
        a[index]=temp;
    }
    
    /**
     * 最小堆,对index下标结点进行“下沉”处理
     * @param index
     * @param n   堆中元素个数
     */
    public void minHeapDown(int index,int n) {
        int temp=a[index];
        int sIndex=2*index+2; //子结点下标为2*index+1、2*index+2
        while(sIndex<=n-1){
            //找出左右子结点中最小的一个
            sIndex=(a[sIndex]>a[sIndex-1])?sIndex-1:sIndex;
            //如果子结点比该结点大,退出循环
            if(a[sIndex]>temp)
                break;
            //如果子节点比该结点小,进行交换
            a[index]=a[sIndex];
            index=sIndex;
            sIndex=2*index+1;
        }
        a[index]=temp;
    }
    
    /**
     * 新添加一个结点在堆的下标index
     * @param index
     * @param content
     */
    public void add(int index,int content) {
        a[index]=content;
        minHeapFixUp(index);
    }
    
    /**
     * 堆的删除操作,即删除头结点
     */
    public void remove() {
        Sort.swap(a, 0, n-1);
        minHeapDown(0,n);
    }
    
    /**
     * 堆排序,,O(nlogn)
     */
    public void minHeapSort(){
        for (int i = n-1; i >=1; i--) {
            Sort.swap(a, 0, i);
            minHeapDown(0,i);
        }
    }
    
    public static void main(String[] args) {
        int[] a={8,9,6,4,7,5,3,4,1};
        Heap heap=new Heap(a, a.length);
        heap.minHeapSort();
        System.out.println(Arrays.toString(a));
    }
}/**output:
[9, 8, 7, 6, 5, 4, 4, 3, 1]
*/

几种排序算法时间复杂度的比较


排序算法严格来说没有最快,各有各的适用环境。
如果非要找出一个最快,笔者通过上网查资料,发现大多都推荐的是桶排序。

参考
排序(本文图片出自此网站)
白话经典算法系列之七 堆与堆排序

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

推荐阅读更多精彩内容

  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,170评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,729评论 0 15
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,245评论 0 2
  • 第一章 绪论 什么是数据结构? 数据结构的定义:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 第二章...
    SeanCheney阅读 5,760评论 0 19
  • 概述排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的...
    Luc_阅读 2,259评论 0 35