6-十大排序篇二

十大排序(2)

今天先学习第二大类排序算法

  • 归并排序
  • 排序排序
  • 希尔排序
  • 堆排序

1.归并排序

sort6.gif
find6.jpg

分析:
利用归并的思想将待排序数据,先划分成一个个小块,在两两合并数据,最后整体有序

举例分析:
假设我们有数据arr[i..k...j],其中的i就是下界,j就是上界,k就是中间位置,我们要让整个数组有序。只需要让数组arr[i..k]有序,arr[k..j]也有序,然后让两个数组合并,那么整体就有序了。

//归并排序
void sort_Merge(int *arr, int low, int high)
{
    int mid;
    if (low < high)
    {
        mid = (low + high) / 2;
        sort_Merge(arr, low, mid);
        sort_Merge(arr, mid+1, high);
        Merge(arr, low, mid, high);
    }
}
//数组合并
void Merge(int *arr, int low, int mid, int high)
{
    int *left, *right,i,j,n,m,k;
    n = mid - low + 1, m = high - mid;
    left = new int[n];
    right = new int[m];
    for (i = 0; i <= n; i++) 
    {
        left[i] = arr[low + i];
    }
    for (i = 0; i < m; i++)
    {
        right[i] = arr[mid + 1 + i];
    }
    i = 0,j = 0,k=low;
    while (i < n && j < m)
    {
        if (left[i] < right[j])
        {
            arr[k++] = left[i++];
        }
        else {
            arr[k++] = right[j++];
        }
    }
    while (i < n)
    {
        arr[k++] = left[i++];
    }
    while (j < m)
    {
        arr[k++] = right[j++];
    }
}

总结:
1.因为归并排序,归并不会打乱相同元素相对位置,所以归并排序是稳定的算法
2.归并排序的最好时间复杂度=最坏时间复杂度=平均时间复杂度=0(nlog n)
3.归并排序性能牛逼,且稳定,但是实际开发很少用,就是因为,归并排序在归并的时候需要消耗内存,其空间复杂度是0(n).

2.快速排序

快速排序的基本思想就是
1.从数列中找到一个基准数字,随便找。
2.进行分区操作,将数列小于基准数字的放在前面,大于基准数字的放后面
3.递归分区,直到每个区域只剩一个数字(一个数字肯定有序)

网上一位大佬用大白话把快速排序描述了,我(太笨拙)就借鉴此人的高超讲解方法,此方法概括为挖坑补坑加分治法

一:初始待排序数组


sort4.jpg

二:

首先基准值是72,i指向下标0,j指向下标5,将基准值看成一个坑,此时下标0位置是一个坑,那么从后面向前找一个小于基准值的数字去填这个坑,找到第一个小于基准值72的是42,所以42放到坑了,此时的i+1,(记住左边坑被填了i+1,,右边坑被填了j-1)所以i指向了1,j还是指向5,且下标5位置变成了一个坑,如下图,就是此时的状态。
sort5.jpg

三:接下来i往后找,找到第一个大于等于72的数字,找到了88,下标是3,所以i停在了3的位置,将88补到坑里,所以下标5的坑里是88,j-1,j变成了4.且i的位置成了新坑,如下图所示


sort6.jpg

四:j继续向前找,找到第一个小于基准值72的,是60,将60放到坑里,i+1,i变成了4,此时i和j相等,分区结束,且新坑位置也是i和j的位置,将基准值放进去,如下图所示


sort7.jpg

上面演示的就是一次分区操作,然后按照分治,直到区间只剩一个元素。想着这个,十分容易写出代码

//快速排序核心
void sort_Qucik(int *arr, int low, int high)
{
    if (low < high)
    {
        int index = partition(arr, low, high);
        sort_Qucik(arr, low, index - 1);
        sort_Qucik(arr, index+1, high);
    }
}
//分区
int partition(int *arr, int low, int high)
{
    int x = arr[low];//第一个数字为分区标价点
    int i = low, j = high;
    while (i < j)
    {
        //从右边开始找小于分区数字的
        while (i < j && arr[j] >= x)
        {
            j--;
        }
        if (i < j)
        {
            arr[i++] = arr[j];
        }
        //从左边开始找大于分区数字的
        while (i < j && arr[i] < x)
        {
            i++;
        }
        if (i < j)
        {
            arr[j--] = arr[i];
        }
    }
    //最终位置访问x
    arr[i] = x;
    return i;
}

