1、快速排序:
主要思路:参考快速排序 - 如果天空不死 - 博客园
选择一个分界值,一般初始可以是数组的第一个元素。第一次排序:使得大于等于该元素的值都在右边,小于该元素的值都在左边。相当于将数组分成两个独立的子数组,再递归对两个子数组进行排序。
快速排序主要用于求解K-th Element问题,即从一个数组中找到第K大的数字。
void quick_sort(vector<int> &nums, int l, int r) {
if (l + 1) >= r return;
int first = l, last = r - 1, key = nums[first];
while (first < last) {
while (first < last && nums[last] >= key) --last;
nums[first] = nums[last];
while (first < last && nums[first <= key]) ++first;
nums[last] = nums[first];
}
nums[first] = key;
quick_sort(nums, l, first);
quick_sort(nums, first+1, r);
}
2、归并排序
主要思路:参考 图解排序算法(四)之归并排序 - dreamcatcher-cx - 博客园
归并排序是分而治之的思想,先去把数组分成最小的块,也就是单个元素,然后有序合并两个块。这里重点是要搞清楚怎么用递归去写正确。
void merge_sort(vector<int> &nums, int l, int r, vector<int> &temp) {
if (l+1) >= r return;
int m = l + (r-l)/2;
//切块
merge_sort(nums, l, m, temp);
merge_sort(nums, m, r, temp);
//合并
int p = l, q = m, i = l;
while (p < m || q < r) {
if (q >= r || nums[p] <= nums[q]) {
temp[i++] = nums[p++];
}
else temp[i++] = nums[q++];
}
//将temp数组复制到nums数组
for (i = l; i < r; i++) nums[i] = temp[i];
}
3、插入排序
主要思路:每一步将一个待排序的数据插入到前面已经排好序的有序序列中,直到插完所有元素为止。
void insert_sort(vector<int> &nums, int n) {
if (n <= 1) return;
for (int i = 1; i < n; i++) {
for (int j = i; j > 0 && nums[j]<nums[j-1]; j--) {
swap(nums[j], nums[j-1]);
}
}
}
4、冒泡排序
主要思路:每一次遍历,都选出最大的元素放到数组末尾,直到所有元素都被选完。
void gudugudu_sort(vector<int> &nums, int n) {
if (n <= 1) return;
bool swapped; //注意在一次遍历中,如果从未交换过,则证明数组本身就是有序的,直接跳出循环。
for (int i = 0; i < n-1; i++) { //只需要遍历n-1次,因为最后剩下的那个元素就是最小的了。
swapped = false;
for (int j = 1; j < n - i; j++) {
if (nums[j] < nums[j-1]) {
swap(nums[j], nums[j-1]);
swapped = true;
}
}
if (!swpped) break;
}
}
5、选择排序
主要思路:每次都从数组中选择最大的元素,放在数组的末尾处。不稳定的排序算法。
void gudugudu_sort(vector<int> &nums, int n) {
if (n <= 1) return;
int m, temp;
for (int i = 0; i < n-1; i++) {
m = 1;
for (int j = 1; j < n - i; j++) {
if (max_val < nums[j]) {
m = j;
}
}
//将m位置,和n-i-1位置交换。
temp = nums[n-i-1];
nums[n-i-1] =nums[m];
nums[m] = temp;
}
}
6、选出无序数组nums中第K大的元素
LeetCode215 力扣
主要思路:
*** 快速排序可以通过一次遍历,就把数组分成:{ 小于等于起始元素的左边区间,起始元素,大于等于起始元素的右边区间 } 这三部分。这种方法天然就比较适合找第K大元素的场景。
**** 对于数组[3,2,1,5,6,4],找它的第2大元素,可以转换成找第5小的元素(5 = nums.size() - K +1)。因为数组的下标从0开始,所以第5小就是有序数组中index=4的位置。 第一遍快排遍历完,数组变为[1,2,3,5,6,4],中间index=2(比较位元素3所落在的位置)。而我们的答案应该是中间index=4的元素。
****第一次快排遍历完,如果中间位置的index并不是K,那么我们可以将区间缩小到[0, 中间index-1],或者[中间index+1, nums.size()-1],继续下一次的快排。对于上面那个例子来说,搜索区间的子数组变为[5,6,4]。继续下一次快排。
**** 注:index表示数组的下标,从0开始计数。
//主函数
int findKthLargest(vector<int>& nums, int k) {
int l = 0, r = nums.size()-1, target = nums.size()-k, mid;
while (l < r) {
mid = quickSort(nums, l, r);
if (mid == target) return nums[mid];
else if (target > mid) l = mid + 1;
else r = mid - 1;
}
return nums[l];
}
//辅助函数,交换函数
void swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
//辅助函数,快排
int quickSort(vector<int>& nums, int l, int r) {
int i = l + 1, j = r; //这里为什么+1,因为l位置是比较位,不参与快排遍历
while (true) {
while (i < r && nums[i] <= nums[l]) i++;
while (j > l && nums[j] >= nums[l]) j--;
if (i >= j) break; //这种情况下,已经表明左边都是小的,右边都是大的。不需要再swap。
swap(nums[i], nums[j]);
}
swap(nums[l], nums[j]);
return j;
}
7、选出前K个高频元素
LeetCode 347 力扣
思路:首先需要知道每个元素的频次是多少;然后定义一个二维数组buckets,buckets[i]存放的是频次为i的所有元素。从后往前遍历buckets,取出K个元素即可。
vector<int> topKFrequent(vector<int>& nums, int k) {
//step1. 统计每个元素的出现频次,存在一个map里
unordered_map<int, int> counts; //注意要学会map的定义。
int max_count = 0;
for (const auto& num: nums) max_count = max(max_count, ++counts[num]);
//step2. 定义一个桶,每个桶存放相同频次的元素。
vector<vector<int>> buckets(max_count+1);
for (const auto& p: counts) {
buckets[p.second].push_back(p.first);
}
//step3. 从后往前遍历buckets,直到取出K个元素为止。
vector<int> res;
bool flag = false;
for (int i = max_count; i >= 0; i--) {
for (const auto& val: buckets[i]) {
if (res.size() >= k) {
flag = true;
break;
}
res.push_back(val);
}
if (flag) break;
}
return res;
}