面试常考的七大排序
快速记法
交换排序:
冒泡排序
快速排序
插入排序:
直接插入排序
希尔排序
选择排序:
直接选择排序
堆排序
归并排序:
归并排序
总结分析
排序方法 | 平均情况 | 最好情况 | 最差情况 | 辅助空间 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n^2) | O(logn)~O(n) | 不稳定 |
直接插入排序 | O(n^2) | O(n) | O(n^2) | O(logn)~O(n) | 稳定 |
希尔排序 | O(nlogn)~O(n^2) | O(n^1.3) | O(n^2) | O(1) | 不稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
交换排序
交换排序的基本思想都为通过比较两个数的大小,当满足某些条件时对它进行交换从而达到排序的目的
冒泡排序
自己理解:从名字看,冒泡嘛,而且它是属于交换排序中的,所以可以理解为每次都会出现一个最大的泡出来,从计算机角度看就是,相互交换,一直到最后,最后一个是最大的,就是冒泡排序
基本思想:
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该是最大的数。
3.针对上所有的元素重复以上的步骤,除了最后一个。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
算法分析(网上找的)
时间复杂度:
若文件的初始状态是正序的,一趟扫描即可完成排序。所需的关键字比较次数和记录移动次数均达到最小值。
若初始文件是反序的,需要进行 趟排序。每趟排序要进行 次关键字的比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值。
代码
(在这之前我们先来学习一些交换方式,面试中会有很好的印象)
基础:
a=a+b;
b=a-b;
a=a-b;
稍微有点逼格:
a=a^b;
b=a^b;
a=a^b;
宏定义实现:
#define swap(x,y)\
(y) = (y)+(x);\
(x) = (y)-(x);\
(y) = (y)-(x);
下面是冒泡算法
#include<iostream>
using namespace std;
#define swap(a,b)\
(b) = (a) + (b);\
(a) = (b) - (a);\
(b) = (b) - (a);
int main(){
int m[10]={1,3,4,2,4,6,7};
for(int i=0;i<6;i++){//趟数等于个数-1 从 0 - 数组长-1
┊ for(int j=0;j<6-i-1;j++){
┊ ┊ if(m[j]>m[j+1]){
┊ ┊ ┊ swap(m[j],m[j+1]);
┊ ┊ }
┊ }
}
for(int i = 0;i <= 6;i++){
┊ cout<<m[i]<<endl;
}
return 0;
}
## 快速排序
我在我前面的文章中有详细讲解,我过一阵再整理过来,主要思想是分治。
插入排序
直接插入排序
将待排序的数组划分为局部有序子数组subSorted和无序子数组subUnSorted,每次排序时从subUnSorted中挑出第一个元素,从后向前将其与subSorted各元素比较大小,按照大小插入合适的位置,插入完成后将此元素从subUnSorted中移除,重复这个过程直至subUnSorted中没有元素,总之就时从后向前,一边比较一边移动。
希尔排序
希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。本文会以图解的方式详细介绍希尔排序的基本思想及其代码实现。
简单插入排序很循规蹈矩,不管数组分布是怎么样的,依然一步一步的对元素进行比较,移动,插入,比如[5,4,3,2,1,0]这种倒序序列,数组末端的0要回到首位置很是费劲,比较和移动元素均需n-1次。而希尔排序在数组中采用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1。希尔排序通过这种策略使得整个数组在初始阶段达到从宏观上看基本有序,小的基本在前,大的基本在后。然后缩小增量,到增量为1时,其实多数情况下只需微调即可,不会涉及过多的数据移动。
我们来看下希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。
选择排序
直接选择排序
基本思想:依次选出数组最小的数放到数组的前面。首先从数组的第二个元素开始往后遍历,找出最小的数放到第一个位置。再从剩下数组中找出最小的数放到第二个位置。以此类推,直到数组有序。
堆(Heap)排序
基本思想:先把数组构造成一个大顶堆(父亲节点大于其子节点),然后把堆顶(数组最大值,数组第一个元素)和数组最后一个元素交换,这样就把最大值放到了数组最后边。把数组长度n-1,再进行构造堆,把剩余的第二大值放到堆顶,输出堆顶(放到剩余未排序数组最后面)。依次类推,直至数组排序完成。
下图为堆结构及其在数组中的表示。可以知道堆顶的元素为数组的首元素,某一个节点的左孩子节点为其在数组中的位置2,其右孩子节点为其在数组中的位置2+1,其父节点为其在数组中的位置/2(假设数组从1开始计数)。
下图为怎么把一个无序的数组构造成一个大堆顶结构的数组的过程,注意其是从下到上,从右到左,从右边第一个非叶子节点开始构建的。
归并排序
基本思想:归并算法应用到分治策略,简单说就是把一个答问题分解成易于解决的小问题后一个个解决,最后在把小问题的一步步合并成总问题的解。这里的排序应用递归来把数组分解成一个个小数组,直到小数组的数位有序,在把有序的小数组两两合并而成有序的大数组。
下图为展示如何归并的合成一个数组
下图展示了归并排序过程各阶段的时间花费。