知道了很多的排序算法也许不一定有一个全局的把握,为了在应用场景做出更耗的选择,在此对各种排序算法做一些总结。
排序分为比较排序和非比较排序,比较排序一般是插入排序,选择排序,冒泡排序,快速排序,还有反应分而治之的快速排序和归并排序,比较排序的下界是O(nlog(n))。 而非比较排序时间复杂度可以低至O(n),比如基数排序,计数排序,桶排序,这些排序对数据有特殊的要求。
另一方面我们还要关心排序的稳定性,通常不稳定排序算法是:
选择排序O(n^2)
快速排序 O(nlogn)平均时间,O(n^2)是最坏情况
堆排序 O(nlogn)
希尔排序 O(nlogn)
基数排序 O(n-k)
稳定性的排序有
插入排序 O(n^2)
冒泡排序 O(n^2)
归并排序 O(nlogn)需要O(n)额外存储空间
计数排序 O(n+K),空间也是O(n+k)
桶排序 O(n) 需要额外O(k)时间
接下来我们介绍这十大排序算法:
冒泡排序
冒泡排序的思路是,每一次从起始位置向右,两两比较,如果不符合顺序,交换位置,每次冒泡都会在尾部差生一个当前最大的数,冒泡结束位置一次靠前一个,以此类推。
这是一种简单的排序,中间有很多的交换和比较工作。小数据集简单实用,这个实现是从后往前冒泡的,不过思路没有差异:
void bubleSort(vector<int> & data){
for(int i =0 ; i< data.size() ; i++)
for(int j = data.size() -1 ; j>i ; j--)
if(data[j]<data[j-1])
{
int temp = data[j-1];
data[j-1] = data[j];
data[j] = temp;
}
}
冒泡排序其实可以从两方面优化
*1 当某一轮两辆比较中没有任何元素交换,算法结束
*2 某一轮中结束位置是j ,该最后一次交换到j之间是有序的,,下一轮结束点可以跳过有序段。
优化代码略
鸡尾酒排序
这种排序是对冒泡排序的改进,也叫双向冒泡排序,第一次从左到右冒泡产生右边最大值,第二次反向产生左边最小值,若干次来回后元素边得有序。与冒泡排序相比区别在于,双向冒泡从高到低从低到高比较,效能要好一些。
代码:
void swap(vector<int> & data, int i, int j){
int temp = data[i];
data[i] =data[j];
data[j] = temp;
}
void WhickySort(vector<int> &data ){
int begin = 0 , end = data.size() -1 ;
while(begin < end)
{
int i = begin;
while(i <end)
{
if(data[i]>data[i+1])
swap(data , i , i+1);
i++;
}
i = --end ;
while(i > begin)
{
if(data[i] < data[i-1])
swap(data , i, i-1);
i--;
}
begin++;
}
}
插入排序
插入排序思想很重要,首先假定前面一部分已经排好序,当下一个元素插进来时候作调整,找到合适的位置插入即可,依然有序,这个有序保持到最后就是排序的结果,这种假定可以解决很多的编程难题。
void insertSort(vector<int> & data){
for(int i =0 ; i < data.size() ; i++)
{
int j = i;
int val = data[i];
while(j>0&&data[j-1]>val)
{
data[j]=data[j-1];
j--;
}
data[j] = val;
}
}
选择排序
选择排序基本思路是首先找出数组中最小元素与第一个元素交换,然后在找出剩下元素最小与第二个元素交换,以此类推。
是一种插入排序的改进,每次新的当前剩余元素最小元素只需要一次交换即可,而不需要插入排序那样一个一个移动元素腾出地方。
void selectSort(vector<int> & data)
{
for(int i =0 ; i< data.size() ; i++)
{
int mini = i ;
for(int j = i ; j < data.size() ; j++)
mini = data[mini]<data[j]?mini:j;
int temp =data[mini];
data[mini] = data[i];
data[i] = temp;
}
}
快速排序
快速排序的思想是分而治之,首先找出一个基准数,此基准数将数组一分为三部分,比基准数笑的在左边,基准数中间,比基准数大的放右边。再对左右两边的部分递归调用,快速排序是种表现良好的方法,比较常用
void quickSort(vector<int> &data , int left , int right){
if(left >= right)return;
int i = left, j = right;
int pivot = data[left];
while(i<j)
{
while(i<j&&data[j]>=pivot)
j--;
data[i] = data[j];
while(i<j&&data[i]<=pivot)
i++;
data[j] = data[i];
}
data[i] = pivot;
quickSort(data,left,i);
quickSort(data,i+1,right);
}
堆排序
大家都知道topk的问题,荡数据量很大的时候会得到一个O(k*logK)*N的解决方案。堆排序的基本思想就是将构建或者调整成堆结构,然后从堆中弹出元素以获得顺序。
首先什么是堆,一般我们用的是二叉堆,以最小堆为例,它首先是一棵完全二叉树,任意一个非叶子节点的左右孩子节点都比它大。空树也是堆,单独一个节点也是堆。我们可以构建一个堆的类。
给定一个数组如何把它调整成一个堆呢(堆用数组存储),首先我们每个叶子节点都是堆,我们只需要调整非叶子节点,按照从右往左,从下层到上层的顺序一次往下调整即可。
当插入一个元素先插入到完全二叉树最后一个非叶子节点第一个空节点,然后向上调整。
当删除一个元素时候,取出根节点元素,将最后一个元素防到根节点,向下调整。
ok,这是heap的类:
struct Heap{
Heap(vector<int> & v){
data = v;
int sz = data.size()-1;
for(int i= (sz-1)/2 ; i>=0 ; i--)
heapDown(i);
}
void push(int e){
data.push_back(e);
heapUp(data.size()-1);
}
void swap(int i ,int j){
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
int pop(){
if(data.empty()){
cout <<"try to pop from an empty heap"<<endl;
return -1;
}
int e= data[0];
data[0] = data[data.size()-1];
data.resize(data.size()-1);
//: cout <<"data size = "<<data.size()<<endl;
heapDown(0);
return e;
}
void heapUp(int i){
int last = i ;
while(last != 0 && data[(last-1)/2] > data[last])
{
swap((last-1)/2 , last);
last = (last-1)/2;
}
}
void heapDown(int i){
while(i < data.size())
{
int left = 2*i + 1, right = 2*i + 2;
int mIdex = left < data.size()?(
right < data.size()?
data[right]>data[left]?left:right : left
): right;
if(mIdex < data.size()&&data[mIdex]<data[i])
swap(mIdex , i);
else break;
i = mIdex;
}
}
void PopPrint(){
int siz = data.size();
for(int i =0 ; i<siz ;i++)
if(i!=0)cout <<" "<< pop();
else cout <<pop();
cout <<endl;
}
vector<int> data;
};
归并排序
归并排序也是分而治之的思想,手下将数组分为左右两部分分别排序,然后两部分merge起来。 左右两部分的排序过程可以递归调用。
void merge(vector<int> &data , int left , int mid , int right){
vector<int> tv;
int i = left , j = mid+1;
while(i<=mid&&j<=right)
{
if(data[i] < data[j]){tv.push_back(data[i]);i++;}
else {tv.push_back(data[j]);j++;}
}
while(i<=mid)tv.push_back(data[i++]);
while(j<=right)tv.push_back(data[j++]);
for(int i = left; i<=right ; i++)data[i]=tv[i-left];
}
void mergeSort(vector<int> &data , int left, int right){
if(left>=right)return;
int mid = (left + right)/2;
mergeSort(data , left, mid);
mergeSort(data , mid+1,right);
merge(data , left , mid , right);
}
希尔排序
这是插入排序的一种改进,针对插入排序数次移动次数较多的问题,希尔排序第一次先从指定步长的子序列作插入排序,依次递减步长,知道最后一次步长为1的排序时数组已经基本有序。
void shellSort(vector<int> & data ){
// gap
int sz = data.size();
for(int gap = sz/2 ; gap>=1 ; gap= gap/2)
for(int i = 0 ; i < gap ; i++)
for(int j = i ; j < sz ; j = j + gap)
{
int k = j;
int val = data[k];
while(k-gap >=0&&data[k-gap]>val)
{
data[k]=data[k-gap];
k = k - gap;
}
data[k] = val;
}
}
计数排序
如果要排序的数可以作为或者调整后可以作为计数数组的下标。只是需要很多的额外的空间的限制。
void countSort(vector<int> &data ){
if(data.empty()) return ;
int maxV = data[0] , minV = data[0];
for(auto i : data)
{
maxV = maxV < i?i:maxV;
minV = minV > i?i:minV;
}
int delt = 0 - minV;
vector<int> Count(maxV - minV +1 , 0);
int k =0;
for(auto i : data)
Count[i+delt]++;
for(int i =0 ; i< maxV - minV +1 ; i++)
for(int j = 0 ; j< Count[i] ; j++)
data[k++] = i-delt;
}
基数排序
依次以低位到高位作为key值对数组进行调整。
int maxDigits(vector<int> & data){
int maxV = data[0];
for(auto i : data) maxV = maxV > i ?maxV: i;
int count =0;
while(maxV!=0)
{
count++;
maxV /=10;
}
return count;
}
int getbit(int val , int base){
return (val%base - val%(base/10))/(base/10);
}
void BSort(vector<int> & data ){
int n = maxDigits(data);
vector<vector<int>> boxes(10,vector<int>());
int base = 10;
while((n--)>0)
{
for(int i =0 ; i< boxes.size() ; i++)boxes[i].clear();
for(auto i : data){
boxes[getbit(i,base)].push_back(i);
}
cout <<"after establish box"<<endl;
int k =0;
for(auto &i: boxes)
for(auto j : i)
data[k++] = j;
base *= 10;
}
}
桶排序
假设有一个长度为n的待排自序列K[1…n],首先将这个序列划分为M个子区间,然后将待排序列的关键字k映射到第i个桶,接下来堆
每个桶进行比较排序,然后依次枚举出B[0] B[1] …B[k]元素即是一个有序的序列。实现中每个桶调用了快排序。
void bucketSort(vector<int> &data, int num){
if(data.empty()) return ;
int maxV = data[0], minV = data[0];
for(auto i : data)
{
maxV = maxV > i ? maxV : i;
minV = minV < i ? minV : i;
}
int delt = 0 - minV ;
int range = maxV - minV +1;
auto bnum = range/num+1;
vector<vector<int>> buckets(num , vector<int>());
for(auto i =0 ; i< data.size() ; i++)
buckets[(data[i]+delt)/bnum].push_back(data[i]);
for(auto i =0 ; i< buckets.size() ; i++)
quickSort(buckets[i], 0 , buckets[i].size()-1);
int k =0 ;
for(auto i=0 ; i< buckets.size() ; i++)
for(auto j =0 ; j<buckets[i].size() ; j++)
data[k++] = buckets[i][j];
}