算法导论第六章堆排序(一)

简介: 现在来看, 堆的含义大概有两种,一种是数据结构,一种是在一些语言中所定义的“垃圾回收机制”,如Java,在书本上的开篇强调了这两者,并强调若非特殊说明,皆把堆看做是一种数据结构。 (二叉)堆的定义: 1)它是一个数组,可以被看成是一棵近似的完全二叉树,树上的每一个节点看做是数组中的每一个元素。

现在来看, 堆的含义大概有两种,一种是数据结构,一种是在一些语言中所定义的“垃圾回收机制”,如Java,在书本上的开篇强调了这两者,并强调若非特殊说明,皆把堆看做是一种数据结构。

(二叉)堆的定义:

1)它是一个数组,可以被看成是一棵近似的完全二叉树,树上的每一个节点看做是数组中的每一个元素。

2)堆分为最大堆和最小堆,最大堆中每一棵子树的父节点的值大于孩子节点,最小堆则相反。

3)表示堆的数组A包括两个属性:A.length和A.heap_size。前者是数组元素的个数,后者是堆元素的个数,heap_size <= length。(怎么理解这里,想象一棵树的节点在减少,但其表示的数组的个数还是不变的)。

4)二叉堆是最常用的,除此之外,还有多叉堆,如习题6-2的d-叉堆。

5)已知一个节点的坐标,容易得到其父节点和孩子节点的坐标:PARENT(i) = i/2; LEFT(i) = 2*i; RIGHT(i)=2*i+1。

由于二叉堆可以看做是一棵完全二叉树,所以树的一些定理,结论可以应用到堆上,如高度为h的堆中元素个数最多为:2^h,最少为:2^(h+1) - 1; 含n个元素的堆高度为:lgn。

堆排序:

为了实现堆排序,需要有这样的几个过程:

1)Build_Max_Heap():建立最大堆,将无序的输入数组构造出一个最大堆;

2)Max_Heapify():维护一个最大堆,即保证满足最大堆的性质;

3)Heap_Sort():堆排序。

以上函数的思路也是比较简单,在此就不做过多记录,即便记录,也是照着书本上的流程来一遍。

首先,Max_Heapify()是一个很关键的函数,它要保证堆中元素在以后的操作过程中,不管怎么变,都要保证满足最大堆的性质,即父节点的值永远大于孩子节点,知道了这一点,就不难写出代码:

函数原型:Max_Heapify( int A[], /* int heap_size, */ int index )

 

 1 void MaxHeapify_Recur(int arr[], int heap_size, int index)
 2 {
 3     if (index <=0 && index >= heap_size)
 4         return;
 5 
 6     int left = LEFT(index);
 7     int right = RIGHT(index);
 8     int largest;
 9 
10     if (left < heap_size && arr[left] > arr[index])
11         largest = left;
12     else 
13         largest = index;
14 
15     if (right < heap_size && arr[right] > arr[largest])
16         largest = right;
17 
18     if (largest != index) {
19         Swap(arr[index], arr[largest]);
20         MaxHeapify_Recur(arr, heap_size, largest);
21     }
22 }
View Code

上面实现的是一个递归的版本,也是书本上的版本,同时习题6.2-5要求实现非递归的版本,其实改动很小,只需加入一个标识符即可,实现如下:

 1 void MaxHeapify(int arr[], int heap_size, int index)
 2 {
 3     if (index < 0 && index >= heap_size)
 4         return;
 5     
 6     bool isHeapify = true; //标识最大堆是否处理完
 7     while (isHeapify && index < heap_size) {
 8         int left = LEFT(index);
 9         int right = RIGHT(index);
10         int largest;
11 
12         if (left < heap_size && arr[left] > arr[index]) 
13             largest = left;
14         else 
15             largest = index;
16 
17         if (right < heap_size && arr[right] > arr[largest])
18             largest = right;
19 
20         if (largest != index) {
21             Swap(arr[index], arr[largest]);
22             index = largest;
23         }
24         else
25             isHeapify = false;
26     }
27 }
View Code

