[算法学习笔记]排序算法——堆排序

堆排序

堆排序(heapsort)也是一种相对高效的排序方法,堆排序的时间复杂度为O(n lgn),同时堆排序使用了一种名为堆的数据结构进行管理。

二叉堆

二叉堆是一种特殊的堆,二叉堆是完全二叉树或者是近似完全二叉树。二叉堆满足堆特性:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子树和右子树都是一个二叉堆。

如上图显示,(a)是一个二叉堆(最大堆), (b)是这个二叉堆在数组中的存储形式。

通过给个一个节点的下标i, 很容易计算出其父节点,左右子节点的的下标,为了方便,二叉堆中的下标从1开始计算

伪代码如下:

PARENT(i)
    // 得到父节点的下标,结果向下取整
    return i/2

LEFT(i)
    // 得到左子节点的下标
    return 2i

RIGHT(i)
    // 得到右子节点的下标
    return 2i+1

在大多数计算机上可以通过位移操作来快速得到结果:

  • LEFT操作: i向左位移一位
  • RIGHT操作: i向左位移一位并且在最低位加1
  • PARENT操作: i向右位移一位

并且这三个函数最好以宏或者内联函数的形式实现,比如说在c语言中使用宏来实现:

#define PARENT(i) i >> 1            // 取得父节点的下标
#define LEFT(i) i << 1              // 取得左边子节点的下标
#define RIGHT(i) (i << 1) + 1       // 取得右边子节点的下标

二叉堆的分类

二叉堆分为两类:最大堆和最小堆

最大堆除了根节点外都要满足一下性质:

A[PARENT(i)] >= A[i]

最小堆除了根节点外都要满足一下性质:

A[PARENT(i)] <= A[i]

在堆排序算法中我们使用的是最大堆

堆排序算法

堆排序算法主要应用到三个函数:

void maxHeapify(int [], int);       // 用于维护堆的性质
void bulidMaxHeap(int []);          // 建堆
void heapSort(int []);              // 堆排序

maxHeapify函数

该函数的功能是维护堆的性质,其第一个参数为存放数据的数组,第二个参数为某个节点的下标。该函数的实现如下:

void maxHeapify(int nums[], int i){
    int l = LEFT(i);    // 取得左子节点的下标
    int r = RIGHT(i);   // 取得右子节点的下标
    int largest;        // 当前节点和左右两个子节点中最大的值的下标
    if(l <= heapSize && nums[l] > nums[i])
        largest = l;
    else
        largest = i;
    if(r <= heapSize && nums[r] > nums[largest])
        largest = r;
    if(largest != i){
        swap(nums+i, nums+largest);
        maxHeapify(nums, largest);
    }
}

该函数实现了维护堆的性质的功能,首先找出当前的节点和其左右子节点这三个节点中的最大的一个节点,如果当前节点为最大节点则结束程序,否则交换当前节点和三个节点中的最大值,交换后对该节点继续判断,直到满足条件或者该节点是叶子节点的时候。

如图所示当执行maxHeapify(nums, 2)时的过程

bulidMaxHeap函数

此函数的作用是将普通的数组变成一个二叉堆,如下c语言的实现:

void bulidMaxHeap(int nums[]){
    int i;
    for(i = MAX_N/2; i >= 1; i--){
        maxHeapify(nums, i);
    }
}

从下往上,从右到左的第一个非叶子节点开始调用maxHeapify函数来维护这个二叉堆

heapSort函数

这个函数实现了排序功能,以下是c语言的实现:

void heapSort(int nums[]){
    bulidMaxHeap(nums);
    int i;
    heapSize = MAX_N;
    for(i = MAX_N; i >= 2; i--){
        swap(nums + 1, nums + i);
        heapSize--;
        maxHeapify(nums, 1);
    }
}

根据最大堆的性质,根节点总是整个堆中最大的数,所以从当前二叉堆中最后一个叶子节点和根节点交换,完成交换后将最后这个节点从二叉堆中删去,就是heapSize–操作,然后利用maxHeapify维护交换后的二叉堆,循环到只剩一个节点的时候就完成了排序。

