排序算法2|简单选择排序与堆排序(C#)

今天我们的目标是选择排序:简单选择排序与堆排序。两者排序的过程都在于每次选择一个最大值或者最小值放到合适的位置,因此都属于选择排序的范畴。区别在于:简单选择排序暴力选择出最大最小值,而堆排序合理的利用完全二叉树的特性使得算法的时间复杂度大大降低。接下来我们详细讲解两种排序:

简单选择排序:

思想:每次从一组数据中,找到最小的,然后放置在队列前面(当然也可以每次找到最大的,甚至有一些优化,每次可以同时找到最大值和最小值进行排序,我们这里采用找最小值)。可以看一下动画演示(动画是用unity制作的):


SelectionSort.gif

接下来着手写代码:

        public static void SelectionSort(int[] arr)
        {
            int length = arr.Length;
            if (length < 2)
            {
                return;
            }

            for (int i = 0; i < length - 1; i++)
            {
                int min = arr[i];
                int minIndex = i;
                //找到数列中的最小值
                for (int j = i + 1; j < length; j++)
                {
                    if (min > arr[j])
                    {
                        min = arr[j];
                        minIndex = j;
                    }
                }
                //与其适应的位置交换
                if (minIndex != i)
                {
                    int temp = arr[i];
                    arr[i] = arr[minIndex];
                    arr[minIndex] = temp;
                }

            }
        }

时间复杂度:
我们主要看两层循环,第一层执行次数为(length-1),第二层最小为1,最大为(length-1)。第1次外循环内循环次数(length-1)次,第2次外循环内循环次数(length-2)…第(length-1)次外循环内循环1次,相加就是(length-1)+(length-2)…+1,即(length)x(length -1)/2,用大O法,通常我们只关心数量级,而不关心系数,所以其复杂度就是:O(n2

空间复杂度: O(1)

稳定性:这里要注意一下,简单选择排序是一种不稳定排序,这是因为在选择了最小值之后,最小值要与数列头部的值进行交换,这样会导致打乱相同数值序列,比如(4,4,2,1)的序列,第一次最小值为1,与头部的4交换位置之后,导致两个4的前后顺序被打乱了,因此是不稳定排序。

堆排序:

堆排序首先是构建最大堆或最小堆。最大堆是用来正序排序,最小堆是用来倒序排序。

最大堆是指二叉树中每个结点的值都比其左右子结点的值大。同理最小堆是指二叉树中每个结点的值都比其左右子结点的值小。

对于二叉树不了解,在这里可以只有一个印象就可以。二叉树就是一个结点最多只有两个左右子结点。至于什么是完全二叉树,这里就不在过多解释,以后有机会写数据结构的时候,会着重解释,但是有一点要知道,数列从上往下,从左往右,按照只有一个根结点,且每个结点有两个子结点这样构建二叉树,那么他就是一颗完全二叉树。

下面我用一张图,来表示上面的概念,并加深印象。


完全二叉树.png

完全二叉树:
可以发现其实每个结点的下标和其左右子结点的下标是有一定关系的,即结点下标为n,左子结点下标为:2n+1,右子结点的下标为:2n+2。

最大堆:


最大堆.png

上图为第一次构建最大堆的结果

可以看出因为根结点要比左右子结点数值大,而且其左右子结点要比其孙子结点数值大,以此类推,此时的根结点即为数列的最大值。

那么我们如何把一个无序构建成一个最大堆。首先看最大堆的最大特点就是:父结点的数值一定比左右结点数值大,我们依照这个规则不断的调整结点使其满足条件即可。

再仔细观察堆我们发现,由一半以上的结点是没有孩子结点的,这部分结点就称为叶子结点,那么也就是说,这部分结点是不需要向下调整的。我们选择从(length/2)-1的下标开始依次从0下标的方向进行调整。每次调整之后,调整的结点还要继续比较他的子结点看看是否仍然满足最大堆特点,一直调整到叶子结点。这样做的目的就是使数列的大值向上浮,小值向下沉。直到下标0结点(根结点)调整完成,此时就是一个最大堆。

此时根结点是一个最大值,我们把最大值排在无序数列最后,即把最大值与队尾交换位置。此时我们发现除了根结点,其他结点仍然是符合最大堆特点的(注意,从这个位置往后,我们讲述的情况都是排除了最后一个数,因为他已经排好了位置)。这时我们只用调整根结点就可以了,调整之后,就得到了数列的第二个最大值。依次调整,直到数列排好即可。

思路:演示动画如下:


HeapSort4.gif

代码如下:

         public static void HeapSort(int[] arr)
        {
            int length = arr.Length;
            if (length < 2)
            {
                return;
            }
            //初次构建最大堆。从后往前第一个非叶子结点开始调整。
            for (int i = length / 2 - 1; i >= 0; i--)
            {
                AdjustHeap(arr, i, length);
            }

            //将堆顶最大值移动到数组末端,再次从根结点开始调整构建最大堆。
            //注意长度要-1,因为队尾的元素已经是排好序的。
            for (int i = 0; i < length -1; i++)
            {
                Swap(arr, 0, length - 1 - i);
                AdjustHeap(arr, 0, length - i - 1);
            }
        }

        /// <summary>
        /// 构建最大堆
        /// </summary>
        /// <param name="arr">需要构建的数组</param>
        /// <param name="index">需要开始调整的结点下标</param>
        /// <param name="length">需要构建的数组长度</param>
        private static void AdjustHeap(int[] arr, int index, int length)
        {
            int leftIndex = index * 2 + 1;              //左孩子结点下标
            int rightIndex = index * 2 + 2;             //右孩子结点下标
            //如果左孩子下标大于等于数组长,则说明其为叶子结点,不需要调整
            if (leftIndex >= length)                    
            {
                return;
            }
            //找到左右结点最大值的下标
            int maxIndex = leftIndex;
            if (rightIndex < length)
            {
                if (arr[leftIndex] < arr[rightIndex])
                {
                    maxIndex = rightIndex;
                }
            }
            //如果孩子结点的值要大于父结点,则交换两个结点的值,
            //并且从交换后的子结点继续向下调整
            if (arr[maxIndex] > arr[index])
            {
                Swap(arr, maxIndex, index);
                AdjustHeap(arr, maxIndex, length);
            }


        }

        private static void Swap(int[] arr, int index1, int index2)
        {
            int length = arr.Length;
            if (index1 >= length || index2 >= length)
            {
                return;
            }
            int temp = arr[index1];
            arr[index1] = arr[index2];
            arr[index2] = temp;
        }

时间复杂度:
首先我们来看函数的递归算法,了解到算法是根据二叉树依次向下分支进行遍历,那么他的运行次数跟树的深度有关,比如index下标结点的调整则最多需要:最深叶子结点的深度减去index下标结点的深度值即可。深度(用h表示,此处根结点的深度按0处理,树的最大深度位log2n)的计算可以自行查阅资料。另外完全二叉树为相同深度的结点个数** 2h-1**。

然后我们看初次构建最大堆,总共有一半的结点数需要调整,其中调整1次的结点为2h-1,调整2次的结点为2h-2,…,调整h次的结点为20。每调整一次的交换次数为2(左右结点比较,然后和父类结点比较)。
所以总的次数为:

    2x2h-1+4x2h-2+…+2xhx20

运用数学归纳法:

    212h-1+222h-2+…+2h20 =S

    1x2h+2x2h-1+…+hx21 =S     公式1;

两边乘以2:

    2h+1+2X2h+…+hx22 =2S     公式2;

公式2-公式1:

    2h+1+2h+…+ 22- hx21 =S    公式3;

两边乘以2:

    2h+2+2h+1+…+ 23- hx22 =2S     公式4;

公式4-公式3:

    2h+2- hx22-22+ hx21=S

简化后可的

    S=2h+2-2*h-4

    h= log2n

所以

    S=2hx22+2-2xh-4 =4xn-2x log2n-4

采用大O法,则其复杂度为O(n)

我们再看排序调整部分,交换算法算1次执行,循环下来执行的次数为(length-1)次,结点的调整情况我们可以类似初始构建最大堆的情况分析。有21的结点调整了1次,有22的结点调整了2次,…,有2h个结点调整了h次。
累加可得:

    2x1x21+2x1x22+…+2xhx2h =S

同样运用数学归纳法得到最终的S:

    S=4n log2n-4n+4

这部分的复杂度为:

    S +n-1 = 4n log2n-3n+3

采用大O法,其复杂度为O(n log2n)

两个阶段总的复杂度即为O(n log2n)

空间复杂度:O(1)

稳定性:不稳定排序。道理同简单选择排序一样。

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

推荐阅读更多精彩内容