【算法】算法笔记_01

1 排序

1.1 快速排序q[n]

快速排序 | 菜鸟教程

平均时间复杂度:n * log 2 n

  • 1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
  • 2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
  • 3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
  • 4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。

1.1.1 快速选择

题目:给定一个长度为n的整数数列,以及一个整数k,请用快速选择算法求出数列的第k小的数是多少(假设这个数一定在区间内)。

进行一次快速排序时,假设分界点左边元素个数为SL,右边元素个数为SR,此时分为两种情况:
(1)k <= SL,递归左边
(2)k > SL,递归右边,k = k - SL

时间复杂度:n + n / 2 + n / 4 + ... = n(1 + 1/2 + 1/4 + 1/8 + ...) <= 2n,所以时间复杂度为O(n)。

#include <iostream>

using namespace std;

const int N = 100010;

int n, k;
int q[N];

int quick_sort(int l, int r, int k){
    if(l == r) return q[l];//因为保证k对应的值始终在区间内,所以区间内至少有一个值,所以可以不写成l >= r

    int x = q[l], i = l - 1, j = r + 1;
    while(i < j){
        while(q[++i] < x);
        while(q[--j] > x);
        if(i < j) swap(q[i], q[j]); 
    }
    
    int sl = j - l + 1;
    if(k <= sl) return quick_sort(l, j, k);
    return quick_sort(j + 1, r, k - sl);
}

int main(){
    cin >> n >> k;
    for(int i = 0; i < n; i++) cin >> q[i];

    cout << quick_sort(0, n - 1, k) << endl;

    return 0;
}

1.2 归并排序

时间复杂度:n * log 2 n

(1)确定分界点mid:将整个数组平均分为左右两半。
mid = (l + r) / 2,两个区间[l,mid],[mid + 1,r]。
(2)对这两半分别进行递归排序每次分两半,直到每对每组大小为1。
n除以2多少次等于1呢?log 2 n次,所以这一操作时间复杂度为O(log 2 n)。

(3)对于每对的两个有序数组 合并为一个有序数组:设i,j两个指针分别指向两个数组的第一个数,比较这两个指针所指向的数的大小,将小的那个取出,相应指针后移一位,再比较这两个指针所指向的数的大小,以此类推,直到一个指针移到数组的末尾,将另一个指针指向的数及其之后的数放到答案数组的最后面即可。

class Solution {
public:
    void merge_sort(vector<int>& nums,int l,int r){//归并排序      
        //数组中没有数据或只有一个数据时,返回
        if(l >= r){
            return;
        }       
        //先进行对半分
        int middle = (l + r) >> 1;
        merge_sort(nums, l, middle);
        merge_sort(nums, middle + 1, r);      
        //然后对每对数组进行排序
        int temp[r - l + 1];//暂存结果
        int n = 0;
        int i = l,j = middle + 1;
        while(i <= middle && j <= r){
            if(nums[i] < nums[j]){
                temp[n++] = nums[i++];      
            }
            else{
                temp[n++] = nums[j++];
            }
        }
        if(i > middle){
            while(j <= r){
                temp[n++] = nums[j++];
            }
        }
        else if(j > r){
            while(i <= middle){
                temp[n++] = nums[i++];
            }
        }
        //从暂存数组中取出数据
        int p = l,q = 0;
        while(p <= r){
            nums[p++] = temp[q++];
        }
    }

    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        int l = 0;
        int r = n - 1;
        merge_sort(nums,l,r);
        return nums;
    }
};

1.2.1 求逆序对的数量

逆序对:序列中的一对数,前面的数比后面的数大。

使用归并排序,共有三种情况:
(1)这一对数都在mid的左边:左半边内部的逆序对数量:merge_sort(L, mid)
(2)这一对数都在mid的右边:右半边内部的逆序对数量:merge_sort(mid + 1, R)
(3)一个数在mid左边,一个数在mid右边:设右边有m个数。对右边第一个点,左边有S1个数比它大,那么有S1个逆序对。以此类推,对右边第m个点,左边有Sm个数比它大,那么有Sm个逆序对。那么这种情况下一共有S1 + S2 + ... + Sm个逆序对。
        在合并过程中,设指针i指向左边,指针j指向右边,若指针i指向的数比指针j指向的数小,则i向后移一位。直到指针i指向的数比指针j指向的数大,则S = mid - i + 1,j向后移一位。

设序列中有n个数,则最多有n(n - 1) / 2个逆序对,数值可能会很大,大于int的最大值,要用long long来存。

class Solution {
public:
    typedef long long LL;
    int merge_sort(int l, int r, vector<int>& nums){
        if(l >= r) return 0;

        int mid = (l + r) >> 1;
        LL res = merge_sort(l, mid, nums) + merge_sort(mid + 1, r, nums);

        int temp[r - l + 1];
        int i = l, j = mid + 1, k = 0;
        while(i <= mid && j <= r){
            if(nums[i] <= nums[j]) temp[k++] = nums[i++];
            else{
                temp[k++] = nums[j++];
                res = res + mid - i + 1;
            }
        }
        //扫尾       
        while(i <= mid) temp[k++] = nums[i++];
        while(j <= r) temp[k++] = nums[j++];
        //物归原主
        for(int p = l, q = 0; p <= r; p++, q++){
            nums[p] = temp[q];
        }

        return res;
    }

    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        int result = merge_sort(0, n - 1, nums);
        return result;
    }
};

1.3 堆

手撕堆排序(含图解,代码,复杂度分析,使用场景) - adeline.pan - 博客园

时间复杂度:O(N*logN), 更详细见上面代码中的注释。简单的理解是每次针对一个节点调整为堆的复杂度是O(logN)级别,需要调整N次,所以乘积可得。这么理解并不十分的精确,但大概意思正确,也比较好理解。

空间复杂度:O(1) 。

(1)堆可以用于求解 TopK Elements 问题,也就是 K 个最小元素的问题。使用最小堆来实现 TopK 问题,最小堆使用大顶堆来实现,大顶堆的堆顶元素为当前堆的最大元素。实现过程:不断地往大顶堆中插入新元素,当堆

posted @ 2023-03-12 21:29  轻闲一号机  阅读(5)  评论(0)    收藏  举报  来源