基本上堆排序就是这样的,最后是堆排序的动态图和完整的c语言代码

堆排序算法的演示。首先,将元素进行重排,以匹配堆的条件。图中排序过程之前简单的绘出了堆树的结构。

完整的c语言实现代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAX_N 10
#define PARENT(i) i >> 1            // 取得父节点的下标
#define LEFT(i) i << 1              // 取得左边子节点的下标
#define RIGHT(i) (i << 1) + 1       // 取得右边子节点的下标

void swap(int *, int *);            // 交换函数
void maxHeapify(int [], int);       // 用于维护堆的性质
void bulidMaxHeap(int []);          // 建堆
void heapSort(int []);              // 堆排序

int heapSize;                       // 堆大小,排序时自减

int main(){
    int nums[MAX_N + 1];    // 这里下标从一开始
    int i;
    heapSize = MAX_N;
    srand(time(0));

    printf("Original array: ");
    for(i = 1; i <= MAX_N; i++){
        nums[i] = rand() % MAX_N;
        printf("%d\t", nums[i]);
    }
    printf("\n");

    bulidMaxHeap(nums);

    printf("After bulid heap:");
    for(i = 1; i <= MAX_N; i++){
        printf("%d\t", nums[i]);
    }
    printf("\n");

    heapSort(nums);

    printf("After heap sort:");
    for(i = 1; i <= MAX_N; i++){
        printf("%d\t", nums[i]);
    }
    printf("\n");
}

void swap(int *a, int *b){
    int temp = *a;
    *a = *b;
    *b = temp;
}

void maxHeapify(int nums[], int i){
    int l = LEFT(i);    // 取得左子节点的下标
    int r = RIGHT(i);   // 取得右子节点的下标
    int largest;        // 当前节点和左右两个子节点中最大的值的下标
    if(l <= heapSize && nums[l] > nums[i])
        largest = l;
    else
        largest = i;
    if(r <= heapSize && nums[r] > nums[largest])
        largest = r;
    if(largest != i){
        swap(nums+i, nums+largest);
        maxHeapify(nums, largest);
    }
}

void bulidMaxHeap(int nums[]){
    int i;
    for(i = MAX_N/2; i >= 1; i--){
        maxHeapify(nums, i);
    }
}

void heapSort(int nums[]){
    bulidMaxHeap(nums);
    int i;
    heapSize = MAX_N;
    for(i = MAX_N; i >= 2; i--){
        swap(nums + 1, nums + i);
        heapSize--;
        maxHeapify(nums, 1);
    }
}
时间: 2024-12-26 00:46:21

[算法学习笔记]排序算法——堆排序的相关文章

算法学习笔记 KMP算法之 next 数组详解

最近回顾了下字符串匹配 KMP 算法,相对于朴素匹配算法,KMP算法核心改进就在于:待匹配串指针 i 不发生回溯,模式串指针 j 跳转到 next[j],即变为了 j = next[j]. 由此时间复杂度由朴素匹配的 O(m*n) 降到了 O(m+n), 其中模式串长度 m, 待匹配文本串长 n. 其中,比较难理解的地方就是 next 数组的求法.next 数组的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀,也可看作有限状态自动机的状态,而且从自动机的角度反而更容易推导一些. "前

ios学习笔记---排序算法

排序算法 1.概念 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.排序算法,就是如何使得记录按照要求排列的方法. 2.选择排序算法时常用的几个参照 a.稳定性 假定在带排序的记录序列中,存在多个具有相同关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri = rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的:否则称为不稳定的. b.时间复杂度 c.空间复杂度 3.算法 冒泡排序 选择排序 插入排序

Java学习笔记——排序算法之希尔排序(Shell Sort)