其次,Build_Max_Heap()的实现需要知道下面一条定理( 习题6.1-7 ) :

当用数组表示存储n个元素的堆时,叶节点下标分别是n/2+1, n/2+2, ..., n (结合树高度的性质,很好证明).

知道了这个定理,为了建立最大堆,我们就可以从第一个非叶子节点开始往后遍历,直到根节点,调用Max_Heapify()来得到一个最大堆,实现如下:

函数原型:Build_Max_Heap( int A[], /* int heap_size, */ )

1 void BuildMaxHeap(int arr[], int heap_size)
2 {
3     if (heap_size == 0)
4         return;
5     
6     for (int i = (heap_size-1)/2; i >= 0; i--)
7         MaxHeapify(arr, heap_size, i);
8 }
View Code

至此,排序思路也就出来了:先建立最大堆,得到最大元素,然后将最大元素放在数组末尾,然后调用Max_Heapify()维护最大堆,依次下去,就得到排序的数组,实现如下:

函数原型:Heap_Sort( int A[], /* int heap_size, */ )

 1 void HeapSort(int arr[], int length)
 2 {
 3     if (length == 0)
 4         return;
 5 
 6     int heap_size = length;
 7     BuildMaxHeap(arr, heap_size);
 8     for (int i = length-1; i >= 1; i --) {
 9         Swap(arr[0], arr[i]);
10         heap_size --;
11         MaxHeapify(arr, heap_size, 0);
12     }
13 }
View Code

堆排序的时间复杂度:

Max_Heapify()可以看到是在不断遍历树,最坏情况下是从根节点开始,则n个节点的树高为lgn,所以其时间复杂度为O(lgn)。

Build_Max_Heap()经过严格推导,可得时间复杂度为线性的,为O(n)。

所以,Heap_Sort()就为O(nlgn)。

同样的思路可以实现最小堆,下面贴出最大堆完整实现的代码:

  1 #include <iostream>
  2 
  3 using namespace std;
  4 
  5 
  6 //#include "HeapSort.h"
  7 
  8 #define PARENT(x) ((x-1)/2)    //求 x 父节点的下标
  9 #define LEFT(x) ((x)*2+1)    //求 x 左孩子的下标
 10 #define RIGHT(x) ((x)*2+2)    //求 x 右孩子的下标
 11 
 12 void MaxHeapify(int arr[], int heap_size, int index);        //维护最大堆的性质
 13 void MaxHeapify_Recur(int arr[], int heap_size, int index); //递归
 14 void BuildMaxHeap(int arr[], int heap_size);                    //从一个无序的数组中构造一个最大堆
 15 void HeapSort(int arr[], int length);                            //堆排序
 16 void Swap(int &a, int &b);
 17 
 18 void MaxHeapify(int arr[], int heap_size, int index)
 19 {
 20     if (index < 0 && index >= heap_size)
 21         return;
 22     
 23     bool isHeapify = true; //标识最大堆是否处理完
 24     while (isHeapify && index < heap_size) {
 25         int left = LEFT(index);
 26         int right = RIGHT(index);
 27         int largest;
 28 
 29         if (left < heap_size && arr[left] > arr[index]) 
 30             largest = left;
 31         else 
 32             largest = index;
 33 
 34         if (right < heap_size && arr[right] > arr[largest])
 35             largest = right;
 36 
 37         if (largest != index) {
 38             Swap(arr[index], arr[largest]);
 39             index = largest;
 40         }
 41         else
 42             isHeapify = false;
 43     }
 44 }
 45 
 46 void MaxHeapify_Recur(int arr[], int heap_size, int index)
 47 {
 48     if (index <=0 && index >= heap_size)
 49         return;
 50 
 51     int left = LEFT(index);
 52     int right = RIGHT(index);
 53     int largest;
 54 
 55     if (left < heap_size && arr[left] > arr[index])
 56         largest = left;
 57     else 
 58         largest = index;
 59 
 60     if (right < heap_size && arr[right] > arr[largest])
 61         largest = right;
 62 
 63     if (largest != index) {
 64         Swap(arr[index], arr[largest]);
 65         MaxHeapify_Recur(arr, heap_size, largest);
 66     }
 67 }
 68 
 69 void BuildMaxHeap(int arr[], int heap_size)
 70 {
 71     if (heap_size == 0)
 72         return;
 73     
 74     for (int i = (heap_size-1)/2; i >= 0; i--)
 75         MaxHeapify(arr, heap_size, i);
 76 }
 77 
 78 void HeapSort(int arr[], int length)
 79 {
 80     if (length == 0)
 81         return;
 82 
 83     int heap_size = length;
 84     BuildMaxHeap(arr, heap_size);
 85     for (int i = length-1; i >= 1; i --) {
 86         Swap(arr[0], arr[i]);
 87         heap_size --;
 88         MaxHeapify(arr, heap_size, 0);
 89     }
 90 }
 91 
 92 void Swap(int &a, int &b)
 93 {
 94     int temp = a;
 95     a = b;
 96     b = temp;
 97 }
 98 
 99 // int main()
