// 08_堆排序(heapsort).cpp
/**
* -> heapsort
* 1. 所谓堆排序,就是指将一组数看作是一个二叉堆,将其排序。
* 2. 实现堆排序:
* 01. 刚刚开始时,数组只是一堆没有排序的随意数,按照题目要求(使用下滤法)选择建立最大堆还是最小堆,
* 当选择了建立最大堆的时候,数组就会从小到大排序,当选择的是最小堆,数组则会从大到小排序。
* 02. 为什么选择了最大堆反而会从小到大排序呢?这是因为实现堆排序还有很重要的第二步:
* 通过设定数组的最末元素不断地与最值堆的首元素交换元素,将所需要的最值移动到元素的最后一位,
* 所以,建立最大堆会变成从小到大排序,而建立最小堆会变成从大到小排序。
**/
#include <iostream>
#include <vector>
#include <conio.h> // _getch();
using std::cin; // using 声明
using std::cout;
using std::endl;
using std::vector;
// ________________________ 主函数 _______________________________
int main()
{
void InsertArr(vector<double> & test);
void heapsort(vector<double> & test);
void ShowArr(vector <double> & test);
bool testagain(true);
char testagainjudge;
vector<double> testArr; // 用于测试的数组
do
{
cout << "------------------------- 现在开始数组的堆排序测试 ---------------------------\n";
cout << " -> 说明:该测试共分为三个步骤:输入 -> (系统内部)排序 -> 输出显示.\n"
<< "-> 注意:在输入时,请按任意字母结束输入。\n";
// 插入
InsertArr(testArr);
ShowArr(testArr);
cout << endl;
// 排序
heapsort(testArr);
ShowArr(testArr);
cout << endl;
cout << "-> 如需重新测试,请按字符'a',否则请按任意键退出...";
testagainjudge = _getch();
if (testagainjudge == 'a')
{
cin.sync();
testArr.clear();
testagain = true;
system("cls");
}
else
{
testagain = false;
}
}while (testagain);
return 0;
}
/**
* 子程序名称:InsertArr
* 子程序返回类型:void
* 子程序入口参数:vector<double> &
* 子程序功能:由用户设定N个数值,并由用户输入这N个数值,程序将其保存入vector<double>入口参数处。
**/
void InsertArr(vector<double> & test)
{
cout << "-> 请输入需要输入的数值个数:";
unsigned int n;
cin >> n;
cout << "-> 现在开始数值输入(请以空格间开):";
for ( unsigned int i = 0; i < n; ++i)
{
double value;
cin >> value;
while(cin.fail())
{
cin.sync();
cin.clear();
cin >> value;
}
test.push_back(value);
}
cout << "-> 输入操作完成.\n";
return;
}
/**
* 子程序名称:heapsort
* 子程序返回类型:void
* 子程序入口参数:vector<double> &
* 子程序功能:将vector<double>内部从小到大的顺序排序。
**/
void heapsort(vector<double> & test)
{
void percDown( vector<double> & test, int parent, int size);
int i;
// 下面第一个循环实现heapsort的第一步,将这个数组变为最大堆
// 通俗一点讲,就是将全部父亲跟儿子的关系搞好,使父亲的值比儿子的值大,
// 在这个循环中,使i的初值为 数组的总大小除以二,在最大堆里面的含义是:这是二叉树里面最右下角的父亲
// 假设有数组a[15], 则下面是它的树的形式表示的关系
// 0
// / \
// 1 2
// / \ / \
// 3 4 5 6
// /\ /\ /\ /\
// 7 8 9 10 11 12 13 14
// 使初值为7,每一次i减1,使i从右往左,从下往上走,直到它走到0的位置为止,
// 而在这个循环体当中,使用了上滤法,功能是找出一棵子树中的最值,并使这个最值放到父亲的位置
// 在下面的这一个循环当中,每一次的最值都会放到i里面,到最后,这个循环结束时,这棵树就可以成为一查最大堆了。
// 假如执行了三次循环,而那些数字又代表了数值的大小,那么, 它的结果将是:
// 0
// / \
// 1 2
// / \ / \
// 3 10 12 14
// /\ /\ /\ /\
// 7 8 9 4 11 5 13 6
// 假如执行了五次循环:
// 0
// / \
// 10 14
// / \ / \
// 8 9 12 13
// /\ /\ /\ /\
// 7 3 1 4 11 5 2 6
// 本次循环全部执行完的最终结果:
// 14
// / \
// 10 13
// / \ / \
// 8 9 12 6
// /\ /\ /\ /\
// 7 3 1 4 11 5 2 0
// 此时,最大堆形成,a[0]的元素为最大,整个数组相对有序。
for (i = static_cast<int>(test.size()) / 2 ; i >= 0; --i)
{
percDown(test, i, test.size()); // 下滤法,以i为父亲,使i的值在子树中最大
}
// 这里开始执行heapsort的第二步:目的是使数组最后以从小到大的方式排序
// 实现这一功能的方法,可以理解成从堆中从尾部开始逐次删除元素,而这个元素在删除前会先与最大堆的最值的数值交换,
// 即删除的是最值,然后把相对小的值,在数组尾部的值调到原来的首位,
// 可以知道,接下来要进行很重要的一点是:再重新将刚刚删除的交换的元素再次排序,使最大堆的第一位的值始终保持最大。
// 假如这里执行了第1步,交换元素,继续上面的例子:(1跟15的位置交换了)
// 0
// / \
// 10 13
// / \ / \
// 8 9 12 6
// /\ /\ /\ /\
// 7 3 1 4 11 5 2 14
// 假如这里执行了第2步,“删除”元素,然后使用下滤法:
// 13
// / \
// 10 12
// / \ / \
// 8 9 11 6
// /\ /\ /\ /\
// 7 3 1 4 0 5 2 [14]<-在使用下滤法时,这个元素已经被忽略不计算了,因为它的位置已经符合我们本来想实现的排序功能。
// 继续看一次程序执行的步骤:
// 执行第一步“交换”元素:
// 2
// / \
// 10 12
// / \ / \
// 8 9 11 6
// /\ /\ /\ /\
// 7 3 1 4 0 5 [13] [14]
// 执行第二步“删除”元素:
// 12
// / \
// 10 6
// / \ / \
// 8 9 11 2
// /\ /\ /\ / \
// 7 3 1 4 0 5 [13] [14]<-括住的元素,在使用下滤法时,可以当作是不存在了。
// 就这样一直往下交换删除排序,最后,这个被要求排序的数组就可以从小到大排序了。
for (i = static_cast<int>(test.size()) - 1 ; i > 0; --i)
{
// 以下,实现该子循环中的交换功能,使最值与相应位置的值交换,并且交换的末位元素的值不再变动
vector<double>::value_type temp = test[i];
test[i] = test[0];
test[0] = temp;
// 这里控制percDown调入的是i就是限定了数组的大小,使已经“删除”的元素位置不再读取,这样就可以保持排序的继续实现
percDown(test, 0, i);
}
return;
}
// 下滤法,调入参数有三:1. 需要过滤的数组,2. 需要比较的子树的父结点, 3. 该数组的大小
void percDown(vector<double> & test, int parent, int size)
{
int child;
// 建立新变量,临时保存[parent]的值
vector<double>::value_type tempparent = test[parent];
// 假如parent * 2 + 1 即儿子的值超出数组范围,则直接跳出循环
// 该循环每执行一次,就会对比了一棵子树,假如该子树中还包含着多个左右儿子,则循环继续,
// parent = child, 让parent下一级,直到为tempparent找到正确的位置.
// 实际上,这个函数要实现的功能就是,把进来的[parent]当作是一个需要插入的元素,
// 然后,为这个元素找到正确的位置。
for (; parent * 2 + 1 < size; parent = child)
{
child = parent * 2 + 1;
// 让child != size - 1 目的是让它不超出边界,假如执行了child + 1,这时会内存溢出
// test[child] < test[child] + 1目的是找出两个儿子中的最大值,假如符合条件,应将儿子加1
if (child != size - 1 && test[child] < test[child + 1])
{
++child;
}
// if语句的执行结果是让老爸比儿子的值大
if (tempparent < test[child])
{
test[parent] = test[child];
}
else
{
break;
}
}
// 最终将parent的值还原到相应的位置中,这方法的实现实际上跟插入排序有点类似的地方
test[parent] = tempparent;
return;
}
/**
* 子程序名称:ShowArr
* 子程序返回类型:void
* 子程序入口参数:vector<double> &
* 子程序功能:遍历并显示vector<double>&。
**/
void ShowArr(vector <double> & test)
{
cout << "-> 现在开始显示确认刚刚所输入的数组顺序:\n";
cout << "-> ";
vector<double>::const_iterator be(test.begin());
vector<double>::const_iterator en(test.end());
while ( be != en)
{
cout << *be++ << " ";
}
return;
}