(1.3.7)选择排序:简单选择、树形选择

来源:互联网 发布:ubuntu源arm 编辑:程序博客网 时间:2024/06/04 20:53

选择排序(Selection Sort)

    经过一趟排序,可以从n-i+1(i=1,2...)个记录中选取关键字最小的记录作为有序序列中第i个记录。也就是说,每一趟排序,都会排好一个元素的最终位置。

最简单的是

    简单选择排序(Simple Selection Sort,也叫直接选择排序)

    简单选择排序的思想:在每一趟排序中,通过n-i次关键字的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录交换,以此确定第i个记录的最终位置。简单说,逐个找出第i小的记录,并将其放到数组的第i个位置。

比较简单,直接看代码;

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void SimpleSelectSort(int a[], int n)  //简单选择排序   
  2. {  
  3.     if(a && n>1)  
  4.     {  
  5.         int index;  
  6.         for(int i=0; i<n; i++)  
  7.         {  
  8.             index=i;  
  9.             for(int j=i+1; j<n; j++)  
  10.             {  
  11.                 if(a[j]<a[index])  
  12.                 index=j;  
  13.             }  
  14.             if(i!=index)  
  15.             {  //交换数据,方法多种  
  16.                 a[i]^=a[index];  
  17.                 a[index]^=a[i];  
  18.                 a[i]^=a[index];  
  19.             }   
  20.         }  
  21.     }  
  22. }  

看下代码,两个for循环,每一个都与n有关,粗略估计时间复杂度是O(n^2)。

下面仔细计算一下:

    若初始序列是有序的,则无需移动元素。若是逆序的,则需移动3(n-1)(这个数字有问题!)。每确定一个元素的最终位置,swap()方法中一般3次移动,但确定了最小的,也同时确定了最大的。所以这个3(n-1)不可信。但无论如何,它都是线性的。

无论初始序列的情况如何,都必须进行 (n-1)+(n-2)+...2+1=n(n-1)/2 次比较。

综上所述,它的时间复杂度是O(n^2)。与我们的估计一样。


    简单选择有可以改进的地方。选择排序的主要操作在关键字的比较上。比如,在n个元素中选出最小的,要进行n-1次比较,而选出次最小的要进行n-2次比较...。为何下一趟排序,不能利用上一趟排序的比较信息呢?若是可以做到,则极大的减少下一趟排序的比较次数。下面介绍的树形选择排序(Tree Select Sort)就是针对这种情况进行改善的。

树形选择排序又叫锦标赛排序(Tournament Sort)。它类似于比赛的过程。如下图:


    叶子节点即是所有的参赛者,两两比较,胜出者参与下一轮,也就是说胜出者上升到父节点。若是参赛者为奇数个,则最后一个参赛者直接晋级。我们按从小到大排序,故小的胜出。上图树顶根节点即最后的胜出者。

    貌似我们只能得到最大或最小。是这样,这只是一趟排序的结果嘛。下一趟排序该如何进行呢?做法:把胜出者的兄弟节点上升到父节点位置。从此位置向上调整树。过程如下图:


    第二个胜出者是7(亚军)。显然这一趟排序,我们利用了上一趟的排序信息:8与23比较,8胜出。这一趟排序我们没有重复比较,这在元素较多时,效果更加明显。后面的以此类推,是否感觉到很简单呢?其实还有很多的细节(包括一些满二叉树的知识:内节点个数和外节点个数的关系,每一层多少节点……)。它的代码很不好写。如果没有弄明白过程,也很难看懂!

    比如,上文的黑体字:把胜出者的兄弟节点上升到父节点位置。看到这句话时,你是否想过:如果兄弟节点已经出局了呢?上图中,7出局后,5会被升上去,而5已经先于7出局了,这样做是否有问题呢?带着这些疑问,反复仔细看下面的代码(结合后面的提示):

