常见的排序算法 原理&时间复杂度&实现

排序算法

排序算法,顾名思义 就是将一组数排好序。如果就按照我们日常生活中思路,我们在排好一组数的时候
最先想到将数排好序的过程的算法如下:
1)是从左到右,依次取数放到A中,每放一个数到集合A中,我们就要保证集合A中的有序性,跟整理扑克牌一样,这样当所有数都取完了,整个数组也就有序了。
2)先找到最小值排在前面,然后在剩余的数中找到第二小的值排在第二的位置,依次排序,直到所有数都排完

  • todo:算法–算法的校验:对数器

一、冒泡排序

1、排序思想:
1)将最大值放在数组最后,然后在剩余数中比较第二大的值。放在倒数第二的位置,直到所有的数都排完成。
2、冒泡排序的过程:
1)依次比较数组A中相邻的两个数X,Y
2)如果X > Y ,则交换数据 X,Y的位置
3)然后X 和 Z进行比较
重复上述过程,直到所有的数都遍历完,此时数组A中最大值存放在数组最后的位置
3、时间复杂度
1)要比较相邻的两个数,那么要遍历完数组中所有的数,这个就是 n
2)每遍历一次数组,只能确定一个数的位置,那么要确定n个数的位置,就需要遍历n次
所有时间复杂度就是O(n*n)即O(n^2)
实际上 每次遍历的数 不是n个,当确定了一个数的正确位置之后,只需要遍历剩下的n-1个位置即可,依次类推。
4、空间复杂度
冒泡排序没有额外的空间开销,只要交换数组中的数即可。
5、算法实现

#include <iostream>
#include <vector>
using namespace std;
//从小到大排序
void bubble_sort(vector<int> &arr){
    int length = (int)arr.size();
    //确定n个数的位置,需要遍历n次
    for(int i = 0; i < length ; i++){
        //由于比较的是arr[j]和arr[j+1],因此要保证 j+1 < length - i(注意是<,因为下标从0开始)
        for(int j = 0; j < length - i - 1; j++){
            if(arr[j] > arr[j + 1]){
                swap(arr[j] , arr[j + 1]);
            }
        }
    }
}

//从大到小排序
void inverse_bubble_sort(vector<int> &arr){
    int length = (int)arr.size();
    //确定n个数的位置,需要遍历n次
    for(int i = 0; i < length ; i++){
        for(int j = 0; j < length - i - 1; j++){
            if(arr[j] < arr[j + 1]){
                swap(arr[j] , arr[j + 1]);
            }
        }
    }
}

//交换两个数组,取地址
void swap(int* num_a, int* num_b){
    int *temp = num_a;
    num_a = num_b;
    num_b = temp;
}

int main(int argc, const char * argv[]) {
    // insert code here...
    int num;
    cout <<"输入数组的个数"<<endl;
    cin>>num;
    vector<int> orgin_arr;
    for(int i = num; i > 0; i--){
        orgin_arr.push_back(i);
    }
    for(int i = 0; i < num; i++){
        cout<<orgin_arr[i]<<endl;
    }
    cout<<endl;
    bubble_sort(orgin_arr);
    cout << "Hello, World!\n";
    for(int i = 0; i < num; i++){
        cout<<orgin_arr[i]<<endl;
    }
    
    cout<<endl;
    
    inverse_bubble_sort(orgin_arr);
    for(int i = 0; i < num; i++){
        cout<<orgin_arr[i]<<endl;
    }
    
    return 0;
}

二、选择排序

1、排序思想
1)数组除a[0]位置外,其他位置的数a[1]~a[n]都和a[0]比较,如果小于a[0],则和原本a[0]的数交换,直到比较完所有的数。
2)选择排序和冒泡排序不一样,虽然都是找到最值放在某一个位置(最前 or 最后),但是冒泡排序是通过交换两个数得到的,选择排序是其他位置的数与某一个位置的数比较大小,交换值得到的。
2、选择排序的过程
1)数组中 a[i] ~ a[n]( i >= 1)位置的数依次和a[i - 1]位置上的数比较
2)如果a[i] > a[i - 1],则交换a[i] 和 a[i - 1]
3)直到比较完下标 i ~ n上面的数
重复以上过程,直到 i == n
3、算法实现