落日楼头,断鸿声里,江南游子.把吴钩看了,栏杆拍遍,无人会,登临意. --水龙吟·登建康赏心亭 希尔算法是希尔(D.L.Shell)于1959年提出的一种排序算法.是第一个时间复杂度突破O(n2)的算法之一. 其基础是插入排序. 上代码: 1 public class ShellSort { 2 3 public static void shellSort(int[] arr){ 4 5 int increment = arr.length; 6 int temp;//牌 7 int i; 8

算法学习之排序算法:插入排序(直接插入排序、折半插入排序、2-路插入排序)

引言: 插入排序作为最简单易于理解的排序算法,基本实现比较简单.本文详细介绍直接插入排序,并给出实现,简单的介绍折半插入排序,并给出2-路插入排序和表插入排序两种插入排序,但并未给出具体实现. 一.直接插入排序 直接插入排序的基本操作是将一个记录插入到已排好序的有序表中,从而得到一个新的.记录数增1的有序表. 算法描述: 步骤1.将待排序的一组记录中的第1个记录拿出来作为一组有序的记录(当然此时该组记录仅有1个记录). 步骤2.依次将待排序的一组记录中的记录拿出来插入到前面已排好序的记录中. 步

算法学习之排序算法(五)(高速排序)

1.算法思想 设要排序的数组是A[0]--A[N-1],首先随意选取一个数据(通常选用数组的第一个数)作为重要数据,然后将全部比它小的数都放到它前面.全部比它大的数都放到它后面.这个过程称为一趟高速排序.值得注意的是,高速排序不是一种稳定的排序算法.也就是说,多个同样的值的相对位置或许会在算法结束时产生变动. 一趟高速排序的算法是: 1)设置两个变量i.j.排序開始的时候:i=0.j=N-1. 2)以第一个数组元素作为重要数据,赋值给key.即key=A[0]. 3)从j開始向前搜索,即由后開始

Java学习笔记——排序算法之进阶排序(堆排序与分治并归排序)

春蚕到死丝方尽,蜡炬成灰泪始干 --无题 这里介绍两个比较难的算法: 1.堆排序 2.分治并归排序 先说堆. 这里请大家先自行了解完全二叉树的数据结构. 堆是完全二叉树.大顶堆是在堆中,任意双亲值都大于(或等于)其孩子值,就称其为大顶堆. 堆排序的步骤: 1.把数组想象成一个堆.数组的index+1就是其对应在堆中的序号 2.调堆中各值的顺序,得到大顶堆 3.将堆首位值与堆末尾值交换,最大值排序完毕 4.将堆得大小减1,重复步骤2和步骤3,直到堆中只剩下一个元素.排序完毕 上代码: 1 publ

算法学习之排序算法:堆排序

要了解堆排序,首先要了解堆的概念,因为本文主要研究堆排序的算法,此处对数据结构堆只是给出概念:n个元素的序列{k1,k2,...kn},当且仅当满足如下关系时,称之为堆. k[i] <= k[2i]且k[i] <= k[2i+1] (或 k[i] >= k[2i]且k[i] >= k[2i+1]) 比如:序列96.83.27.38.11.09(或12.36.24.85.47.30.53.91)都是堆. 如果将堆对应的一维数组看成是一个二叉树,则堆的含义表明:完全二叉树中所有非终端结

Java学习笔记——排序算法之O(n&#178;)排序

男儿何不带吴钩,收取关山五十州.请君暂上凌烟阁,若个书生万户侯? --南园十三首 三种排序法: 1.冒泡法 2.简单选择法 3.直接插入法 上代码: 1.冒泡排序 1 public class BubbleSort { 2 3 //最简单的排序,从0开始逐个对比往上冒 4 public static void bubbleSort(int[] arr) { 5 for (int i = 0; i < arr.length-1; i++) { 6 for (int j = i+1; j < ar

算法学习之排序算法:归并排序

"归并"的含义是将两个或两个以上的有序表组合成一个新的有序表.无论是顺序存储还是链表存储结构,都可在O(m+n)的时间量级上实现. 归并排序又是一类不同的排序方法.假设初始序列含有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2个为2或1的有序子序列:再两两归并,....... ,如此重复,直至得到一个长度为n的有序序列为止. 初始关键字:[49]   [38]   [65]   [97]   [76]   [13]   [27] A       A