目录
排序算法
冒泡排序
/* 通过两两比较交换每次把较大或较小的放到末端,是一种交换排序。 */
void bubblesort(vector<int>& arr) {
int f = arr.size();
while (f > 0) {
int j = f;
f = 0;
for (int i = 1; i < j; i++) {
if (arr[i - 1] > arr[i]) { // 改成≥就不稳定了
swap(arr[i - 1], arr[i]);
f = i;
}
}
}
}
选择排序
void selectionsort(vector<int>& nums) {
int n = nums.size();
int p = 0, q = n - 1;
while (p < q) {
int l = p, r = q;
for (int i = p; i <= q; i++) {
if (nums[i] < nums[l])
l = i;
if (nums[i] > nums[r])
r = i;
}
swap(nums[r], nums[q]);
swap(nums[l == q ? r : l], nums[p]);
p++;
q--;
}
}
插入排序
/* 最差时间复杂度:O(n^2)
最优时间复杂度:O(n)
平均时间复杂度:O(n^2)
稳定性:稳定 */
template <typename T>
void insertionsort(vector<T>& nums) {
for (int i = 1; i < (int)nums.size(); i++) {
T k = nums[i];
int j = i - 1;
while (j >= 0 && nums[j] > k) {
nums[j + 1] = nums[j];
j--;
}
nums[j + 1] = k;
}
}
希尔排序
/* https://www.jianshu.com/p/d730ae586cf3
最好情况:序列是正序,比较操作n-1次,后移赋值0次,O(n)
最坏情况:O(nlog2n)
渐进时间复杂度(平均时间复杂度):O(nlog2n)
不稳定 */
template <typename T>
void shellsort(vector<T>& nums) {
int n = nums.size();
for (int g = (n >> 1); g > 0; g >>= 1) {
for (int i = g; i < n; i++) {
T k = nums[i];
int j = i - g;
while (j >= 0 && nums[j] > k) {
nums[j + g] = nums[j];
j -= g;
}
nums[j + g] = k;
}
}
}
归并排序
#define GETMID(i,j) ((i) + (((j) - (i)) >> 1))
void merge(vector<int>& nums, int l, int mid, int r) {
vector<int> arr(r - l + 1);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
arr[k++] = nums[i] <= nums[j] ? nums[i++] : nums[j++];
while (i <= mid)
arr[k++] = nums[i++];
while (j <= r)
arr[k++] = nums[j++];
for (i = 0; i <= r - l; i++)
nums[l + i] = arr[i];
}
void mergesort(vector<int>& nums, int l, int r) {
if (l == r)
return;
int mid = GETMID(l, r);
mergesort(nums, l, mid);
mergesort(nums, mid + 1, r);
merge(nums, l, mid, r); // 直到子数组只有一个元素开始合并(比如l==0,mid==0,r==1)
}
在比较类排序中,归并排序号称最快,其次是快速排序和堆排序,两者不相伯仲,但是有一点需要注意,数据初始排序状态对堆排序不会产生太大的影响,而快速排序却恰恰相反。
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。把长度为n的输入序列分成两个长度为n/2的子序列;对这两个子序列分别采用归并排序;将两个排序好的子序列合并成一个最终的排序序列。
每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。
快速排序
实现
void quicksort(int low, int high, vector<int>& v) {
if (low >= high)
return;
int i = low, j = high, t = 0, pivot = v[low];
while (i < j) {
// 不稳定是pivot与v[i]或v[j]交换导致的,即使没有等号也是不稳定的
// 如果v[i]或者v[j]附近有相等的元素,与pivot交换后就会产生相对位置的改变
while (i < j && v[j] >= pivot)
j--;
while (i < j && v[i] <= pivot)
i++;
if (i < j)
swap(nums[i], nums[j]);
}
swap(nums[i], nums[low]);
quicksort(low, i - 1, v);
quicksort(j + 1, high, v);
}
应用
首先我们来回顾一下快速排序,这是一个典型的分治算法。对数组a[l⋯r]做快速排序的过程是(参考《算法导论》):
分解: 将数组a[l⋯r] 「划分」成两个子数组a[l⋯q−1]、a[q+1⋯r],使得a[l⋯q−1] 中的每个元素小于等于a[q],且a[q]小于等于a[q+1⋯r]中的每个元素。其中,计算下标q也是「划分」过程的一部分。
解决: 对子数组a[l⋯q−1]和a[q+1⋯r]递归调用快速排序。
合并: 因为子数组都是原址排序的,所以不需要进行合并操作,a[l⋯r]已经有序。
上文中提到的 「划分」 过程是:从子数组a[l⋯r]中选择任意一个元素作为主元,调整子数组的元素使得左边的元素都小于等于它,右边的元素都大于等于它,最终位置就是q。由此可以发现每次经过「划分」操作后,我们一定可以确定一个元素的最终位置,即最终位置为q,并且保证a[l⋯q−1]中的每个元素小于等于a[q],a[q]小于等于a[q+1⋯r]中的每个元素。所以只要某次划分的q为倒数第k个下标的时候,我们就已经找到了topK。因此我们可以改进快速排序算法:在分解的过程当中,我们会对子数组进行划分,如果划分得到的q正好就是我们需要的下标,就直接返回a[q];否则,就递归左子区间或右子区间。这样,就可以把原来递归两个区间变成只递归一个区间,提高了时间效率。这就是「快速选择」算法。我们知道快速排序的性能和「划分」出的子数组的长度密切相关。直观地理解如果每次规模为n的问题我们都划分成1和n−1,每次递归的时候又向n−1的集合中递归,这种情况是最坏的,时间代价是O(n^2)。可以引入随机化来加速这个过程,它的时间代价的期望是 O(n),证明过程可以参考「《算法导论》9.2:期望为线性的选择算法」。
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/kth-largest-element-in-an-array/solution/shu-zu-zhong-de-di-kge-zui-da-yuan-su-by-leetcode-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
/* 小规模TopK问题(未随机化) */
class Solution {
public:
int quick_selection(vector<int>& nums, int k, int l, int r) {
int pivot = nums[l], i = l, j = r;
while (i < j) {
while (i < j && nums[j] <= pivot)
j--;
while (i < j && nums[i] >= pivot)
i++;
if (i < j)
swap(nums[i], nums[j]);
}
swap(nums[i], nums[l]);
if (i == k - 1)
return pivot;
if (i > k - 1)
return quick_selection(nums, k, l, i - 1);
return quick_selection(nums, k, i + 1, r);
}
int findKthLargest(vector<int>& nums, int k) {
int n = nums.size();
assert(n >= k);
return quick_selection(nums, k, 0, n - 1);
}
};
堆排序
实现
非递归版本
void siftdown(vector<int>& arr, int n, int i) { // 非递归版本
int l = 2 * i + 1;
int r = l + 1;
int idx = i;
int midx = i;
if (l <= n && arr[l] > arr[midx])
midx = l;
if (r <= n && arr[r] > arr[midx])
midx = r;
while (midx != idx) {
int t = arr[midx];
arr[midx] = arr[idx];
arr[idx] = t;
idx = midx;
l = 2 * idx + 1;
r = l + 1;
if (l <= n && arr[l] > arr[midx])
midx = l;
if (r <= n && arr[r] > arr[midx])
midx = r;
}
}
void makeheap(vector<int>& arr, int n) {
for (int i = (n - 1) >> 1; i >= 0; i--) {
siftdown(arr, n, i);
}
}
void sortheap(vector<int>& nums) {
int n = nums.size() - 1;
makeheap(nums, n);
for (int i = n; i > 0; i--) {
int t = nums[0];
nums[0] = nums[i];
nums[i] = t;
siftdown(nums, i - 1, 0);
}
}
/* 递归版本 */
void siftdown(vector<int>& nums, int i, int n) {
int l = 2 * i + 1, r = 2 * i + 2, j = i;
if (l <= n && nums[l] > nums[j])
j = l;
if (r <= n && nums[r] > nums[j])
j = r;
if (j == i)
return;
swap(nums[i], nums[j]);
siftdown(nums, j, n);
}
void makeheap(vector<int>& nums, int n) {
for (int i = (n - 1) >> 1; i >= 0; i--)
siftdown(nums, i, n);
}
vector<int> heapsort(vector<int>& nums) {
int n = nums.size() - 1;
makeheap(nums, n);
for (int i = n; i > 0; i--) {
swap(nums[i], nums[0]);
siftdown(nums, 0, i - 1);
}
return nums;
}
应用
堆是一棵完全二叉树,树中每个结点的值都不小于其左右孩子的值即为大顶堆。STL中的priority_queue默认为大顶堆。 如果将待排序序列构造成一个大顶堆(makeheap or heapify),此时整个序列的最大值就是根结点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆(siftdown),这样会得到n个元素的次小值,这样反复执行n-1次,便能得到一个升序序列(sortheap)。堆排序是一种选择排序。
接下来说堆,堆分为大顶堆和小顶堆,大顶堆要求父结点要大于它的两个子结点,小顶堆要求父结点要小于它的两个子结点。而堆排序就是移除位于第一个数据的根结点,并做堆调整的递归运算。它涉及两个关键问题:(1)如何由无序序列建“初始堆”? (2)输出堆顶后,如何进行堆的筛选(调整)?
建立堆的操作如下:
(1)以给定的无序序列为基础建立一个完全二叉树
(2)由于叶子结点本身肯定满足堆结构,那么从第一个非叶子结点(比如第一个叶子结点的父结点)开始向上调整,重复(2)步骤
当然你也可以定一个顶点为堆,然后往这个堆里添加元素,但是这个方式建堆的时间复杂度是 O(nlgn),前一个方法建堆的时间复杂度是 O(n)
输出堆顶后,此时需要维护堆,操作如下:
(1)堆顶与堆尾交换并删除堆尾,被删除的堆尾的元素就是输出过的元素
(2)把当前堆顶向下调整,直到满足构成堆的条件,重复(2)步骤
很显然,在建立堆的调整步骤里,由于关键字相同的两个记录位置并不会被调换,所以建堆的时候是稳定的。但是,在堆顶与堆尾交换的时候两个相等的记录在序列中的相对位置就可能发生改变,这就影响其稳定性了。
——大峰子的博客
拓扑排序
vector<int> topological_sort(vector<vector<int>>& g, vector<int>& deg) {
int cnt = 0, n = g.size();
vector<int> nums;
queue<int> que;
for (int i = 0; i < n; i++)
if (deg[i] == 0)
que.push(i);
while (!que.empty()) {
int u = que.front();
que.pop();
cnt++;
nums.push_back(u);
for (int i = 0; i < (int)g[u].size(); i++) {
int v = g[u][i];
deg[v]--;
if (deg[v] == 0)
que.push(v);
}
}
return nums;
}
IPC
生产者-消费者
单-单
Producer
#include <iostream>
#include <windows.h>
using namespace std;
struct stuff {
string s[15];
int i;
stuff() : i(0) {}
};
int main()
{
HANDLE hMapFile, mutex, empty, full;
LPVOID lpBase;
if ((hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1024, L"SharedMemory")) == NULL) {
cout << "Failed to create shared memory!" << endl;
return 0;
}
if ((lpBase = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 1024)) == NULL) {
cout << "Failed to create shared memory!" << endl;
CloseHandle(hMapFile);
return 0;
}
mutex = CreateMutex(NULL, FALSE, (LPCWSTR)L"mutex"); // 互斥量
empty = CreateSemaphore(NULL, 10, 10, (LPCWSTR)L"empty"); // 商品空位数量
full = CreateSemaphore(NULL, 0, 10, (LPCWSTR)L"full"); // 产品的数量
const string itemlist[6] = { "猪", "牛", "羊", "鸡", "鸭", "鹅" }; // 商品种类
stuff* shm = new(lpBase) stuff; // 共享内存
for (;;) {
Sleep(2000); // item = produce_item(shared_stuff);
string item = itemlist[rand() % 6];
WaitForSingleObject(empty, INFINITE); // P(empty)
WaitForSingleObject(mutex, INFINITE); // P(mutex)
shm->s[(shm->i)] = item; // insert_item(shared_stuff,item);
cout << "Inserting item " << shm->s[(shm->i)++] << endl; // display_buffer();
ReleaseMutex(mutex); // V(mutex)
ReleaseSemaphore(full, 1, NULL); // V(full)
}
}
Consumer
#include <iostream>
#include <windows.h>
using namespace std;
struct stuff {
string s[15];
int i;
stuff() : i(0) {}
};
int main()
{
HANDLE hMapFile, mutex, empty, full;
LPVOID lpBase;
if ((hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, L"SharedMemory")) == NULL) {
cout << "Failed to open shared memory!" << endl;
return 0;
}
if ((lpBase = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 1024)) == NULL) {
cout << "Failed to open shared memory!" << endl;
return 0;
}
if ((mutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, (LPCWSTR)L"mutex")) == NULL) {
cout << "Failed to open mutex!" << endl;
return 0;
}
if ((empty = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, (LPCWSTR)L"empty")) == NULL) {
cout << "Failed to open semaphore!" << endl;
return 0;
}
if ((full = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, (LPCWSTR)L"full")) == NULL) {
cout << "Failed to open semaphore!" << endl;
return 0;
}
stuff* shm = (stuff*)lpBase;
for (;;) {
WaitForSingleObject(full, INFINITE);
WaitForSingleObject(mutex, INFINITE);
//item = remove_item();
Sleep(100);
string item = shm->s[--(shm->i)];
cout << "Removing item " << shm->s[(shm->i)] << endl;
//display_buffer();
ReleaseMutex(mutex);
ReleaseSemaphore(empty, 1, NULL);
//consume_item(item);
}
}