[cpp] view plaincopy
  1. #include<iostream>       
  2. #include<math.h>  
  3. #include<iomanip>  
  4. using namespace std;  
  5. typedef struct rec  
  6. {  
  7.     int data;  
  8.     int index;  
  9.     bool active;   //节点未出局,则是true,其它false     
  10. }Rec;  
  11. void fixUpTree(Rec* tree, int pos)  //从pos位置向上调整     
  12. {  
  13.     int i = pos;  
  14.     if (i % 2)   //i位于右子树    
  15.         tree[(i - 1) / 2] = tree[i + 1];   //左孩子上升到父节点     
  16.     else  
  17.         tree[(i - 1) / 2] = tree[i - 1];   //右孩子上升到父节点     
  18.     i = (i - 1) / 2;  
  19.     int j;  
  20.     while (i)     //上升到根节点,则终止循环     
  21.     {  
  22.         i % 2 ? j = i + 1 : j = i - 1;     //确定i的兄弟j的下标     
  23.         if (!tree[i].active || !tree[j].active)  //左右孩子有一个为空     
  24.         {  
  25.             if (tree[i].active)  
  26.                 tree[(i - 1) / 2] = tree[i];  
  27.             else  
  28.                 tree[(i - 1) / 2] = tree[j];  
  29.         }  
  30.         else  //左右孩子都不为空     
  31.         {  
  32.             if (tree[i].data <= tree[j].data)  
  33.                 tree[(i - 1) / 2] = tree[i];  
  34.             else  
  35.                 tree[(i - 1) / 2] = tree[j];  
  36.         }  
  37.         i = (i - 1) / 2;   //回到上一层     
  38.     }  
  39. }  
  40. void TreeSelectSort(int a[], int n) //树形选择排序     
  41. {  
  42.     int i = 0;  
  43.     while (pow(double(2), i) < n)  
  44.         i++;  
  45.     int leaf = pow(2, i);   //完全二叉树叶子节点个数     
  46.     int size = 2 * leaf - 1;   //树节点总数  提示3    
  47.     Rec *tree = new Rec[size];  //顺序存储一棵树     
  48.     for (i = 0; i < leaf; i++)  
  49.     {  
  50.         if (i < n)  
  51.         {  
  52.             //leaf-1是叶子节点的起始下标,想想是这样吗?  
  53.             tree[i + leaf - 1].data = a[i];  
  54.             tree[i + leaf - 1].index = i;  
  55.             tree[i + leaf - 1].active = true;  
  56.         }  
  57.         else//叶子节点下标从 leaf-1+n开始,后面都是空的,无此参赛者    
  58.             tree[i + leaf - 1].active = false;      
  59.     }  
  60.     i = leaf - 1;    //提示3    
  61.     int j;  
  62.     while (i)   //上升到根节点,则终止循环     
  63.     {  
  64.         j = i;  
  65.         while (j<2 * i)   //下面的提示4    
  66.         {  
  67.             //无右节点或右节点已出局,即使存在右节点,其值域也比左节点大    
  68.             if (!tree[j + 1].active || tree[j + 1].data>tree[j].data)    
  69.                 tree[(j - 1) / 2] = tree[j];  
  70.             else  
  71.                 tree[(j - 1) / 2] = tree[j + 1];  
  72.             j += 2;     //两两比较     
  73.         }  
  74.         i = (i - 1) / 2;  //回到上一层      
  75.     }  
  76.     i = 0;  
  77.     while (i < n - 1) //确定剩下的n-1个节点的次序     
  78.     {  
  79.         a[i] = tree[0].data;  
  80.         tree[leaf - 1 + tree[0].index].active = false//出局,不参与下一轮  
  81.         //每次出局后都需调整  
  82.         fixUpTree(tree, leaf - 1 + tree[0].index);  
  83.         i++;  
  84.     }  
  85.     a[n - 1] = tree[0].data;  //最后一个归位     
  86.     delete[]tree;  
  87. }  


提示

1、理解了标志位active的作用,就可解释上面的疑问。

2、对一棵满二叉树按层次遍历顺序存储,下标从0开始。若i是父亲,则2*i+1是其左孩子,2*i+2是其右孩子。并且,无论i是左孩子还是右孩子,(i-1)/2都是其父亲。动动小手,画图理解哦!

3、对于满二叉树,外节点(叶子节点)数比内部节点(非叶节点)多一个。叶子节点的起始下标是leaf-1,leaf是叶子节点个数。不清楚就动手画图哦!

4、对于满二叉树,每一层最后一个节点的下标是第一个节点下标的2倍。


下面用主函数测试下:

[cpp] view plaincopy
  1. int main()  
  2. {  
  3.     cout << "------树形选择排序---by David---" << endl;  
  4.     int n;  
  5.     cout << "输入排序元素个数:";  
  6.     scanf_s("%d", &n);  
  7.     int *array = new int[n];  
  8.     int i = 0;  
  9.     while (i < n)  
  10.     {  
  11.         scanf_s("%d", &array[i]);  
  12.         i++;  
  13.     }  
  14.     cout << "经过树形选择排序" << endl;  
  15.     TreeSelectSort(array, n);  
  16.     for (i = 0; i < n; i++)  
  17.         cout << setw(4) << array[i];  
  18.     cout << endl;  
  19.     delete[]array;  
  20.     system("pause");  
  21.     return 0;  
  22. }  
运行




转载请注明出处,本文地址:http://blog.csdn.net/zhangxiangdavaid/article/details/29844821


0 0
原创粉丝点击