总结:
1.最好事件复杂度=平均时间复杂度=0(nlog n)
2.最坏时间复杂度=0(n^2)
3.快速排序是不稳定的算法

3.希尔排序

简介:希尔排序是分组插入排序,是对插入排序的升级。

该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。

举例分析

以n=10的一个数组49, 38, 65, 97, 26, 13, 27, 49, 55, 4为例
每一步的演示有两个图片,上图是划分一组数据,下图是排序后的情况

第一次:step = 10/2 = 5;

所以每一次步长是5,下标0和下标5是一组,下标1和下标6是一组,我用字母相同的表示一组。然后对每一组进行排序(插入排序)排序后的如图所示。
第一次排序后的数组是13,27,49,55,4,49,38,65,97,26

sort8.jpg
sort9.jpg

第二次:step = 5/2 = 2;

所以每一次步长是2,下标0,2,4,6,8是一组,下标1,3,5,7,9是一组,我用字母相同的表示一组。然后对每一组进行排序(插入排序)排序后的如图所示。
第二次排序后的数组是4,26,13,27,38,49,49,55,97,65


sort10.jpg

sort11.jpg

第三次:step = 2/2 = 1;

所以每一次步长是1,下标0,1,2,3,4,5,6,7,8,9是一组。然后对这一组进行排序(插入排序)排序后的如图所示。
第三次排序后的数组是4,13,26,27,38,49,49,55,65,97

sort12.jpg

sort13.jpg
void sort_Shell(int *arr, int len)
{
    int step, i, j,data,index;
    for (step = len / 2; step > 0; step /= 2)
    {
        for (i = 0; i < step; i++)
        {
            //开始插入排序
            for (j = i+step; j < len; j += step)
            {
                data = arr[j];
                index = j - step;
                while (index >= i && data < arr[index])
                {
                    arr[index + step] = arr[index];
                    index = index - step;
                }
                arr[index + step] = data;
            }
        }
    }
}

要想写出这段代码,必须先把上述过程写一遍,对比着写就可以写出来。
总结:
1.最好时间复杂度=最坏时间复杂度=平均复杂度=0(nlog n)
2.因为步长排序,所以希尔排序是不稳定的算法。

4.堆排序

堆排序是借助完全二叉树完成的,在树中,我们讲过,完全二叉树用数组存储(空间不会太浪费)。在讲解堆排序前,我们需要了解什么是堆?

问题:什么是堆?
堆有两种,大堆和小堆,我们分别用概念和图示表达。


sort14.jpg

大根堆就是左图,所有节点总是大于其左右孩子节点的值。
小根堆就是右图,所有节点总是小于其左右孩子节点的值。

堆排序整个详细过程描述

1.我们准备一个数组

array[3,7,8,20,16,17]

2.数组对应的完全二叉树就是下图

sort15.jpg

3.我们先初始化堆,默认初始化为大堆

**初始化堆的关键,从倒数第一个非叶子节点开始调整,按照定义(大于左右孩子),一直调整到根节点停止,初始的大堆就建立好了。
**
注意点1.完全二叉树下标是有特点的,第一个非叶子节点下标是len/2-1(默认下标从0开始),
注意点2.堆中每个节点在调整时,并不是调整一次,要一直调整到叶子上停止


sort16.jpg

4.在初始好的堆上,开始每次将第一个数,与最后一个数交换。然后len-1,(可理解为,将第一个数就是最大的数归入已经排序好的数组里,后面不考虑),开始调整堆,,重复上述操作,直到len=1停止。

交换第一个与最后一个数据,如下图


sort17.jpg

对应数组就是最后一个是最大的,不考虑了,如下图


sort18.jpg

将交换数据后的堆调整,按照步骤3调整规则,如下图


sort19.jpg

下面是整个过程图,如下图


sort20.jpg
//堆排序主体
void sort_Heap(int *arr, int len)
{
    //从非叶子节点到根节点开始调整,构建大顶堆
    for (int i = len / 2 - 1; i >= 0; i--)
    {
        adjust_Heap(arr, i,len);
    }
    print(arr, len);
    //开始堆排序过程
    for (int i = len-1; i > 0; i--)
    {
        int temp = arr[i];
        arr[i] = arr[0];
        arr[0] = temp;
        //交换数据结束,立马调整堆
        adjust_Heap(arr, 0, i);
    }
}

