数据结构之排序(3)——选择排序

本文详细介绍了选择排序中的简单选择排序和堆排序,包括它们的时间复杂度和空间复杂度分析。简单选择排序的时间复杂度固定为O(n²),而堆排序在建堆时的时间复杂度为O(n),后续操作的时间复杂度为O(log2n),整体复杂度介于O(n)和O(nlog2n)之间。堆排序在删除和插入元素时的时间复杂度分别为O(log2n)。两种排序算法均为不稳定的排序方法。
摘要由CSDN通过智能技术生成

选择排序的基本思想是:每一趟(如第i趟)在后面n-i+1个待排序元素中选择关键字最小的元素,作为有序子序列的第i个元素,执行n-1趟,最后那个不用选了。
主要包括:简单选择排序和堆排序。

简单选择排序

这里写图片描述

void selectSort(int arr[], int n)
{   
    int i, j, min;
    for (i = 0; i < n; ++i){
        min = i;//最小元素下标
        for (j = i + 1; j < n; ++j)
            if (arr[j] < arr[min])
                min = j;
        if (min != j)
            swap(arr[i],arr[min]);
    }   
}

空间复杂度: O(1)

时间复杂度:为了找到最小的,必须要和无序区的所有元素比较,比较的复杂度固定为 O(n2) ;移动的复杂度看情况:

  • 最好情况下,最小值就在首位,不用交换,那么移动的复杂度为 0
  • 最差情况下,每轮最小值都在后面,则要进行一次交换,一次交换为3 n1 次一共为 3(n1) ,复杂度为 O(n) ;

加上比较的复杂度,则时间复杂度固定为 O(n2)

稳定性:不稳定,如 3 3* 1,1 3互换,3和3*位置反了。

堆排序

这里写图片描述
堆常用于实现优先队列。

先看一下堆的定义:

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

  1. L(i)L(2i) L(i)L(2i+1) 1in/2
  2. L(i)L(2i) L(i)L(2i+1) 1in/2

满足1为小顶堆,满足2为大顶堆。

如图:
这里写图片描述

创建大顶堆算法思路:
1. 最后一个节点n的下标为n-1,它的父节点下标为 (n2)/2 ,然后从这个父节点下标开始,执行以下操作:
2. 比较父节点的左右两个孩子,大的那个如果比父节点还大,则交换;
3. 如果有交换,以进行交换的那个子节点为子树向下进行调整,递归2-3.
4. 执行完2和3后,父节点下标减一,直至到达根节点。

void buildMaxHeap(int arr[], int len)
{   
    int i;
    for ( i = len / 2; i > 0; --i)
        adjustDown(arr, i, len);
}
void adjustDown(int arr[], int k, int len)
{
    arr[0] = arr[k];//arr[0]暂时存储子树的根节点
    int i;
    for (i = 2 * k; i <= len; i *= 2){//沿key较大的子节点向下筛选
        if (i < len&&arr[i] < arr[i + 1])
            ++i;
        if (arr[0] >= arr[i])
            break;//父节点大于左右两个孩子,子树没问题了,直接退出
        else{
            arr[k] = arr[i];//大的那个孩子调到父节点
            k = i;//k来记录空缺的地方
        }
    }
    arr[k] = arr[0];//找到树下合适的地方落脚
}

建堆的时间复杂度:在n各元素上建堆的时间复杂度为 O(n)

假如有N个节点,那么高度为H=logN,最后一层每个父节点最多只需要下调1次,倒数第二层最多只需要下调2次,顶点最多需要下调H次,而最后一层父节点共有 2(H1) 个,倒数第二层公有 2(H2) ,顶点只有1(2^0)个,所以总共的时间复杂度为 s=12(H1)+22(H2)+...+(H1)21+H20 ,乘2相减中间有个等比数列,将H代入后s= 2N2log2N ,近似的时间复杂度就是 O(N)

建完大顶堆后,顶元素就是最大值了,然后最后一个元素换到顶上,来一遍向下调整,如此n-1次即可。

void heapSort(int arr[], int len)
{
    buildMaxHeap(arr, len);
    int i;
    for (i = len; i > 1; --i){
        swap(arr[i], arr[1]);//最大值交换到最后
        adjustDown(arr, 1, i - 1);//整理剩余的i-1个元素
    }
}

堆的删除

只能删除堆顶的元素,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次向下调整,代码同上。

时间复杂度: log2n

堆的插入

每次插入都是将先将新数据放在数组最后,然后执行一次向上调整。

void adjustUp(int arr[], int k)//k是堆元素的个数(首位不算),也是待向上调整的节点,
{
    arr[0] = arr[k];
    int i = k/2;
    while (i > 0 && arr[0] > arr[i]){
        arr[k] = arr[i];
        k = i;//k来记录“空”位子
        i = k / 2;
    }
    arr[k] = arr[0];
}

向上调整(插入)的时间复杂度:与数字大小有关,最多 log2n ,即向上到了根节点

整个堆排序的性能分析

空间复杂度: O(1)

时间复杂度:建堆为 O(n) 毋庸置疑,之后有n-1次向下调整的操作,每次调整的时间复杂度与树高有关,为 O(h) 。第i次向下调整,深度为 log2(ni+1) ,每次向下调整都要比较深度次数次,网上很多都说每次比较都是 log2n 次,但我感觉排序的话n每次会减少1啊,最终值是 log2n!1 ,在 O(n) O(nlog2n) 之间。

稳定性:不稳定,如 1 2* 2,第一次变成 2* 1 2,即2*最大,与2位子反了

各算法复杂度表

加上前几篇的内容,表扩充为:

算法平均时间复杂度最好时间复杂度最差时间复杂度空间复杂度稳定性备注
直接插入 O(n2) O(n) O(n2) O(1) 稳定
折半插入 O(n2) O(nlog2n) O(n2) O(1) 稳定
shell O(n1.3) O(n) O(n2) O(1) 不稳定和增量序列有关
冒泡 O(n2) O(n) O(n2) O(1) 稳定子序列全局有序,不同于插入排序
快排 O(nlog2n) O(nlog2n) O(n2) O(log2n) 不稳定
简单选择 O(n2) O(n2) O(n2) O(1) 不稳定
堆排序 O(nlog2n) O(nlog2n) O(nlog2n) O(1) 不稳定
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值