各种排序对比分析
冒泡排序
冒泡排序:基本思想是,依次把大的往后放,这样最后面的就是最大的了,依次缩小范围,大的逐渐见往后排
class Solution {
public:
vector<int> MySort(vector<int>& arr) {
// write code here
for(int i = arr.size() - 1; i >= 1; i --){
bool is_sort = true;
for(int j = 1; j <= i; j ++){
if(arr[j - 1] > arr[j]){
swap(arr[j - 1] , arr[j]);
is_sort = false;
}
}
if(is_sort) break;
}
return arr;
}
};
//冒泡排序——递归
class Solution {
private:
void back_sort(vector<int>& num, int end){
if(end == 0) return;
bool is_sort = true;
for(int j = 1; j <= end; j ++){
if(num[j - 1] > num[j]){
swap(num[j - 1] , num[j]);
is_sort = false;
}
}
if(is_sort) return;
back_sort(num, --end);
return;
}
public:
vector<int> MySort(vector<int>& arr) {
// write code here
back_sort(arr, arr.size() - 1);
return arr;
}
};
选择排序
选择排序:走一趟找到最小的,放第一个,然后再在剩下的里面找最小的,放第二个,直到全部找完
改进的思路的话,走一趟不仅选最小的,也选出最大的。
//选择排序:找到最小的,放第一个,然后再在剩下的里面找最小的,放第二个,直到全部找完
class Solution {
public:
vector<int> MySort(vector<int>& arr) {
// write code here
for(int i = 0; i < arr.size(); i ++){
int min_indx = i;
for(int j = i; j < arr.size(); j ++ ){
if(arr[j] < arr[min_indx]) {
min_indx = j;
}
}
swap(arr[i], arr[min_indx]);
}
return arr;
}
};
递归实现
//选择排序,这里还增加了优化,一趟寻找下来,既找最小的也找最大的,上面迭代法也可以这样修改
class Solution {
void back_sort(vector<int>& num, int start, int end){
if(start >= num.size()) return;
int min_indx = start;
int max_indx = end;
for(int j = start; j <= end; j ++ ){
if(num[j] < num[min_indx]) min_indx = j;
if(num[j] > num[max_indx]) max_indx = j;
}
//这里有这些判断的目的是防止被交换过去的,又被换走
if(min_indx != start) swap(num[min_indx], num[start]);
if(max_indx == start) max_indx = min_indx;
if(max_indx != end) swap(num[end], num[max_indx]);
back_sort(num, ++start, --end);
}
public:
vector<int> MySort(vector<int>& arr) {
// write code here
back_sort(arr, 0, arr.size() - 1);
return arr;
}
};
插入排序
插入排序:就类似于打牌给牌排序一样,一张一张拿出来插入到已经排好的序列中
class Solution {
public:
vector<int> MySort(vector<int>& arr) {
// write code here
for(int i = 1; i < arr.size(); i ++){
for(int j = i; j >= 1; j --){
if(arr[j] > arr[j - 1]) break
else if(arr[j - 1] > arr[j]) swap(arr[j - 1] , arr[j]);
}
}
return arr;
}
};
递归方法
插入排序-递归实现
class Solution {
void back_sort(vector<int>& arr, int end){
if(end == arr.size()) return;
for(int j = end; j >= 1; j --){
if(arr[j] > arr[j - 1]) break;
else if(arr[j - 1] > arr[j]) swap(arr[j - 1] , arr[j]);
}
back_sort(arr, ++end);
}
public:
vector<int> MySort(vector<int>& arr) {
// write code here
back_sort(arr, 1);
return arr;
}
};
希尔排序
由于插入排序中,如果一个很小的数在很后面,那它插到前面的话需要移动很远,这样效率不高,因此希尔排序的思想是把数据分成一些组,组内使用插入排序,组内移动距离就变短了,再逐渐改变组的规模。
//希尔排序——相当于是插入排序的改进版
void group_sort(vector<int>& arr, int start, int step){
for(int i = start + step; i < arr.size(); i += step){
for(int j = i; j >= start + step; j -= step){
if(arr[j] > arr[j - 1]) break;
else if(arr[j - 1] > arr[j]) swap(arr[j - 1] , arr[j]);
}
}
}
vector<int> MySort(vector<int>& arr) {
//step逐渐减半,就是分组逐渐变多,直到最后一个元素一组
//这样的目的是相当于减少在插入排序中移动位置的距离,不然如果有的小数很靠后,要移动很远
int step = arr.size()/2;
while(step){
//这层负责把每组都用插入排序弄一遍
for(int i = 0; i < step; i ++){
//这里是进行插入排序,到最后step=1的时候,就相当于是全部来了一遍插入排序
group_sort(arr, i, step);
}
//每次分组数量增多,这里就选用增加一倍
step = step/2;
}
return arr;
}
快速排序
实现思路一:挖坑填数+分治
- 为了方便,可以第一次选第一个作为基数就行,后面采用挖坑填数法
- 先来处理基数右边区间,把基数挖出去留下坑,左指针留在坑这里,右指针从右区间最右边开始走,找到比基数大的,填到坑里
- 左指针右走一下,挖坑,右指针从右区间最右边开始走,找到比基数大的,填到坑里
- 直到左右指针重合,那就把基数放到坑这里,基数位置就确定好了
- 下面处理基数左边区间,选择左边区间,选择左区间最最左边的作为基数,挖坑
- 然后对基数右边按照2、3、4步骤再来
- 重复以上操作,直到基数左边、右边的区间大小都小于2了,那就停止
class Solution {
private:
void quick_sort(vector<int>& num, int start, int end){
if(end - start < 1) return;//区间里面的数字少于两个了,那就不用排序了
int center_base = num[start];//选择中心基准数,这里为了方便就选第一个了,选的好的话可以提高排序效率
bool move = true;//true——左移, false——右移
int left = start, right = end;
while(left < right){
if(move){//左移
//找比center_base小的数字,放到左边去
if(num[right] < center_base){
num[left] = num[right];
move = false;//找到之后,换成右移
left ++;//left需要更新,右移一下
}else right --;
}else{//右移
//找比center_base大的数字,放到右边去
if(num[left] > center_base){
num[right] = num[left];
move = true;//找到之后,换成右移
right --;//right需要更新,左移一下
}else left ++;
}
}
//left和right重合,那就把center_base放在这里
num[left] = center_base;
quick_sort(num, start, left - 1);//排序左边区间
quick_sort(num, left + 1, end);//排序右边区间
}
public:
vector<int> MySort(vector<int>& arr) {
quick_sort(arr, 0, arr.size() - 1);
return arr;
}
};
可以优化的思路:
归并排序
//归并排序
class Solution {
public:
vector<int> MySort(vector<int>& arr) {
vector<int> tmp(arr);
for(int step = 1; step < tmp.size(); step *= 2){
int p = 0;//用来填数字的下标,每轮都要从0开始
//这样一层循环是处理一对,一对里面有两组数
for(int start = 0; start <arr.size(); start += (step * 2)){
//第一组就是start开头
int start_1 = start;
//组1的结束就是组2的开始,它们最大也不能超过size
int end_1 ,start_2;
end_1 = start_2 = (start_1 + step) < tmp.size() ? (start_1 + step) : tmp.size();
//第二组的结束最大也不能超过size
int end_2 = (start_2 + step) < tmp.size() ? (start_2 + step) : tmp.size();
while(start_1 < end_1 && start_2 < end_2){
//小的排前面,大的排后面
if(tmp[start_1] < tmp[start_2]) arr[p ++] = tmp[start_1 ++];
else arr[p ++] = tmp[start_2 ++];
}
//第一组里还有数据的话,都放到后面
while(start_1 < end_1)
arr[p ++] = tmp[start_1 ++];
//第二组里还有数据的话,都放到后面
while(start_2 < end_2)
arr[p ++] = tmp[start_2 ++];
}
tmp = arr;//把这一轮排好的复制到tmp里面
}
return arr;
}
};
堆排序
实现步骤
- 把序列按层构建成完全二叉树
其实数组,按照层序的方式,它就可以表示树的结构了
2. 调整为大顶堆:从倒数第二层的最后一个节点(最后一个父节点)开始调整,小的往下交换
经过调整,变成了大顶堆
3. 把根节点和最后一个叶子节点交换,这样最后那个叶子节点就位置确定了
4. 交换之后,树就不是大顶堆了,根节点比较小,这个时候,把根节点下沉(下沉原则是把他和比较大的子节点交换)
经过下沉调整,树又变成大顶堆了
5. 把倒数第二个叶子节点和根节点交换(这样这个节点的位置也确认好了),重复3、4,直到整棵树的节点都完成3、4这样的步骤
6.
//堆排序
class Solution {
//这个函数的功能就是,选中一个子树的根节点,把这个子树调整成大顶堆(小的往下,大的在上)
void headpify(vector<int>& arr, int node_indx, int end){
int dad = node_indx;
int son = dad * 2 + 1;//从左孩子开始看
//还有孩子就需要考虑下沉
while(son <= end){
//如果右孩子大于左孩子,那最大的孩子就是右孩子
if(son + 1 <= end && arr[son] < arr[son + 1]) son ++;
//如果dad比最大的孩子还大,那就不用下沉了
if(arr[dad] > arr[son]) return;
else{//不然,就是dad比最大的孩子小,那就需要把他和最大的孩子交换
swap(arr[dad] , arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
public:
vector<int> MySort(vector<int>& arr) {
//有了arr之后,其实就有了树了,只是说还不是大顶堆
//调整成大顶堆:就是从最后一个父节点(就是最后一个节点的父亲:(arr.size()-1)-1)/2)开始,一直使用hedapify
for(int i = ((arr.size() - 1) - 1)/2; i >= 0; i --)
headpify(arr, i, arr.size() - 1);
//从最后往前,依次换到根节点上,再做headpify
for(int i = arr.size() - 1; i >= 1; i --){
//换到根节点上,这样i的位置就是确定好了
swap(arr[0], arr[i]);
//把根节点拿去headpify,headpify的范围不要包含到已经确定好位置的地方
headpify(arr, 0, i - 1);
}
return arr;
}
};
计数排序
- 找出序列中最大的值
- 创建一个长度为最大值+1的临时容器
- 以元素值为下标,统计每个元素出现的次数
- 按照次数挨个把元素排列出来
他的应用比较局限
改进思路,不要弄0-max这样的的数组了,这样很浪费,我们用min-max这样的数组来存
桶排序
设计几个桶,把元素都放到对应的桶里,再对每个桶进行排序,采用别的(快速、希尔、堆排等),排完之后,再把同之间的元素合并
基数排序
- 确定出序列里面最大的是905,最大为百位
- 按个位分配到桶里
- 把桶里的数据依次放出来,从下标0到9,按照先进先出原则,这样数据就变成按个位有序的了
- 按十位分配到桶里
- 把桶里的数据依次放出来,从下标0到9,按照先进先出原则,这样数据就变成按十位有序的了
- 按百位分配到桶里
- 把桶里的数据依次放出来,从下标0到9,按照先进先出原则,这样数据就变成按百位有序的了,同样,到这里就完成了排序, 个十百位都弄完了
代码实现
class Solution {
public:
vector<int> MySort(vector<int>& arr) {
//创建桶,0-9,每个桶装对应位数的元素
vector<vector<int>> buckets(10);
//浏览一遍,找到最大的,确定出他有几位
int max_num = arr[0];
for(int a : arr){
if(a > max_num) max_num = a;
}
int pos = 1;//先从个位开始
int a_pos_num;
//max_num的位数决定着循环的次数
while(max_num){
for(int i = 0; i < buckets.size(); i ++) {
buckets[i].clear();
}
//挨个求它们的位上的数字,放到桶里
for(int a : arr){
//如果a小于要求的位数,那这个 位 肯定就是0
if(a < pos) buckets[0].push_back(a);
else {
//求a对应pos位上的数字
a_pos_num = (a / pos) % 10;
buckets[a_pos_num].push_back(a);
}
}
int indx = 0;
//按照0-9的顺序,取出来,先进先出
for(int i = 0; i < buckets.size(); i ++){
for(int j = 0; j < buckets[i].size(); j ++){
arr[indx] = buckets[i][j];
indx ++;
}
}
pos *= 10;//位数往前一位
max_num /= 10;
}
return arr;
}
};
基数排序本质理解为按关键字排序,对于整数,我们选择用他们的每个位上的数字来作为关键字,这样的方法也可以用于堆其他类型 进行排序