100 // {
101 //     int arr[10] = {10,14,16,8,7,9,3,2,4,1};
102 //     HeapSort(arr, 10);
103 //     for (int i = 0; i < 10; i ++)
104 //         cout << arr[i] << " ";
105 //     return 0;
106 // }
View Code

 

目录
相关文章
|
5月前
|
算法 Python
数据结构算法--4堆排序
堆排序过程概述:建立大根堆,将堆顶最大元素移出并替换为末尾元素,调整保持堆性质,重复此过程直至堆为空,实现排序。时间复杂度为O(nlogn)。Python中可用heapq模块进行堆操作。
|
6月前
|
机器学习/深度学习 人工智能 算法
数据结构与算法:堆排序和TOP-K问题
朋友们大家好,本节内容来到堆的应用:堆排序和topk问题
数据结构与算法:堆排序和TOP-K问题
|
6月前
|
存储 人工智能 算法
深入浅出堆排序: 高效算法背后的原理与性能
深入浅出堆排序: 高效算法背后的原理与性能
114 1
|
1月前
|
算法 搜索推荐
数据结构与算法学习十八:堆排序
这篇文章介绍了堆排序是一种通过构建堆数据结构来实现的高效排序算法,具有平均和最坏时间复杂度为O(nlogn)的特点。
70 0
数据结构与算法学习十八:堆排序
|
1月前
|
算法 搜索推荐
算法之堆排序
本文介绍了堆排序算法的原理和实现,通过构建最大堆或最小堆,利用堆的性质进行高效的排序,并提供了具体的编程实现细节和示例。
20 0
算法之堆排序
|
1月前
|
算法 Java Go
深入了解堆排序算法
深入了解堆排序算法
22 1
|
6月前
|
移动开发 算法 前端开发
前端算法之堆排序
前端算法之堆排序
40 1
|
5月前
|
搜索推荐 算法 Java
Java中的快速排序、归并排序和堆排序是常见的排序算法。
【6月更文挑战第21天】Java中的快速排序、归并排序和堆排序是常见的排序算法。快速排序采用分治,以基准元素划分数组并递归排序;归并排序同样分治,先分割再合并有序子数组;堆排序通过构建堆来排序,保持堆性质并交换堆顶元素。每种算法各有优劣:快排平均高效,最坏O(n²);归并稳定O(n log n)但需额外空间;堆排序O(n log n)且原地排序,但不稳定。
46 3
|
5月前
|
搜索推荐 算法
【C/排序算法】:堆排序和选择排序
【C/排序算法】:堆排序和选择排序
34 0
|
5月前
|
存储 算法 C语言
数据结构和算法——堆排序(选择排序、思路图解、代码、时间复杂度、堆排序及代码)
数据结构和算法——堆排序(选择排序、思路图解、代码、时间复杂度、堆排序及代码)
36 0