八大排序之堆排序

在这里插入图片描述
从效率最高的 堆排序 / 快速排序/归并排序/基数排序开始记录。

堆排序

辅助性理解图:https://www.cnblogs.com/chengxiao/p/6129630.html

1. 堆的理解

  • 堆的概念
    我们一般提到堆排序里的堆指的是二叉堆(binary heap),是一种完全二叉树,二叉堆有两种:最大堆和最小堆,特点是父节点的值大于(小于)两个小节点的值。
  • 基础知识
    完全二叉树有一个性质是,除了最底层,每一层都是满的,这使得堆可以利用数组来表示,每个结点对应数组中的一个元素,如下图所示
    对于给定的某个结点的下标 i(从1开始),可以很容易的计算出这个结点的父结点、孩子结点的下标:
    在这里插入图片描述
    父节点与孩子节点的相对关系计算如下:
Parent(i) = floor((i-1)/2),i 的父节点下标
Left(i) = 2i + 1,i 的左子节点下标
Right(i) = 2(i + 1),i 的右子节点下标

2. 堆的基本操作

  • 最大堆调整
    该操作主要用于维持堆的基本性质。假设数组A和下标i,假定以Left(i)和Right(i)为根结点的左右两棵子树都已经是最大堆,节点i的值可能小于其子节点。调整节点i的位置,使得子节点永远小于父节点,过程如下图所示:
    在这里插入图片描述
    由于一次调整后,堆仍然违反堆性质,所以需要递归的测试,使得整个堆都满足堆性质。

  • 创建最大堆
    创建最大堆(Build-Max-Heap)的作用是将一个数组改造成一个最大堆,接受数组和堆大小两个参数,Build-Max-Heap 将自下而上的调用 Max-Heapify 来改造数组,建立最大堆。因为 Max-Heapify 能够保证下标 i 的结点之后结点都满足最大堆的性质,所以自下而上的调用 Max-Heapify 能够在改造过程中保持这一性质。如果最大堆的数量元素是 n,那么 Build-Max-Heap 从 Parent(n) 开始,往上依次调用 Max-Heapify。流程如下:
    在这里插入图片描述
    c++实现创建大顶堆

void build_max_heap(int *datas,int length)
2 {
3     int i;
4     //build max heap from the last parent node
5     for(i=length/2;i>0;i--)
6         adjust_max_heap(datas,length,i);
7 }

3. 堆排序算法

堆排序算法过程为:先调用创建堆函数将输入数组A[1…n]造成一个最大堆,使得最大的值存放在数组第一个位置A[1],然后用数组最后一个位置元素与第一个位置进行交换,并将堆的大小减少1,并调用最大堆调整函数从第一个位置调整最大堆。
在这里插入图片描述

void heap_sort(int *datas,int length)
{
    int i,temp;
    //bulid max heap
    build_max_heap(datas,length);
    i=length;
    //exchange the first value to the last unitl i=1
    while(i>1)
    {
        temp = datas[i];
        datas[i] = datas[1];
        datas[1] =temp;
        i--;
        //adjust max heap,make sure the fisrt value is the largest
        adjust_max_heap(datas,i,1);
    }
}

其中,建立大顶堆的算法如下:

void build_max_heap(int *datas,int length)
2 {
3     int i;
4     //build max heap from the last parent node
5     for(i=length/2;i>0;i--)
6         adjust_max_heap(datas,length,i);
7 }

调整为大顶堆的算法如下:

void adjust_max_heap(int *datas,int length,int i)
{
    int left,right,largest;
    int temp;
    while(1)
    {
        left = LEFT(i);   //left child
        right = RIGHT(i); //right child
        //find the largest value among left and rihgt and i.
        if(left <= length && datas[left] > datas[i])
            largest = left;
        else
            largest = i;
        if(right <= length && datas[right] > datas[largest])
            largest = right;
        //exchange i and largest
        if(largest != i)
        {
            temp = datas[i];
            datas[i] = datas[largest];
            datas[largest] = temp;
            i = largest;
            continue;
        }
        else
            break;
    }
}

Java算法如下:

import java.util.Arrays;
/**
 * 
 * @author Administrator
 *
 */
public class HeapSort {
    public static void main(String []args){
        int []arr = {7,6,7,11,5,12,3,0,1};
        System.out.println("排序前:"+Arrays.toString(arr));
        sort(arr);
        System.out.println("排序前:"+Arrays.toString(arr));
    }
 
    public static void sort(int []arr){
        //1.构建大顶堆
        for(int i=arr.length/2-1;i>=0;i--){
            //从第一个非叶子结点从下至上,从右至左调整结构
            adjustHeap(arr,i,arr.length);
        }
        //2.调整堆结构+交换堆顶元素与末尾元素
        for(int j=arr.length-1;j>0;j--){
            swap(arr,0,j);//将堆顶元素与末尾元素进行交换
            adjustHeap(arr,0,j);//重新对堆进行调整
        }
 
    }
 
    /**
     * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
     * @param arr
     * @param i
     * @param length
     */
    public static void adjustHeap(int []arr,int i,int length){
        int temp = arr[i];//先取出当前元素i
        for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
            if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
                k++;
            }
            if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
                arr[i] = arr[k];
                i = k;
            }else{
                break;
            }
        }
        arr[i] = temp;//将temp值放到最终的位置
    }
 
    /**
     * 交换元素
     * @param arr
     * @param a
     * @param b
     */
    public static void swap(int []arr,int a ,int b){
        int temp=arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}

在这里插入图片描述
https://img-blog.csdn.net/20180908013007479?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2MTg2Njkw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

4. 时间复杂度与排序稳定性

我们知道n个元素的完全二叉树的深度h=floor(logn),分析各个环节的时间复杂度如下。

  1. 堆调整时间复杂度
    从堆调整的代码可以看到是当前节点与其子节点比较两次,交换一次。父节点与哪一个子节点进行交换,就对该子节点递归进行此操作,设对调整的时间复杂度为T(k)(k为该层节点到叶节点的距离),那么有:

T(k)=T(k-1)+3, k∈[2,h]
T(1)=3
迭代法计算结果为:

T(h)=3h=3floor(log n)
所以堆调整的时间复杂度是O(log n) 。

  1. 建堆的时间复杂度
    n个节点的堆,树高度是h=floor(log n)。

对深度为于h-1层的节点,比较2次,交换1次,这一层最多有2(h-1)个节点,总共操作次数最多为3(12(h-1));对深度为h-2层的节点,总共有2(h-2)个,每个节点最多比较4次,交换2次,所以操作次数最多为3(22(h-2))……
以此类推,从最后一个父节点到根结点进行堆调整的总共操作次数为:

s=3*[2^(h-1) + 22^(h-2) + 32^(h-3) + … + h2^0] a
2s=3
[2^h + 22^(h-1) + 32(h-2) + … + h2^1] b
b-a,得到一个等比数列,根据等比数列求和公式
s = 2s - s = 3
[2^h + 2^(h-1) + 2^(h-2) + … + 2 - h]=3*[2^(h+1)- 2 - h]≈3*n
所以建堆的时间复杂度是O(n)。

  • 堆排序时间复杂度
    从上面的代码知道,堆排序的时间等于建堆和进行堆调整的时间之和,所以堆排序的时间复杂度是O(nlog n + n) =O(nlog n)。

5. 稳定性

堆排序是不稳定的算法,它不满足稳定算法的定义。它在交换数据的时候,是比较父结点和子节点之间的数据,所以,即便是存在两个数值相等的兄弟节点,它们的相对顺序在排序也可能发生变化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值