八大排序

冒泡排序:
public  void  BubbleSort( int []  x )
{
        for ( int  i =1; i < x . length ; i ++)  //若数组长度为n,则执行n-1次“冒泡”即可(每次可找出剩余元素的最大值,剩下的一个为最小值)
       {
              for ( int  j =0; j < x . length - i ; j ++)  //执行第i次“冒泡”的时候在数组尾部已经有i-1个排好序的“最大值”
             {
                     if ( x [ j ]> x [ j +1])
                           swap( x , j , j +1);
             }
       }
}
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
快速排序:
算法流程举例说明:
[4,5,3,9,10,1],首次执行:start=0, end=5;使用首个元素4做标兵,小于4的放在最左边,大于4的放在最右边,最后[1,3,4,9,10,5],返回mid为4的索引2;
然后对start=0,end=2-1=1递归;
然后对start=2+1=3,end=5递归;
public  void  QuickSort( int []  x ,  int  start ,  int  end )
{
        if ( start >= end )
              return  ;
        int  mid =getMiddleIndex( x , start , end );      //mid左面的部分都小于等于x[mid],mid右面的部分都大于等于x[mid]
       QuickSort( x , start , mid -1);
       QuickSort( x , mid +1, end );
}
public  int  getMiddleIndex( int []  x ,  int  start ,  int  end )
{
        int  t = x [ start ];                        //选当前无序部分的第一个元素做标兵
        while ( start < end )
       {
              while ( start < end && x [ end ]>= t )
                     end --;
              x [ start ]= x [ end ];                 //x[end]是本次从右至左遍历找到的第一个小于t的值
              while ( start < end && x [ start ]<= t )
                     start ++;
              x [ end ]= x [ start ];                 //x[start]是本次从左至右遍历找到的第一个大于t的值
       }
        x [ start ]= t ;                           //循环结束后,对应本次函数处理的部分数组(假设索引从a到b),a到start-1为小于等于t的部分,start+1到b为大于等于                                                       
                                               t的部分
        return  start ;                          //此时start=end
}

void QuickSort(vector<int> data, int start,int end){
if(start==end) return;
int index=Partition(data,start,end);
if(index>start)
QuickSort(data,start,index-1);
if(index<end)
QuickSort(data,index+1,end);
}
int Partition(vector<int> data, int start, int end){
if(data==NULL || start<0 || end>=data.size())
return 0;
int index=start+rand()%(end-start+1);
swap(&data[index],&data[end]);
int small=start-1;
for(index=start;index<end;++index){
if(data[index]<data[end]){
++small;
if(small!=index)
swap(&data[index],&data[small]);
}
}
++small;
swap(&data[small],&data[end]);
return small;
}
时间复杂度:O(n*logn)
空间复杂度:O(n*logn)
稳定性:不稳定

简单选择排序:

算法流程:每次找到未排序部分的最小值的位置,和未排序部分的第一个元素交换位置。虽然交换了,但还是选择排序!!!
public  void  SimpleSelectSort( int []  x )
{
        for ( int  i =0; i < x . length -1; i ++)            //每次找到当前数组剩余元素中的最小值,和剩余部分元素的首元素交换。五个元素交换四次即可。
       {                                     //i指向当前未排序部分(即剩余部分)的首元素
              int  min = x [ i ];                     //先假定x[i]是x[i]到x[x.length-1]的最小值
              int  index = i ;
              for ( int  j = i +1; j < x . length ; j ++){    //从i+1开始直到数组最后找到最小值和它的index
                     if ( x [ j ]< min ){
                            min = x [ j ];
                            index = j ;
                    }
             }
             swap( x , i , index );
       }
}
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定 ([2,2,1],循环首次执行之后1与首元素,即第一个2交换,变成[1,2,2]。第一个二跑到了最后,在原来第二个2之后)

堆排序:
     关于堆的介绍参考: http://www.cnblogs.com/skywang12345/p/3610390.html

最大堆:父结点的值大于等于任意一个子节点的值;
最小堆:父结点的值小于等于任意一个子节点的值;

