堆排序、堆排序优化、索引堆排序git
注: 堆排序、索引堆排序 都是不稳定的排序。
注:索引最大堆排序有误!!!有没有大神能够指点一二???github
一、堆:
全部元素 都从索引0开始web
父亲结点索引:i;
左孩子结点索引: 2i+1;
右孩子结点索引: 2i+2;
左后一个非叶子结点索引:(n-1)/2; 用于构建堆,从最后一个非叶子结点索引开始调整堆,直至到达索引为0的首个父亲结点数组
二、堆排序(升序为例):
共两步:
step一、构建堆
step二、原地堆排序数据结构
step一、构建堆
从最后一个非叶子结点索引开始调整堆,直至到达索引为0的首个父亲结点。
step二、原地堆排序:
每次循环,使用O(1)的时间索引到当前循环的最大值a[0],
将该最大值交换到数组末尾,
数组元素减1,
对新的堆进行调整,为下一轮循环作准备。svg
三、调整堆函数shiftDown()思路:
从当前父亲结点k开始,
每次比较 当前父亲结点值与该父亲结点对应的左右孩子节点中的最大值,
若是 “父亲结点值”>“最大孩子节点值”,就表明堆调整好了,提早结束循环。
若是 “父亲结点值”<“最大孩子节点值”,那么,当前"父亲结点值"更换为“最大孩子节点值"。更新父亲结点值,继续向下调整。函数
四、区别
4.一、普通堆排序的 shiftDown中使用“移动赋值”操做 与 “交换数据元素” 操做 区别:
使用“移动赋值”操做 比 使用"交换数据元素" 操做,的时间损耗少了2/3.
eg:shiftDown中总共进行m次循环,
—使用“移动赋值”操做:全部操做数为:m次移动+1次赋值+1个额外空间的申请,共计 m+1次 赋值操做 + 1个额外空间的申请
—使用“交换数据”操做:全部操做数为:3m次赋值+1个额外空间的申请, 共计 3m 次赋值操做 + 1个额外空间的申请测试
4.二、索引堆排序 与 普通堆排序 区别:
(1)定义
索引堆:数据域 与 索引域 是分开存储的。
排序过程当中:数据元素的相对位置保持不变,这样能够使得 堆排序 优化为稳定的排序;改变的是索引数组的相对位置。
最后造成的索引数组,就是所谓的索引堆。优化
索引堆排序:
不改变原来数据域元素的位置,只是新开辟了一个索引域来表明原来数据域的相应数据进行排序,其本质仍是一个堆排序。重点内容code
(2)排序
比较值大小时,用的是 数据域 data数组中的元素
移动、赋值、交换时,用的是 索引域 index数组中的元素
(3)排序输出
索引堆排序:输出时,只需依次取出索引数组中对应索引的对应数据域元素,便可。
普通堆排序:依次输出,改变后的数组元素。
(4)消耗
索引堆排序 比 普通堆排序 多占用一个O(n)的int型空间,用于存放代替数据元素进行堆排序的索引数据。
(5)优势
索引堆排序与普通堆排序,优势为:
若是原来数据域中,每个元素的数据结构很复杂,或者数据的size都很大,那么,使用普通堆排序,在移动、赋值、交换数据域的元素过程花费会很是的巨大。
而使用索引堆排序,则只是花费了一个O(n)的int类型空间,在在移动、赋值、交换操做中,都是一个int型的索引元素在参与运算,花费很是小。
五、核心代码:
///////////////////////////////////////////////////////// //三个版本的原地堆排序 //version1 最大堆排序 shiftDown()中"交换数据元素"操做 void shiftDown(int a[], int n, int k){//以k为开始调整的父节点,自上而下调整 while (2 * k + 1 < n){//存在孩子节点时,最少存在一个“左孩子结点” int j = 2 * k + 1;//j:左右孩子结点最大值的索引 初始化为 左孩子结点索引 if (j + 1 < n&&a[j + 1] > a[j])j += 1;//若是存在右孩子结点,且右孩子结点值大于左孩子结点值,更新孩子节点最大值索引为右孩子结点索引。 if (a[k] > a[j])break;//循环提早结束标志,当当前父节点值大于该父节点对应的最大孩子节点值时,堆调整好啦,退出循环 swap(a[k], a[j]);//交换 不然的话,交换父节点与最大值孩子节点 k = j;//更新父亲结点为当前最大值孩子节点,继续向下调整堆 } } void maxHeapSort(int a[], int n){ //建堆 for (int i = (n - 1) / 2; i >= 0; --i) shiftDown(a, n, i); //显示堆 cout << "建堆" << endl; printArr(a, n); //原地堆排序 for (int i = n - 1; i > 0; --i){//i:每轮循环要处理的堆元素个数 swap(a[0], a[i]);//交换最大值到数组末尾 shiftDown(a, i, 0);//调整去掉最大值后的剩余堆元素 为最大堆 } } //version2 最大堆排序优化 shiftDown()中使用“移动赋值”操做取代"交换数据元素" 操做 //思路源于 插入排序 void shiftDown2(int a[], int n, int k){ int tmp = a[k]; while (2 * k + 1 < n){ int j = 2 * k + 1; if (j + 1 < n && a[j + 1] > a[j])j += 1; if (tmp > a[j])break; a[k] = a[j];//移动 k = j;//更新 父亲结点的索引,切记!! } a[k] = tmp;//赋值 } //有问题 !!!version3 最大索引堆排序 shiftDown()中使用“移动赋值”操做 void shiftDown3(int a[], int index[], int n, int k){ int tmp_index = index[k];//开始的临时变量是 索引为k的data域中的元素,即 第索引号为k的索引域中的元素 index[k] while (2 * k + 1 < n){ int j = 2 * k + 1; if (j + 1 < n && a[index[j + 1]] > a[index[j]])j += 1;//a[index[j + 1]] > a[index[j]]:比较的是数据域中的元素值 if (a[tmp_index] > a[j])break;//a[tmp_index] > a[j]:比较的是数据域中的元素值 index[k] = index[j];//移动的是 索引域中元素 k = j; } index[k] = tmp_index;//赋值的是 索引域中元素 } void IndexMaxHeapSort(int a[], int index[], int n){ for (int i = (n - 1) / 2; i >= 0; --i)//建堆 shiftDown3(a, index, n, i); cout << "建堆" << endl; printIndexMaxHeap(a, index, n); for (int i = n - 1; i > 0; --i){//原地堆排序 swap(index[0], index[i]); shiftDown3(a, index, i, 0); } } void printIndexMaxHeap(int a[], int index[], int n){ for (int i = 0; i < n; ++i) cout << a[index[i]] << "\t"; cout << endl; } //version3 end
六、完整代码,请移步个人GitHub
https://github.com/MissStrickland/maxHeapSort_indexMaxHeapSort/blob/master/main_heapSort.cpp
七、测试:
起始数据: 2 9 5 6 4 10 8 3 5 8
//建成堆以下:
10 / \ 9 8 / \ / \ 6 8 5 2 / \ / 3 5 4
测试结果以下图所示:
从测试结果看, 前俩个版本是好的, 可是,索引最大堆排序有问题!!目前找不到问题所在,还望路过的大神指点一二!!