【算法】算法笔记_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 问题,最小堆使用大顶堆来实现,大顶堆的堆顶元素为当前堆的最大元素。实现过程:不断地往大顶堆中插入新元素,当堆
浙公网安备 33010602011771号