【数据结构基础/经典排序汇总】各类排序汇总第二弹之直接选择排序&堆排序&冒泡排序(编写思路加逻辑分析加代码实操,一应俱全的汇总)

直接选择排序

代码展示

//最简单的
//遍历一遍选出最大放到最后
//再遍历一遍选出剩下的最大的放到后面,以此类推
//这样很慢!O(N*N)
//我们尝试用一次选一个最大的和一个最小的,这样就把效率提升一倍,但总体来说还是很慢
void Swap(int *px, int *py)
{
    int tmp = *px;
    *px = *py;
    *py = tmp;
}
void SeletSort(int *a, int n)
{
    int begin = 0, end = n - 1;
    //我们在这里需要选择的是最小值和最大值的下标,否则一旦将其覆盖掉就不清楚了。
    while (begin < end)
    {
        int min = begin, max = begin;
        for (int i = begin; i <= end; i++)
        {
            if (a[i] < a[min]) //如果a[i]最小
            {
                //记录下标
                min = i;
            }
            if (a[i] > a[max])
            {
                max = i;
            }
            //交换
            //Swap(&a[begin], &a[min]);
            //Swap(&a[end], &a[max]);
            //这里会出问题,如果max就是他的begin,begin和max重叠了,明明a[min]和a[begin]已经换过了,但是因为max下标没动,导致初始位置又和end发生交换了。
            Swap(&a[begin], &a[min]);
            if (begin == max)
            {
                max = min;
            }
            Swap(&a[end], &a[max]);
        }
        begin++;
        end--;
    }
}

时间复杂度

虽然经过了优化,但是时间复杂度还是应为:O(N2)

  • 最好的情况就是虽然全部有序,但是程序拿到数组之后都要经过遍历才能判断出最大值最小值。所以仍旧是O(N2)

所以选择排序是最烂、最慢排序。(虽然代码很简单

堆排序

编写思路

先建堆,然后进行堆排序。

排升序,按理来说需要找出最小值,然后找出次小的值,以此类推,这样思考的话我们需要建立小堆,但是!!

建立小堆
添加一个数随后向上调整
PopK次
此路不通
添加一个数然后向下调整
PopK次
此路不通

试想如果建立小堆,无论是向上调整,还是向下调整,如果删掉堆顶数据,从堆顶的下一个位置视作堆顶。那么之前建立好的堆的关系就全部乱了,必须重新排列重新建堆,那么时间复杂度会飙升->O(N*N)。(这个时间复杂度还不如遍历选数呢)

所以我们需要建大堆

因为大堆的向下调整必须要求根的左右子树都为大堆,所以我们需要从倒数第一个叶子节点的父节点开始向下调整。随后减减父亲节点的下标,就可以逐个找到所有的父亲节点,并且正好是整棵树都向下调整完毕。

这样子我们就选出了根节点(最大值),随后让其与数组中的最后一个元素交换位置,然后屏蔽掉最后一个元素(最大值已经找到了,就可以把它屏蔽掉了)

随后向下调整,以此类推,就可以找到最大值,次大值,…,最后选出最小值。

向下调整
从根向下调整
数组建堆
根与最后一个元素交换顺序
升序数列

代码展示

void HeapSort(int *a, int n)
{
    for (int i = (n - 1 - 1) / 2; i >= 0;i--)
    {
        AdjustDown(a, n, i);
    }
    int end = n - 1;
    while(end>0)
    {
        Swap(&a[0], &a[end]);
        AdjustDown(a, end--, 0);
    }
}

时间复杂度

时间复杂度为:O(N*log2N),类似于ShellSort

冒泡排序

冒泡排序和快速排序都是属于交换排序。

编写思路

冒泡排序的思想就是前一个如果比后一个大的话就交换位置(排升序)。这样一次可以找出来一个最大值,所以我们需要再嵌套循环n次。

首先我们还是写一趟的循环。

    for (int i = 1; i < n;i++)
    {
        if(a[i-1]>a[i])
        {
            Swap(a[i - 1], a[i]);
        }
        i++;
    }

嵌套循环:

//最终代码
void BubbleSort(int *a, int n)
{
    //冒泡排序就是前一个比后一个大就交换
    for (int j = 0; j < n;j++)
    {
        for (int i = 1; i < n-j; i++)
        {
            if (a[i - 1] > a[i])
            {
                Swap(a[i - 1], a[i]);
            }
            i++;
        }
    }
}

代码优化

冒泡排序还是可以进行一定的优化的:

可以考虑下面这种情况:

1
2
3
4
5
6

相邻的两个数 前数都小于后一个数,这就代表数组以及就有序。我们可以加一个变量来判断是否发生过交换,如果没发生过交换,那么就已经有序了。

void BubbleSort(int *a, int n)
{
    //冒泡排序就是前一个比后一个大就交换
    for (int j = 0; j < n;j++)
    {
      	int exchange = 0;
        for (int i = 1; i < n-j; i++)
        {
            if (a[i - 1] > a[i])
            {
                Swap(a[i - 1], a[i]);
            }
            i++;
          if(exchange)
     		   {
            break;
     		   }
        }
    }
}

同样的,它也适用于接近有序的情况,考虑

1
2
3
4
5
6

交换完一次后,第二轮的循环开始,发现exchange为0,说明并为交换,那么循环中止,完成排序。

时间复杂度

冒泡排序是典型的O(N2)排序。

  • 最坏情况是N2
  • 最好情况是N

选择排序、直接插入排序、冒泡排序的比较

这三个排序都是时间复杂度O(N2)级别的排序,所以可以拿出来比较一下。

选择排序无疑是最差的,因为它无论最好情况还是最坏情况都是O(N2)。

而直接插入排序和冒泡排序,最好情况是O(N)。但是虽然最好情况相同,直接插入排序更优。因为:

  • 对于已经有序的数组排序,一样好。
  • 对于接近有序的数组,直接插入排序更好。
  • 综合来说,直接插入排序对局部有序的数组的适应性更优。

所以,直接插入排序更好。

考虑如下数组:

1
2
3
4
5
6
  • 冒泡排序比了:n-1 + n-2 次

  • 直接插入比了:n次

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值