(2011.12.03) 08_堆排序(heapsort).cpp

来源:互联网 发布:慕清明知乎 编辑:程序博客网 时间:2024/06/06 18:33
 
 // 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目的是找出两个儿子中的最大值,假如符合条件,应将儿子加1if (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;}