无聊写排序之 ---- 堆排序(HeadSort)

来源:互联网 发布:中世纪2优化9俄罗斯 编辑:程序博客网 时间:2024/06/05 18:31

   还记得之前说的选择排序么?忘记了看这里<选择排序>。 选择排序i时,我们需要从n-i+1个序列中选择一个最小的数字, 那么势必就要比较多次来寻找到最小的那一个元素从而来进行排序,这种排序虽然相对于冒泡排序有效的减少了交换所带来的开销,但是多次比较对于比较的结果却没有进行记录,当排序第1个数字的时候需要从第1.....n中找到最小的数字进行和第1个进行比较交换,当我们需要排序第2个数字时,同样需要将上一步比较过的过程重新进行一次。这样的操作并没有把每一趟的比较结果保存下来,在后一趟的比较中,有许多比较在前一趟已经做过了,但由于前一趟排序时未保存这些比较结果,所以后一趟排序时又重复执行了这些比较操作,因而记录的比较次数较多。Floyd和Williams在1964年共同发明发明了堆这样的数据结构,同时提出了堆排序的概念,堆排序其实是选择排序的一个升级版本,是在每一次选择最小的记录的同时对其他的元素也相应的进行调整。 从而有效的减少了比较的次数而带来的价值就是运算效率的大幅度提升。要讲堆排序需要先讲解堆这种数据结构。
   堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。其实对就是一种完全二叉树,只是节点元素的关系满足根节点比孩子节点都大或者小,所以不难看出一个堆的最值在root根节点,堆结构中的每一个子树也都是一个堆结构。对堆结构的二叉树按照按层顺序标号,root节点为1则有如下关系:

大堆: P[i] >= P[2*i] && P[i] >= P[2*i+1] 小堆:P[i] <= P[2*i] && P[i] <= P[2*i+1] 1<=i<= n/2



          堆结构的存储:堆结构按照完全二叉树的按层次遍历存储进数组,这样就可以完全保护现有的结构,其中数组下标就可以表示父节点和子节点的对应关系,比如下标i的元素的左孩子在2*i的位置,而右孩子在2*i+1的位置:

   堆排序(Heap Sort)就是利用堆进行排序的方法。它的基本思想是,将待排序的序列构造成一个堆,如大顶堆。此时,整个序列的最大值就是堆顶的根结点。将其与堆数组的末尾元素交换,此时末尾元素就是最大值,然后将剩余的1到n-1个序列重新构造成一个堆,这样就会得到剩余n-1个元素中的最大值,然后继续交换到末尾(注意现在的末尾是n-1),上述过程反复执行,最终会得到一个排好序的数组了。


code:

// 在已知arr[s...m]中除了s外所有的元素已经满足堆// HeadAdjust 调整arr[s], 使得序列变成一个大顶堆void HeadAdjust(int arr[], int s, int m){int tmp;tmp = arr[s];// 沿着元素值较大的子节点向下筛选for (int j = 2 * s; j <=  m; j *= 2){if (arr[j] < arr[j+1])  // 找到左右子节点中较大的下标 j++;// 如果根节点较大, 已经是最优则退出if (tmp >= arr[j]) break;// 将较大的子节点调整到根节点即堆顶arr[s] = arr[j];s = j;// 调整剩下的堆结构}// 将最后的堆元素补齐arr[s] = tmp;}void HeadSort(int arr[], int n){// 从下向上 从右往左一次对每一个非叶子节点构建堆结构  for (int i = n/2; i > 0; i--)  {  HeadAdjust(arr, i, n);  }  for (int i = n; i > 1; i--)  {  Swap(arr, 1, i);// 堆顶最大的元素换到最后面  HeadAdjust(arr, 1, i-1);// 从新调整堆结构为大顶堆s  }}

        堆排序的时间复杂度:堆排序的主要是消耗在初始构建堆和在重建堆时的反复筛选上。在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和若有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。在正式排序时,第i次取堆顶记录重建堆需要O(logi)(完全二叉树的某个结点到根结点的距离为⌊log2i⌋+1),并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)。
        所以总体来说,堆排序的时间复杂度为O(nlogn)。由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)。这在性能上显然要远远好过于冒泡、简单选择、直接插入的O(n2)的时间复杂度了,空间复杂度上,它只有一个用来交换的暂存单元,也算是非常的不错。不过由于记录的比较与交换是跳跃式进行,因此堆排序也是一种不稳定的排序方法。
       另外,由于初始构建堆所需的比较次数较多,因此,它并不适合待排序序列个数较少的情况。 


    概念部分摘抄自大话数据结构

 

0 0
原创粉丝点击