排序算法
一、常见的排序算法
????????以下是一些最基本的排序算法。虽然在 C++
里可以通过
std::sort()
快速排序,而且刷题时很少需要自己手写排序算法,但是熟习各种排序算法可以加深自己对算法的基本理解,以及解出由这些排序算法引申出来的题目。
快速排序(Quicksort)
????????我们采用左闭右开的二分写法。
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);
}
归并排序(Merge Sort)
void merge_sort(vector<int> &nums, int l, int r, vector<int> &temp) {
if (l + 1 >= r) {
return;
}
// divide
int m = l + (r - l) / 2;
merge_sort(nums, l, m, temp);
merge_sort(nums, m, r, temp);
// conquer
int p = l, q = m, i = l;
while (p < m || q < r) {
if (q >= r || (p < m && nums[p] <= nums[q])) {
temp[i++] = nums[p++];
} else {
temp[i++] = nums[q++];
}
}
for (i = l; i < r; ++i) {
nums[i] = temp[i];
}
}
插入排序(Insertion Sort)
void insertion_sort(vector<int> &nums, int n) {
for (int i = 0; i < n; ++i) {
for (int j = i; j > 0 && nums[j] < nums[j-1]; --j) {
swap(nums[j], nums[j-1]);
}
}
}
冒泡排序(Bubble Sort)
void bubble_sort(vector<int> &nums, int n) {
bool swapped;
for (int i = 1; i < n; ++i) {
swapped = false;
for (int j = 1; j < n - i + 1; ++j) {
if (nums[j] < nums[j-1]) {
swap(nums[j], nums[j-1]);
swapped = true;
}
}
if (!swapped) {
break;
}
}
}
选择排序(Selection Sort)
void selection_sort(vector<int> &nums, int n) {
int mid;
for (int i = 0; i < n - 1; ++i) {
mid = i;
for (int j = i + 1; j < n; ++j) {
if (nums[j] < nums[mid]) {
mid = j;
}
}
swap(nums[mid], nums[i]);
}
}
以上排序代码调用方法为:
void sort() {
vector<int> nums = {1,3,5,7,2,6,4,8,9,2,8,7,6,0,3,5,9,4,1,0};
vector<int> temp(nums.size());
sort(nums.begin(), nums.end());
quick_sort(nums, 0, nums.size());
merge_sort(nums, 0, nums.size(), temp);
insertion_sort(nums, nums.size());
bubble_sort(nums, nums.size());
selection_sort(nums, nums.size());
}
二、经典问题?
1. 快速选择
215. 数组中的第K个最大元素
215. Kth Largest Element in an Array
????????给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
????????请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
????????你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
????????快速选择一般用于求解 k-th Element
问题,可以在
O
(
n
)
时间复杂度,
O
(
1
)
空间复杂度完成求 解工作。快速选择的实现和快速排序相似,不过只需要找到第 k
大的枢(
pivot
)即可,不需要对其左右再进行排序。与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为
。
????????数组中第 k 个最大元素其实就是求数组中第 nums.length - k 个最小元素(从第0小开始),我们定义target = nums.length - k。先找一个中枢点(pivot),然后遍历其他数字,小于 pivot 的排左边,大于 pivot 的排右边,中枢点是数组中的第几小的数字就确定了,如果 pivot 与 target 相等,直接返回pivot 位置的数字,如果大于 target ,说明要求的数字在左边部分,否则在右边部分。剩下的就是递归了。
class Solution {
public:
// 主函数
int findKthLargest(vector<int>& nums, int k) {
random_shuffle(nums.begin(),nums.end()); // 打乱数组
int l = 0,r = nums.size() - 1,target = nums.size() - k;
while(l <= r){
int mid = quickSelection(nums,l,r);
if(mid == target){
return nums[mid];
}
if(mid < target){
l = mid + 1;
}else{
r = mid - 1;
}
}
return nums[l];
}
// 辅函数 - 快速选择
int quickSelection(vector<int>& nums,int l,int r){
int i = l + 1,j = r;
while(true){
while(i < r && nums[i] <= nums[l]){ // i从左往右寻找大于nums[l]的并停止
++i;
}
while(l < j && nums[j] >= nums[l]){ // j从右往左寻找小于nums[l]的并停止
--j;
}
if(i >= j){ // i和j相遇或擦肩而过
break;
}
swap(nums[i],nums[j]); // 如果没有break,就像快速排序一样一直交换大小值
}
swap(nums[l],nums[j]); // l归位
return j; // 返回的是这次快排归位的那个数的下标
}
};
2. 桶排序
347. 前 K 个高频元素
347. Top K Frequent Elements
????????给你一个整数数组?nums ?和一个整数?k ?,请你返回其中出现频率前?k ?高的元素。你可以按?任意顺序?返回答案。
????????顾名思义,桶排序的意思是为每个值设立一个桶,桶内记录这个值出现的次数(或其它属 性),然后对桶进行排序。针对样例来说,我们先通过桶排序得到四个桶 [1,2,3,4]
,它们的值分别 为 [4,2,1,1]
,表示每个数字出现的次数。
????????紧接着,我们对桶的频次进行排序,前 k
大个桶即是前
k
个频繁的数。这里我们可以使用各种 排序算法,甚至可以再进行一次桶排序,把每个旧桶根据频次放在不同的新桶内。针对样例来说, 因为目前最大的频次是 4
,我们建立
[1,2,3,4]
四个新桶,它们分别放入的旧桶为
[[3,4],[2],[],[1]]
, 表示不同数字出现的频率。最后,我们从后往前遍历,直到找到 k
个旧桶。
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> counts; // 无序映射,用法count[key]=value
int max_count = 0;
for(const int & num : nums){ // foreach循环遍历nums数组,每次遍历时把nums中的值赋给num
max_count = max(max_count, ++counts[num]); // 找出每个num出现频次最多的为多少次
}
vector<vector<int>> buckets(max_count + 1);
// vector<vector<int>>a(row,vector<column,0>);定义一个row*column二维动态数组,并初始化为0,row必须定义
for(const auto & p : counts){
buckets[p.second].push_back(p.first);
// buckets[出现次数].push_back(当前元素);p.first为key值为num,p.second为value值为++counts[num]的值
}
vector<int> ans;
for(int i = max_count; i >= 0 && ans.size() < k; --i){ // 索引越高,所储存的数组中的元素出现的次数越多
for(const int & num : buckets[i]){
ans.push_back(num);
if(ans.size() == k){
break;
}
}
}
return ans;
}
};
三、巩固练习
451. 根据字符出现频率排序
451. Sort Characters By Frequency
????????给定一个字符串 s ,根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。
????????返回 已排序的字符串?。如果有多个答案,返回其中任何一个。
桶排序的变形题。
class Solution {
public:
string frequencySort(string s) {
unordered_map<int, int> counts;
int max_count = 0;
for(const char & num : s){
max_count = max(max_count, ++counts[num]);
}
vector<vector<int>> buckets(max_count + 1);
for(const auto & p : counts){
buckets[p.second].push_back(p.first);
}
string ans = "";
for(int i = max_count; i >= 0 ; --i){
for(const char & num : buckets[i]){
int tmp = i;
while(tmp--){
ans.push_back(num);
}
}
}
return ans;
}
};
75. 颜色分类
75. Sort Colors
????????给定一个包含红色、白色和蓝色、共?n 个元素的数组?nums?,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
????????我们使用整数 0、?1 和 2 分别表示红色、白色和蓝色。
????????必须在不使用库的sort函数的情况下解决这个问题。
很经典的荷兰国旗问题,考察如何对三个重复且打乱的值进行排序。
class Solution {
public:
// 主函数
void sortColors(vector<int>& nums) {
quick_sort(nums,0,nums.size());
}
// 辅函数 - 快速排序
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);
}
};
欢迎大家共同学习和纠正指教
|