#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;
vector<int> select_sort(vector<int> &arr){
    int length = (int)arr.size();
    //n个位置的上的数都需要比较一次,确定了length - 1个数的位置,那么最后一个数也就确定了
    for(int i = 0; i < length - 1; i++){
        for(int j = i + 1; j < length; j++){
            if(arr[i] > arr[j]){
                swap(arr[i],arr[j]);
            }
        }
    }
    return arr;
}
void swap(int *num1, int *num2){
    int *temp = num1;
    num1 = num2;
    num2 = temp;
}

int main(int argc, const char * argv[]) {
    // insert code here...
    int num;
    cout <<"输入数组的个数"<<endl;
    cin>>num;
    vector<int> orgin_arr;
    for(int i = num; i > 0; i--){
        orgin_arr.push_back(i);
    }
    for(int i = 0; i < num; i++){
        cout<<orgin_arr[i]<<endl;
    }
    cout<<endl;
    select_sort(orgin_arr);
    //cout << "Hello, World!\n";
    for(int i = 0; i < num; i++){
        cout<<orgin_arr[i]<<endl;
    }
    
    cout<<endl;
    std::cout << "Hello, World!\n";
    return 0;
}

三、插入排序

1、排序思想
跟整理扑克牌一样,先根据当前数排好序,然后插入一个新的数,保证插入新的数之后,原来有序的部分 仍然有序。
2、插入排序过程
1)数组A中,先取一个数a[i]
2)插入数a[i+1],比较a[i]和a[i+1]的大小,使其变得有序
3)然后插入数a[i+2],使得a[i],a[i+1],a[i+2]的这三个数都是有序
4)依次插入新的数,到a[i]~a[i+n]的数组上,使得插入新数的序列仍有序
3、算法实现

#include <iostream>
#include <vector>
using namespace std;
//非优解
void insert_sort(vector<int> &arr){
    int index = 0; //当前有序数组中最有一个数的下标
    int p_index = 0; //新插入的数应该在的位置
    int length = (int)arr.size();
    for(index = 0; index < length - 1; index++){
        p_index = index + 1; //要插入的数组下标
        //从有序数组的最后一个数开始往前比较
        for(int j = index; j >= 0; j--){
            //记录带插入的数temp
            int temp = arr[p_index];
            //插入的数temp比有序数组最后一个数小,插入的数前移
            while(temp < arr[p_index - 1]){
                //找到待插入数的位置
                arr[p_index] = arr[p_index - 1];
                arr[p_index - 1] = temp;
                p_index--;
            }
        }
    }
}
int main(int argc, const char * argv[]) {
    // insert code here...
    int num;
    cout <<"输入数组的个数"<<endl;
    cin>>num;
    vector<int> orgin_arr;
    for(int i = num; i > 0; i--){
        orgin_arr.push_back(i);
    }
    for(int i = 0; i < num; i++){
        cout<<orgin_arr[i]<<endl;
    }
    cout<<endl;
    insert_sort(orgin_arr);
    for(int i = 0; i < num; i++){
        cout<<orgin_arr[i]<<endl;
    }
    return 0;
}

插入排序里面的部分操作实际上可以优化用vector里面自带的方法进行插入数组和反转。

  • todo:c++基础–stl中vector源码解析。

四、快速排序

1、排序思想
递归 + 分治
2、随机快排过程:
1)在数组随机选择一个数A,比数A小的放在数A的左边,比数A大的放在其右边
2)对比A小的数(即A左边)进行类似1)中的操作
3)对比A大的数(即A右边)同样进行类似1)中的操作
3、算法实现


五、归并排序

1、排序思想:
分治的算法思想 + 递归
归并排序的过程用到递归(链接)
2、归并的排序过程
将一个数组arr[3,7,6,2,5,9]拆分成两个部分Left[3,7,6]、Right[2,9,5]
Left = [3,7,6]、Right= [2,9,5]
Left中下标用index_a进行记录,Right中下标用index_b记录
1)如果Left[index_a] < Right[index_b],则将Left[index_a]的元素值存放到新的数组C中,然后执行index_a++
2)如果Left[index_a] > Right[index_b],则将Right[index_b]中的元素存放到薪的数组C中,然后执行index_b++
3)如果数组Left先遍历完成,Right没有遍历完,则将Right中剩余元素放到C中
4)如果数组Right先遍历完成,Left没有遍历完,则将Left中剩余元素放到C中
5)然后将数组C的元素重新覆盖arr。此时arr变为有序的
3、算法实现

#include <iostream>
#include <vector>

using namespace std;

