zoukankan      html  css  js  c++  java
  • 堆排序

    堆排序算法简介:

    堆可以看为一颗完全二叉树,满足,任何一个非叶结点的值都不大于(或不小于)其左右孩子结点的值。若父亲大孩子小,则称为大顶堆;若父亲小孩子大,则称为小顶堆
    根据堆的定义,其根结点值是最大的(或最小的),因此,将一个无序序列调整为一个堆,就可以找出这个序列的最大(或最小)值,然后将找出的这个值交换到序列的最后(或最前),这样有序的序列元素增加1,无序序列元素减少1,重复操作,就可以实现排序。

    举例:

    原始序列:49,38,65,97,76,13,27,49(1)
    构建大顶堆:原始序列对应的完全二叉树为:
    这里写图片描述
    结点76,13,27,49(1)是叶子结点,没有左右孩子,所以满足堆的定义。从97开始调整,按97,65,38,49的顺序依次调整非叶子结点。
    (1)调整97,因为97>49(1),所以97和它的孩子49(1)满足堆的定义,不需要调整;
    (2)调整65,同97;
    (3)调整38,38<97, 38<76, 不满足大顶堆的定义,需要调整,因为它的左右孩子都大于它本身,应该和两者最大的那个交换,即和97交换。因为如果和76交换,76<97还是不满足大顶堆的定义。又38和97交换后,38成了49(1)的根结点,且38<49(1),所以继续交换,变为:
    这里写图片描述
    (4)调整49,原理同调整38的步骤,调整后结果:
    这里写图片描述
    此时我们已经建立一个大顶堆,对应序列为:97,76,65,49(1),49,13,27,38。然后将堆顶97和序列的最后一个元素38交换。第一趟堆排序完成,97达到最终的位置。将除97外的其他序列,再次调整为大顶堆。现在只需要调整顶点38即可(因为其他的元素没有变化,满足大顶堆的定义)。
    调整38后:
    这里写图片描述
    现有序列:76,49(1),65,38,49,13,27,97。交换堆顶76和序列最后一个元素27,序列变为:27,49(1),65,38,49,13,76,97,则76达到最终的位置。,依次重复上述步骤直到完成排序。

    代码:

    #include <iostream>
    using namespace std;
    
    void Sift(int a[], int low, int high) {
        int left = low * 2 + 1;//左孩子
        int right = left + 1;//右孩子
        int index = low; //记录当前位
        while (left < high && a[left] > a[index]) index = left;
        while (right < high && a[right] > a[index]) index = right;
        if (index != low) {//位置有调整
            int temp = a[index];
            a[index] = a[low];
            a[low] = temp;
            Sift(a, index, high);
        }
    }
    
    void heap(int a[], int n) {
        if (n <= 0) return;
        for (int i = n / 2 - 1; i >= 0; --i) {
            //建立最大堆,将堆中最大的值交换到根节点
            Sift(a, i, n);
        }
        for (int i = n - 1; i >= 1; --i) {
            //将当前堆的根节点交换到堆尾的指定位置
            int temp = a[0];
            a[0] = a[i];
            a[i] = temp;
            //建立下一次的最大堆
            Sift(a, 0, i);
        }
    }
    
    int main()
    {
        int a[8] = { 49,38,65,97,76,13,27,49 };
        int n = 8;
        heap(a, n);
        for (int i = 0; i < n; i++)
        {
            cout << a[i] << " ";
        }
        cout << endl;
        return 0;
    }

    结果:

    这里写图片描述

    时间复杂度分析

    对于Sift()函数,完全二叉树的高度为log2(n+1)(向上取整),即对于每个节点的调整时间复杂度为O(log2n)。对于heap()函数,基本操作总次数是两个并列的for循环中基本操作次数相加,第一个for循环的基本操作次数是O(log2n)×(n/2),第二个for循环的次数为O(log2n)×(n1),因此整个算法的时间复杂度为O(log2n)×(n/2)+O(log2n)×(n1),化简后的时间复杂度为O(nlog2n)

    空间复杂度分析

    只需要一个额外的空间temp,因此空间复杂度为O(1)

  • 相关阅读:
    x-www-form-urlencoded与multipart/form-data区别
    objc_msgSend method_getTypeEncoding 与 @encode
    历史文件备份,原文件已损失
    和安全有关的那些事(非对称加密、数字摘要、数字签名、数字证书、SSL、HTTPS及其他)
    HTTP权威指南 目录
    Makefile 与tab
    NSString+URLParser NSScanner
    (转)虚拟文件系统(VFS)浅析
    Linux套接字与虚拟文件系统(1):初始化和创建
    linux内核中的文件描述符(二)--socket和文件描述符
  • 原文地址:https://www.cnblogs.com/xiaocai-ios/p/7779757.html
Copyright © 2011-2022 走看看