选择排序(简单选择排序堆排序的算法思想及代码实现)

Posted 薛定谔的猫ovo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了选择排序(简单选择排序堆排序的算法思想及代码实现)相关的知识,希望对你有一定的参考价值。


选择排序

选择排序的基本思想是:
在由 n n n个元素组成的序列中,选择一个具有最小(或最大)关键字的元素,把它加入到有序序列中,然后在剩余的元素序列中再选一个具有最小(或最大)关键码的元素,把它加入到有序序列中。如此继续,直到元素序列中只剩下一个元素为止,排序结束。


简单选择排序

简单选择排序的基本思想

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


简单选择排序的实现代码

//简单选择排序
void SelectSort(SeqList &L)
    int n = L.n;
    for(int i=0; i<n-1; i++) //一共进行n-1趟排序
        int min=i;  //记录最小元素的位置
        for(int j=i+1; j<n; j++)  //从L.data[i...n-1]中选择最小的元素
            if(L.data[j] < L.data[min])
                min = j;  //更新最小元素的位置
            
        
        if(min!=i)
            swap(L.data[i], L.data[min]); //与第i个位置交换
    


简单选择排序的性能分析

空间复杂度

仅使用了常数个辅助单元,只需要一个用于数据交换的工作单元,故空间复杂度为 O ( 1 ) O(1) O(1)

时间复杂度

简单选择排序的关键字的比较次数和元素的初始排列状态无关,而元素的移动次数与元素的初始排列状态有关

总的关键字的比较次数 n − 1 + n − 2 + . . . + 1 = n ( n − 1 ) 2 n-1+n-2+...+1 = \\fracn(n-1)2 n1+n2+...+1=2n(n1)

元素的移动次数:

  • 最好情况:元素已经有序(正序),移动次数为 0 0 0
  • 最坏情况:每一次都要进行交换,移动次数为 3 ( n − 1 ) 3(n-1) 3(n1)

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

稳定性

在第 i i i 趟找到最小元素后,和第 i i i 个元素交换,可能会导致第 i i i 个元素与其含有相同关键字元素的相对位置发生改变。故简单选择排序是一种不稳定的排序方法


堆排序

堆排序的基本思想

逻辑上是一个完全二叉树组织的非线性结构,在物理上是用一个一维数组存储的。

