选择排序

简单选择排序

简单选择排序是每次从第i个到最后一个元素之间选取一个最小的(升序)作为有序序列中的第i个记录。
简单选择排序和直接插入排序有些类似,直接插入排序是按次序拿数据选择合适的位置插入,简单选择排序是选择合适的数据按次序放好。
这里写图片描述

代码:

void select_sort(int arr[], int length)
{
    int i,j;
    for(i=0; i<length-1; i++){
        int min = i;

        //找到最小的那个元素的下标,存储在min中
        for(j = i+1; j<length; j++)
            if(arr[j]<arr[min])
                min = j;

        //如果最小的元素不在arr[i]这个位置,把它放过去
        if(i!=min)
            swap(arr[i],arr[min]);
    }   
}

因为选择要放入已排序序列的元素时,是从前往后遍历的,可以保证相同元素前面的先放入已排序序列,所有简单选择排序是一种稳定的算法。
从n个元素中选最小的要进行n-1此比较,从n-1个元素中选最小的要进行n-2次比较…….所以简单选择排序的时间复杂度为O(n^2)。

树形选择排序

在简单选择排序中,每次比较都没有用到上次比较的结果,想要降低时间复杂度,就要把比较过程中的大小关系保存起来。
树形选择排序又叫“锦标赛排序”,让n个元素两两比较选出n/2个较小的,再让这n/2个元素两两比较选出n/4个更小的,依次类推,直到选出一个最小的。这个过程可以看成一颗满二叉树,元素不够则用无穷大补满 。
这里写图片描述
从第二次开始,每次只需log_2n 次比较就能选出剩余未排序序列中最小的元素,所以时间复杂度是O(n*log_2 n)。
接下来的问题是我们该怎样存储这颗树呢,因为我们最开始是只知道最后一层的元素,然后是一层一层向上推的,所以不能像一般的二叉树那样从根节点创建。
我们可以创建若干个数组,第一个数组将所有元素拷贝过来,然后第二个数组只有第一个一半大小,存储第一次比较的结果,第三个数组为第二个的一半大小,以此类推,直到最后一个数组只有一个元素。
接下来的任务是把这些数组联系起来,我们可以创建一个指针数组,让这个数组的每个元素分别指向一个数组的起始位置。
这里写图片描述

但是最后写代码的时候我发现这样存储很难找到树顶元素在树底的对应位置,所以最终除了树底的那个数组外,其余所有数组只存储对应的下标而不是数据,我们可以很轻松的通过树顶存储的下标将树底对应元素置为无穷大。
代码:

void select_sort(int arr[], int length)
{
    if(length<=1)
        return;
    int i,j;

    //创建比较树
    //计算树的深度
    int h,ret;
    for(h=1,ret=1; ret<length; h++,ret*=2)
        if(ret == length)
            break;
    //创建指向每个数组的指针数组
    int **tree = new int*[h];

    //创建一个数组拷贝所有元素并让tree[0]指向它
    tree[0] = new int[ret];//这里大小为ret,保证是满二叉树
    //拷贝元素
    for(i=0; i<length; i++)
        tree[0][i] = arr[i];
    //元素不满用无穷大补满
    for( ;i<ret;i++)
        tree[0][i] = (int)((unsigned)-1>>1);

    //每轮比较只存储下标
    int pre = ret; //记录上一轮数组的大小
    int cur = pre/2;//这一轮数组的大小
    int index = 0;//下标
    int left,right;

    tree[1] = new int[cur];//创建第一次比较数组
    for(i=0; i<pre; i+=2)//储存第一轮优胜的下标
        tree[1][index++] =(tree[0][i]<=tree[0][i+1])?i:i+1;
    pre = cur;//更新pre的值
    //依次进行每轮比较直到数组大小为1
    int n =2;//记录这是第几轮比较
    for(cur=cur/2; cur>0; cur>>=1,n++){
        tree[n] = new int[cur];//创建数组
        index = 0;

        //将上一组数据存放的下标对应元素比较,较小的下标存入本次数组
        for(i=0; i<pre; i+=2){
            //上组数据储存的是下标,先吧它取出来再用它取对应元素
            left = tree[n-1][i];
            right = tree[n-1][i+1];
            tree[n][index++] = (tree[0][left]<tree[0][right])?left:right;
        }
        pre = cur;//更新pre
    }

    for(i=0; i<length; i++){
        //将每次最后一个数组的元素(是一个下标)对应的元素放入原序列中
        index = tree[h-1][0];
        arr[i] = tree[0][index];

        //重新调整树
        //将此元素置为无穷大
        tree[0][index] = (int)((unsigned)-1 >>1);
        //此元素参与过的比较重新进行一遍

        //第一次比较
        if(index%2 == 0)
            tree[1][index/2] = index+1;
        else
            tree[1][index/2] = index-1;

        index/=2;//下个数组中要重新比较的元素下标

        //余下几次比较
        for(n = 2; n<h; n++){
            //判断本次参与比较元素在左还是在右
            if(index%2 == 0){
                left = tree[n-1][index];
                right = tree[n-1][index+1];
            }
            else{
                left = tree[n-1][index-1];
                right = tree[n-1][index];
            }
            //将较小的赋给下一个数组中的对应元素
            tree[n][index/=2] = (tree[0][left]<=tree[0][right])?left:right;
        }
    }
    //释放空间
    for(i=0; i<h; i++)
        delete[] tree[i];
    delete[] tree;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值