• 经典排序算法


    经典排序算法

    1.冒泡排序

    • 依次将未排序的最大元素放到序列尾部,就像水中的泡泡付出水面那样
    #include <iostream>
    #include <algorithm>
    #include <vector>
    
    using namespace std;
    
    void bubble_sort(vector<int> &q) {
        for(int i = q.size() - 1; i > 0; i -- ) {
            bool flag = false; //本轮循环是否进行过交换操作,如果没有进行过,则说明已经有序,结束排序过程
            for(int j = 0; j + 1 <= i; j ++ )
                if(q[j] > q[j + 1]) {
                    swap(q[j], q[j + 1]);
                    flag = true;
                }
        }
    }
    //主函数(后续排序算法不再写出)
    int main()
    {
        int n;
        vector<int> q;
        //处理输入
        cin >> n;
        for(int i = 0, t; i < n; i ++ ) {
            cin >> t;
            q.push_back(t);
        }
        //排序
        bubble_sort(q);
        //输出
        for(auto x : q) cout << x << ' ';
        cout << endl;
        
        return 0;
    }
    

    2.选择排序

    • 依次从未排序部分中找出最小元素,按序放置
    void selectionSort(vector<int>& q) {
        for(int i = 0;i < q.size(); i ++ ) {
            for(int j = i + 1; j < q.size(); j ++ ) {
                if(q[i] > q[j])
                    swap(q[i], q[j]);//内层循环相当于从[i, end]中找到最小值并放置到就绪序列的下一个位置
            }
        }
    }
    

    3. 插入排序

    • 类比整理扑克牌的过程
    void insertionSort(vector<int> &q) {
        for(int i = 1; i < q.size(); i ++ ) {
            int t = q[i];
            for(int j = i - 1; j >= 0; j -- ) {//依次与已经有序的序列比较
                if(q[j] > t) 
                    q[j + 1] = q[j];
                else break;
            }
            q[j + 1] = t;
        }
    }
    

    4. 希尔排序

    • 缩小增量排序
    • 预先设定好一些增量,比如将增量设置成t,则将整个待排序序列分割为t个子序列分别进行直接插入排序
    • 第一个突破n^2的排序算法
    //省略
    

    5. 归并排序(熟练掌握

    • 分治思想
    • 很多其他算法思想的基础
    • 可以用归并思想解决很多问题,比如“求数组逆序对的数目”
    //自顶向下 或 自底向上
    //链表 或 数组
    //交叉有四种实现
    void merge_sort(vector<int>& q, int l, int r) {
        if(l >= r) return;
        int mid = l + r >> 1;
        merge_sort(q, l, mid);
        merge_sort(q, mid + 1, r);
        
        static vector<int> w; //辅助数组,用来合并当前层的左右两部分; 开成静态空间可以提高效率
        w.clear();
        
        //两个指针分别从两部分从小到大比较遍历,归并
        int i = l, j = mid + 1;
        while(l <= mid && j <= r) {
            if(q[i] <= q[j])
                w.push_back(q[i ++ ]);
            else w.push_back(q[j ++ ]);
        }
            
            //将最后剩下的尾部接上去
            while(i <= mid) w.push_back(q[i ++ ]);
            while(j <= r) w.push_back(q[j ++ ]);
            
            for(int i = l, j = 0; j < w.size(); i ++, j ++ ) q[i] = w[j];//将归并完成的部分存到原数组的空间中
    }
    
    //求逆序对
    int merge_sort(vector<int>& q, int l, int r) {
        if(l >= r) return 0;
        int res = 0; //当前区间的逆序对个数
        int mid = l + r >> 1;
        res += merge_sort(q, l, mid);
        res += merge_sort(q, mid + 1, r);
        
        static vector<int> w; //辅助数组,用来合并当前层的左右两部分; 开成静态空间可以提高效率
        w.clear();
        
        //两个指针分别从两部分从小到大比较遍历,归并
        int i = l, j = mid + 1;
        while(l <= mid && j <= r) {
            if(q[i] <= q[j])
                w.push_back(q[i ++ ]);
            else {
                w.push_back(q[j ++ ]);
                res += mid - i + 1 //从i到mid的所有数都比q[j]大
            }
        }
            
            //将最后剩下的尾部接上去
            while(i <= mid) w.push_back(q[i ++ ]);
            while(j <= r) w.push_back(q[j ++ ]);
            
            for(int i = l, j = 0; j < w.size(); i ++, j ++ ) q[i] = w[j];//将归并完成的部分存到原数组的空间中
    }
    

    6. 快速排序(必须掌握

    • 分治思想:从数组中选择一个基准值,把其他数按照与基准值的大小关系分到基准值的左右两边
    • 要想用快速排序实现稳定排序,只需要将单关键字编程双关键子,比如给每个元素附一个下标,将其变成pair
    void quick_sort(vector<int>& q, int l, int r) {
        if(l >= r) return;//只有一个数直接返回
        int i = l - 1, j = r + 1, x= q[l + r >> 1]; //选择中点为基准值
        while(i < j) {
            //左右指针技巧
            do i ++; while(q[i] < x);//从左边找一个小于x的数
            do j ++; while(q[j] > x);//从右边找一个大于x的数
            if(i < j) swap(q[i], q[j]);//i和j还没有相遇,分别指向两个不属于它所在位置的数,互相交换
            else quick_sort(q, l, j), quick_sort(q, j + 1, r);//注意这儿有一个思维误区,取中间点为基准值并不是说该点一直会在中点,而是按照该基准值对依次对区间内的数做分拣
        }
    }
    
    //基于链表的快速排序
    

    7. 堆排序

    • 堆是一棵完全二叉树,同时每个根节点的值都比左右儿子的值大/小
    • 堆排序的思想,先在原数组的基础之上,将堆建出来,根节点就是当前最小值,将根节点取出来,然后将整个堆的最后一个元素插入根节点位置,再维护堆(堆化),每次堆化的时间复杂度是O(logn), 依次类推
    • 堆存储时所有元素按照层级序列存在一个数组中, i的左儿子2i,右儿子2i + 1
    • 堆的常用操作:
      • push_down: 如果某个时刻除了根节点之外,所有节点都是满足堆的定义的,则通过一系列比较和交换将当前根节点移动到属于它的位置,使得整棵二叉树满足堆的定义; 每一层只会有一组操作,整棵树的层数是logn量级的,所以时间复杂度是O(logn);
      • 类似地,有push_up操作,时间复杂度同样是O(logn)
    void push_down(vector<int>& heap, int size, int u) {
        int t = u, left = u * 2, right = u * 2 + 1;
        if(left <= size && heap[left] > heap[t]) t = left;
        if(right <= size && heap[right] > heap[t]) t = right;
        if(t != u) {
            swap(heap[u], heap[t]);
            push_down(heap, size, t);
        }
    }
    
    void push_up(vector<int>& heap, int size, int u) {
        while(u /2 && heap[u / 2] < heap[u]) {
            swap(heap[u / 2], heap[u]);
            u /= 2;
        }
    }
    //插入一个元素
    void insert(vector<int>& heap, int size, int x) {
        heap[ ++ size] = x;
        push_up(heap, size, x);
    }
    //删除一个元素
    void remove_top(vector<int>& heap, int& size) {
        heap[1] = heap[size];
        size -- ;
        push_down(heap, size, 1);
    }
    /*改变中间某个节点的值,执行下面两句,顺序无关
    //push_up(x);
    push_down(x);*/
    //建堆
    void heap_sort(vector<int>& q, int size) {
        for(int i = 1; i <= n; i ++ ) push_up(q, i);//建堆,时间复杂度为O(nlogn)
        for(int i = 1; i <= n; i ++) {
            swap(q[1], q[size]);
            size -- ; //相当与将最大的数拿出来
            push_down(q, size, 1);
        }
    }
    
    int main() {
        vector<int> q;
        int n;
        cin >> n;
        q.resize(n + 1);
        for(int i = 1; i <= n; i ++ ) cin >> q[i];
        
        heap_sort(q, n);
        
        for(int i = 0; i <= n; i++ ) cout << q[i] << ' '; cout << endl;
        return 0;
    }
    

    8.计数排序

    • 要求数据范围和数的个数差不多,能达到线性时间复杂度
    • 应用该算法的题目的典型特征类似这样“给定10^7个数,每个数在0~9999999之间”
    • 算法思想应用于后缀数组
    void counting_sort(vector<int>& q, int n) {
    	vector<int> cnt(101, 0);
        
        for(int i = 0; i <= n; i ++ ) cnt[q[i]] ++ ; //统计每个数出现了多少次
        
        for(int i = 1, k = 1; i <= 100; i ++ ) //从前往后遍历
            while(cnt[i]) {
                q[k ++ ] = i;
                cnt[i] --;
            }
    }
    

    9. 桶排序

    • 将元素均匀分到(按照某种方式映射)一定数量的桶中,在桶内部再做排序,桶和桶之间是有序的
    //省略
    

    10. 基数排序

    • 在桶排序思想下按照数字的位来划分桶,就是基数排序
    • 比sort慢很多,除法和取模大大增大了常数项
    //假设所有的数都在三位以内
    int get(int x, int i) {
        while(i -- ) x /= 10;
        return x%10;
    }
    void radix_sort(vector<int>& q, int n) {
    	vector<vector<int>> cnt(10);//定义桶
        
        for(int i = 0; i < 10; i ++ ) { //依次按照
            for(int j = 0; j < 10; j++ ) cnt[j].clear();
            
        	for(int j = 1; j <= n; j ++ ) 
            	cnt[get(q[j], i)].push_back(q[j]);
        
        	for(int j = 0, k = 1; j < 10; j ++ )
            	for(int x : cnt[j])
                	q[k ++ ] = x;
        }
    }
    
  • 相关阅读:
    MVC常用跳转页面的方法
    简单工厂模式
    MySQL 查询昨天中午12点到今天中午12点的数据
    Windows安装yarn
    MapStruct
    CSS 3D
    05-序列化器ModelSerializer
    django基础之Django中间件
    04-序列化器Serializer
    03-四大基本模块
  • 原文地址:https://www.cnblogs.com/huhu555/p/14628377.html
Copyright © 2020-2023  润新知