【数据结构之排序系列】直接插入排序,冒泡排序,直接选择排序,堆排序,希尔排序
创始人
2024-05-21 04:38:37
0

目录

    • 前言
    • 一、直接插入排序
    • 二、冒泡排序
    • 三、堆排序
    • 四、直接选择排序
    • 五、希尔排序

前言

排序算法章节在校招方面考察是相对比较频繁的,所以本章中所学习的所有排序算法需要引起很大的重视。需要掌握各种排序算法的时间复杂度,注意事项

一、直接插入排序

思想:对于一个数组而言,假定第一个元素默认是有序的,从第二个位置的元素开始,每次插入之后与前面的数据进行比较,假如排成升序,如果插入的数据比前面的数据小,则需要将前面的数据进行挪动,直到找到前面的数据是小于等于插入的数据,那么再将此插入的数据放到该数据之后。具体代码如下:

// 插入排序
void InsertSort(int* a, int n)
{assert(a);for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];// 保存每次插入的数据while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];--end;}else{break;}}// 循环走到这里是有两种情况的:// 1.循环条件结束// 2.break出来的a[end + 1] = tmp;}
}

插入排序中需要注意的点:

  1. 第一个for循环中,第一次循环end是从第一个位置开始的,然后每次比较后面的数和end位置的大小关系,最后一次循环end是在倒数第二个位置上,也就是相当于插入最后一个数据时,前面的数据已经有序,那么此时再比较最后一个数据和前面数据的大小关系即可。
  2. while循环中,主要是找到a[end+1]的合适位置,不符合时就依次挪动数据,符合数据时退出循环,相当于找到了a[end+1]的合适位置
  3. 跳出循环有两种情况:
  • 循环条件结束(循环条件不满足了),此时end在下标为-1的位置
  • break出来的(a[end+1]找到了合适的位置),此时end还没有发生越界

时间复杂度的分析

插入排序中,最坏的情况就是数据刚好是逆序的,此时每次插入数据都需要移动对应的数据,第一个数据默认有序的话,那么插入第二个数据需要移动1次,第三个数据需要移动2次,第四个数据需要移动3次…第n个数据需要移动n-1次,显然,这是一个等差数列的求和公式,因此时间复杂度为O(N^2)。最好的情况就是有序时,其实本质只需要执行外层的for循环,因此只需要执行n-1,因为当数据有序时,相当于每次插入的数据都是在合适的位置,不需要另找位置,也就不需要挪动数据的位置,因此这种情况的时间复杂度为O(N)
综上,插入排序的时间复杂度为O(N^2)

二、冒泡排序

思想:冒泡排序本质上是属于一种交换排序,冒泡排序算法中需要使用到交换函数,其实就是一个交换的过程,假如排成升序,每轮冒泡排序就是将数据中的最大值交换到最后的位置,然后不考虑该最大值,剩余数据继续进行冒泡排序。代码实现如下:

// 冒泡排序
void BubbleSort(int* a, int n)
{assert(a);for (int j = 0; j < n - 1; j++){for (int i = 0; i < n-j-1; i++){if (a[i] > a[i + 1]){Swap(&a[i], &a[i + 1]);}}}
}

分析

外层循环主要是控制冒泡排序的趟数,10个数其实我们只需要进行9趟就可以了,因为最后一个就默认是最小的,不需要进行冒泡,因此n个数据就是需要进行n趟冒泡排序。内层循环控制的是每一趟冒泡排序控制的数据范围,第一趟冒泡排序控制的数据范围是整个数据,第二趟得将最大值去掉得到剩余的数据,第三趟冒泡排序需要去掉第二趟中的最大值得到第三趟的冒泡排序的数据,依此类推。假如排成升序,每一趟冒泡排序需要做的事情就是将该趟冒泡排序控制的数据的最大值冒泡到数据的结尾,因此最终可以排成升序。
注意事项
上述的代码其实不是冒泡排序的最优解,因为上述的冒泡排序中,无法判断数据有序的情况,当数据已经有序时,该排序算法仍然会进行无脑的冒泡,因此效率较低,下面将给出优化方案:增加一个标志位,标志上一趟冒泡排序中是否发生交换,如果发生交换,则说明上一趟的数据仍然不是有序的,需要进行冒泡,如果上一趟没有发生交换,则说明数据已经达到有序,此时只需要退出循环即可,不需要再进行冒泡排序。代码实现如下:

