基于比较的排序
通过比较大小来决定元素间的相对次序
可以证明时间复杂度下界为O(NlogN)——不可能突破这个复杂度达到更快
非比较类排序
不通过比较大小来决定元素间的相对次序
时间复杂度受元素的范围以及分布等多种因素影响,不单纯取决于元素数量N
选择排序(Selection Sort)——“该放哪个数了?“
每次从未排序数据中找最小值,放到已排序序列的末尾
插入排序(Insertion Sort)——“这个数该放哪儿?”
从前到后依次考虑每个未排序数据,在已排序序列中找到合适位置插入
冒泡排序(Bubble Sort)
不断循环扫描,每次查看相邻的元素,如果逆序,则交换
平均时间复杂度均为O(N2)
堆排序(Heap Sort)是对选择排序的优化——利用二叉堆高效地选出最小值
建立一个包含所有N个元素的二叉堆
重复N次从堆中取出最小值,即可得到有序序列
时间复杂度O(NlogN)
void heap_sort(vector<int>& a, int n) {
priority_queue<int> q;
for(int i = 0; i < n; i++) {
q.push(-a[i]);
}
for(int i = 0; i < n; i++) {
a[i] = -q.top();
q.pop();
}
}
希尔排序(Shell Sort)是对插入排序的优化——增量分组插入排序
希尔排序的时间复杂度取决于增量序列(步长序列)的选取目前已知的最好序列可以做到o(N4/3)或o(Nlog2N)
归并排序(Merge Sort)是一个基于分治的算法
时间复杂度O(NlogN)
原问题:把数组排序
子问题:把数组前一半、后一半分别排序
然后再合并左右两半(两个有序数组)就可以了
public static void mergeSort(int[] arr, int l, int r) { // sort arr[l..r]
if (l >= r) return;
int mid = (l + r) >> 1; // (l + r) / 2
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1]; //
int i = left, j = mid + 1;
for (int k = 0; k < temp.length; k++) { //
if (j > right || (i <= mid && arr[i] <= arr[j]))
temp[k] = arr[i++];
else
temp[k] = arr[j++];
}
for (int k = 0; k < temp.length; k++) { //
arr[left + k] = temp[k];
}
}
快速排序(Quick Sort)也是一个基于分治的算法
快速排序和归并排序具有相似性,但步骤顺序相反
随机选取pivot,期望时间复杂度O(NlogN)
快速排序可以通过适当的交换来原地实现数组调配,避免占用额外空间最经典和高效的调配方式叫作 Hoare Partition
public static void quickSort(int[] arr, int l, int r) {
if (l >= r) return;
int pivot = partition(arr, l, r);
quickSort(arr, l, pivot);
quickSort(arr, pivot + 1, r);
}
static int partition(int[] a, int l, int r) {
int pivot = l + (int)(Math.random() * (r - l + 1));
int pivotVal = a[pivot];
while (l <= r) {
while (a[l] < pivotVal) l++;
while (a[r] > pivotVal) r--;
if (l == r) break;
if (l < r) {
int temp = a[l]; a[l] = a[r]; a[r] = temp;
l++;
r--;
}
}
return r;
}
void quickSort(vector<int>& arr, int l, int r) {
if (l >= r) return;
int pivot = partition(arr, l, r);
quickSort(arr, l, pivot);
quickSort(arr, pivot + 1, r);
}
int partition(vector<int>& a, int l, int r) {
int pivot = l + rand() % (r - l + 1);
int pivotVal = a[pivot];
while (l <= r) {
while (a[l] < pivotVal) l++;
while (a[r] > pivotVal) r--;
if (l == r ) break;
if (l < r) {
swap(a[l++] ,a[r--]);
}
}
return r;
}
计数排序(Counting Sort)
计数排序要求输入的数据必须是有确定范围的整数。将输入的数据作为key存储在额外的数组中,然后依次把计数大于1的填充回原数组
时间复杂度O(N+M),N为元素个数,M为数值范围
桶排序( Bucket Sort)
桶排序假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能使用别的排序算法,或是以递归方式继续使用桶排序)
时间复杂度O(N)~o(N^2)
基数排序(Radix Sort)
基数排序把数据切割成一位位数字(O-9),从低位到高位对每一位分别进行计数排序时间复杂度O(NK),K为数字位数
对于序列中存在的若干个关键字相等的元素
如果排序前后它们的相对次序一定保持不变,就称排序算法是稳定的否则就称排序算法是不稳定的
插入、冒泡、归并、计数、基数和桶排序是稳定的
选择、希尔、快速、堆排序是不稳定的
912.排序数组
https://leetcode.cn/problems/sort-an-array/
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
heap_sort(nums,nums.size());
//quickSort(nums ,0 ,nums.size() - 1);
//sort(nums.begin(),nums.end());
return nums;
}
void heap_sort(vector<int>& a, int n) {
priority_queue<int> q;
for(int i = 0; i < n; i++) {
q.push(-a[i]);
}
for(int i = 0; i < n; i++) {
a[i] = -q.top();
q.pop();
}
}
void quickSort(vector<int>& arr, int l, int r) {
if (l >= r) return;
int pivot = partition(arr, l, r);
quickSort(arr, l, pivot);
quickSort(arr, pivot + 1, r);
}
int partition(vector<int>& a, int l, int r) {
int pivot = l + rand() % (r - l + 1);
int pivotVal = a[pivot];
while (l <= r) {
while (a[l] < pivotVal) l++;
while (a[r] > pivotVal) r--;
if (l == r ) break;
if (l < r) {
swap(a[l++] ,a[r--]);
}
}
return r;
}
};
class Solution {
public int[] sortArray(int[] nums) {
//mergeSort(nums,0 ,nums.length - 1);
quickSort(nums,0 ,nums.length - 1);
return nums;
}
// void heap_sort(int a[], int n) {
// priority_queue q;
// for(int i = 0; i < n; i++) {
// q.push(-a[i]);
// }
// for(int i = 0; i < n; i++) {
// a[i] = -q.top();
// q.pop();
// }
// }
public static void mergeSort(int[] arr, int l, int r) { // sort arr[l..r]
if (l >= r) return;
int mid = (l + r) >> 1; // (l + r) / 2
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1]; //
int i = left, j = mid + 1;
for (int k = 0; k < temp.length; k++) { //
if (j > right || (i <= mid && arr[i] <= arr[j]))
temp[k] = arr[i++];
else
temp[k] = arr[j++];
}
for (int k = 0; k < temp.length; k++) { //
arr[left + k] = temp[k];
}
}
public static void quickSort(int[] arr, int l, int r) {
if (l >= r) return;
int pivot = partition(arr, l, r);
quickSort(arr, l, pivot);
quickSort(arr, pivot + 1, r);
}
static int partition(int[] a, int l, int r) {
int pivot = l + (int)(Math.random() * (r - l + 1));
int pivotVal = a[pivot];
while (l <= r) {
while (a[l] < pivotVal) l++;
while (a[r] > pivotVal) r--;
if (l == r) break;
if (l < r) {
int temp = a[l]; a[l] = a[r]; a[r] = temp;
l++;
r--;
}
}
return r;
}
}
1122.数组的相对排序
https://leetcode.cn/problems/relative-sort-array/
比较类排序
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
unordered_map<int, int> arr2orders;
for (int i = 0;i < arr2.size(); i++) {
arr2orders[arr2[i]] = i;
}
sort(arr1.begin(), arr1.end(), [&](int x, int y) {
int xPos = arr2orders.find(x) != arr2orders.end() ? arr2orders[x] : arr2.size();
int yPos = arr2orders.find(y) != arr2orders.end() ? arr2orders[y] : arr2.size();
return xPos < yPos || (xPos == yPos && x < y);
});
return arr1;
}
};
非比较类排序计数排序
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
vector<int> count(1001, 0);
for (int val : arr1) {
count[val]++;
}
vector<int> ans;
for (int val : arr2) {
while (count[val] > 0) {
ans.push_back(val);
count[val]--;
}
}
for (int val = 0; val <= 1000; val++) {
while (count[val] > 0) {
ans.push_back(val);
count[val]--;
}
}
return ans;
}
};
56.合并区间
https://leetcode.cn/problems/merge-intervals/
方法一:对区间进行双关键字排序(左右端点)
然后扫描合并(排序后能合并的区间一定是连续的)
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(),
[](const vector<int>& a, const vector<int>& b) {
return a[0] < b[0] || (a[0] == b[0] && a[1] < b[1]);
});
vector<vector<int>> ans;
int farthest = -1;
int start = -1;
for(const vector<int>& intervals : intervals) {
int left = intervals[0];
int right = intervals[1];
if (left <= farthest) {
farthest = max(farthest,right);
}else {
if (farthest != -1) {
ans.push_back({start, farthest});
}
start = left;
farthest = right;
}
}
ans.push_back({start, farthest});
return ans;
}
};
方法二:差分思想、关键事件思想
把每个区间[lr]看作一次+1的覆盖,进一步转化为“l处+1”、“r+1处-1”两个事件
把2n个事件排序、扫描,用一个计数变量记录覆盖次数,0变1、1变0时就找到了合并后的区间端点
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
vector<pair<int, int>> events;
for (const vector<int>& interval : intervals) {
events.push_back({interval[0], 1});
events.push_back({interval[1] + 1, -1});
}
sort(events.begin(), events.end());
int covering = 0;
int start;
vector<vector<int>> ans;
for (const pair<int, int>& event : events) {
if (covering == 0) start = event.first;
covering += event.second;
if (covering == 0) {
ans.push_back({start, event.first - 1});
}
}
return ans;
}
};
215.数组中的第K个最大元素
https://leetcode.cn/problems/kth-largest-element-in-an-array/
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
// sort(nums.begin(), nums.end());
// return nums[nums.size() - k];
return quickSort(nums, 0, nums.size() - 1, nums.size() - k);
}
private:
int quickSort(vector<int>& arr, int l, int r, int index) {
if (l >= r) return arr[l];
int pivot = partition(arr, l, r);
if (index <= pivot) return quickSort(arr, l, pivot, index);
else return quickSort(arr, pivot + 1, r, index);
}
int partition(vector<int>& a, int l, int r) {
int pivot = l + rand() % (r - l + 1);
int pivotVal = a[pivot];
while (l <= r) {
while (a[l] < pivotVal) l++;
while (a[r] > pivotVal) r--;
if (l == r) break;
if (l < r) {
swap(a[l++], a[r--]);
}
}
return r;
}
};
104.货仓选址
https://www.acwing.com/problem/content/description/106/
在一条数轴上有N家商店,它们的坐标分别为A~An。
现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。
为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。1 ≤N≤ 100000
#include
using namespace std;
int a[100005];
int main() {
int n;
long long ans = 0;
cin >> n;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
sort(a, a + n);
int pos = a[n / 2];
for (int i = 0; i< n; i++) ans += abs(pos - a[i]);
cout << ans << endl;
}
493.翻转对
https://leetcode.cn/problems/reverse-pairs/
class Solution {
public int reversePairs(int[] nums) {
ans = 0;
mergeSort(nums, 0, nums.length - 1);
return ans;
}
void mergeSort(int[] arr, int l, int r) { // sort arr[l..r]
if (l >= r) return;
int mid = (l + r) >> 1; // (l + r) / 2
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
calculate(arr, l, mid ,r);
merge(arr, l, mid, r);
}
void calculate(int[] arr, int left, int mid, int right) {
int j = mid;
for (int i = left; i <= mid; i++) {
while (j < right && arr[i] > 2l * arr[j + 1]) j++;
ans += j - mid;
}
}
void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1]; //
int i = left, j = mid + 1;
for (int k = 0; k < temp.length; k++) { //
if (j > right || (i <= mid && arr[i] <= arr[j]))
temp[k] = arr[i++];
else
temp[k] = arr[j++];
}
for (int k = 0; k < temp.length; k++) { //
arr[left + k] = temp[k];
}
}
int ans;
}
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习