原地堆排序
来源:互联网 发布:阿里云怎么打开iis 编辑:程序博客网 时间:2024/05/16 12:09
上一节讲的两种堆排序都需要开辟O(n)的辅助空间(构造函数中使用new分配的辅助空间),程序在开辟辅助空间和释放空间的时候也会消耗一定的时间,若能多数组进行原地堆排序,则省去了开辟和释放空间的时间,时间性能会好一些。
给定一个大小为n的数组,将这个数组heapify,变为最大堆,此时数组的第一个元素就是最大值,将该值与数组最后一个元素交换位置后,对前n-1个元素进行heapify,变成最大堆,将第一个元素与数组倒数第二个元素交换位置...以此类推,最后得到的数组就是从小到大排序的。
注意,上一节中堆排序堆的实现是从1开始,所以当前元素(下标 i)的双亲下标为 i/2,左孩子下标为 2i,右孩子下标为 2i+1;而原地堆排序中直接对数组进行操作,数组的下标是从0开始,所以当前元素(下标i)的双亲为 (i-1)/2,左孩子下标为 2i+1,右孩子下标为 2i+2。
原地堆排序的C++实现如下:
SortTestHelper.h文件(包含辅助函数)
#include <iostream>#include <cstdlib>#include <ctime> //clock()、CLOCKS_PER_SEC#include <cassert> //包含函数assert()using namespace std;namespace SortTestHelper{ //辅助函数 - 随机产生一个数组 int* generateRandomArray(int n, int RangeL, int RangeR) //返回数组首地址 { //判断RangeL是否<=RangeR assert(RangeL <= RangeR); //参数为表达式,表达式为真时返回true,否则打印错误信息 int *arr = new int[n]; srand(time(0)); for(int i = 0; i < n ; i++) { arr[i] = rand() % (RangeR - RangeL + 1) + RangeL; //使得产生的随机数在RangeL和RangeR之间 } return arr; } //辅助函数 - 产生一个近乎有序的随机数组 int* generateNearlyOrderedArray(int n, int swapTime) { int *arr = new int[n]; for(int i = 0; i < n; i++) { arr[i] = i; //先生成一个完全有序的数组 } //然后交换几组元素,使之变成无序但近乎有序的数组 srand(time(0)); for(int j = 0; j < swapTime; j++) { //随机生成一个x位置和y位置 int posx = rand() % n; int posy = rand() % n; //交换x和y处的元素 swap(arr[posx], arr[posy]); } return arr; } //辅助数组 - 产生一个完全有序数组 int* generateTotallyOrderedArray(int n) { int *arr = new int[n]; for(int i = 0; i < n; i++) { arr[i] = i; } return arr; } //辅助函数 - 打印数组 template<typename T> void printArray(T arr[], int n) { for(int i = 0; i < n; i++) { cout << arr[i] << " "; } cout << endl; } //辅助函数 - 判断数组是否有序(升序) template<typename T> bool isSorted(T arr[], int n) { for(int i = 0; i < n - 1; i++) { if(arr[i] > arr[i + 1]) { return false; } } return true; } //辅助函数 - 测试算法的时间 template<typename T> void testSort(string sortname, void(*sort)(T[], int), T arr[], int n) //arr[]和n是函数指针需要的参数 { clock_t starttime = clock(); sort(arr, n); //调用函数sort() clock_t endtime = clock(); //判断排序是否成功 assert(isSorted(arr, n)); //若是数组无序,则assert会自动调用abort()退出程序,不会执行下面的语句 cout << sortname << " needs " << double(endtime - starttime) / CLOCKS_PER_SEC << "s." << endl; } //辅助函数 - 拷贝数组 int* copyIntArray(int a[], int n) { int *arr = new int[n]; //使用C++函数copy() copy(a, a + n, arr); return arr; }}HeapSort.h文件(包含上一节所讲的两种空间复杂度为O(n)的堆排序算法)
#include <iostream>#include <cassert>#include <algorithm>#include <cstdlib>#include <ctime>using namespace std;template<typename Item>class MaxHeap{private: Item *data; int count; int capacity; void shiftUp(int k) { //凡是涉及到数组下标的,都要限定它不出界 /*while(k > 1 && data[k] > data[k / 2]) { swap(data[k], data[k / 2]); k /= 2; }*/ //优化:用赋值取代交换 Item item = data[k]; while( k>1 && data[k/2] < item ) { data[k] = data[k/2]; k /= 2; } data[k] = item; } void shiftDown(int k) { //当k有孩子的情况下开始shiftDown操作 /*while( 2*k <= count ) { int j = 2*k; //j就是要和data[k]交换的元素的位置下标 //判断是否有右孩子 if( j+1 <= count && data[j+1] > data[j] ) { j = j + 1; } if(data[k] >= data[j]) { break; } swap(data[k], data[j]); k = j; }*/ //优化:用赋值取代交换 Item item = data[k]; while( 2*k <= count ) { int j = 2*k; if(j + 1 <= count && data[j + 1] > data[j]) { j = j + 1; } if(data[j] <= item) { break; } data[k] = data[j]; k = j; } data[k] = item; }public: MaxHeap(int capacity) { data = new Item[capacity + 1]; //从1号单元开始存放 count = 0; this->capacity = capacity; } MaxHeap(Item arr[], int n) { count = n; capacity = n; data = new Item[n + 1]; for(int i = 1; i <= n; i++) { data[i] = arr[i - 1]; } //将传入的数组整合成堆的样子 for(int i = count / 2; i >= 1; i--) { shiftDown(i); } } ~MaxHeap() { delete[] data; } //返回堆现在的大小 int size() const { return count; } //判断堆是否为空堆 bool isEmpty() const { return count == 0; } //向堆中插入一个元素 void insert(Item item) { //判断count+1是否出界 assert(count + 1 <= capacity); data[count + 1] = item; count++; //从count位置处开始向上调整堆 shiftUp(count); //这个函数不需要被用户调用,所以设为私有 } //取出堆中的一个元素(只能取出根结点) Item extractMax() { //首先判断堆是否为空 assert(count > 0); Item ret = data[1]; data[1] = data[count]; count--; shiftDown(1); return ret; } //打印堆中内容 void printData() const { for(int i = 1; i <= count; i++) { cout << data[i] << " "; } cout << endl; }};template<typename T>void HeapSort1(T arr[], int n){ MaxHeap<T> maxheap = MaxHeap<T>(n); for(int i = 0; i < n; i++) { maxheap.insert(arr[i]); } //从小到大排序 for(int i = n - 1; i >= 0; i--) { arr[i] = maxheap.extractMax(); }}template<typename T>void HeapSort2(T arr[], int n){ MaxHeap<T> maxheap = MaxHeap<T>(arr, n); //从小到大排序 for(int i = n - 1; i >= 0; i--) { arr[i] = maxheap.extractMax(); }}main.cpp文件(包含这一节所讲的原地堆排序算法)
#include <iostream>#include "HeapSort.h"#include "SortTestHelper.h"using namespace std;//对arr[0...n-1]中的下标为k的元素进行shiftDown操作template<typename T>void __shiftDown(T arr[], int n, int k){ T v = arr[k]; while(2*k + 1 < n) //有左孩子 { int j = 2*k + 1; if(j+1 < n && arr[j] < arr[j+1]) //有右孩子并且右孩子大于左孩子 { j += 1; } if(arr[j] < v) { break; } arr[k] = arr[j]; k = j; } arr[k] = v;}//原地堆排序template<typename T>void HeapSort3(T arr[], int n){ //对数组arr[0...n-1]进行heapify for(int i = (n-2)/2; i >= 0; i--) { __shiftDown(arr, n, i); } for(int i = n-1; i > 0; i--) { swap(arr[0], arr[i]); __shiftDown(arr, i, 0); }}int main(){ int n = 1000000; //测试 - 随机序列 int *arr = SortTestHelper::generateRandomArray(n, 0, n); int *arr2 = SortTestHelper::copyIntArray(arr, n); int *arr3 = SortTestHelper::copyIntArray(arr, n); SortTestHelper::testSort("HeapSort1", HeapSort1, arr, n); SortTestHelper::testSort("HeapSort2", HeapSort2, arr2, n); SortTestHelper::testSort("HeapSort3", HeapSort3, arr3, n); cout << endl; delete[] arr; delete[] arr2; delete[] arr3; //测试 - 近乎有序序列 int swaptime = 100; int *ar = SortTestHelper::generateNearlyOrderedArray(n, swaptime); int *ar2 = SortTestHelper::copyIntArray(ar, n); int *ar3 = SortTestHelper::copyIntArray(ar, n); SortTestHelper::testSort("HeapSort1", HeapSort1, ar, n); SortTestHelper::testSort("HeapSort2", HeapSort2, ar2, n); SortTestHelper::testSort("HeapSort3", HeapSort3, ar3, n); cout << endl; delete[] ar; delete[] ar2; delete[] ar3; //测试 - 重复键值序列 int *a = SortTestHelper::generateRandomArray(n, 0, 10); int *a2 = SortTestHelper::copyIntArray(a, n); int *a3 = SortTestHelper::copyIntArray(a, n); SortTestHelper::testSort("HeapSort1", HeapSort1, a, n); SortTestHelper::testSort("HeapSort2", HeapSort2, a2, n); SortTestHelper::testSort("HeapSort3", HeapSort3, a3, n); cout << endl; delete[] a; delete[] a2; delete[] a3; return 0;}测试结果:
HeapSort1 needs 0.3s.
HeapSort2 needs 0.293s.
HeapSort3 needs 0.288s.
HeapSort1 needs 0.26s.
HeapSort2 needs 0.173s.
HeapSort3 needs 0.169s.
HeapSort1 needs 0.186s.
HeapSort2 needs 0.181s.
HeapSort3 needs 0.179s.
从测试结果中可以看出,不同测试用例中,原地堆排序都比HeapSort1和HeapSort2快一些,这正是由于省去了开辟堆空间和释放堆空间的时间。
- 原地堆排序
- 原地堆排序
- [Algorithm_Learn_03]原地排序之堆排序
- 某公司面试题原地堆排序
- 按照堆排序的方式原地进行升序排列
- 《算法与数据结构》学习笔记 4-6 优化的堆排序(原地堆排序)
- 挖掘算法中的数据结构(四):堆排序之 二叉堆(Heapify、原地堆排序优化)
- 原地归并排序
- 原地归并排序
- 原地置换排序
- 原地归并排序变形
- 原地归并排序
- 原地归并排序
- 原地归并排序
- 原地归并排序
- 原地归并排序
- C原地快速排序
- 原地归并排序
- smoj2010(广义后缀自动机)
- syslog-ng详解——syslog-ng配置语法
- Springboot系列:Springboot与Thymeleaf模板引擎整合基础教程(附源码)
- HDU1021
- 各大OJ题
- 原地堆排序
- php经典算法(二分法、快速排序)
- Android获取当前时间
- 微信营销更具互动性,微信公众平台做哪些活动好呢
- CentOS 7 yum 源改为阿里 的
- 嵌入式操作系统内核原理和开发(总结篇--很全面多角度)
- Android 两种 防止控件重复点击 多次提交
- 软件工程测试
- [Python面试知识]数据结构之栈和队列实现