zoukankan      html  css  js  c++  java
  • 十大排序及C++实现

    No.1 冒泡排序

      从左至右依次消除相邻逆序对。

     1 void swap(int &a, int &b)
     2 {
     3     int temp = a;
     4     a = b;
     5     b = temp;
     6 }
     7 void bubble_sort(int *array,int length)
     8 {
     9     for( int i=0; i<length-1; i++ )
    10     {
    11         for( int j=0; j<length-i-1; j++ )
    12         {
    13             if( array[j]>array[j+1] )
    14             {
    15                 swap(array[j],array[j+1]);
    16             }
    17         }    
    18     }    
    19 }
    View Code

      每次内层循环后最大值都来到未排序数组最右端,就像大泡泡依次向上冒出。

      双重循环,时间复杂度O(n^2)。

      冒泡排序简单易理解,但每次都不管三七二十一直接两重循环。一个优化方法是每次内层循环都判断剩余数组是否有序,若有序则排序完成。

     1 void bubble_sort(int *array,int length)
     2 {
     3     for( int i=0; i<length-1; i++ )
     4     {
     5         bool is_sorted = true;
     6         for( int j=0; j<length-i-1; j++ )
     7         {
     8             if( array[j]>array[j+1] )
     9             {
    10                 swap(array[j],array[j+1]);
    11                 is_sorted = false;
    12             }
    13         }    
    14         if( is_sorted )    break;
    15     }    
    16 } 
    View Code

    No.2 选择排序

      每次选择最小值,将其与未排序数组最左端交换。即每次内层循环都在未排序数组中使用FindMin()函数,并与未排序数组左端交换。

     1 void select_sort(int *array,int length)
     2 {
     3     for( int i=0; i<length-1; i++ )
     4     {
     5         int min = i; //array[i,...,length]最小值下标 
     6         for( int j=i+1; j<length; j++ )
     7         {
     8             if( array[min]>array[j] ){
     9                 min = j;
    10             }
    11         }
    12         swap(array[i],array[min]);
    13     }
    14 }
    View Code

      双重循环,时间复杂度O(n^2)。


    No.3 插入排序

      算法过程类似于打扑克抓手牌,每次从牌堆抓一张牌后,从右至左在手牌中找到合适位置插入新手牌,保持手牌有序。

     1 void insert_sort( int *array, int length )
     2 {
     3     for( int i=1; i<length; i++ )
     4     {
     5         int j = i - 1;
     6         int key = array[i]; /*暂时保存新的手牌 */ 
     7         while( j>=0 && array[j]>key )/*找到合适位置*/ 
     8         {
     9             array[j+1] = array[j];
    10             j--;
    11         }
    12         array[j+1] = key; /*将新手牌放入合适位置*/ 
    13     }
    14 }
    View Code

      在最好情况即有序情况下时间复杂度为外层循环O(n),在最坏情况下即逆序情况下时间复杂度为双重循环O(n^2)。


    No.4 希尔排序

      插入排序的改进版本,又称为缩减增量排序。其思想是每次用增量ht(>=1)作为间隔将数组划分为子问题进行插入排序,每次排序后增量缩减,

    重复上述过程,直到增量减为1,此时的排序与插入排序相同。

     1 void shell_sort( int *array, int length )
     2 {
     3     /*计算初始增量*/
     4     int gap = 1;
     5     while( gap<length )    gap = gap * 3 + 1;
     6     /*以增量为间隔排序*/
     7     while( gap>0 )
     8     {
     9         for( int i=gap; i<length; i++ )
    10         {
    11             /*当gap=1时程序与插入排序相同*/
    12             int j = i - gap;
    13             int key = array[i];
    14             while( j>=0 && array[j]>key )
    15             {
    16                 array[j+gap] = array[j];
    17                 j -= gap;
    18             }
    19             array[j+gap] = key;
    20         }
    21         gap /= 3; /*增量递减*/
    22     }
    23 }
    View Code

      通过将完全无序的初始序列,通过缩减增量逐步将序列有序,最后一步的插入排序只需交换个别元素位置,使时间复杂度优于插入排序。

      增量的初值及缩减规则没有最优标准。


    No.5 归并排序

      分治法思想,即划分,排序,合并。每次将序列两两分组,直至序列长度为1,接着逐步合并子序列并保持合并序列有序。

     1 const int INF = 10000000;
     2 void merge(int *array, int p, int q, int r )
     3 {
     4     int length1 = q - p + 1, length2 = r - q;
     5     int *A = new int[length1+1];
     6     int *B = new int[length2+1]; //临时数组
     7     
     8     for( int i=p; i<=q; i++ )
     9     {
    10         A[i-p] = array[i];    
    11     }    
    12     for( int i=q+1; i<=r; i++ )
    13     {
    14         B[i-q-1] = array[i];
    15     }
    16     A[length1] = B[length2] = INF; //为统一操作 
    17     
    18     int i = 0, j = 0;
    19     for( int k=p; k<=r; k++ )
    20     {
    21         if( A[i]<B[j] )
    22         {
    23             array[k] = A[i];
    24             i++; 
    25         }
    26         else
    27         {
    28             array[k] = B[j];
    29             j++;
    30         }
    31     }
    32     
    33     delete []A;    delete []B; //释放内存 
    34 }
    35 
    36 void merge_sort( int *array, int p, int r )
    37 {
    38     if( p<r )
    39     {
    40         int q = (p+r)/2;
    41         merge_sort(array,p,q);
    42         merge_sort(array,q+1,r);
    43         merge(array,p,q,r);
    44     }
    45 }
    View Code

      算法每次将问题划分为两个相等的子问题,合并与排序同时进行,需要O(n)时间。

      T(n)  = 2T(n/2) + O(n)  =  T(nlogn)。


    No.6 快速排序

      分治法思想,每次在序列中选取一个基准值,经过一次排序操作后,位于基准值左边的元素不大于基准值、位于基准值右边的元素不小于基准值。

    接着在基准值左右两边分别选取新的基准值重复操作,直到左右元素个数不大于1,序列排序完成。

      基准值的选取影响算法时间的好坏,这里统一选取数组的最右端作为基准值。

      找到基准值后对元素的划分有多种方法。

      单边扫描法:从左至右遍历元素,若元素小于基准值,则与左端元素交换。

     1 int patition( int *array, int p, int r )
     2 {
     3     int x = array[r]; //以最右端元素作为基准值
     4     int i = p - 1;
     5     for( int j=p; j<r; j++ )
     6     {
     7         if( array[j]<x )
     8         {/*将小于基准值元素放在左边*/
     9             i++;
    10             swap(array[i],array[j]);    
    11         }    
    12     }
    13     swap(array[i+1],array[r]);
    14     return i+1; 
    15 }
    16 
    17 void quick_sort( int *array, int p, int r )
    18 {
    19     if( p<r )
    20     {
    21         int q = patition(array,p,r);
    22         quick_sort(array,p,q-1);
    23         quick_sort(array,q+1,r);
    24     }
    25 }
    View Code

      双边扫描法:先从左至右找到大于基准值元素,再从右至左找到小于基准值元素,交换元素,重复上述过程直至左右指针相遇。

     1 int patition( int *array, int p, int r )
     2 {
     3     int x = array[r]; //以最右端元素作为基准值
     4     int i = p, j = r - 1;
     5     while( i<=j )
     6     {/* 保证i左边的元素<=x,j右边元素(不包括r)>=x */
     7         while( i<=j && array[i]<=x )    i++;
     8         while( i<=j && array[j]>=x )    j--;
     9         if( i<=j )
    10         {
    11             swap(array[i],array[j]);    
    12         }    
    13     } 
    14     /* 此时array[i]>x */
    15     swap(array[r],array[i]);
    16     return i; 
    17 }
    18 
    19 void quick_sort( int *array, int p, int r )
    20 {
    21     if( p<r )
    22     {
    23         int q = patition(array,p,r);
    24         quick_sort(array,p,q-1);
    25         quick_sort(array,q+1,r);
    26     }
    27 }
    View Code

      快排在最坏情况下:每次划分后一段元素个数为0,另一端为length-1 时间复杂度为O(n^2),在一般情况下时间复杂度为O(nlogn)。


    No.7 堆排序

      利用数据结构排序,每次取堆顶元素。

      自己的思路是利用堆插入元素构建堆,接着依次取出堆顶元素,实现排序。

     1 const int Max_N = 10000;
     2 
     3 int sz = 0;//堆元素个数 
     4 int heap[Max_N]; //
     5 
     6 void swap(int &a, int &b)
     7 {
     8     int temp = a;
     9     a = b;
    10     b = temp;
    11 }
    12 
    13 void push( int x )
    14 {
    15     //自己节点编号
    16     int i = sz++;
    17     
    18     while( i>0 )
    19     {
    20         //父亲节点编号 
    21         int p = (i-1)/2;    
    22         
    23         //如果没有大小颠倒则推出
    24         if( heap[p]<=x )    break;
    25         
    26         //把父亲节点数值放下来,将自己下标与之交换
    27         heap[i] = heap[p];
    28         i = p; 
    29     } 
    30     
    31     heap[i] = x;
    32 }
    33 
    34 int pop()
    35 {
    36     //最小值 
    37     int ret = heap[0];
    38     
    39     //提到根的数值
    40     int x = heap[--sz];
    41     
    42     //从根开始向下交换
    43     int i = 0;
    44     while( i*2+1<sz )
    45     {
    46         int a = 2*i+1, b = 2*i+2;
    47         if( b<sz && heap[b]<heap[a] )    a = b; /*a为孩子较小值下标*/
    48         
    49         //如果没有大小颠倒则退出 
    50         if( heap[a]>=x )    break;
    51         
    52         //将儿子提上来 
    53         heap[i] = heap[a]; 
    54         i = a; 
    55     } 
    56     
    57     heap[i] = x;
    58     
    59     return ret;
    60 }
    61 
    62 void heap_sort( int *array, int length )
    63 {
    64     for( int i=0; i<length; i++ )
    65     {
    66         push(array[i]);
    67     }
    68     
    69     for( int i=0; i<length; i++ )
    70     {
    71         array[i] = pop();
    72     }
    73 }
    View Code

      这种思路比较耗时,构建堆过程中每一个元素都要从堆尾到堆顶做交换比较。

      堆排序思路:先将元素加入堆中,再对堆非叶子节点做调整以构建堆,接着依次重复将堆顶下沉到堆尾(与堆尾交换)、重建堆建构,实现排序。

     1 void adjust_heap(int *heap,int i,int length)
     2 {
     3     int x = heap[i];
     4     int sz = length;
     5     
     6     while( i*2+1<sz )
     7     {
     8         int a = i*2+1, b = i*2+2;
     9         if( b<sz && heap[b]>heap[a] )
    10         {
    11             a = b;/*a为孩子较大值下标*/
    12         }
    13         
    14         //如果没有大小颠倒,退出 
    15         if( heap[a]<=x )    break;
    16         
    17         //将孩子提上来
    18         heap[i] = heap[a];
    19         i = a; 
    20     }
    21     
    22     heap[i] = x;
    23 }
    24 
    25 void heap_sort( int *array, int length )
    26 {
    27     /*调整构建大顶堆*/
    28     //第一个非叶子节点是(length-1)/2是因为它是最后一个节点的父亲 
    29     for( int i=(length-1)/2; i>=0; i-- )
    30     {/*从第一个非叶子节点从下至上,从右至左调整结构*/
    31         adjust_heap(array,i,length);
    32     }
    33     
    34     for( int i=length-1; i>0; i-- )
    35     {
    36         /*将堆顶元素下沉*/
    37         swap(array[0],array[i]);
    38         adjust_heap(array,0,i);//重写调整堆结构 
    39     }
    40 }
    View Code

      对于每个元素其调整操作最多需要O(logn),共有n个元素,所以堆排序时间复杂度为O(nlogn)。

      堆排序实际上也是一种选择排序,是一种树形选择排序。普通排序在选择最小值过程中已经与此小值做过了比较,但没有保存,所以每次都要重新比较一遍。

    而堆排序利用树形结构保存了中间比较的结果,减少比较次数。


    No.8 计数排序

      不同于上述算法的是计数排序不是基于比较的排序算法。算法的思路是用一个数组count[ i ] : 记录在数组中小于等于元素 i 的个数,即元素 i 的升序排名。

     1 void count_sort( int *array, int length )
     2 {
     3     int max = array[0];
     4     for( int i=1; i<length; i++ )
     5     {
     6         max = max < array[i] ? array[i] : max; 
     7     }
     8     
     9     int *count = new int[max+1]; //count[0,...,max]
    10     memset(count,0,sizeof(int)*(max+1));
    11     for( int i=0; i<length; i++ )
    12     {
    13         /*记录 array[i] 出现的次数*/
    14         count[ array[i] ]++;
    15     }
    16 
    17     for( int i=1; i<=max; i++ )
    18     {
    19         /* 记录 i 在array 中的排名*/
    20         count[i] = count[i-1] + count[i];
    21     }
    22     
    23     int *temp = new int[length];
    24     for( int i=0; i<length; i++ )
    25     {
    26         /* 以array[i]的排名放入临时数组 --因为数组从0开始*/
    27         temp[ --count[array[i]] ] = array[i];
    28     }
    29     
    30     for( int i=0; i<length; i++ )
    31     {
    32         /* 将排序后元素放回原数组 */
    33         array[i] = temp[i];
    34     }
    35     
    36     delete []count;    delete []temp;
    37 }
    View Code

      计数排序采用用空间换时间的方法,使用简单,时间复杂度为O(n+max)。同时具有局限性,比如数值的差距很大,那么有大量的空间没有被使用、因为使用

    数组下标计数,所以只适用于整数。

      计数排序适用于一定范围内的整数排序,且时间复杂度能达到O(n)。


    No.9 桶排序

      桶排序可以看作是计数排序的优化,算法将待排数据放入一定大小(数值范围)的有序桶里,再对桶里数据进行排序(可自己选择排序算法),

    最后按序从桶中取出数据。

     1 const int BUCKET_SIZE = 5;//默认用5个桶 
     2 
     3 void bucket_sort( int *array, int length )
     4 {
     5     /*最大/最小值*/
     6     int max_element,min_element;
     7     max_element = min_element = array[0];
     8     for( int i=1; i<length; i++ )
     9     {
    10         max_element = max( max_element, array[i] );
    11         min_element = min( min_element, array[i] );
    12     }
    13     
    14     /*差值*/
    15     int dif = max_element - min_element;
    16     /*用二维向量作为桶*/
    17     vector<int> bucket_list[BUCKET_SIZE];
    18     
    19     /*单个桶的区间值*/
    20     float section = (float)dif/(BUCKET_SIZE);
    21     
    22     /*数据入桶*/
    23     for( int i=0; i<length; i++ )
    24     {
    25         /*(当前数-min)/区间-1 -> 桶的下标*/
    26         int num = (int)((array[i]-min_element)/section)-1;
    27         if( num<0 )    num = 0;
    28         bucket_list[num].push_back(array[i]);    
    29     } 
    30     
    31     //桶内排序
    32     for(int i=0; i<BUCKET_SIZE; i++ )
    33     {
    34         sort(bucket_list[i].begin(),bucket_list[i].end());
    35     }
    36     
    37     int index = 0;
    38     for( int i=0; i<BUCKET_SIZE; i++ )
    39     {
    40         for( int j=0; j<bucket_list[i].size(); j++ )
    41         {
    42             array[index++] = bucket_list[i][j];
    43         }
    44     }
    45 }
    View Code

      桶排序是非比较整数排序。

      最优情况为数据均匀分布于桶中,最坏情况为所有数据在一个桶中,此时的时间复杂度取决于桶内排序算法的选取。所以桶的大小与个数选取十分重要。

      当所有数值都有唯一对应的桶时,此时无需做桶内排序,可以看作是计数排序。


    No.10 基数排序

      非比较整数排序,其思想为按位数逐步划分数据。

     1 const int size = 10;
     2 void radix_sort( int *array, int length )
     3 {    
     4     int max_element = array[0];
     5     for( int i=1; i<length; i++ )
     6     {
     7         max_element = max( max_element, array[i] );    
     8     } 
     9     
    10     int pow = 1;
    11     while( true )
    12     {
    13         vector<int> bucket_list[size];
    14         
    15         for( int i=0; i<length; i++ )
    16         {
    17             int radix = (array[i]/pow)%10; /*当前排序位*/
    18             bucket_list[ radix ].push_back( array[i] ); 
    19         }
    20         
    21         int index = 0;
    22         for( int i=0; i<size; i++ )
    23         {
    24             for( int j=0; j<bucket_list[i].size(); j++ )
    25             {
    26                 array[index++] = bucket_list[i][j];//放回数据 
    27             }
    28         }
    29         
    30         pow *= 10;
    31         if( pow>max_element )    break;
    32     }
    33 }
    View Code

      基数排序可以看作是固定10个桶的桶排序,其排序规则是按照位数,不断的将数据放入桶和从桶中取出数据。

      可以按位数从左向右排序,也可从右向左。


    主要参考:https://mp.weixin.qq.com/s/Qf416rfT4pwURpW3aDHuCg

      

  • 相关阅读:
    python之各种包
    正则表达式
    import/模块的导入
    迭代器/可迭代对象/生成器
    Day2 列表list
    Day1 字符串格式化
    Day1 字符编码及编码函数
    Python 学习笔记 之 随着学习不断更新的Python特性搜集
    Day1 input&print
    Newtonsoft.Json日期转换
  • 原文地址:https://www.cnblogs.com/w-like-code/p/14289519.html
Copyright © 2011-2022 走看看