堆的定义如下:
n n n个关键字序列 L [ 1... n ] L[1...n] L[1...n]称为堆,当且仅当该序列满足:
L [ i ] ≤ L [ 2 i ] L[i]≤L[2i] L[i]L[2i] L [ i ] ≤ L [ 2 i + 1 ] L[i]≤L[2i+1] L[i]L[2i+1]小根堆
或者 L [ i ] ≥ L [ 2 i ] L[i]≥L[2i] L[i]L[2i] L [ i ] ≥ L [ 2 i ] L[i]≥L[2i] L[i]L[2i]大根堆) ( 1 ≤ i ≤ ⌊ n / 2 ⌋ 1≤i≤\\lfloor n/2 \\rfloor 1in/2

显然,在大根堆中,最大元素存放在根结点中,且对其任一非根结点,它的值小于或等于其双亲结点值;
小根堆中,根结点是最小元素,且对其任一非根结点,它的值大于或等于其双亲结点值。

堆排序是一种树形选择排序方法,其特点是:在排序过程中, L [ 1... n ] L[1...n] L[1...n]视为一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的元素。

堆排序的排序算法的步骤如下
1、 把数组heap中的元素序列用筛选法 A d j u s t D o w n AdjustDown AdjustDown调整为大根堆(即初始堆)。

2、 令 i i i n − 1 n-1 n1 循环到 1 1 1,重复执行。

  • 处于堆顶的元素 h e a p [ 0 ] heap[0] heap[0] h e a p [ i ] heap[i] heap[i] 对调,把有最大关键字的元素交换到最后。
  • 对前面的 i − 1 i-1 i1 个元素,使用堆的筛选算法 A d j u s t D o w n AdjustDown AdjustDown重新调整为大根堆。

3、 循环结束,最后得到全部排序好的元素序列。


堆排序的实现代码

//大根堆的向下调整算法
void AdjustDown(Heap &H, int k, int len)
    //将元素k向下调整
    H.heap[0] = H.heap[k]; //暂存H.heap[k]的值
    for(int i=2*k; i<=len; i*=2) //延关键字较大的子结点向下筛选
        if(i<len && H.heap[i]<H.heap[i+1])
            i++; //选取关键字较大的子结点的下标
        if(H.heap[0] >= H.heap[i])
            break;  //筛选结束
        else
            H.heap[k] = H.heap[i];  //将H.heap[i]调整到双亲结点上
            k = i; //修改k值,以便继续向下筛选
        
    
    H.heap[k] = H.heap[0];  //被筛选结点的值放入最终位置


//建立大根堆
void CreateMaxHeap(Heap &H, int arr[], int len)
    for(int i=0; i<len; i++)
        H.heap[i+1] = arr[i]; //从下标1开始存储
    
    H.n = len+1; //长度需加1
    for(int i=len/2; i>0; i--)
        AdjustDown(H,i,len);  //从i=n/2 ~ 1,反复调整堆
    


//堆排序
void HeapSort(Heap &H, int arr[], int len)
    CreateMaxHeap(H, arr, len);  //初始建堆
    for(int i=len; i>1; i--)  //n-1趟交换和建堆过程
        swap(H.heap[1], H.heap[i]);  //输出堆顶元素(和堆底元素交换)
        AdjustDown(H, 1, i-1);  //把剩余的i-1个元素整理称堆
    

对于大根堆的建立过程请参照文章堆的定义及其基本操作(存储、建立、插入、删除),由于文章篇幅原因不再赘述。


堆排序的性能分析

空间复杂度

只用了常数个辅助单元(对调元素),故空间复杂度为 O ( 1 ) O(1) O(1)

时间复杂度

建堆时间为 O ( n ) O(n) O(n),之后有 n − 1 n-1 n1 次向下调整操作,每次调整的时间复杂度为 O ( h ) O(h) O(h),故在最好、最坏和平均情况下,堆排序的时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

稳定性

由于在进行筛选时,有可能把后面相同关键字的元素调整到前面,所以堆排序算法是一种不稳定的排序算法


完整代码

简单选择排序

#include<bits/stdc++.h>
using namespace std;

//设待排序序列存储在静态分配的顺序表中
#define maxSize 20
typedef struct
    int data[maxSize];
    int n; 
SeqList;

//输入待排序列并存入顺序表中
void CreateList(SeqList &L, int n)
    L.n = n; 
    for(int i=0; i<n; i++)
        int x;
        cin>>x;
        L.data[i] = x;
    

//输出序列
void PrintList(SeqList L)
    for(int i=0; i<L.n; i++)
        cout<<L.data[i]<<" ";
    
    cout<<endl<<endl;


//简单选择排序
void SelectSort(SeqList &L)
    int n = L.n;
    for(int i=0; i<n-1; i++) //一共进行n-1趟排序
        int min=i;  //记录最小元素的位置
        for(int j=i+1; j<n; j++)  //从L.data[i...n-1]中选择最小的元素
            if(L.data[j] < L.data[min])
                min = j;  //更新最小元素的位置
            
        
        if(min!=i)
            swap(L.data[i], L.data[min]); //与第i个位置交换
    


int main()
    SeqList L;
    int n;
    cin>>n; //元素个数
    CreateList(L, n);
    cout<<endl<<"L: ";
    PrintList(L);
    //简单选择排序
    SelectSort(L);
    PrintList(L);
    return 0;

运行结果:


堆排序

#include<bits/stdc++.h>
using namespace std;

//堆的存储结构
#define maxSize 20
typedef struct
    int heap[maxSize];  //存放堆中元素的数组,一般从下标1开始存储(为了对应完全二叉树)
    int n; //当前元素个数
Heap;


//输出序列
void PrintList(Heap &H)
    for(int i=1; i<H.n; i++)
        cout<<H.heap[i]<<" ";
    
    cout<<endl<<endl;


//大根堆的向下调整算法
void AdjustDown(Heap &H, int k, int len)
    //将元素k向下调整
    H.heap[0] = H.heap[k]; //暂存H.heap[k]的值
    for(int i=2*k; i<=len; i*=2) //延关键字较大的子结点向下筛选
        if(i<len && H.heap[i]<H.heap[i+1])
            i++; //选取关键字较大的子结点的下标
        if(H.heap[0] >= H.heap[i])
            break;  //筛选结束
        else
            H.heap[k] = H.heap[i];  //将H.heap[i]调整到双亲结点上
            k = i; //修改k值,以便继续向下筛选
        
    
    H.heap[k] = H.heap[0];  //被筛选结点的值放入最终位置


//建立大根堆
void CreateMaxHeap(Heap &H, int arr[], int len)
    for(int i=0; i<len; i++)
        H.heap[i+1] = arr[i]; //从下标1开始存储
    以上是关于选择排序(简单选择排序堆排序的算法思想及代码实现)的主要内容,如果未能解决你的问题,请参考以下文章

七大排序之:直接选择排序和堆排序

排序算法之选择排序(简单选择排序堆排序)

数据结构图解七大排序

八大排序算法总结

简单选择排序和堆排序

排序算法堆排序的Python实现及算法详解