选择排序(简单选择排序,树形选择排序,堆排序)

简单选择排序

基本思想:第i趟排序经过n-i次关键字的比较在n-i+1(i=1,2,3,......n-1)个记录中选取关键字最小的记录作为序列的第i个记录 


 直接看代码!没啥好说的这个!

void SelectSort(SqList *L)
{
    for(int i=0;i<L->length-1;i++){//总共进行n-1趟排序,便可使整个序列有序
    {
        int index=i;//假设第i个记录就是最小值
        for(int j=i+1;j<L->length;j++)
            if(L->r[j]<L->r[index])//找到比index指向记录有更小关键字的记录
                index=j;//index指向带新最小关键字的记录
        if(index!=i)
        {
            int key=L->r[index];
            L->r[index]=L->r[i];
            L->r[i]=key;//交换记录
        }
    }   
    return;
}
    

简单选择排序:

记录移动次数最小为0次,最大值为3*(n-1)次

记录比较次数:第1趟排序比较n-1次,第i趟排序比较n-i次,共有n-1趟,故共比较n(n-1)/2次

故:简单选择排序的时间复杂度为O(n^2)


树形选择排序(锦标赛排序)

树形选择排序是对简单选择排序的改进。简单选择排序的主要操作是进行关键字间的比较,因此我们通过减少比较次数而改进简单选择排序。


改进思想:在n个关键字中选出最小值,需要进行n-1次比较,然而,继续在剩余的n-1个关键字中选择次小值并非一定要进行n-2次比较,若能利用前n-1次比较所得信息,则可减少以后各趟选择排序中比较次数。

主要步骤:

  1. 对n个记录的叶子节点进行两两比较,从根结点得到最小关键字的记录
  2. 找到与根结点关键字相同的叶子节点记录,更改关键字为最大值
  3. 从该叶子节点开始,和其左兄弟或者右兄弟进行比较,修改从叶子结点到根路径上各结点的关键字,得到次小关键字
  4. 重复上述第2~3步依次选出从小到大的所有关键字
#include <limit.h>
void TreeSelectSort(SqList *L)
{
     int n;//根据叶子节点个数计算整棵树的结点数
     if(L->length%2==0)
        n=L->length*2-1;//此时为叶子节点为L->length的满二叉树,叶子节点个数为偶数个
     else
        n=L->length*2;//此时为叶子节点为L->length的完全二叉树,叶子节点个数为奇数个
     int *Tree=(int*)malloc((n+1)*sizeof(int);//分配大小为n+1的数组,使用Tree[1]到Tree[n]
     if(!Tree)  exit(-1);
     for(int i=n,int j=L->length-1;i>=0;i--,j--){
         if(j>=0)    Tree[i]=L->r[j];
         else        Tree[i]=0;
     }//将顺序表记录依次从Tree[n-L->length+1]到Tree[n]进行赋值,其他赋值为0
     for(int k=n/2;k>0;k--){
        if(2*k+1<=n)
            Tree[k]=Tree[2*k]<Tree[2*k+1]?Tree[2*k]:Tree[2*k+1];
        else 
            Tree[k]=Tree[2*k];
     }//比较得到最小关键值
     L->r[0]=Tree[1];
     for(int i=1;i<L->length;i++){//进行L->length-1趟剩余最小关键字的选择
        int j=1;
        while(2*j<=n){//寻找次小关键字所在叶子节点
            if(Tree[j]==Tree[2*j])
                j=2*j;
            else
                j=2*j+1;
        }
        Tree[j]=MAX_INT;//最小关键字所在的叶子结点赋值为MAX_INT
        while(j!=1){//选出次小关键字
            j=j/2;
            if(2*j+1>n||Tree[2*j]<Tree[2*j+1])//如果j没有右孩子结点或者左结点<右结点
                 Tree[j]=Tree[2*j];//将左结点关键值赋值Tree[j]
            else
                 Tree[j]=Tree[2*j+1];//将右结点关键值赋值Tree[j]
                
        }
        L->r[i]=Tree[1];//得到次小关键值
    }
    return;
}   
     
            
        

时间复杂度: 

含有n个叶子结点的完全二叉树的深度为\left \lceil Log_{_2{}}n \right \rceil+1

则除了最小关键字的那一趟选择,每选择一个次小关键字仅需要比较\left \lceil Log_{_2{}}n \right \rceil次,因此时间复杂度为O(nlogn)

缺点:

  • 需要的辅助存储空间比较多
  • 需要和变成最大值的叶子结点进行比较

堆排序

堆的含义:完全二叉树的所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。若系列是堆,则堆顶元素必定为序列中n个元素的最小值(或最大值)。


实现堆排序面临两问题:

  1. 如何由一个无序序列建成一个堆
  2. 在输出堆顶元素之后,如何调整剩余元素成为一个新的堆

先解决第二个问题:以一维数组作为此序列的存储结构,假设为初始堆为大顶堆

  1. 将堆顶元素和最后一个元素互换,此时根结点的左右子树均为大顶堆
  2. 若根结点存在左右子树,则比较根结点和左右子树根结点的值,否则调整结束,跳到步骤1
  3. 若根结点值依旧均大于左右子树的根结点的值,则跳转到第1步执行
  4. 否则将根结点和左右子树根结点较大值互换,跳转到第2步,以被交换的子树的根结点作为第2步根结点执行

解决第一个问题:

从无序序列建堆的过程就是反复调整筛选的过程,设序列大小为n,将其看做是是一个完全二叉树,则第一个非终端结点为第\left \lfloor n/2 \right \rfloor个元素,从第\left \lfloor n/2 \right \rfloor个元素开始到第一个元素结束,分别以其作为根结点进行筛选调整。


typedef SqList HeapType;//给SqList定义别名为HeapType
//做非递减堆排序
void HeapAdjust(HeapType *H,int first,int end)//堆顶关键字小于左右子树根结点,需调整堆
{
    int i=first;//i指向堆顶,从堆顶开始进行堆调整
    while(2*i<=end){//当i不是叶子结点时,进入循环
        int key=H->r[i];//用key临时保存堆顶关键字
        int j=2*i;//用j指向最大子树根结点,假设此时左子树根结点为最大,用j指向它
        if(j+1<=end&&H->r[j]<H->r[j+1]) j++;//如果右子树根结点存在,大于左子树根结点,则j++
        if(Key>H->r[j]) break;//如果i大于其最大子树根结点,说明已经成堆,无需再进行调整,break
        H->r[i]=H->r[j];//否则,将最大子树根结点关键字赋值堆顶元素
        i=j;//最大子树结点成为子堆堆顶
        H->r[i]=key;//原有堆顶关键字赋值子堆堆顶
    }//以子堆堆顶i重新进行堆调整
    return;
}
//SqList序列表长度为H->length+1,但实际记录为H->length个,i=0不使用,从i=1到H->length
void HeapSort(HeapType *H)
{   
    int n=H->length;
    for(int i=H->length/2;i>0;--i)//对无序堆进行调整,从i=H->length/2开始到i=1;
        HeapAdjust(H,i,H->length);
    for(int i=H->length;i>1;i--)//只需进行H->length-1趟排序就OK
    {
        int key=H->r[1];
        H->r[1]=H->r[i];
        H->r[i]=key;//将堆顶的最大值记录和尾部记录交换
        HeapAdjust(H,1,i-1);//从1到i-1进行堆调整
    }
    return;
}

时间复杂度:堆排序的时间复杂堆为O(nlogn)

空间复杂度:只需要1个记录大小供交换用的辅助空间 O(1)

 

posted on 2019-10-09 19:44  偷影子的人儿  阅读(10)  评论(0编辑  收藏  举报