冒泡排序、插入排序、快速排序、堆排序、归并排序总结

排序在算法学习中占用很重要的地位,也很实用。就用这篇博客来总结一下常用的几种排序算法。

冒泡排序

在水中,大的泡泡会往上浮。
在冒泡排序中,通过不断交换两个相邻的数据,使大的(或小的)数字往数组头部移动。最终将数组全部排序。


image.png

如上图所示
数组 [4,6,7,5]。我们将这个数组降序排列。
首先从数组最后一个数开始,依次与他前一个数比较,如果大于前一个数,交换。这样经过一轮循环。我们将最大的数放到了数组index=0的位置。
然后继续上述操作,将数组中第二大的元素移至index=1的位置。
依次下去,直到完成数组的排序。
代码

public class BubbleSort {
    public void sort(int[] src)throws Exception {
        if(src==null||src.length==0)
            throw new Exception("参数错误");
        for(int i=1;i<src.length-1;i++) {        //循环,将所有数字放到正确的位置
            for(int j=src.length-1;j>=i;j--) {   //将第i大的数字方法正确的位置
                if(src[j]>src[j-1])
                    swap(src,j,j-1);
            }
        }
    }
    
    private void swap(int[] src,int index_a,int index_b) {
        int temp=src[index_a];
        src[index_a]=src[index_b];
        src[index_b]=temp;
    }
    
    public static void main(String[] args) {
        BubbleSort bubbleSort=new BubbleSort();
        int[] src= {90,70,30,80,60,10,40,50,20};
        try {
            bubbleSort.sort(src);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        for(int i=0;i<src.length;i++) {
            System.out.println(src[i]);
        }
    }
}

时间复杂度
最坏的情况,就是数组本来是升序的,要将其变为降序的。共需要进行1+2+3+。。。+n-1=n*(n-1)/2次比较和交换操作,时间复杂度是o(n^2)。没有额外使用空间来存储中间变量,空间复杂度是o(1)。最好的情况,数组已经是排序好的,只需要遍历一遍,时间复杂度是o(n)。

插入排序

如果你打扑克,并且喜欢将手里的牌按顺序排好,那么你一定会插入排序。
在插入排序中,我们维护一个有序数组,每来一个数字,我们将他插入到有序数组中,使该数组仍然有序。直到我们将所有的数字都插入到合适的位置。


image.png

如上图所示,我们在数组前部维护一个有序序列,然后依次将数字插入到有序序列的适合位置,直到所有数字都插入到有序数列中。
代码

public class InsertSort {
    public void insertSort(int[] src)throws Exception {
        if(src==null||src.length==0)
            throw new Exception("参数错误");
        int temp=0;
        int i,j;
        for(i=1;i<src.length;i++) {
            if(src[i]>src[i-1]) {
                temp=src[i];
                for(j=i-1;src[j]<temp&&j>=0;j--) {  //将第I个数组插入到合适的位置
                    src[j+1]=src[j];
                }
                src[j+1]=temp;
            }
        }
    }
    
    public static void main(String[] args) {
        InsertSort insertSort=new InsertSort();
        int[] src= {90,70,30,80,60,10,40,50,20};
        try {
            insertSort.insertSort(src);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        for(int i=0;i<src.length;i++) {
            System.out.println(src[i]);
        }
    }
}

时间复杂度
在最坏的情况下,也就是数组本来是升序的,我们要把它变为降序的,共需要1+2+3+。。+n-1次比较和移动,时间复杂度是o(n^2)。最好情况下,数组本来就是排序好的,只需要遍历一遍就行,时间复杂度是o(n)

快速排序

快速排序的思想是从数组中取一个数,将这个数字放到适合的位置,使它前面的数字比它小(或者大),后面的数字比它大(或者小)。这样就将数组分为两个部分,然后依次对这两个子数组进行相同的操作,直到所有数组都在正确的位置。


image.png

如上图所示,我们使用两个指针,一个指向数组最左,一个指向数组最右。将最左的数作为基准值。先拿基准值和右边指针指向的数组进行比较,如果基准值较小,交换两个指针的值。移动左边指针,再比较基准值和左边的指针指向的数字,重复操作。直到将基准值放到合适的位置上。这就将数组分成了两部分,然后再分为对这两部分做同样的操作,直到所有数组都在正确的位置上。
代码

public class QuickSort {
    public void quickSort(int[] src)throws Exception {
        if(src==null||src.length==0)
            throw new Exception("参数错误");
        partition(src,0,src.length-1);
    }
    
    public void partition(int[] src,int start,int end) {
        if(start>=end)
            return;
        int key=src[start];
        int i=start;
        int j=end;
        while(i<j) {
            while(i<j&&src[j]>=key) 
                j--;
            if(i<j)
                src[i]=src[j];
            while(i<j&&src[i]<=key)
                i++;
            if(i<j)
                src[j]=src[i];
        }
        src[i]=key;
        partition(src,start,i-1);
        partition(src,i+1,end);
    }
    
