2.2 选择排序算法之堆排序

1、堆排序(Heap Sort)

[知识点补充]
堆: 完全二叉树,每个节点值始终不小于其子节点值,为大根堆;每个节点值始终不大于其两个子节点的值,为小根堆。
        
此外完全二叉树的特性,用数组存储,父节点与其左右孩子节点的编号容易表示,例如: k 号节点,其左孩子节点的编号为 (2*k),右孩子节点的编号为 (2*k+1),在数组中就用下标表示。

一般用数组来表示,注:数组的下标起始为 0 !
例: 一个堆结构的数组 (画个图就很明朗了)

  • 下标为 i 的节点的父节点下标为 (i-1)/2
  • 左右子节点的下标为 (2i + 1) , (2i + 2)


堆排序的基本思想

  • 如果初始序列是堆,则可以通过反复执行如下操作,最终得到一个有序序列
    1、”输出根” : 将根(第一个元素)与当前子序列中的最后一个元素交换
    2、调整堆 : 将”输出根”之后的序列调整为堆.

  • 如果初始序列不是堆,则首先要将其先调整为堆,然后在按上述步骤实现


实现堆排序有两个核心的操作:

  1. 如何将一个序列建成一个堆?
  2. 如何在”输出根”后,调整剩余元素成为一个新堆?

操作2的解决方法:
在输出堆顶元素之后,以堆中最后一个元素替代交换之,此时根节点的左,右子树均为堆,则仅需自上至下的进行调整即可,这个种自堆顶至叶子的调整过程称为” 筛选 “。
以大根堆为例:大根堆堆顶元素与最后一个元素互换(输出根),当前的”堆顶”肯定不满足堆的定义,开始自上而下的调整,因为需要建立大根堆,那么先比较当前”堆顶”的左右孩子,选择其中较大者,与”堆顶“互换,再从互换调整的节点的子树着手,同样的自上而下的调整(见下面图例过程更明朗)

操作1的解决方法:
从一个无序序列建堆的过程就是一个反复” 筛选 “的过程。若将此序列看成是一个完全二叉树,则最后一个非终端结点是 第 ⌞n/2⌟ 个元素,由此“筛选”只需从第 ⌞n/2⌟ 个元素开始。


语言很苍白,用图例来解决 (以大根堆为例)

  1. 操作2图例 (筛选过程)
    这里写图片描述

    这里写图片描述

上面的图例只展示了第一次的输出根调整堆的过程,整个的堆排序是在这个堆结构的序列上不断输出根,调整堆,直至输出最后一个根。

2 . 操作1图例 (堆化序列过程)

给定的无序序列不一定就是堆结构,那么如何将一个无序序列建成一个堆呢?

  • 堆化整个无序序列,过程是一个反复”筛选”的过程 (反复调整堆的过程)
  • 建堆过程要从下往上逐棵子树地进行筛选,
    即根的下标按照从n/2到1的次序将各子树调整为堆。

代码:

//堆排序   Heap_sort
//  3个步骤 
//子树的调整-----即子树根结点的筛选
//无序数组的堆化
//堆排序(输出根节点,以及调整堆)

 //实现小根堆 
#include <iostream>
using namespace std; 

void Swap(int &a,int &b)
 {
    cout<<b<<"  ";
    int temp=a;
    a=b;
    b=temp;
 }

void Sift(int a[],int i,int n)
{//数组中下标为i的节点进行调整,对应的子树进行"筛选调整" ,n为节点的总数 

    int temp=a[i];
    int j=2*i+1;    //i 的 左孩子 

    while(j<n)        //孩子数小于n 
    {
        if(j+1<n && a[j+1]<a[j])   //找左右子节点更小者 
        {
            j=j+1;
        }
        if(temp<a[j])   //如果根最小,没必要调整了 
        {
            break;
        }

        a[i]=a[j];      //根节点换成小的 
        i=j;            //可能 根节点调整后造成下面的节点仍需要调整 
        j=2*i+1;
    }
    a[i]=temp;         //最后给temp安个家 
} 


//无序数组堆化(小根堆处理) 
void HeapAdjust(int a[],int n)
{
    for(int i=n/2-1;i>=0;i--)     //i大于等于0,等于不要丢,第一个节点也需要调整 
    {
        Sift(a,i,n);
    }
 } 


 void Heap_sort(int a[],int n)
 {
    HeapAdjust(a,n);
    for(int i=n-1;i>0;i--)
    {
        Swap(a[i],a[0]);
        Sift(a,0,i);
     }
 }

int main()
{
    int a[10]={3,2,5,4,1,9,6,8,19,11};
    int n=10;
    Heap_sort(a,10);
    return 0;
}

总结之大白话 : 堆排序

整个实现过程:
1. 堆化整个序列
2. 输出根 & 调整堆(筛选过程) ; 反复执行直至输出最后一个根

核心部分就是“筛选过程”的实现算法
堆化序列也是基于”筛选过程”实现的,至于是逐个子树自下而上进行”筛选”,并且是从下标第 ⌞n/2⌟ 个到1的次序,(因为不需要从叶子节点开始调整)可以自行推导一下,理解之后记忆住就行


猜你喜欢

转载自blog.csdn.net/zxj820/article/details/80428104
2.2