排序算法
十大排序算法视频教程:https://www.bilibili.com/video/BV1Ur4y1w7tv
排序算法时间、空间复杂度总结(n是数据规模,k是”桶“的个数,d是最大的位数)
名称 | 平均 | 最好 | 最差 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 (Bubble Sort) | O(n2) | O(n) | O(n2) | O(1) | 稳定 |
选择排序 (Selection Sort) | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
插入排序 (Insertion Sort) | O(n2) | O(n) | O(n2) | O(1) | 稳定 |
希尔排序 (Shell Sort) | O(n4/3) | O(n) | O(n4/3)~O(n2) | O(1) | 不稳定 |
快速排序 (Quick Sort) | O(nlogn) | O(nlogn) | O(n2) | O(nlogn) | 不稳定 |
归并排序 (Merge Sort) | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
堆排序 (Heap Sort) | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
计数排序 (Counting Sort) | O(n+k) | O(n+k) | O(n+k) | O(n+k) | 稳定 |
桶排序 (Bucket Sort) | O(n+k) | O(n+k) | O(n+k) | O(n+k) | 稳定 |
基数排序 (Radix Sort) | O(d*(n+k)) | O(d*(n+k)) | O(d*(n+k)) | O(n+k) | 稳定 |
1 冒泡排序
如果前一个数比后一个数大,就交换两个数!
vector<int> bubbleSort(const vector<int> &nums) {
auto n = nums.size();
vector<int> result = nums;
for (int i = n - 1; i > 0; --i) {
for (int j = 0; j < i; ++j) {
if (result[j] > result[j + 1])
swap(result[j], result[j + 1]);
}
}
return result;
}
2 选择排序
每次查找出后面未排序的数组中最小的那个和最前面的交换!
vector<int> selectSort(const vector<int> &nums) {
auto n = nums.size();
vector<int> result = nums;
for (int i = 0; i < n - 1; ++i) {
int minIdx = i, minNum = result[i];
for (int j = i + 1; j < n; ++j) {
if (result[j] < minNum) {
minNum = result[j];
minIdx = j;
}
}
swap(result[i], result[minIdx]);
}
return result;
}
3 插入排序
将第n个数与前n-1个有序的数做比较,一直交换,直到将这个数插入到合适的位置!
vector<int> insertSort(const vector<int> &nums) {
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
auto n = nums.size();
vector<int> result = nums;
for (int i = 1; i < n; ++i) {
for (int j = i; j > 0; --j) {
if (result[j] < result[j - 1])
swap(result[j], result[j - 1]);
}
}
return result;
}
4 希尔排序
将数据分组,每隔step作为一组,step每论除以2,直到step==1。分组后的数据使用插入排序!
vector<int> shellSort(const vector<int> &nums) {
auto n = nums.size();
vector<int> result = nums;
for (int step = n / 2; step > 0; step /= 2) {
for (int group = 0; group < step; ++group) { // 按照步长进行分组
for (int i = group + step; i < n; i += step) { // 对每一组进行插入排序
for (int j = i; j >= step; j -= step) {
if (result[j] < result[j - step])
swap(result[j], result[j - step]);
}
}
}
}
return result;
}
5 快速排序
1)先从数列中取出一个元素作为基准数。2)扫描数列,将比基准数小的元素全部放到它的左边,大于或等于基准数的元素全部放到它的右边,得到左右两个区间。3)再对左右区间重复第二步.直到各区间少于两个元素。
// 递归方法1 https://blog.csdn.net/weixin_44915226/article/details/119535259
void quicksort1(vector<int> &nums, int start, int end) {
int left = start, right = end - 1;
if (left >= right)
return;
int base = nums[start];
while (left < right) {
while (nums[right] >= base && left < right) --right;
nums[left] = nums[right];
while (nums[left] <= base && left < right) ++left;
nums[right] = nums[left];
}
nums[left] = base;
quicksort1(nums, start, left);
quicksort1(nums, left + 1, end);
}
// 递归方法2 https://blog.csdn.net/weixin_44915226/article/details/119535259
void quicksort2(vector<int> &nums, int start, int end) {
int left = start, right = end - 1;
if (left >= right)
return;
while (left < right) {
while (nums[right] >= nums[start] && left < right) --right;
while (nums[left] <= nums[start] && left < right) ++left;
if (left < right) swap(nums[left], nums[right]);
}
swap(nums[left], nums[start]);
quicksort2(nums, start, left);
quicksort2(nums, left + 1, end);
}
// 快速排序
vector<int> quickSort(const vector<int> &nums) {
vector<int> result = nums;
quicksort1(result, 0, nums.size());
return result;
}
6 归并排序
归并排序是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。可以使用递归和循环实现。
// 递归函数
void _mergeSortR(vector<int> &nums, vector<int> &tempNums, int start, int end) {
if (start >= end) return;
// 分成左右两段然后分别排序
int mid = start + (end - start) / 2;
int start1 = start, end1 = mid;
int start2 = mid + 1, end2 = end;
_mergeSortR(nums, tempNums, start1, end1);
_mergeSortR(nums, tempNums, start2, end2);
// 将排序后的两个数组合并
int i = start;
while (start1 <= end1 && start2 <= end2)
tempNums[i++] = nums[start1] < nums[start2] ? nums[start1++] : nums[start2++];
while (start1 <= end1) tempNums[i++] = nums[start1++];
while (start2 <= end2) tempNums[i++] = nums[start2++];
for (i = start; i <= end; ++i) // 赋值给原数组
nums[i] = tempNums[i];
}
//归并排序,递归
vector<int> mergeSortR(const vector<int> &nums) {
auto n = nums.size();
vector<int> result = nums, tempNums = nums;
_mergeSortR(result, tempNums, 0, n - 1);
return result;
}
// 归并排序,循环
vector<int> mergeSortF(const vector<int> &nums) {
int n = nums.size();
vector<int> result = nums, tempNums = nums;
for (int seg = 1; seg < n; seg *= 2) { // 排序的趟数的循环
for (int start = 0; start < n; start += 2 * seg) { // 其中一段数组分成两段来排序
// 分成左右两段
int mid = min(start + seg, n);
int right = min(start + 2 * seg, n);
int start1 = start, end1 = mid;
int start2 = mid, end2 = right;
// 合并
int i = start;
while (start1 < end1 && start2 < end2)
tempNums[i++]=result[start1]<result[start2]?result[start1++]:result[start2++];
while (start1 < end1) tempNums[i++] = result[start1++];
while (start2 < end2) tempNums[i++] = result[start2++];
for (i = start; i < right; ++i)
result[i] = tempNums[i];
}
}
return result;
}
7 堆排序
堆排序的基本思想是:1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。
//堆排序,递归方法
void heapifyR(vector<int> &nums,int start,int end){
int father=start;
int child=father*2+1;
if(child>end) return;
if((child+1)<=end && (nums[child]<nums[child+1])) ++child;
if(nums[father]>nums[child]) return;
swap(nums[father],nums[child]);
heapifyR(nums,child,end);
}
//堆排序,循环方法
void heapifyF(vector<int> &nums,int start,int end){
int father=start;
int child=father*2+1;
while(child<end){
if((child+1)<=end && (nums[child]<nums[child+1])) ++child;
if(nums[father]>nums[child]) return;
swap(nums[father],nums[child]);
father=child;
child=father*2+1;
}
}
//堆排序
vector<int> heapSort(const vector<int> &nums){
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
int n = nums.size();
vector<int> result = nums;
// 第一次初始化大顶堆,从最后一个父节点开始调整
for (int i = (n-2)/2; i >= 0 ; --i)
heapifyR(result,i,n-1); // heapifyF(result,i,n-1);
// 把第一个元素与堆最后一个元素做交换,然后重新调整,知道排序完毕
for (int i = n-1; i > 0 ; --i){
swap(result[0],result[i]);
heapifyR(result,0,i-1); // heapifyF(result,0,i-1);
}
return result;
}
8 计数排序
计数排序是一个非基于比较的排序算法,它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(nlog(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(nlog(n)), 如归并排序,堆排序)
vector<int> countSort(const vector<int> &nums){
vector<int> result = nums;
int maxNum= result[0];
for (const auto& num:result) // 寻找数组中的最大值
maxNum = max(num, maxNum);
vector<int> countArr(maxNum+1,0); // 创建辅助数组
for (const auto& num:result) // 计数
++countArr[num];
int k=0;
for (int i = 0; i <= maxNum; ++i) // 重新排序
for (int j = 0; j < countArr[i]; ++j)
result[k++]=i;
return result;
}
9 桶排序
桶排序的基本思想是假设数据在[min,max]之间均匀分布,其中min、max分别指数据中的最小值和最大值。那么将区间[min,max]等分成n份,这n个区间便称为n个桶。将数据加入对应的桶中,然后每个桶内单独排序。由于桶之间有大小关系,因此可以从大到小(或从小到大)将桶中元素放入到数组中。
vector<int> bucketSort(const vector<int> &nums){
int n = nums.size();
vector<int> result = nums;
int minNum=result[0],maxNum=minNum;
for (const auto &num:result) {
minNum = min(minNum,num);
maxNum = max(maxNum,num);
}
double range = (double)(maxNum-minNum)/(n-1);
vector<vector<int>> buckets(n);
for (const auto &num:result) // 分桶装数据
buckets[static_cast<int>( (num-minNum)/range)].push_back(num);
for (auto &bucket:buckets) // 桶类排序
sort(bucket.begin(),bucket.end());
int i=0;
for (const auto &bucket:buckets)
for (const auto &num:bucket)
result[i++] = num;
return result;
}
10 基数排序
基数排序(Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。它是这样实现的: 将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零. 然后, 从最低位开始, 依次进行一次排序.这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
vector<int> radixSort(const vector<int> &nums) {
int n = nums.size();
vector<int> result = nums;
int maxNum = *max_element(result.begin(), result.end());
for (int digit = 1; maxNum / digit > 0; digit *= 10) {
vector<int> buckets(10, 0),tempArr(n,0);
for (const auto &num:result) ++buckets[num / digit % 10];
for (int i = 1; i < 10; ++i) buckets[i] += buckets[i - 1];
for (int i = n - 1; i >= 0; --i) {
int id=result[i]/digit%10;
tempArr[buckets[id]-1]=result[i];
--buckets[id];
}
result = tempArr;
}
return result;
}