今天面试的时候问了归并和快排,没有讲得太清楚,掌握地不太熟练,不能太依赖sort函数,所以自己整理一下。
项目地址
选择排序
目前的想法看来这个是最简单的排序了,我叫他笨蛋法。思想就是遍历数组,把最小的放在最前面,然后再后移一位遍历剩下的数组,找出剩下的最小的放在第二位,一直到最大的放在最后一位。
代码
std::vector<int> selectSort::sort(std::vector<int> nums){
for(int i = 0;i < nums.size() - 1;i++){
int min = i;
// 遍历寻找最小的数组
for(int j = i + 1;j < nums.size();j++){
if(nums[j] < nums[min]){
min = j;
}
}
std::swap(nums[i],nums[min]);
}
return nums;
}
运行结果无误。
复杂度分析
时间复杂度为 O ( N 2 ) O(N^2) O(N2),需要进行 ( N − 1 + N − 2 + . . . + 1 ) (N-1+N-2+...+1) (N−1+N−2+...+1)次比较,最多 N − 1 N-1 N−1次交换操作,不管输入的数组如何,都需要 O ( N 2 ) O(N^2) O(N2)的时间复杂度,因为无论如何都要比较,即使数组本身有序。
插入排序
遍历数组,把每个数字插入到左侧已经排好顺序的数组里,保证插入后依然有序,通过与左侧元素交换位置直到左侧无元素或者左侧元素大于自己为止来实现。
代码
std::vector<int> insertSort::sort(std::vector<int> nums){
for(int i = 1;i<nums.size();i++){
// 向左遍历,直到找到比自己小的元素或队首
for(int j = i;j > 0 && nums[j] < nums[j-1];j--){
std::swap(nums[j],nums[j-1]);
}
}
return nums;
}
复杂度分析
平均需要
N
2
/
4
N^2/4
N2/4 次比较以及
N
2
/
4
N^2/4
N2/4 次交换,
最坏的情况是倒序数组,需要
N
2
/
2
N^2/2
N2/2 比较以及
N
2
/
2
N^2/2
N2/2 次交换,
最好的情况下顺序数组,需要
N
−
1
N-1
N−1 次比较和
0
0
0 次交换。
快速排序
选定一个基准数(一般可以选数组头部元素),分别从头尾对数组同时进行遍历,使得左边的的数都小于这个基准数,右边的数都大于这个基准数,再分别对两边的数组进行快速排序,最终就能得到一个排好序的数组。
代码
std::vector<int> quickSort::sort(std::vector<int> nums) {
// 左右的起点分别设为0和len-1
sort(nums,0,nums.size()-1);
return nums;
}
void quickSort::sort(std::vector<int>& nums,int l,int r){
// 如果当前队伍l已经大于r,说明已经排序完毕
if(l>r) return ;
int head = l;
int tail = r;
// 注意这里的边界,保证 l!=r
while(l!=r){
// 注意顺序,要先对r进行操作
while(l<r && nums[r]>=nums[head]) r--;
while(l<r && nums[l]<=nums[head]) l++;
if(l<r)
std::swap(nums[l],nums[r]);
}
std::swap(nums[l],nums[head]);
// 递归
sort(nums,head,l-1);
sort(nums,l+1,tail);
}
注意事项
快速排序的思想其实很简单,但是要完全自己写下来实际上还是有一定难度的(对于本菜鸡而言),因为有一些边界和顺序的问题需要注意。
在遍历时要注意的是l和r能不能相等的问题,这里是选了不能相等,因为我们是先对r进行向左的遍历,r一定会停留在一个小于基准数的位置,而l是在这之后向右遍历,应该停留在一个比基准数大的位置,如果没有这样的位置,l就应该停留在最后一个小于基准数的位置,才能再和基准数交换位置后保证左小右大,如果允许l和r相等的话,首先没有意义,因为r所处的位置肯定小于基准数,其次我们要做交换操作还需要将l回退一位来交换。
复杂度分析
最好的情况下是每次都正好将数组对半分,这样的时间效率最高,复杂度为 O ( N l o g N ) O(NlogN) O(NlogN),但最坏的情况下,每一次的基准数都很不巧地选为最大值或最小值,相当于没有做切分,最坏的情况下需要比较 N 2 / 2 N^2/2 N2/2 次。
在面试的时候考官就问到了这个问题,如果多线程执行的话,归并和快速应该选哪个好,那由于快速排序的不稳定性,可能一边数组很短甚至没有,一边数组很长,那就会导致线程之间运行时间差很多,实际上也造成浪费,所以应该选择人为切分自定义长度的归并排序更合适。