排序算法效率比较
排序方法 | 平均时间复杂度 | 最坏情况复杂度 | 额外空间复杂度 | 稳定性 |
---|---|---|---|---|
基数排序 | O(D(N+R)) | O(D(N+R)) | O(N) | 稳定 |
归并排序 | O(N logN ) | O(N logN) | O(N) | 稳定 |
冒泡排序 | O( N 2 N^2 N2) | O( N 2 N^2 N2) | O(1) | 稳定 |
堆排序 | O(N logN) | O(N logN) | O(1) | 不稳定 |
快速排序 | O(N logN) | O( N 2 N^2 N2) | O(N logN) | 不稳定 |
-
排序算法的稳定性是指:在一组待排序记录中,如果存在任意两个相等的记录R和S,且待排记录R在S前,如果存在排序后R依然在S前,即它们的前后位置在排序前后不发生变化,则该排序算法是稳定的。(参照:《数据结构》(陈越))
-
基数排序的效率与基数的选择有关,其实际效果可以高于 O(N log N);
快速排序具体到实际的平均时间效率上,是最优的;最坏情况下,不如堆排和归并排序;
海量数据,用堆排。
1、基数排序
以 10 为基,次位优先的顺序依次调整数的顺序(个、十、百……),直到最大值所拥有的最高位。
每一趟中,按照当前的位,进桶,再统一收集……
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class Randix_sort{
public:
void sort(vector<int>&nums);
private:
int maxNum;
vector<vector<int> >buckets;
};
void Randix_sort::sort(vector<int>&nums)
{
if (nums.empty())
{
return ;
}
buckets.resize(10);
maxNum = *max_element(nums.begin(),nums.end());
int i = 1;
while(maxNum)
{
for (int j = 0;j<nums.size();j++)
{
if (nums[j]<0)
{
cout<<"can't sort the negative value"<<endl;
return;
}
int tmp = (nums[j]/i) % 10;
buckets[tmp].push_back(nums[j]);
}
nums.clear();
for (int k = 0;k<buckets.size();k++)
{
for (int kk=0;kk<buckets[k].size();kk++)
{
nums.push_back(buckets[k][kk]);
}
}
for (int j=0;j<10;j++)
{
buckets[j].clear();
}
maxNum /= 10;
i*=10;
}
}
int main()
{
vector<int>input{10,3,12,2,34};
Randix_sort r;
r.sort(input) ;
for (auto o : input)
{
cout<<o<<endl;
}
return 0;
}
2、快速排序
主元pivot的选择会影响效率。
这里将 A[Low]、A[High]、A[(low+high)/2]三者关键字的中值作为pivot,并调整顺序为“最左元<=主元<=最右元”,将主元放在倒数第二的位置上(因为最右元大于主元)
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
void Quick_Sort(vector<int>&v, int left, int right)
{
if (left < right)
{
// 调整主元顺序
int mid = (left + right) / 2;
if (v[left]>v[right])swap(v[left], v[right]);
if (v[left]>v[mid])swap(v[left], v[mid]);
if (v[mid]>v[right])swap(v[mid], v[right]);
int pivot = v[mid];
swap(v[mid], v[right - 1]); // 将主元放在倒数第二位
int i = left, j = right - 1;
while (1) { // 每一趟 的调整
if (i == right || j == left)break;
while (v[++i]<pivot);
while (v[--j]>pivot);
if (i<j)swap(v[i], v[j]);
else break;
}
swap(v[right - 1], v[i]); // 左边小于主元,右边大于主元;
Quick_Sort(v, left, i - 1);
Quick_Sort(v, i + 1, right);
}
}
int main()
{
vector<int>v = { 1,4,2,99,5,724,33 };
Quick_Sort(v, 0, v.size() - 1);
for (int i = 0; i<v.size(); i++)
{
cout << v[i] << " ";
}
getchar();
return 0;
}
如果是从大到小排序,注意,大于小于号的顺序,另外,特别注意,mid = (left+right+1)/2,每次交换的是主元和 r
void Quick_Sort(vector<int>&v, int left, int right)
{
if (left < right)
{
// 调整主元顺序
int mid = (left+right+1)/2; //!!!!!
// int mid;
// if (((left+right)%2)==0)
if (v[left]<v[right])swap(v[left],v[right]);
if (v[left]<v[mid])swap(v[left],v[mid]);
if (v[mid]<v[right])swap(v[mid],v[right]);
int pivot = v[mid];
// cout<<"pivot: "<<pivot<<" ";
swap(v[mid],v[left+1]);
int l = left+1;
//int l = left;
int r = right;
while(1)
{
if (l==right || r==left)break;
while( v[++l]>pivot);
while( v[--r]<pivot);
if (l<r)swap(v[l],v[r]);
else
{
// cout<<"yse"<<" ";
// cout<<" L: "<<l<<" R: "<<r<<" ";
break;
}
}
swap(v[r],v[left+1]);
//swap(v[r],v[mid]);
// cout<<"left: "<<left<<" right: "<<right<<" v[mid] "<<v[mid]<<" r: "<<r<<endl;
// for(int i=0;i<v.size();i++)cout<<v[i]<<" ";
// cout<<endl;
Quick_Sort(v, left, r - 1);
Quick_Sort(v, r + 1, right);
}
}
topK,找第K大的数,也可以用快排做,每次返回的是 中轴位置,根据位置,判读向左半边还是右半边找第K大。
时间复杂度是 O(N)??http://haoyuanliu.github.io/2016/12/18/Partition算法剖析/
题目:
https://leetcode.com/problems/kth-largest-element-in-an-array/submissions/
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int left = 0;
int right = nums.size()-1;
while (1)
{
int t = Partition(nums,left,right);
if (t == k-1)return nums[t];
else if (t>k-1)right = t-1;
else if (t<k-1)left = t+1;
}
}
int Partition(vector<int>&v,int left,int right)
{
//if (left<right){
if (left==right)return left;
int mid = (left+right+1)/2;
if (v[left]<v[right])swap(v[left],v[right]);
if (v[left]<v[mid])swap(v[left],v[mid]);
if (v[mid]<v[right])swap(v[mid],v[right]);
int pivot = v[mid];
swap(v[mid],v[left+1]);
int l = left+1;
int r = right;
while(l<=r)
{
if (l==right || r==left)break;
while(v[++l]>pivot);
while(v[--r]<pivot);
if (l<r)swap(v[l],v[r]);
else break;
}
swap(v[r],v[left+1]);
return r;
// }
// else
// return left;
// return r;
}
};
3、归并排序(递归与非递归)
递归算法从上往下考虑,假设左半部分和右半部分已经排序好,需要将这两部分merge ;
非递归算分从下往上考虑,从size = 1开始,size依次乘以2
#include <iostream>
#include <vector>
#include<iterator>
using namespace std;
void merge(vector<int>&tmp, vector<int>&v, int left, int right)
{
int mid = (left + right) / 2;
int k = left;
int r = mid + 1;
int i = k;
for (; k <= mid & r <= right;)
{
if (v[k]<v[r])
{
tmp[i++] = v[k++];
}
else
{
tmp[i++] = v[r++];
}
}
while (k <= mid)tmp[i++] = v[k++];
while (r <= right)tmp[i++] = v[r++];
for (int j = left; j <= right; j++)
{
v[j] = tmp[j];
}
//copy(v.begin() + left, v.begin() + right, back_inserter(tmp));
}
void merge_sort(vector<int>&tmp, vector<int>&v, int left, int right)
{
if (left <right)
{
int mid = (left + right) / 2;
merge_sort(tmp, v, left, mid);
merge_sort(tmp, v, mid + 1, right);
merge(tmp, v, left, right);
}
}
// 非递归算法
void merge_sor_no_recursion(vector<int>&v)
{
int n = v.size();
int l1, l2, h1, h2, k;
vector<int>tmp(n);
int size = 1;
for (; size<n; size *= 2) // 每一趟 归并
{
l1 = 0; //左部分的最小下标
k = 0;
while (l1 + size<n)
{
h1 = l1 + size - 1; // 左部分的最高下标
l2 = h1 + 1; // 右部分的最小下标
h2 = l2 + size - 1; // 右部分的最大下标
if (h2 >= n)h2 = n - 1;
int i = l1;
int j = l2;
while (i <= h1 && j <= h2)
{
if (v[i]<v[j])
{
tmp[k++] = v[i++];
}
else
{
tmp[k++] = v[j++];
}
}
while (i <= h1)tmp[k++] = v[i++];
while (j <= h2)tmp[k++] = v[j++];
l1 = h2 + 1;
}
while (k<n)tmp[k++] = v[l1++];
for (int i = 0; i<n; i++)v[i] = tmp[i]; // 一趟结束
printf("\nSize=%d \nElements are : ", size);
for (int i = 0; i<n; i++)
printf("%d ", v[i]);
}
}
int main()
{
vector<int>v = { 2,33,3,3,1,88,4,56,20 };
int n = v.size();
vector<int>tmp(n);
// merge_sort(tmp, v, 0, n - 1);
merge_sor_no_recursion(v);
cout << endl;
for (int i = 0; i<n; i++)
{
cout << v[i] << " ";
}
getchar();
return 0;
}
// reference: https://www.includehelp.com/ds/merge-sort-with-and-without-recursion-using-c-program.aspx
4、堆排序
如果要从小到大排,则建立一个最大堆,每次把最大堆堆顶的元素移到末尾,堆的数量减1,因此,不用额外的空间。
建堆和调整的核心代码一致。
下标[N/2 -1] 是最后一个有叶节点的节点。
#include<iostream>
#include<vector>
using namespace std;
void PercDown(vector<int>&v, int N, int p)
{
int parent = p;
int tmp = v[parent];
int child;
for (; (parent * 2 + 1)<N; parent = child) {
child = 2 * parent + 1;
if ((child + 1) < N && v[child]<v[child + 1])
{
child = child + 1;
}
if (tmp >= v[child])
{
break;
}
else {
v[parent] = v[child];
}
}
v[parent] = tmp;
}
int main()
{
vector<int>v = { 2,1,8,3,5 };
int N = v.size();
for (int k = N / 2 - 1; k >= 0; k--)
{
PercDown(v, N, k);
}
for (int i = N - 1; i >= 0; i--)
{
swap(v[i], v[0]);
PercDown(v, i, 0);
}
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
getchar();
return 0;
}
5、冒泡排序
冒泡排序要排 (N-1)趟,每一趟要把剩余要排序列的最大值排在最后。
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int>v = { 2,1,88,4,9,5 };
int N = v.size();
bool flag;
for (int i = 0; i<N - 1; i++) // 每次把最大的移到最后
{
flag = false;
for (int j = 0; j <N - i -1; j++) // 只对前 N - i 个进行比较
{
if (v[j]>v[j + 1])
{
swap(v[j], v[j + 1]);
flag = true;
}
}
if (flag == false)break;
}
for (int i = 0; i<N; i++)
{
cout << v[i] << " ";
}
getchar();
return 0;
}