经典排序算法

经典排序算法

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;
    }
}
原文地址:https://www.cnblogs.com/huhu555/p/14628377.html