- 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
- 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序
算法复杂度:
文章目录
归并排序
(1)基本思想
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
(2)分而治之
可以看到这种**结构很像一棵完全二叉树,**本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。
(3) 合并相邻有序子序列
再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。
(4)归并排序基于分治算法。最容易想到的实现方式是自顶向下的递归实现,时间复杂度: T(n)
归并算法是一个不断递归的过程,假设数组的元素个数是n。
时间复杂度是T(n)的函数: T(n) = 2*T(n/2) + O(n)
怎么解这个公式呢?
对于规模为n的问题,一共要进行log(n)层的大小切分;
每一层的合并复杂度都是O(n);
所以整体的复杂度就是O(nlogn)。
空间复杂度: O(n)
由于合并n个元素需要分配一个大小为n的额外数组,合并完成之后,这个数组的空间就会被释放。
void merge(vector<int>& nums, vector<int>& tmp, int l, int r) {
if (l >= r) return;
int m = l + (r - l) / 2;
// 不断进行拆分
merge(nums, tmp, l, m);
merge(nums, tmp, m + 1, r);
// 拆分完后开始进行排序
int i = l, j = m + 1;
for (int k = l; k <= r; k++) tmp[k] = nums[k];
for (int k = l; k <= r; k++) {
if (i == m + 1) nums[k] = tmp[j++];
// 下面可以选择是降序还是升序排序
else if (j == r + 1 || tmp[i] <= tmp[j]) nums[k] = tmp[i++];
else nums[k] = tmp[j++];
}
}
void mergerSort(vector<int>& arr){
int n = arr.size();
vector<int> tmp(n);
merge(arr, tmp, 0, n-1);
}
剑指 Offer 51. 数组中的逆序对
力扣链接
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
解法1:归并排序
思路:
这道题能够用归并排序做的原因是,在merge过程中可以计算出左右两个有序子数组的逆序对数。 而且,“排序”虽然修改了原数组的元素顺序,但是修改前已经统计了在排序前所有可能的逆序对数,所以不会产生错误。
本题的解法,只需要在归并排序判断nums[pos1] > nums[pos2]处加上一行统计代码即可(ret += (mid - pos1 + 1);)。为什么是这样统计的呢?假如有两个子数组:
1 6 8 9
1 3 4 5
在pos1指向6,pos2指向3时,按照归并排序思想要将3放入tmp数组。此时,以3结尾的逆序对为(6,3), (8, 3), (9, 3)共三个,计算方法为3 - 1 + 1 = 3(mid = 3, pos1 = 1)。
其余代码部分和归并排序代码并无二致。
代码:
class Solution {
public:
int cnt = 0;
int reversePairs(vector<int>& nums) {
mergeSort(nums);
return cnt;
}
void mergeSort(vector<int>& arr){
int n = arr.size();
vector<int> tmp(n);
merge(arr, tmp, 0, n-1);
}
void merge(vector<int>& arr, vector<int>& tmp, int l, int r){
if(l >= r) return;
int mid = l + (r-l)/2;
//分
merge(arr,tmp,l,mid);
merge(arr,tmp,mid+1,r);
//合并
int i = l, j = mid+1;
for(int k = l;k<=r;k++) tmp[k] = arr[k];
for(int k = l;k<=r;k++){
if(i == mid+1) arr[k] = tmp[j++];
//下面可以选择是降序还是升序
else if(j == r+1 || tmp[i] <= tmp[j]) arr[k] = tmp[i++];
else{
arr[k] = tmp[j++];
cnt += mid - i + 1;
}
}
}
};
解法2:桶排序
class Solution {
public:
string frequencySort(string s) {
unordered_map<char,int> umap;
int maxFre = 0;
int n = s.size();
for(char& c:s){
umap[c]++;
maxFre = max(maxFre,umap[c]);
}
vector<string> buckets(maxFre+1);
for(auto& [ch,cnt]:umap){
buckets[cnt].push_back(ch);
}
string res;
for(int i = maxFre;i>0;i--){
string bucket = buckets[i];
for(auto& ch:bucket){
for(int k = 0;k<i;k++){
res.push_back(ch);
}
}
}
return res;
}
};
969. 煎饼排序
力扣链接
给你一个整数数组 arr ,请使用 煎饼翻转 完成对数组的排序。
一次煎饼翻转的执行过程如下:
选择一个整数 k ,1 <= k <= arr.length
反转子数组 arr[0…k-1](下标从 0 开始)
例如,arr = [3,2,1,4] ,选择 k = 3 进行一次煎饼翻转,反转子数组 [3,2,1] ,得到 arr = [1,2,3,4] 。
以数组形式返回能使 arr 有序的煎饼翻转操作所对应的 k 值序列。任何将数组排序且翻转次数在 10 * arr.length 范围内的有效答案都将被判断为正确。
示例 1:
输入:[3,2,4,1]
输出:[4,2,4,3]
解释:
我们执行 4 次煎饼翻转,k 值分别为 4,2,4,和 3。
初始状态 arr = [3, 2, 4, 1]
第一次翻转后(k = 4):arr = [1, 4, 2, 3]
第二次翻转后(k = 2):arr = [4, 1, 2, 3]
第三次翻转后(k = 4):arr = [3, 2, 1, 4]
第四次翻转后(k = 3):arr = [1, 2, 3, 4],此时已完成排序。
示例 2:
输入:[1,2,3]
输出:[]
解释:
输入已经排序,因此不需要翻转任何内容。
请注意,其他可能的答案,如 [3,3] ,也将被判断为正确。
提示:
1 <= arr.length <= 100
1 <= arr[i] <= arr.length
arr 中的所有整数互不相同(即,arr 是从 1 到 arr.length 整数的一个排列)
解法1:类选择排序
思路:
设一个元素的下标是 index,我们可以通过两次煎饼排序将它放到尾部:
-
第一步选择k=index+1,然后反转子数组 arr[0…k−1],此时该元素已经被放到首部。
-
第二步选择k=n,其中 n 是数组 arr 的长度,然后反转整个数组,此时该元素已经被放到尾部。
通过以上两步操作,我们可以将当前数组的最大值放到尾部,然后将去掉尾部元素的数组作为新的处理对象,重复以上操作,直到处理对象的长度等于一,此时原数组已经完成排序,且需要的总操作数是2×(n−1),符合题目要求。如果最大值已经在尾部,我们可以省略对应的操作。
代码:
class Solution {
public:
vector<int> pancakeSort(vector<int>& arr) {
vector<int> result;
int n = arr.size();
for(int i = 0;i<n;i++){
int index = max_element(arr.begin(),arr.end()-i) - arr.begin();
if(index == n - i - 1) continue;//最大值已经在和合适的位置
reverse(arr.begin(),arr.begin()+index+1);
reverse(arr.begin(),arr.end()-i);
result.push_back(index+1);
result.push_back(arr.end()-i-arr.begin());
}
return result;
}
};
桶排序
快速排序
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序
912. 排序数组
力扣链接
给你一个整数数组 nums,请你将该数组升序排列。
示例 1:
输入:nums = [5,2,3,1]
输出:[1,2,3,5]
示例 2:
输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]
提示:
1 <= nums.length <= 5 * 104
-5 * 104 <= nums[i] <= 5 * 104
解法1:快速排序(超时)
class Solution {
public:
void qSort(vector<int>& nums, int l, int r){
int p;
if(l < r){
p = partition(nums,l,r);
qSort(nums,l,p-1);
qSort(nums,p+1,r);
}
}
int partition(vector<int>& nums, int l, int r){
int p = nums[l];
while(l<r){
while(l < r && nums[r] >= p) r--;
swap(nums[l],nums[r]);
while(l < r && nums[l] < p) l++;
swap(nums[l], nums[r]);
}
return l;
}
vector<int> sortArray(vector<int>& nums) {
//快速排序
qSort(nums, 0, nums.size()-1);
return nums;
}
};
解法2:快速排序(随机优化)
class Solution {
public:
void qSort(vector<int>& nums, int l, int r){
int p;
if(l < r){
p = partition(nums,l,r);
qSort(nums,l,p-1);
qSort(nums,p+1,r);
}
}
int partition(vector<int>& nums, int l, int r){//交换数组中子表的记录,使枢轴记录到位,并返回其所在位置
int i = rand()%(r-l+1) + l;
swap(nums[l],nums[i]);
int p = nums[l];
while(l<r){//从表的两端交替向中间扫描
while(l < r && nums[r] >= p) r--;
swap(nums[l],nums[r]);//比枢纽小的交换到低端
while(l < r && nums[l] < p) l++;
swap(nums[l], nums[r]);
}
return l;
}
vector<int> sortArray(vector<int>& nums) {
//快速排序
qSort(nums, 0, nums.size()-1);
return nums;
}
};
23. 合并K个升序链表
likou
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i] 按 升序 排列
lists[i].length 的总和不超过 10^4
解法1:分治
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size() == 0) return nullptr;
return helper(lists,0,lists.size()-1);
}
ListNode* helper(vector<ListNode*>& lists, int start, int end){
if(start == end) return lists[start];
int mid = start + (end-start) / 2;
ListNode* left = helper(lists,start,mid);
ListNode* right = helper(lists,mid+1,end);
return mergeTwoLists(left, right);
}
ListNode* mergeTwoLists(ListNode* a, ListNode* b){//合并
if(a == nullptr) return b;
else if (b == nullptr) return a;
else if (a->val < b->val){
a->next = mergeTwoLists(a->next, b);
return a;
}else{
b->next = mergeTwoLists(a,b->next);
return b;
}
}
};
解法2:两两合并
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size() == 0) return nullptr;
//两两合并
ListNode* result = lists[0];
for(int i = 1;i<lists.size();i++){
result = mergeTwoLists(result,lists[i]);
}
return result;
}
ListNode* mergeTwoLists(ListNode* a, ListNode* b){
if(a == nullptr) return b;
else if (b == nullptr) return a;
else if(a->val < b->val){
a->next = mergeTwoLists(a->next,b);
return a;
}else{
b->next = mergeTwoLists(a,b->next);
return b;
}
}
};
解法3:最小堆
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
struct Node{
int val;
ListNode* ptr;
bool operator < (const Node& r) const{
return val > r.val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<Node> que;
ListNode* head = new ListNode;
ListNode* pre = head;
//这里跟上一版不一样,不再是一股脑全部放到堆中
//而是只把k个链表的第一个节点放入到堆中
for(int i = 0;i<lists.size();i++){
if (lists[i] != nullptr) que.push({lists[i]->val,lists[i]});
}
//之后不断从堆中取出节点,如果这个节点还有下一个节点,
//就将下个节点也放入堆中
while(!que.empty()){
pre->next = que.top().ptr;
que.pop();
pre = pre->next;
if(pre->next != nullptr) que.push({pre->next->val,pre->next});
}
pre->next = nullptr;
return head->next;
}
};
215. 数组中的第K个最大元素
力扣链接
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
提示:
1 <= k <= nums.length <= 104
-104 <= nums[i] <= 104
解法1:快排
class Solution {
public:
//快排思想
int partition(vector<int>& nums, int l, int r){
int i = rand() % (r-l+1) + l;
swap(nums[i],nums[l]);
int p = nums[l];
while(l < r){
while(l < r && nums[r] >= p ) r--;
swap(nums[r],nums[l]);
while(l < r && nums[l] < p ) l++;
swap(nums[r],nums[l]);
}
return l;
}
int qSort(vector<int>& nums, int l, int r, int k){
int p = partition(nums, l, r);
if(p == nums.size() - k) return nums[p];
else if(p < nums.size() - k) return qSort(nums, p+1, r, k);
else return qSort(nums, l, p-1, k);
}
int findKthLargest(vector<int>& nums, int k) {
return qSort(nums, 0, nums.size() - 1, k);
}
};
解法2:最小堆
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
//小顶堆
priority_queue<int,vector<int>,greater<int>> pq;
for(int i = 0;i<k;i++){
pq.push(nums[i]);
}
for(int i = k;i<nums.size();i++){
if(nums[i] > pq.top()){
pq.pop();
pq.push(nums[i]);
}
}
return pq.top();
}
};
class Solution {
public:
void heapAdjust(vector<int>& arr,int i, int heapSize){
int l = i*2+1, r = i*2+2,largest = i;
if(l < heapSize && arr[l] > arr[largest]) largest = l;
if(r < heapSize && arr[r] > arr[largest]) largest = r;
if(largest != i){
swap(arr[i],arr[largest]);
heapAdjust(arr,largest, heapSize);
}
}
void buildHeap(vector<int>& arr, int heapSize){
for(int i = heapSize/2;i>=0;i--){
heapAdjust(arr, i, heapSize);
}
}
int findKthLargest(vector<int>& nums, int k) {
int heapSize = nums.size();
buildHeap(nums,heapSize);
for(int i = nums.size() - 1;i>=nums.size()-k+1;i--){
swap(nums[0],nums[i]);
--heapSize;
heapAdjust(nums, 0, heapSize);
}
return nums[0];
}
};