public  void  HeapSort( int []  x ){ //每次将0到i的元素建堆(只是把数组想作堆结构)后,找到堆顶的元素(即x[0])与i位置的元素交换。此时0到i-1为当前无序部分,i及之后都是有序部分。
        for ( int  i = x . length -1; i >=1; i --){ //使堆中所有的子节点都小于等于它的父结点
             BuildHeap( x , i );
             swap( x ,0, i );
       }
}
public  void  BuildHeap( int []  x ,  int  LastIndex ){ //从LastIndex到1位置的元素(不用管0位置的元素,因为它没有父结点,它是根)分别找到它们的父结点,如果父结点的值比它的全部子节点的值都大,则不变;否则将父结点的值与子节点中的较大值互换(可能存在当前父结点没有右子节点的情况)
        for ( int  i = LastIndex ; i >=1; i --){
              int  curParentNode =( i -1)/2;   //(当前结点下标-1)/2即为它的父结点的下标,无论左右结点都适用
              int  biggerSonNode =-1;
              if (2* curParentNode +2> LastIndex   //如果当前检测结点的父结点没有右子节点
                     biggerSonNode = i ;
              else
                     biggerSonNode = x [ curParentNode *2+1]>= x [ curParentNode *2+2]?( curParentNode *2+1):( curParentNode *2+2);
              if ( x [ curParentNode ]< x [ biggerSonNode ])
                    swap( x , curParentNode , biggerSonNode );
       }
}

void HeapSort(vector<int> num){
if(num.size()<=0) return;
for(int i=num.size()-1;i>=1;--i){
BuildHeap(num,i);
swap(num[0],num[i]);
}
}
void BuildHeap(vector<int> num, int Last){
for(int i=last;i>=1;--i){
int Parent=(i-1)/2;
int biggerSon=-1;
if(2*Parent+2>Last)
biggerSon=i;
else biggerSon=num[Parent*2+1]>=num[Parent*2+2]?Parent*2+1:Parent*2+2;
if(num[Parent]<num[biggerSon])
swap(num[Parent],num[biggerSon]);
}
}
时间复杂度:O(n*logn)
空间复杂度:O(1)
稳定性:不稳定

直接插入排序:
void StraightInsertSort(vector<int> num){
for(int i=1;i<num.size();++i){
int cur=num[i];
int j=i-1;
for(;j>=0 && num[j]>cur;j--)
num[j+1]=num[j];
num[j+1]=cur;
}
}
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
希尔排序:(最小增量排序)
算法流程举例说明:
[5,1,3,7,23,72,7],gap为3;故分为三组:5,7,7;1,23;3,72;先分别对三组数据进行直接插入排序。数组变成[5,1,3,7,23,72,7]。然后gap变为1,将数组分为一组,即本身,然后对这一组进行直接插入排序。
void ShellSort(vector<int> num){
int gap=num.size()/2;
while(gap>=1){ //最外层循环控制步长
for(int i=0;i<gap;++i){
  for(int j=i+gap;j<=num.size();j+=gap){ //对每一组子序列进行单独进行直接插入排序
int cur=num[j];
int index=j-gap;
for(;index>=0 && num[index]>cur;index-=gap){
num[index+gap]=num[index];
}
num[index+gap]=cur;
}
}
gap/=2;
}
}
时间复杂度:O(n^1.25)到O((1.6n)^1.25)(经验公式)
空间复杂度:O(1)
稳定性:不稳定

基数排序:(使用了桶排序原理)
算法流程举例说明:
//x中最初存储[5,1,3,7,23,72,7].确认最大位数为十位,故只需整体入桶两次即可(先按照个位后按照十位)。
//首先按照个位入桶
//桶1:1
//桶2:72
//桶3:3,23
//桶5:5
//桶7:7,7
//其余的桶为空
//按桶从小到大,同一个桶从左到右的顺序将全部元素放回数组x中(由于使用的是队列,每次头元素入x的同时就会清空桶(队列)中的头元素)
//x为[1,72,3,23,5,7,7],再按照十位入桶
//桶0:1,3,5,7,7
//桶2:23
//桶7:72
//其余的桶为空
//按桶从小到大,同一个桶从左到右的顺序将全部元素放回数组x中
//x为[1,3,5,7,7,23,72],完毕
public  void  RadixSort( int []  x )
{
        int  max = x [0];
        //找到数组中的最大值
        for ( int  i : x )
       {
              if ( i > max )
                     max = i ;
       }
        int  maxbit =0;
       //计算这个最大值对应十进制有多少位
        while ( max !=0)
       {
              max /=10;
              maxbit ++;
       }
        int  dividend =1;
       //构建十个桶,每个桶用LinkedList实现
       List<LinkedList<Integer>>  list = new  ArrayList<>();
        for ( int  i =0; i <10; i ++)
              list .add( new  LinkedList<Integer>());
       //从个位到高位处理
        for ( int  i =0; i < maxbit ; i ++)
       {
              for ( int  j =0; j < x . length ; j ++)
                     list .get(( x [ j ]/ dividend )%10).offer( x [ j ]);
             //将每次将桶号从小到大,每个桶中从头到尾的顺序,将排好的数放回到x数组中
              int  m =0;
              for ( int  j =0; j < list .size(); j ++)
             {
                     while ( list .get( j ).size()!=0)
                            x [ m++ ]= list .get( j ).poll();
             }
              dividend *=10;
       }
}

int   maxbit( int   data[],  int   n)  //辅助函数,求数据的最大位数
{
     int   d = 1;  //保存最大的位数
     int   p = 10;
     for ( int   i = 0; i < n; ++i)
     {
         while (data[i] >= p)
         {
             p *= 10;
             ++d;
         }
     }
     return   d;
}
void   radixsort( int   data[],  int   n)  //基数排序
{
     int   d = maxbit(data, n);
     int   *tmp = new int[n];
     int   *count = new int[10];  //计数器
     int   i, j, k;
     int   radix = 1;
     for (i = 1; i <= d; i++)  //进行d次排序
     {
         for (j = 0; j < 10; j++)
             count[j] = 0;  //每次分配前清空计数器
         for (j = 0; j < n; j++)
         {
             k = (data[j] / radix) % 10;  //统计每个桶中的记录数
             count[k]++;
         }
         for (j = 1; j < 10; j++)
             count[j] = count[j - 1] + count[j];  //将tmp中的位置依次分配给每个桶
         for (j = n - 1; j >= 0; j--)  //将所有桶中记录依次收集到tmp中
         {
             k = (data[j] / radix) % 10;
             tmp[count[k] - 1] = data[j];
             count[k]--;
         }
         for (j = 0; j < n; j++)  //将临时数组的内容复制到data中
             data[j] = tmp[j];
         radix = radix * 10;
     }
     delete []tmp;
     delete []count;
}
时间复杂度:O(d*n)     d为长度(对应代码中maxbit),n为数组长度
空间复杂度:O(n)
稳定性:稳定


归并排序:
void MergeSort(vector<int> num, int start, int end){ //将当前待处理的部分继续划分为两部分,初始输入的start=1,end=x.length-1
if(start>=end)  return;
int mid=start+(end-start)/2;
MergeSort(num,start,mid);
MergeSort(num,mid+1,end);
Merge(num,start,mid,mid+1,end); //将划分好的两部分归并。每次归并的两个部分都已经是有序的了。
}
void Merge(vector<int> num, int start1, int end1, int start2, int end2){
vector<int> temp(end2-start1+1); //临时生成一个新的数组用来存储
int tempIndex=0;
int first=start1;
int second=start2;
while(first<=end1 && second<=end2){ //直到遍历到某个部分的最后一个元素
if(num[first]<=num[second]){ /first和second始终指向当前第一(二)个部分剩余元素的最小值
temp[tempIndex]=num[first];
tempIndex++;
first++;
}
else{
temp[tempIndex]=num[second];
tempIndex++;
second++;
}
}
while(first<=end1) //若第一个部分还有剩余没有并入新的临时数组,则直接并入
temp[tempIndex++]=num[first++];
while(second<=end2) //若第二个部分还有剩余没有并入新的临时数组,则直接并入
temp[tempIndex++]=num[second++];
for(int i=0;i<tempIndex;++i) //将并好的有序数组写入原始数组的相应位置(从第一部分的起始到第二部分的结束)
num[start1+i]=temp[i];
}
时间复杂度:O(n*logn)
空间复杂度:O(n):n+logn
稳定性:稳定


种类 名称 时间复杂度 空间复杂度 稳定性
交换排序 冒泡排序 O(n^2) O(1) 稳定
  快速排序 O(n*logn) O(n*logn) 不稳定
选择排序 简单选择排序 O(n^2) O(1) 不稳定
  堆排序 O(n*logn) O(1) 不稳定
插入排序 直接插入排序 O(n^2) O(1) 稳定
  希尔排序 O(n^1.25)到O((1.6n)^1.25) O(1) 不稳定
归并排序 归并排序 O(n*logn) O(n) 稳定
基数排序 基数排序 O(d*n) O(n) 稳定

选择排序算法准则
每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。
影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:
1.待排序的记录数目n的大小;
2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;
3.关键字的结构及其分布情况;
4.对排序稳定性的要求。
 
设待排序元素的个数为n。
1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。
快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序:如果内存空间允许且要求稳定性的,
归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。
 
2)当n较大,内存空间允许,且要求稳定性,推荐归并排序
 
3)当n较小,可采用直接插入或直接选择排序。
直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。
直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序
 
5)一般不使用或不直接使用传统的冒泡排序。
 
6)基数排序
它是一种稳定的排序算法,但有一定的局限性:
  1、关键字可分解。
  2、记录的关键字位数较少,如果密集更好
  3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值