    public static void main(String[] args) {
        QuickSort quickSort=new QuickSort();
        int[] src= {90,70,30,80,60,10,40,50,20};
        try {
            quickSort.quickSort(src);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        for(int i=0;i<src.length;i++) {
            System.out.println(src[i]);
        }
    }
}

时间复杂度
快速排序的递归中,数组形成了一个二叉树,快速排序的时间复杂度和二叉树的高度有关。在这个二叉树中,每一层的时间复杂度是n,树的高度与key值的选择有关。如果选择的key值恰好能将数组等分成两半,则树的高度是log(n),如果key值只能将数组分成一份,则数的高度是n。所以,快速排序的时间复杂度最好是n*log(n),最坏是n^2。
快速排序优化
如果key值取得不好,那快速排序的时间复杂度最坏可能是n^2。key值取恰好能将数组平分为最好。因此有对于key值得优化方法,如三数区中法(三个数中间值作为key值),和随机法(随机取key值)。


快速排序最好情况,树的高度是log(n),时间复杂度是n*log(n)

快速排序最坏情况,树的高度是n,时间复杂度是n^2

堆排序

堆排序是基于堆这种数据结构的一种排序算法。
堆是一颗完全二叉树,堆有最大堆和最小堆。最大堆中每个父节点都比它的子结点大,最小堆中每个父节点都比它的子结点小。
因此,堆有以下的性质:
1.堆中根结点是最大值或者最小值。
2.将堆层序遍历的结果放入数组中(数组index从0开始),index=a的两个子结点是2a+1和2a+2
在了解了堆以后,我们来说以下堆排序。堆排序中,首先将数组元素构建成堆(最大堆和最小堆),然后将堆中根结点和堆中最后一个结点交换,重新构建堆。直到将所有的元素排序成功。

image.png

代码

public class HeapSort {
    
    public void headSort(int[] src)throws Exception {
        if(src==null||src.length==0)
            throw new Exception("参数错误");
        for(int i=(src.length-2)/2;i>=0;i--) {
            heapAdjust(src,i,src.length-1);
        }
        for(int i=src.length-1;i>0;i--) {
            swap(src,0,i);
            heapAdjust(src,0,i-1);
        }
    }
    
    public void heapAdjust(int[] src,int start,int end) {
        int temp=src[start];
        int j=start*2+1;
        for(j=start*2+1;j<=end;j=j*2+1) {
            if(j<end&&src[j]<src[j+1])
                j++;
            if(temp>=src[j])
                break;
            src[start]=src[j];
            start=j;
        }
        src[start]=temp;
    }
    public void swap(int[] src,int index_a,int index_b) {
        int temp=src[index_a];
        src[index_a]=src[index_b];
        src[index_b]=temp;
    }
    
    public static void main(String[] args) {
        HeapSort heapSort=new HeapSort();
        int[] src= {90,70,30,80,60,10,40,50,20};
        try {
            heapSort.headSort(src);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        for(int i=0;i<src.length;i++) {
            System.out.println(src[i]);
        }
    }
}

时间复杂度
构建堆的时间复杂度是o(n),第a次交换堆顶元素并重建堆的时间复杂度是log(a),共需要交换n-1次。因此,时间复杂度是log(1)+log(2)+...+log(n-1)=nlog(n)。堆排序的时间复杂度对原始数组的排列不敏感,最好最坏平均的时间复杂度都是nlog(n)。

归并排序

快速排序,堆排序都用到了二叉树的高度是log(n)。归并排序也是如此。
假设原始数据长度为n,归并排序先将数据一分为2,长度分别是n/2。然后对子数组继续二分,直到每个数组长度为1。然后对数组两两进行归并和排序,直到恢复原始数据长度。如下图所示。


image.png

上图中,首先对数据进行二分,直到子数组长度为1。然后对数组进行合并,排序,直到恢复原始数组长度。
代码

public class MergeSort {
    public void mergeSort(int[] src)throws Exception {
        if(src==null||src.length==0)
            throw new Exception("参数错误");
        sort(src,0,src.length-1);
    }
    
    public void sort(int[] src,int start,int end) {
        if(start>=end)
            return;
        int mid=(start+end)/2;
        sort(src,start,mid);
        sort(src,mid+1,end);
        merge(src,start,mid,end);
    }
    
    public void merge(int[] src,int start,int mid,int end) {
        int[] temp=new int[end-start+1];
        int index=0;
        int i=start;
        int j=mid+1;
        while(i<=mid&&j<=end) {
            if(src[i]<=src[j]) {
                temp[index]=src[i];
                i++;
                index++;
            }else {
                temp[index]=src[j];
                j++;
                index++;
            }
        }
        while(i<=mid) {
            temp[index]=src[i];
            i++;
            index++;
        }
        while(j<=end) {
            temp[index]=src[j];
            j++;
            index++;
        }
        for(index=0;index<temp.length;index++) {
            src[start+index]=temp[index];
        }
    }
    
    public static void main(String[] args) {
        MergeSort mergeSort=new MergeSort();
        int[] src= {50,10,90,30,70,40,80,60,20};
        try {
            mergeSort.mergeSort(src);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        for(int i=0;i<src.length;i++) {
            System.out.println(src[i]);
        }
    }
}

时间复杂度
归并排序在分解数组的时候时间复杂度是1,在合并数组的时候,时间复杂度是n,并且合并的次数是log(n),因此总的时间复杂度是nlog(n)。并且归并排事件复杂度序对原始数据不敏感,最好,最坏,平均事件复杂度都是nlog(n)。

几种排序算法分析

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