// 冒泡排序
void BubbleSort(int* a, int n)
{assert(a);for (int j = 0; j < n - 1; j++){int exchange = 0;for (int i = 0; i < n-j-1; i++){if (a[i] > a[i + 1]){// 如果发生交换,修改标志位exchange = 1;Swap(&a[i], &a[i + 1]);}}if (exchange == 0){// 说明上一趟冒泡排序中没有发生交换,数据已经达到有序break;}}
}

三、堆排序

思想:堆排序本质是在一棵完全二叉树中对数据进行选择排序,该排序中需要使用向上调整算法和向下调整算法。堆排序的步骤:建堆,如果排成升序则建大堆,排成降序,则建小堆,然后每次将第一个位置的元素和最后一个元素的值进行交换,因为堆顶存放的是最值,所以每次交换之后,对剩余的数据进行向下调整,使剩余的数据仍然能够满足堆的性质。

  • 向上调整算法代码
// 向上调整算法
void AdjustUp(int* a, int child)
{// child表示调整的起始位置int parent = (child - 1) / 2;while (child > 0){// 调成大堆if (a[parent] < a[child]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}
  • 向下调整算法代码
// 向下调整算法
void AdjustDown(int* a, int n, int root)
{// root表示从根节点开始进行调整int parent = root;int child = parent * 2 + 1;while (child < n){// 调成大堆if (child + 1 < n && a[child + 1] > a[child]){child++;}if (a[child] > a[parent]){// 需要进行交换Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{// 半途不需要进行调整了break;}}
}
  • 堆排序
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);end--;}
}

堆排序的本质就是将堆顶数放到最后一个位置,然后再对剩余的数据进行调整,使剩余的数据仍然满足堆的性质。

注意:

  • 如果排成升序,则需要建大堆,因为大堆的堆顶存放的是数据的最大值,所以每次将堆顶数据放到数据的最后一个位置,就可以将数据排成升序。
  • 如果排成降序,则需要建小堆,因为小堆的堆顶存放的是数据的最小值,所以每次将堆顶数据放到数据的最后一个位置,就可以将数据排成降序。

四、直接选择排序

思想:我们需要两个下标,分别称为左右下标,左下标是作为左区间,右下标作为右区间,每次遍历该区间,选择出区间中的最小值和最大值,分别放到左边和右边,再缩小区间范围,即左下标向前走一步,右下标向后走一步,当左右下标相遇时循环结束。代码实现如下:

// 直接选择排序
void SelectSort(int* a, int n)
{assert(a);int left = 0;int right = n - 1;while (left < right){int mini = left;int maxi = left;for (int i = left; i <= right; i++){if (a[i] < a[mini]){mini = i;}if (a[i] > a[maxi]){maxi = i;}}Swap(&a[mini], &a[left]);Swap(&a[maxi], &a[right]);left++;right--;}
}

注意:
上面的代码是存在一定的问题的,在一些极端场景中,上面的代码就会出现问题,如果在遍历的时候,最大值的下标和左下标是一样,相当于就是说,交换前,最大值在数据的最左边,那么如果我们将最小值和最左边的数据进行交换的话,显然会将最大值换到了原来最小值的地方,所以此时我们需要更新最大值的下标,也就是交换前最小值的下标。处理后的代码如下:

// 直接选择排序
void SelectSort(int* a, int n)
{assert(a);int left = 0;int right = n - 1;while (left < right){int mini = left;int maxi = left;for (int i = left; i <= right; i++){if (a[i] < a[mini]){mini = i;}if (a[i] > a[maxi]){maxi = i;}}Swap(&a[mini], &a[left]);if (maxi == left){maxi = mini;}Swap(&a[maxi], &a[right]);left++;right--;}
}

五、希尔排序

思想:希尔排序是在直接插入排序的基础上进行改造的,插入排序中,当数据出现逆序时,时间复杂度为O(N^2)效率比较低,所以希尔排序针对直接插入排序中存在的这样的问题进行优化。在希尔排序中,首先会对数据进行预排序预排序的结果就是使数据接近有序,而不至于逆序,当数据接近有序时,此时在使用直接插入排序算法对数据进行排序,效率就会比较高。希尔排序中需要确定一个间隔gap,gap越大,表明大的数越快到达后面,小的数越快到达前面,但是数据越不接近有序,当gap越小时,预排序之后数据越接近有序。代码实现如下:

// 希尔排序
void ShellSort(int* a, int n)
{assert(a);// 预排序int gap = n;while (gap > 1){gap = gap / 3 + 1;for (int i = 0; i + gap < n; i++){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}
}

上面的Gap一般建议是数据个数的三分之一左右,再加1能够保证最后一次的gap一定是1,表示直接插入排序,注意每次是将a[end]a[end+gap]的数据进行比较,而不是a[end]a[end+1]的数据进行比较,这就是希尔排序直接插入排序的区别之一。

相关内容

热门资讯

最绚丽的安卓系统,最绚丽版本全... 哇,你知道吗?在安卓的世界里,有一款系统,它就像是一颗璀璨的明珠,闪耀着最绚丽的色彩。它就是——最绚...
小米系统安卓通知权限,深度解析... 亲爱的手机控们,你是否曾为手机通知栏里乱糟糟的信息而烦恼?又或者,你是否好奇过,为什么有些应用总是能...
安卓7.0系统能玩吗,体验全新... 你有没有想过,你的安卓手机升级到7.0系统后,那些曾经陪伴你度过无数时光的游戏,还能不能继续畅玩呢?...
平板安卓系统哪家好,安卓平板系... 你有没有想过,在这个科技飞速发展的时代,拥有一台性能出色的平板电脑是多么重要的事情呢?想象无论是追剧...
安卓好的点歌系统,打造个性化音... 你有没有想过,在安卓手机上,点歌系统竟然也能如此精彩?没错,就是那个我们每天都会用到,却又常常忽略的...
熊猫安卓系统直播软件,解锁互动... 你知道吗?最近有个超级酷炫的直播软件在熊猫迷们中间火得一塌糊涂!它就是熊猫安卓系统直播软件。别看它名...
安卓点播系统开发,Androi... 你有没有想过,手机里那些让你爱不释手的视频,其实背后有着一套复杂的安卓点播系统在默默支撑呢?今天,就...
安卓6.0系统加权限,深度解析... 你有没有发现,自从手机升级到安卓6.0系统后,权限管理变得超级严格呢?这可真是让人又爱又恨啊!今天,...
哪些电视带安卓系统,多款热门智... 你有没有想过,家里的电视竟然也能装上安卓系统?听起来是不是有点不可思议?没错,现在市面上就有不少电视...
苹果怎么运用安卓系统,揭秘如何... 你知道吗?最近有个大新闻在科技圈里炸开了锅,那就是苹果竟然开始运用安卓系统了!是不是觉得有点不可思议...
安卓系统能转什么系统好,探索最... 你有没有想过,你的安卓手机是不是也能换换口味,体验一下其他系统的魅力呢?没错,今天就来聊聊这个话题:...
龙之狂热安卓系统,释放龙族狂热 亲爱的手机控们,你是否曾为拥有一款独特的安卓系统而疯狂?今天,就让我带你走进一个充满奇幻色彩的龙之狂...
vivo手机安卓系统怎么升级系... 亲爱的手机控们,你是不是也和我一样,对手机的新功能充满期待呢?尤其是vivo手机的用户,是不是也在想...
鸿蒙2.0退回安卓系统,一场系... 你知道吗?最近科技圈里可是炸开了锅,因为华为的鸿蒙2.0操作系统竟然要退回安卓系统了!这可不是一个简...
安卓系统怎么复制卡,安卓系统卡... 你有没有遇到过这种情况:手机里的照片、视频或者重要文件,突然想备份到电脑上,却发现安卓系统的卡复制功...
app兼容低安卓系统,打造全民... 你有没有发现,现在手机APP更新换代的速度简直就像坐上了火箭!不过,你知道吗?有些APP可是特别贴心...
中间安卓系统叫什么,中间安卓系... 你有没有想过,安卓系统里竟然还有一个中间的版本?没错,就是那个让很多手机用户既熟悉又陌生的版本。今天...
安卓怎么用os系统,利用And... 你有没有想过,你的安卓手机其实可以变身成一个功能强大的操作系统呢?没错,就是那个我们平时在电脑上使用...
pe系统安卓能做么,探索安卓平... 亲爱的读者,你是否曾好奇过,那款在安卓设备上大受欢迎的PE系统,它究竟能做什么呢?今天,就让我带你一...
安卓 打印机系统,安卓打印机系... 你有没有想过,家里的安卓手机和打印机之间竟然能建立起如此紧密的联系?没错,就是那个安卓打印机系统!今...