关于数据结构排序学习

概述

排序算法分类

在我们日常处理数据的时候,排序是最经常用到,如果一层层的嵌套for循环会让代码的效率变得非常低,这个时候,我们就要借用排序的理念来优化我们的代码,目前有十个比较经典的排序算法:
1、冒泡排序
2、快速排序
3、插入排序
4、希尔排序
5、选择排序
6、堆排序
7、归并排序
8、计数排序
9、桶排序
10、基数排序
具体的如下图所示:(图转自https://www.cnblogs.com/onepixel/articles/7674659.html

image.png

非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。
线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。

算法复杂度
image.png

稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。

1、冒泡排序

冒泡排序:
1、设置个标识位;
2、不断的比较前后两个元素的大小,如果前一个值比后一个值大就交换两个元素的位置,改变标识位的值,否则不变,并且指针指向后一个元素;
3、重复2的步骤,当遇到标识位没有变化或者循环结束,排序完成
代码块:

    boolean flag = true;
    for (int i = 0;i<data.length;i++){
        for (int j = 0;j<data.length-i-1;j++){
            if (data[j]>data[j+1]){
                int temp = data[j+1];
                data[j+1] = data[j];
                data[j] = temp;
                //转换标识位
                flag = false;
            }
        }
        if (flag)
        //如果没有进行转换,flag=true,跳出循环
            break;
        else
            flag = true;
    }

2、快速排序

快速排序:(申明两个指针,head指向链表头,tail指向链表尾)
从链表尾tail开始,当tail指向元素的值比head大的时候,则tail往前移动一个位置,当tail指向元素的值小于head指向元素的值得时候,交换head和tail所指元素的值,然后从head继续开始比较,如果head指向元素的值大于tail指向的值,则交换head和tail所指元素的值,否则head往后移动一个位置,直到head=tail的时候,结束第一轮循环。
其实head第一次指向的值,我们称它为一个基准,然后通过上面的思路循环,最后可以做到这个基准值的左边是小于它的,基准值得右边是大于它的,这样才算完成第一轮的循环,而左右两个区间还是无序数列,需要通过递归的方式,一步步对这些无序数列进行排序。
代码块:

    public void kpSort(int[] source,int start,int end){
        //保留大区间的头尾
        int i = start;
        int j = end;
        int value = source[start];
        //当i!=j的时候,说明本轮循环还未结束
        while (i<j){
            //从链表尾开始循环
            while (source[j]>=source[i] && j>i)
                j--;
            source[i] = source[j];
            source[j] = value;

            //从链表头开始循环
            while (source[i]<=source[j] && i<j)
                i++;
            source[j] = source[i];
            source[i] = value;
        }
         //递归左右两个无序区间的数列
            if (i>start)kpSort(source,start,i-1);
            if (j<end)kpSort(source,j+1,end);
    }

3、插入排序

插入排序的思路:
1、从当前排序数列的第二个元素开始,比较第二个元素与第一个元素的大小,如果小于则两个元素互换位置
2、当进行步骤一之后,会将指针继续指向下一个元素,指针前的元素都是排好序的了,那么指针指向下一个元素,比较指针指向元素的值与前一个值的大小,如果大于则不变,如果小于则将前一个元素后移一位,然后继续比较移动元素前一位元素的值,直到找到比指针指向元素值来的小的值,则插到该值的后边
3、循环第二步操作,直到数列尾
这个插入排序还是很好理解的,也就是当前有个排好序的数列,如果你要插入一个元素,你就需要从后面一个个的往前进行比较,比较的过程中还要将比当前元素大的值往后移动一位,直到找到符合条件的位置,才可以插入,这样当前数列还是一个有序数列,以此类推循环到数列尾。
代码块:

    int nowValue;
    int preIndex;
    for (int i = 1 ;i<data.length;i++){
        nowValue = data[i];
        preIndex = i-1;
        //如果需要比较的前一个元素的索引比0来的大
        //或者preIndex所指元素的值大于需要插入的值则后移比较的元素
        while (preIndex>=0 && data[preIndex]>nowValue){
            data[preIndex+1] = data[preIndex];
            preIndex--;
        }
        data[preIndex+1] = nowValue;
    }

4、希尔排序

希尔排序:希尔排序其实是插入排序的改进版,因为在上面插入排序的过程中,我们发现这个排序需要从后面一直往前进行比较,也就是如果值特别小的情况下,可能还需要进行n次的比较才能进入下一次的插入,这样效率上很慢,在这个基础上,这个排序算法增多了一个区间,也就是保证左边区间的值在固定规则上比右边来的小,这样就减少了插入排序可能比较的时间。
代码块:

    //间隔区间的值
    int grep = data.length/2;
    int j;
    for (;grep>0;grep/=2){
        for (int i = grep;i<data.length;i++){
            //nowValue表示当前需要比较元素的值
            int nowValue = data[i];
            //将当前元素与相隔grep元素的前一个元素进行比较,找到交换点
            for (j = i-grep;j>=0 && data[j]>nowValue;j-=grep){
                data[j+grep] = data[j];
            }
            data[j+grep] = nowValue;
        }
    }

这个代码块最需要注意的其实是最里层的循环,这个循环有两个跳出循环的条件
1、j>=0:这个条件表示当前指针指向的元素没有再相隔grep个元素的前一个元素了,也就是当j为负数的时候,跳出循环,将比较的值赋值给第一个元素。
2、data[j]>nowValue:这个条件又是怎么回事呢,你会发现我们一开始i是从比较小的值开始的,也就是从数组前面的某个位置开始跟相隔grep元素进行比较,在外层,i的值一直增加,也就意味着后面j一开始的值也会变得越来越大,然后我们要比较相隔grep元素,可能就不止一个了,比如你当前元素可能比相隔2*grep的元素还小,那么就不可能直接交换了,基于此也可以看出,这个交换也是基于插入排序的,按照插入排序的思路,需要一步步的往前进行比较,直到找到比当前元素还小的值,就在这个元素后的grep替换元素的值。这个时候,我们应该知道跳出循环后为啥还有个data[j+grep] = nowValue;语句了,因为如果你要插入的元素刚刚好是第一个元素的情况下(相隔grep前没有元素),你跳出了循环,就没可能给第一个元素进行赋值操作,那么最后得到的结果也就是错的,这个地方就是为了解决这种情况。

5、选择排序

选择排序也是一个非常容易理解的排序算法
1、将数组第一个值作为最小值,然后遍历整个数组,找到真正的最小值,然后跟这个值进行替换
2、循环一的步骤,直到数组尾

代码块:

    int minValue;
    int index;
    for (int i = 0 ;i<data.length;i++){
        minValue = data[i];
        index = i;
        for (int j = i+1;j<data.length;j++){
            if (data[j]<minValue){
                minValue = data[j];
                index = j;
            }
        }
        data[index] = data[i];
        data[i] = minValue;
    }

关于为什么说选择排序也是不稳定排序呢,我举个例子,需要排序{5,4,3,5,2}
发现了什么,我第一次交换的时候,把第一个元素5的值交换到数组最后,然后再继续进行交换的操作,但是数组第四个元素5的值会一直在第一个元素5的值得前面,也就是相同元素值排序之后无法保证前后顺序,所以说选择排序是不稳定的排序。

6、堆排序

堆排序的结构类似于完全二叉树的结构,有两种方式进行排序,一种是小根堆,小根堆的根是完全二叉树中最小的,可以取最小的值保存为数组的第一个元素,以此递归,直到数组结束;另一种是大根堆,大根堆正好和小根堆相反,大根堆的根是完全二叉树中最大的,可以取最大的值保存为数组的最后一个元素,以此递归。
我以大根堆举例,一个完整的节点有它的本身节点、父节点、左孩子、右孩子,而大根堆的结构是要保证父节点肯定比本身节点来的大,并且本身节点、左孩子、右孩子三者中,本身节点的值也必须是最大的,只有实现这的规则才算的上是大根堆。
代码块如下:

    public static void heapSort(int[] a) {
        int i;
        //i的第一个取值很重要,a.length/2-1的值代表有叶子结点的最后一个根节点
        //获取到这个节点,我们就可以一层层的往上进行调整
        for (i = a.length / 2 -1; i >= 0; i--) {// 构建一个大顶堆
            adjustHeap(a, i, a.length - 1);
        }
        for (i = a.length-1; i > 0; i--) {// 将堆顶记录和当前未经排序子序列的最后一个记录交换
            int temp = a[0];
            a[0] = a[i];
            a[i] = temp;
            adjustHeap(a, 0, i - 1);// 将a中前i-1个记录重新调整为大顶堆
        }
    }  


    public static void adjustHeap(int[] a, int i, int len) {
        int temp, j;
        temp = a[i];
        //2*i+1代表当前节点的左孩子
        for (j = 2 * i+1; j < len; j = j*2+1) {// 沿关键字较大的孩子结点向下筛选
            //如果左孩子比右孩子小,则将指针指向右孩子
            if (j < len && a[j] < a[j + 1])
                ++j; // j为关键字中较大记录的下标
            //如果左右孩子中最大的一个值不大于根节点的值,则直接跳出
            if (temp >= a[j])
                break;
            //将左右孩子值中最大的一个并且大于根节点的值赋值给当前的根节点
            //将指针移到左右孩子中最大的一个,往值大的左或者右二叉树进行调整
            a[i] = a[j];
            i = j;
        }
        //将最初始指向的值赋值给当前i指向的节点
        a[i] = temp;
    }

代码块中需要注意几个点:
第一,我们建立大顶堆需要获取到带有叶子结点的最后一个根节点,从这个根节点开始往回进行调整,而这个节点的值=数组长度/2-1
第二,当我们比较完当前节点和左右孩子中最大的一个值得时候,同时也要保证左右孩子的下一层结构也符合大顶堆的结构,这样就需要一层层的往下进行遍历,直到没有左右孩子为止。

7、归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
1、不断分裂成子序列,递归到左边界大于等于右边界返回;
2、将已经排序好的子序列不断合并,直到没有子序列,返回结果。

代码块:

    public int[] sort(int[] source, int left, int right) {
        if (left >= right)
            return source;

        int mid = (right + left) / 2;
        //排序左边区域
        sort(source, left, mid);
        //排序右边区域
        sort(source, mid + 1, right);
        //返回合并的值
        return merge(source, left, mid, right);
    }

    public int[] merge(int[] source, int left, int mid, int right) {
        //申请个临时的数组空间存储排序好的子序列
        int[] temp = new int[right - left + 1];
        //左子序列第一个元素指针
        int i = left;
        //右子序列第一个元素指针
        int j = mid + 1;
        //临时数组空间索引
        int index = 0;
        //对左右子序列进行比较大小后插入到临时数组
        while (i <= mid && j <= right) {
            if (source[i] > source[j])
                temp[index++] = source[j++];
            else
                temp[index++] = source[i++];
        }

        //如果i<=mid说明左子序列已经全部插入到临时数组空间
        //将右子序列剩下的值插入到临时数组空间的尾部
        while (i <= mid)
            temp[index++] = source[i++];

        //跟上面正好相反,右子序列已经全部插入,左子序列剩余部分插入操作
        while (j <= right)
            temp[index++] = source[j++];

        //将临时数组数据覆盖本次左右子序列所涉及的数组范围
        for (int x = 0; x < temp.length; x++)
            source[x + left] = temp[x];

        return source;
    }

在只有十个元素的数据量情况下排序费时情况

冒泡排序

image.png

选择排序

image.png

插入排序

image.png

希尔排序

image.png

归并排序

image.png

快速排序

image.png

堆排序

image.png

总结

本次学习了七种排序算法,受益良多,从上面的时间也可以看出,在数据量比较少的时候,归并排序、快速排序、堆排序其实费时还比较长,所以应该根据不同情况来选择适合的排序算法。

参考链接:
https://www.cnblogs.com/onepixel/articles/7674659.html
https://blog.csdn.net/jianyuerensheng/article/details/51263453
https://blog.csdn.net/qq_27680317/article/details/78549875?utm_source=blogxgwz0
https://blog.csdn.net/qq_39776901/article/details/77387923?utm_source=blogxgwz2
https://blog.csdn.net/min996358312/article/details/68068689?utm_source=blogxgwz1

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

推荐阅读更多精彩内容

  • 一些概念 数据结构就是研究数据的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义相应的运算,而且确保经过这...
    Winterfell_Z阅读 5,654评论 0 13
  • 1 初级排序算法 排序算法关注的主要是重新排列数组元素,其中每个元素都有一个主键。排序算法是将所有元素主键按某种方...
    深度沉迷学习阅读 1,391评论 0 1
  • 痛苦的背后 是无尽的欲望 是你的 或者是别人的 恶魔掌控了噩梦 虚弱了身体 疲惫了心灵 赶走了快乐 你看到 或者没...
    壶上春秋阅读 224评论 0 0
  • 晨练第413天:深蹲100个 读经第293天: 诗经·考槃 考槃在涧,硕人之宽。独寐寤言,永矢弗諼。 考槃在阿,硕...
    山缘有约阅读 157评论 0 0
  • 皇尊御一品:凯旋亲王【太皇太后的曾孙子也是皇上的大儿子】王妃 皇尊从一品:凯翼亲王【太皇太后的曾孙子也是皇上的二儿...
    女二号叶以澜阅读 5,622评论 1 2