数据结构复习-选择排序

来源:互联网 发布:我的世界fps优化 编辑:程序博客网 时间:2024/05/20 13:08

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

1. 简单选择排序

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

void SelectSort(ElemType A[],int n) {    for(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])    }}

简单选择排序算法的性能分析如下:
空间效率:仅使用常数个辅助单元,故而空间效率为O(1)。
时间效率:简单选择排序过程中,元素移动的次数很少,不会超过3(n-1)次,最好的情况是移动0次,此时对应的表已经有序;但元素间比较的次数与序列的初始状态无关,始终是n(n-1)/2次,所以时间复杂度始终是O(n^2)。
稳定性:在第i趟找到最小元素后,和第i个元素交换,可能会导致第i个元素与其含有相同关键字元素的相对位置发生改变。因此,简单选择排序是一个不稳定的排序方法。

2. 堆排序

堆排序是一种树形选择排序方法,它的特点是:在排序过程中,将L[1...n]看成是一课完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的元素。
堆的定义如下: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⌋)
满足第①种情况的堆称为小根堆(小顶堆),满足第②种情况的堆称为大根堆(大顶堆)。在大根堆中,最大元素存放在根结点中,且对其任一非根结点,它的值小于或等于其双亲结点的值。小根堆的定义刚好相反,根结点是最小元素。
堆排序的关键是构造初始堆,对初始序列建堆,就是一个反复筛选的过程。n个结点的完全二叉树,最后一个结点是第⌊n/2⌋个结点的孩子。对第⌊n/2⌋个结点为根的子树筛选(选对于大根堆:若根结点的关键字小于左右子女中关键字较大者,则交换),使该子树成为堆。之后向前依次对各结点(⌊n/2⌋-1~1)为根的子树进行筛选,看该结点值是否大于其左右子树结点的值,若不是,将左右子结点中较大值与之交换,交换后可能会破坏下一级的堆,于是继续采用上述方法构造下一级的堆,直到以该结点为根的子树构成对为止。反复利用上述调整堆的方法建堆,直到根结点。
下面是建立大根堆的算法:

void BuildMaxHeap(ElemType A[], int len) {    for(int i=len/2; i>0; i--) {        AdjustDown(A,i,len);    }}void AdjustDown(ElemType A[],int k,int len) {    A[0] = A[k];    for(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的序列上建堆,其时间复杂度为O(n),这说明可以在线性时间内,将一个无序数组建成一个大顶堆。
应用堆这种数据结构进行排序的思路很简单,首先将存放在L[1...n]中的n个元素建成初始堆,由于堆本身的特点(以大顶堆为例),堆顶元素就是最大值。输出堆顶元素后,通常将堆底元素送入堆顶,此时根结点已不满足大顶堆的性质,堆被破坏,将堆顶元素向下调整使其继续保持大顶堆的性质,再输出堆顶元素。如此重复,直到堆中仅剩下一个元素位置。

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

同时,堆也支持删除和插入的操作,由于堆顶元素或为最大值或为最小值,删除堆顶元素时,先将堆的最后一个元素与堆顶元素交换,由于此时堆的性质被破坏,需要对此时的根结点进行向下调整操作。对堆进行插入操作时,先将新结点放在堆的末端,再对这个新结点执行向上调整操作。
下面是向上调整堆的算法:

void AdjustUp(ElemType A[],int k) {    A[0] = A[k];    int i = k/2;    while(i>0&&A[i]<A[0]) {        A[k] = A[i];        k=i;        i=k/2;    }    A[k]=A[0];}

堆排序算法的性能分析如下:
空间效率:仅使用了常数个辅助单元,所以空间复杂度为O(1)。
时间效率:建堆时间为O(n),之后有n-1次向下调整操作,每次调整的时间复杂度为O(h),故在最好、最坏和平均情况下,堆排序的时间复杂度为O(nlog2(n))。
稳定性:在进行筛选时,有可能把后面相同关键字的元素调整到前面,所以堆排序算法是一种不稳定的排序算法。

原创粉丝点击