void merge(vector<int> &arr, int left, int mid, int right){
    vector<int> new_arr;  //辅助数组C,暂存合并后有序的数
    int left_index = left; 
    int right_index = mid + 1;
    while(left_index <= mid && right_index <= right){
        if(arr[left_index] <= arr[right_index]){
        	//如果Left当前下标数小于Right当前下标数,则将C中存放Left的当前下标数,并且下标后移一位
            new_arr.push_back(arr[left_index++]);
        }
        if(arr[left_index] > arr[right_index]){
            new_arr.push_back(arr[right_index++]);
        }
        /*new_arr[i++] = arr[left_index] > arr[right_index]?arr[right_index++]:arr[left_index++];*/
    }
    //如果Left中还有剩余元素,则所有剩余元素放入C中
    while(left_index <= mid){
        new_arr.push_back(arr[left_index++]);
    }
    //如果Right中还有剩余元素,则所有剩余元素放入C中
    while(right_index <= right){
        new_arr.push_back(arr[right_index++]);
    }
    for(int i = 0; i < right - left + 1; i++){
        arr[left+i] = new_arr[i];
    }
}
void sort_partition(vector<int> &arr, int left, int right){
    if(right == left){ //只有一个元素的话,就不进行排序
        return;
    }
    int mid = left +  (right - left) / 2;
    sort_partition(arr, left, mid);
    sort_partition(arr, mid+1, right);
    merge(arr, left, mid, right);
}
//要取arr的地址,不然值不会覆盖。这块涉及深浅复制的知识点
void merge_sort(vector<int> &arr, int left, int right){
    if(arr.size() < 2){
        return;
    }
    sort_partition(arr, left, right);
}
int main(int argc, const char * argv[]) {
    // insert code here...
    int num;
    cout <<"输入数组的个数"<<endl;
    cin>>num;
    vector<int> orgin_arr;
    for(int i = num; i > 0; i--){
        orgin_arr.push_back(i);
    }
    merge_sort(orgin_arr, 0,(int)orgin_arr.size()-1);
    for(int i = 0; i < num; i++){
        cout<<orgin_arr[i]<<endl;
    }
    return 0;
}
  • todo:c++基础–深复制和浅复制,函数入参取地址&,取指针*理解,结合内存原理去理解

六、堆排序

1、排序思想
利用堆结构进行排序。
2、堆排序的过程

在这里插入代码片

3、堆排序思想

  • todo:数据结构–堆结构
  • todo:数据结构–二叉树

3、排序过程
4、算法实现


七、桶排序

了解

八、希尔排序

了解

九、排序算法总结

1、算法复杂度对比

算法时间复杂度空间复杂度稳定性
冒泡排序O(n^2)O(1)稳定
选择排序O(n^2)O(1)不稳定
快速排序O(nlogn)O(1)稳定
插入排序O(n^2)O(1)稳定
归并排序O(nlogn)O(n)稳定
堆排序O(nlogn)O(1)不稳定

2、稳定性
1)稳定性:排序之后,大小相同的数,相对位置不变。
比如:
原数组:1 2 3’ 4 3 (3‘ 在 3 之前)
排序完:1 2 3‘ 3 4 (排序完成之后,3‘ 在 3 之前,则算法是稳定的)
排序完:1 2 3 3‘ 4 (排序完成之后,3‘ 在 3 之后,相对位置打乱,则算法是稳定的)

自己在学习算法的时候,比如学习常见的排序算法,以及对应的时间复杂度或者空间复杂度分析的时候,刚学习或者刚复习的那段时间,还能够记住,但是时间久了之后,就会忘记。
可能主要原因在于,自己在学习的过程中,书上怎么说 或者 说教程中怎么讲 自己就怎么记,缺乏一个思考的过程。比如为什么要这么做?为什么这种方法可以实现?有没有更优的算法?或者当前题解涉及到哪些知识点?能否串起来?可能对于我来说,加深印象主要在于有些东西是自己思考得出来的,自己归纳总结出来的,而不是看书看来的。
希望自己以后能够多思考,多总结,这样才能记得更牢靠,理解的更深入。涉及到的知识点,除了总结以外,最好还是动手实现一边

参考资料:
1、 左程云的算法基础班 1-1 ~ 1-3
本篇博客内容 = 左神的课堂笔记 + 自己一些知识的总结 + 自己不熟悉知识点的扩展 + 相关扩展知识的博客链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值