//堆调整代码
void adjust_Heap(int *arr, int index,int len)
{
    //从当前节点开始调整,一直向下走到最后一个非叶子节点
    int maxIndex,temp;
    while (index <= len/2-1)
    {
        maxIndex = index;
        //找出左右孩子和自己三个之间的最大值。
        if (2*index+1 <len && arr[maxIndex]< arr[2 * index+1])
        {
            maxIndex = 2 * index + 1;
        }
        if (2 * index + 2 < len && arr[maxIndex]< arr[2 * index + 2])
        {
            maxIndex = 2 * index + 2;
        }
        //如果找到的最大值不是自己,交换数据,继续向下调整堆.
        if (maxIndex != index)
        {
            temp = arr[maxIndex];
            arr[maxIndex] = arr[index];
            arr[index] = temp;
            index = maxIndex;
        }
        else
        {
            return ;
        }
    }
}

上面所有讲解的都是堆排序,堆还有一个自己重要的两个操作
堆顶的删除和堆插入数据,其实堆排序中这两个思想都已经体现出来了。

堆顶的删除

和堆排序的思想一样,将堆顶元素和最后一个元素交换,调整堆,向下调整,将len-1

堆中插入数据

和堆排序的思想一样,将数据线插入到堆的最后一个位置,len+1,然后第一个从该插入节点父亲开始向上条调整,直到根。

堆数据只有在初始化建堆,是从倒数第一个非叶子点到根,逐个的调整,每个都要向下调整,到叶子停止,在堆的删除中,交换头尾,将根向下调整,在堆的插入时,数据放到最后面,从该数据的父元素开始,向上调整。

堆的应用

查找海量数据中的前k大或者小(实际应用到今日微博点击量前10等)
如果你说微博的工程师,你将如何设计微博每日动态的前10点击量?

步骤一:我们假设数据量很大,超过几亿条点击查询,我们可以将每次浏览的数据遍历一遍。通过哈希值(设计好的)存储在哈希表中,如果这条数据在哈希表中有,在原数据+1,代表访问次数,没有的,根据哈希值,插入到哈希表里面,并设置访问记录为1,这样,我们的哈希表就存储了不重复的浏览记录,同时记录了不同记录的访问量。
步骤二:我们可以设计规模为k的小根堆(此处k为10),我们给规模为k的小根堆全部初始化为0,然后依次遍历散列表。如果哪条数据大于堆顶数据,删除堆顶,将这条数据放到堆顶,向下调整堆。经过一轮遍历散列表,此时的小根堆就保存了前k大数据。
问题:为什么设计的小根堆?我可以通过排序,找出前k大啊.对于静态的数据,我们可以通过数组保存,我们可排序数据,时间复杂度0(nlog n),堆是0(klogn),可以等同,但是微博点击肯定是动态的,那个数据访问量是变化的,那么我们就需要每变化依次,排序依次事件复杂度就剧增o(n*nlogn),且数据假设重复性很低,数组开辟的空间岂不是很大的。而我们用堆,新数据产生是,计算哈希值,如果存在,給对应数据访问量+1,并判断是否入堆。整个过程,我们每次都是计算了哈希值 ,时间复杂度是0(nlogn)。

总结:
1.堆排序因为发生了位置交换,所以不是稳定的排序算法。
2.最好时间复杂度=最坏-平均=0(n logn)

获取完整代码

我分别用C,C++,JAVA三种主流语言编写了完整代码,请大家指导批正,一起学习。

点击查看

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

推荐阅读更多精彩内容

  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,186评论 0 52
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,258评论 0 2
  • 本文首发于我的个人博客:尾尾部落 排序算法是最经典的算法知识。因为其实现代码短,应该广,在面试中经常会问到排序算法...
    繁著阅读 4,574评论 3 119
  • 1 初级排序算法 排序算法关注的主要是重新排列数组元素,其中每个元素都有一个主键。排序算法是将所有元素主键按某种方...
    深度沉迷学习阅读 1,408评论 0 1
  • 一些概念 数据结构就是研究数据的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义相应的运算,而且确保经过这...
    Winterfell_Z阅读 5,821评论 0 13