《算法导论》学习笔记(1)——堆与堆排序
2014-02-09 16:38
288 查看
堆排序( Heapsort )是指利用“堆”这种数据结构所设计的一种排序算法。
堆是一种数据结构,是一个数组。它可以被看成一个近似的完全二叉树,树上的每一个结点对应数组中的一个元素。除了最底层外,即叶子结点,该树是完全充满的,而且是从左到右填充。
最大堆的每个结点都要满足堆的性质,此外还有其他的约束:堆中的最大元素存放在根结点中,并且在任一子树中,该子树所包含的所有结点的值都不大于该子树根结点的值。
堆排序就是利用最大堆的性质来排序。大致步骤如下:
①建最大堆
②将根结点(最大元素)与最后一个结点的元素互换,并将剩下的结点维护最大堆的性质。
③重复步骤②,直到将每个元素排完序。
下面就讲解几个重要过程:
维护最大堆的性质:MaxHeapify
传入参数:待排序数组、需要维护的下标、当前堆的大小。返回值:空
每次在传入参数的下标、该下标的左、右孩子结点三者中选出最大的元素。如果最大元素是该下标,则满足最大堆性质,结束。否则,需要将父亲与最大的孩子交换位置,使得当前下标及其左右孩子满足最大堆的性质。之后递归调用,使得整个子树都满足最大堆的性质。
特别需要注意,数组的大小和堆的大小是不一样的概念。堆的大小表示有多少个堆元素存储在数组中。在进行堆排序的过程中,每次需要把根结点元素与最后一个元素交换,这样堆的大小每次就会减一。而数组大小是不会变的。也就是:
0 ≤ 堆的大小 ≤ 数组的大小
建最大堆:BuildMaxHeap
传入参数:待排序数组、当前堆的大小。返回值:空
只需保证所有的非叶子结点满足最大堆的性质即可。非叶子结点的下标为从0到堆大小 / 2(向下取整)。
堆排序:HeapSort
传入参数:待排序数组、当前堆的大小。返回值:空
首先建堆,然后每次去除根结点元素(最大元素),然后维护最大堆的性质。如此循环,直到所有元素排序完毕。
实现代码如下:
时空复杂度:
由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。故堆排序的时间复杂度为O(N * logN)。
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O(1), 它是不稳定的排序方法。
堆是一种数据结构,是一个数组。它可以被看成一个近似的完全二叉树,树上的每一个结点对应数组中的一个元素。除了最底层外,即叶子结点,该树是完全充满的,而且是从左到右填充。
最大堆的每个结点都要满足堆的性质,此外还有其他的约束:堆中的最大元素存放在根结点中,并且在任一子树中,该子树所包含的所有结点的值都不大于该子树根结点的值。
堆排序就是利用最大堆的性质来排序。大致步骤如下:
①建最大堆
②将根结点(最大元素)与最后一个结点的元素互换,并将剩下的结点维护最大堆的性质。
③重复步骤②,直到将每个元素排完序。
下面就讲解几个重要过程:
维护最大堆的性质:MaxHeapify
传入参数:待排序数组、需要维护的下标、当前堆的大小。返回值:空
每次在传入参数的下标、该下标的左、右孩子结点三者中选出最大的元素。如果最大元素是该下标,则满足最大堆性质,结束。否则,需要将父亲与最大的孩子交换位置,使得当前下标及其左右孩子满足最大堆的性质。之后递归调用,使得整个子树都满足最大堆的性质。
特别需要注意,数组的大小和堆的大小是不一样的概念。堆的大小表示有多少个堆元素存储在数组中。在进行堆排序的过程中,每次需要把根结点元素与最后一个元素交换,这样堆的大小每次就会减一。而数组大小是不会变的。也就是:
0 ≤ 堆的大小 ≤ 数组的大小
建最大堆:BuildMaxHeap
传入参数:待排序数组、当前堆的大小。返回值:空
只需保证所有的非叶子结点满足最大堆的性质即可。非叶子结点的下标为从0到堆大小 / 2(向下取整)。
堆排序:HeapSort
传入参数:待排序数组、当前堆的大小。返回值:空
首先建堆,然后每次去除根结点元素(最大元素),然后维护最大堆的性质。如此循环,直到所有元素排序完毕。
实现代码如下:
#include <iostream> using namespace std; typedef int ElemType; int getLeftChild( int i ) //返回左孩子下标。注:数组从位置0开始 { return 2 * i + 1; } int getRightChild( int i ) //返回右孩子下标。注:数组从位置0开始 { return 2 * i + 2; } void swap( ElemType *a, ElemType *b ) //交换两个元素 { ElemType temp = *a; *a = *b; *b = temp; } /* 维护最大堆的性质。 * 参数:待排序数组、需要维护的下标、当前堆的大小 * 返回值:空 */ void MaxHeapify( ElemType *A, int i, int num ) { int left = getLeftChild( i ); int right = getRightChild( i ); int largest; // 存放父结点、左右子节点三者的最大值的下标位置 if( left <= num && A[left] > A[i] ) largest = left; else largest = i; if( right <= num && A[right] > A[largest] ) largest = right; if( largest != i ) { swap( &A[i], &A[largest] ); MaxHeapify( A, largest, num ); } } /* 建最大堆 * 传入参数:待排序数组、当前堆的大小 * 返回值:空 */ void buildMaxHeap( ElemType *A, int num ) { for( int i=num/2; i>=0; i-- ) MaxHeapify( A, i, num ); } /* 堆排序 * 待排序数组、当前堆的大小 * 返回值:空 */ void heapSort( ElemType *A, int num ) { buildMaxHeap( A, num-1 ); int HeapSize = num - 1; //当前堆的大小 for( int i=num-1; i>0; i-- ) { swap( &A[0], &A[i] ); HeapSize--; MaxHeapify( A, 0, HeapSize ); } } int main() { ElemType A[10] = {1,3,6,2,4,8,0,9,7,5}; heapSort( A, 10 ); for( int j=0; j<10; j++ ) cout << A[j] << " "; cout << endl; return 0; }
时空复杂度:
由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。故堆排序的时间复杂度为O(N * logN)。
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O(1), 它是不稳定的排序方法。
相关文章推荐
- 《算法导论》学习笔记——堆排序
- 日常记录:《算法导论》学习笔记之二
- 《算法导论》 - 第6章 - 堆排序 - 习题解答
- 《算法导论》第6章 堆排序 个人笔记
- 《算法导论的Java实现》 7 堆排序
- 算法导论学习笔记(一)排序算法之堆排序
- 算法导论——第二章——堆排序
- 《算法导论》第6章 堆排序 (1)最大堆与堆排序
- 《算法导论》学习笔记之Chapter13红黑树
- 算法导论 第6章 堆排序(简单选择排序、堆排序)
- 栈和队列的数组实现—《算法导论》学习笔记之六
- 算法导论第六章 堆排序
- 算法导论学习笔记——第6章 堆排序
- 算法导论第六章总结:堆排序
- 《算法导论》 - 第6章 - 堆排序 - 习题解答
- 快速排序-《算法导论》学习笔记七
- 算法导论系列文章之堆排序
- 算法导论 堆排序
- 《算法导论》学习摘要chapter-6——堆排序
- 算法导论:堆排序