数据结构之排序(3)

167 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第28天,点击查看活动详情

数据结构之排序(3)

选择排序

       选择排序的基本思想是:每一-趟(如第i趟)在后面n-i+1 (i=1,2,。。。,n-1) 个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1趟做完,待排序元素只剩下1个,就不用再选了。

简单选择排序

       根据上面选择排序的思想,可以很直观地得出简单选择排序算法的思想:假设排序表为L[1..n],第i趟排序即从L[i..n]中选择关键字最小的元素与L(i)交换,每一趟排序可以确定一个元素的最终位置,这样经过n-1趟排序就可使得整个排序表有序。

       简单选择排序算法的代码如下;

void SelectSort(ElemType A[],int n){
    for(int i=0;i<n-1;i++){
        min=i;
        for(j=i+1;j<n;j++){
            if(A[j]<A[min]){
                min=j;
            }
        }
        if(min!=i){
            swap(A[i],A[min]);
        }
    }
}

简单选择排序算法的性能分析如下:

       空间效率:仅使用常数个辅助单元,故空间效率为0(1)。

       时间效率:从上述伪码中不难看出,在简单选择排序过程中,元素移动的操作次数很少,不会超过3(n- 1)次,最好的情况是移动0次,此时对应的表已经有序;但元素间比较的次数与序列的初始状态无关,始终是n(n- 1)/2次,因此时间复杂度始终是0(n2),

       稳定性:在第i趟找到最小元素后,和第i个元素交换,可能会导致第i个元素与其含有相同关键字元素的相对位置发生改变。例如,表L={2,2, 1},经过一趟排序后L={1,2,2}, 最终排序序列也是L={1,2,2},显然,2与2的相对次序已发生变化。因此,简单选择排序是一种不稳定的排序方法。

堆排序

堆的定义如下,n个关键字序列L[1..n]称为堆,当且仅当该序列满足:

       ①L(i)>=L(2i)且L(i)>=L(2i+1) 或

       ②L(i)<=L(2i)且L(i)<=L(2i+1) (1≤i≤⌊n/2」)

       可以将该一-维数组视为一棵完全二叉树,满足条件①的堆称为大根堆( 大顶堆),大根堆的最大元素存放在根结点,且其任一非根结点的值小于等于其双亲结点值。满足条件②的堆称为小根堆(小顶堆),小根堆的定义刚好相反,根结点是最小元素。如图所示为一个大根堆。

image.png

       堆排序的思路很简单:首先将存放在L[1..n]中的n个元素建成初始堆,由于堆本身的特点(以大顶堆为例),堆顶元素就是最大值。输出堆顶元素后,通常将堆底元素送入堆顶,此时根结点已不满足大顶堆的性质,堆被破坏,将堆顶元素向下调整使其继续保持大顶堆的性质,再输出堆顶元素。如此重复,直到堆中仅剩一个元素为止。

       堆排序的关键是构造初始堆。n个结点的完全二叉树,最后一个结点是第⌊n/2」个结点的孩子。对第⌊n/2」个结点为根的子树筛选(对于大根堆,若根结点的关键字小于左右孩子中关键字较大者,则交换),使该子树成为堆。之后向前依次对各结点(⌊n/2」-1~1)为根的子树进行筛选,看该结点值是否大于其左右子结点的值,若不大于,则将左右子结点中的较大值与之交换,交换后可能会破坏下一级的堆, 于是继续采用上述方法构造下一级的堆,直到以该结点为根的子树构成堆为止。反复利用上述调整堆的方法建堆,直到根结点。

下面是建立大根堆的算法:

void BuildMaxHeap(ElemType A[],int len){
    for(int i=len/2;i>0;i--){
        HeadAdjust(A,i,len);
    }
}
void HeadAdjust(ElemType A[],int k,int len){
    A[0]=A[k];
    for(int i=2*k;i<len;i*=2){
        if(i<len&&A[i]<A[i+1])
            i++;
        if(A[0]>=A[i]) break;
        else{
            A[k]=A[i];
            k=i;
        }
    }
    A[k]=A[0];
}

       调整的时间与树高有关,为O(h)。在建含n个元素的堆时,关键字的比较总次数不超过4n,时间复杂度为0(n),这说明可以在线性时间内将一个无序数组建成一个堆。

堆排序算法:

void HeapSort(ElemType A[],int len){
    BuildMaxHeap(A,len);
    for(i=len;i>1;i--){
        Swap(A[i],A[1]);
        HeadAdjust(A,1,i-1);
    }
}

堆排序算法的性能分析如下:

       空间效率:仅使用了常数个辅助单元,所以空间复杂度为0(1)。

       时间效率:建堆时间为O(n),之后有n-1次向下调整操作,每次调整的时间复杂度为O(h),故在最好、最坏和平均情况下,堆排序的时间复杂度为O(nlog2n)。

       稳定性:进行筛选时,有可能把后面相同关键字的元素调整到前面,所以堆排序算法是一种不稳定的排序方法。例如,表L= {1,2, 2},构造初始堆时可能将2交换到堆顶,此时L= {2, 1,2},最终排序序列为L={1,2,2},显然,2与2的相对次序已发生变化。