排序算法个人总结

参考:
《大话数据结构》
http://blog.csdn.net/hguisu/article/details/7776068#t5

本文主要是个人结合大话数据结构和另一位博主的内容,总结的自己写的7种排序算法的代码,并与前人代码进行了对比。

一、排序算法分类


二、排序算法复杂度一览


这里写图片描述

三、算法解析


1. 插入排序类——直接插入排序(Straight Insertion Sort)

基本思想:
直接插入排序的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数曾1的有序表。
关键字:哨兵 后移

如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
这里写图片描述
算法实现:

#include <iostream>
using namespace std ;
void print( int a [], int n, int i ){
        cout << i << ":" ;
        for ( int j = 0 ; j <8 ; j ++){
               cout << a[ j ] << " " ;
        }
        cout << endl;
}

void InsertSort( int a [], int n)
{
        for ( int i = 1 ; i <n ; i ++){
               if ( a [i ] < a[ i - 1]){                //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
                      int j = i - 1;
                      int x = a [i ];         //复制为哨兵,即存储待排序元素
                      a [i ] = a[ i - 1];            //先后移一个元素
                      while ( x < a[ j ]){  //查找在有序表的插入位置
                            a [j + 1 ] = a [ j];
                            j --;         //元素后移
                      }
                      a [j + 1 ] = x ;       //插入到正确位置
               }
               print (a , n , i );            //打印每趟排序的结果
        }

}

//自己写的代码============================
void InsertSort1( int a [], int length)       
{
        for ( int i = 1 ; i < length; i++)     //从a[1]开始,直接与a[0]比较,如果比前面的数大,就直接插在本身的位置,不用往前遍历
        {
               if ( a [i ] < a[ i - 1])     //当当前元素a[i]比前一个元素a[i-1]小的时候,需要往前遍历,将a[i]的元素插到前面的序列中
               {
                      int temp = a [i];     //把待插入的元素拿出来(哨兵),防止元素后移时把待插入元素覆盖
                      int j = i - 1;      //从待插入元素的位置i前一个i-1开始往前遍历
                      for (; temp < a [ j]&& j >=0 ; j --)     //如果待插入元素temp<a[j],j向前移动一个位置(当a[j]中的j=0时,有可能使j=-1)
                      //j>=0可以省去,因为当j<0时,a[j]越界了,自动退出循环。 最好别省
                      {
                            a [j + 1 ] = a [ j];     //把a[j]元素后移,即让a[j+1]元素等于a[j]
                      }
                      a [j + 1 ] = temp ;     //当待插入元素temp>a[j]或者j=-1(也就是temp比所有的元素都小)时,把temp赋给a[j+1]
               }
               print (a , length , i );     //打印第i次排序后的数组
        }
}
//自己写的代码============================

int main(){
        int a [ 8] = { 3 , 5 , 7 , 1 , 2 , 8 , 4 , 6 };
        InsertSort (a , 8 );

        return 0 ;
}

参考代码和自己代码相比较:
参考代码使用while循环
我使用for循环

需要在注意的地方:
外层循环从i=1开始;
要考虑待插入元素与前面一位元素大小关系;
考虑边界问题;

效率:
时间复杂度:O(n^2) (因为有两层循环)
其他的插入排序有二分插入排序,2-路插入排序。

2. 插入排序希尔排序(Shells Sort)

希尔排序是1959 年由D.L.Shell 提出来的,相对直接排序有较大的改进。希尔排序又叫缩小增量排序

基本思想:
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
采取跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入序列排序后得到的结果是基本有序而不是局部有序。
关键字:跳跃分割 插入排序
这里写图片描述
操作方法:
1选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2按增量序列个数k,对序列进行k 趟排序;
3每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

希尔排序时效分析很难,关键码的比较次数与记录移动次数依赖于增量因子序列d的选取,特定情况下可以准确估算出关键码的比较次数和记录的移动次数。目前还没有人给出选取最好的增量因子序列的方法。增量因子序列可以有各种取法,有取奇数的,也有取质数的,但需要注意:增量因子中除1 外没有公因子,且最后一个增量因子必须为1。希尔排序方法是一个不稳定的排序方法。

算法实现:

#include <iostream>
using namespace std ;

void print( int a [], int n, int i ){
        cout << i << ":" ;
        for ( int j = 0 ; j <8 ; j ++){
               cout << a[ j ] << " " ;
        }
        cout << endl;
}
/**
* 直接插入排序的一般形式
*
* @param int dk 缩小增量,如果是直接插入排序,dk=1
*
*/
void ShellInsertSort1( int a [], int n, int dk )
{
        for ( int i = dk ; i <n ; ++ i ){
               if ( a [i ] < a[ i - dk]){           //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
                      int j = i - dk;
                      int x = a [i ];            //复制为哨兵,即存储待排序元素
                      a [i ] = a[ i - dk];          //首先后移一个元素
                      while ( x < a[ j ]){     //查找在有序表的插入位置
                            a [j + dk ] = a [ j];
                            j -= dk;              //元素后移
                      }
                      a [j + dk ] = x ;             //插入到正确位置
               }
               print (a , n , i );
        }
}

/**
* 先按增量d(n/2,n为要排序数的个数进行希尔排序
*
*/

void ShellSort( int a [], int n){

        int dk = n / 2;
        while ( dk >= 1){
               ShellInsertSort1 (a , n , dk );
               dk = dk / 2 ;
        }
}

//自己写的代码===============================
void ShellSort1( int a [], int length)     //内容与直接插入排序大致相同,只不过多了一个增量分组类容,多一个循环。并且指针移动的步长变成了increament
{
        int increament = length ;
        do
        {
               increament = increament / 3 + 1 ;     //这俩increament变化取3,是因为业界认为3的效率最好,最后+1是为了防止increament变成0
               //下面内容与直接插入排序一模一样,只是指针移动的步长是动态的,将increament变为1,整个代码与插入排序一样。
               for ( int i = increament ; i < length; i++)
               {
                      if ( a [i ] < a[ i - increament])
                      {
                            int temp = a [i ];
                            int j = i - increament;
                            for (; j>=0&&temp < a [ j]; j -= increament )
                            {
                                   a [j + increament ] = a [ j];
                            }
                            a [j + increament ] = temp ;     //将元素插入正确位置
                      }
                      print (a , length , i );
               }
        } while (increament != 1 );
}
//自己写的代码===============================

int main(){
        int a [ 8] = { 3 , 1 , 5 , 7 , 2 , 4 , 9 , 6 };
        //ShellInsertSort(a,8,1); //直接插入排序
        ShellSort (a , 8 );            //希尔插入排序
        print (a , 8 , 8 );

        return 0 ;
}

参考代码和自己代码相比较:
参考代码中增量的取法是/2,可以不用+1;
我的代码中增量的取法是/3,如果没有+1,当上一轮increament=2时,下一轮的increament=2/3=0,不正确;

需要注意的地方:
代码与直接插入排序几乎一样,需要设置增量,并且增量递减至最后为1;

效率:
时间复杂度为:O(nlogn)~O(n^2) (与increament选取有关)

3.选择排序简单选择排序(Simple Selection Sort)

基本思想:
简单选择排序法就是通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n)个记录交换之。
在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
这里写图片描述
操作方法:
第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;
第二趟,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;
以此类推…..
第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,
直到整个序列按关键码有序。
在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。

算法实现:

#include <iostream>
using namespace std ;

void print( int a [], int n, int i ){
        cout << "第" << i + 1 << "趟 : " ;
        for ( int j = 0 ; j <8 ; j ++){
               cout << a[ j ] << "  " ;
        }
        cout << endl;
}
/**
* 数组的最小值
*
* @return int 数组的键值
*/
int SelectMinKey( int a [], int n, int i )
{
        int k = i ;
        for ( int j = i + 1; j< n; ++j ) {
               if ( a [k ] > a[ j ]) k = j;
        }
        return k ;
}

/**
* 选择排序
*
*/
void SelectSort( int a [], int n){
        int key , tmp ;
        for ( int i = 0 ; i < n ; ++ i) {
               key = SelectMinKey( a , n , i );           //选择最小的元素
               if ( key != i){
                      tmp = a[ i ];  a [i ] = a[ key ]; a [key ] = tmp; //最小元素与第i位置元素互换
               }
               print (a , n , i );
        }
}

//自己写的代码========================
void myswap( int * a, int * b )
{
        int temp = * a;
        * a = * b ;
        * b = temp ;
}

void SelectSort1( int a [], int length )
{
        for ( int i = 0 ; i < length- 1 ; i ++)
        {
               for ( int j = i +1 ; j < length ; j ++)
               {
                      if ( a [i ] > a[ j ])
                      {
                            myswap (&a [ i], &a [ j]);        //如果前一个元素比后一个元素大,将两个元素进行交换
                      }
               }
               print (a , length , i );
        }

}
//自己写的代码========================

int main(){
        int a [ 8] = { 3 , 1 , 5 , 7 , 2 , 4 , 9 , 6 };
        cout << "初始值:" ;
        for ( int j = 0 ; j <8 ; j ++){
               cout << a[ j ] << "  " ;
        }
        cout << endl << endl ;
        SelectSort1 (a , 8 );
        print (a , 8 , 8 );

        return 0 ;
}

参考代码和自己代码相比较:
参考代码使用了min标志,存储了循环遍历过程中最小值的下标,最后进行交换的时候,只进行了一次交换;
我的代码在没轮循环中,只要两个元素的值满足交换的条件,就进行过了交换,比参考代码多出了好多个交换语句,需要改进;(但是大话数据结构书上的代码是和我这里写的一样的)
改进为:

//自己写的代码========================
void myswap( int * a, int * b )
{
        int temp = * a;
        * a = * b ;
        * b = temp ;
}

void SelectSort1( int a [], int length)
{
        for ( int i = 0 ; i < length - 1 ; i ++)
        {
               int min =i ;
               for ( int j = i + 1; j < length ; j ++)
               {
                      if ( a [min ] > a[ j ])
                      {
                            min = j;     //记录最小值的下标,存在min中
                      }
               }
               myswap (&a [ i], &a [ min]);     //每轮只用交换一次就可以
               print (a , length , i );
        }
}
//自己写的代码========================

需要注意的地方:
尽量减少交换的次数,以提高性能更;

效率:
时间复杂度:O(n^2) (两层循环)

简单选择排序的改进——二元选择排序

简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。具体实现如下:

void SelectSort(int r[],int n) {
    int i ,j , min ,max, tmp;
    for (i=1 ;i <= n/2;i++) {
        // 做不超过n/2趟选择排序
        min = i; max = i ; //分别记录最大和最小关键字记录位置
        for (j= i+1; j<= n-i; j++) {
            if (r[j] > r[max]) {
                max = j ; continue ;
            }
            if (r[j]< r[min]) {
                min = j ;
            }
      }
      //该交换操作还可分情况讨论以提高效率
      tmp = r[i-1]; r[i-1] = r[min]; r[min] = tmp;
      tmp = r[n-i]; r[n-i] = r[max]; r[max] = tmp;
    }
}

我觉得这段代码还是有问题,首先没考虑数组的长度是奇偶数,另外如果当数组是6,5,4的时候,min=1,max=3,进行两次交换的时候,还是变成了6,5,4,所以排序会出问题。
我自己还没弄清楚二元选择排序的写法是怎样。

4.选择排序堆排序(Heap Sort)

堆是具有下列性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
这里写图片描述

基本思想:
将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。
这里写图片描述
这里写图片描述
这里写图片描述
要实现还需要解决两个问题:
1.如何有一个无序序列构建成一个堆
2.如何在输出堆顶元素后,调整剩余元素成为一个新的堆

首先讨论第二个问题:输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。
调整小顶堆的方法:

1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。
2)将根结点与左、右子树中较小元素的进行交换。
3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).
4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).
5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。

称这个自根结点到叶子结点的调整过程为筛选。如图:
这里写图片描述
再讨论对n 个元素初始建堆的过程。
建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。

1)n 个结点的完全二叉树,则最后一个结点是第个结点的子树。
2)筛选从第个结点为根的子树开始,该子树成为堆。
3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。
如图建堆初始过程:无序序列:(49,38,65,97,76,13,27,49)
这里写图片描述

 首先,将n维数组写成二叉树的形式,也就是要按按层序遍历的方式写出来,这时候的二叉树是一个完全二叉树。
 那么,1~n/2个节点是非叶子节点,也就是要把以这些结点为根节点的子树调整为小顶堆(大顶堆)。从后往前遍历,因为是完全二叉树,所以i结点的左孩子是2i,右孩子是2i+1,把三个数中最小(最大)的数放在根节点上,然后调整上一个节点i-1。
 当小顶堆(大顶堆)构造完成后,把整个堆的根节点和最后一个最大(最小)数交换,也就是1与n交换,数组长度减一,可以得到最小的那个数字,依次循环可以找出递增的排序。

算法实现:

#include <iostream>
using namespace std;

void print(int a[], int n) {
                 for (int j = 0; j<n; j++) {
                                cout << a [j] << "  ";
                }
                cout << endl;
}
/**
* 已知H[s…m]除了H[s] 外均满足堆的定义
* 调整H[s],使其成为大顶堆.即将对第s个结点为根的子树筛选,
*
* @param H是待调整的堆数组
* @param s是待调整的数组元素的位置
* @param length是数组的长度
*/
void HeapAdjust(int H[], int s, int length)
{
                 int tmp = H [s];
                 int child = 2 * s + 1; //左孩子结点的位置。(i+1 为当前调整结点的右孩子结点的位置)
                 while (child < length ) {
                                 if (child + 1 <length && H[child]< H[child + 1]) { // 如果右孩子大于左孩子(找到比当前待调整结点大的孩子结点)
                                                ++child;
                                }
                                 if (H [s]<H[child]) {  // 如果较大的子结点大于父结点
                                                 H[s ] = H[child]; // 那么把较大的子结点往上移动,替换它的父结点
                                                 s = child;       // 重新设置s ,即待调整的下一个结点的位置
                                                child = 2 * s + 1;
                                }
                                 else {            // 如果当前待调整结点大于它的左右孩子,则不需要调整,直接退出
                                                 break;
                                }
                                 H[s ] = tmp;         // 当前待调整的结点放到比其大的孩子结点位置上
                }
                print( H, length );
}
/**
* 初始堆进行调整
* 将H[0..length-1]建成堆
* 调整完之后第一个元素是序列的最小的元素
*/
void BuildingHeap(int H[], int length)
{
                 //最后一个有孩子的节点的位置 i=  (length -1) / 2
                 for (int i = (length - 1) / 2; i >= 0; --i)
                                HeapAdjust( H, i, length );
}
/**
* 堆排序算法
*/
void HeapSort(int H[], int length)
{
                 //初始堆
                BuildingHeap( H, length );
                 //从最后一个元素开始对序列进行调整
                 for (int i = length - 1; i > 0; --i)
                {
                                 //交换堆顶元素H[0]和堆中最后一个元素
                                 int temp = H [i]; H[i] = H[0]; H [0] = temp;
                                 //每次交换堆顶元素和堆中最后一个元素之后,都要对堆进行调整
                                HeapAdjust( H, 0, i);
                }
}

//自己写的代码===========================
void myswap(int &a,int &b)
{
       int temp = a;
       a = b;
       b = temp;

}

void HeapAdjust1(int *arr, int n, int length)   //这里将子树调整为大顶堆,然后把最大的数放到最后面
{
       int temp = arr[n];
       int i = 2 * n;
       for (i; i <= length; i *= 2)      //每次遍历都是先遍历的左孩子
       {
              if (i + 1 <= length) //如果结点i有右孩子
              {
                     if (arr[i] > arr[i + 1]) //比较左右孩子大小
                           ++i;   //如果左孩子比右孩子大,则把右孩子记为较大的数
              }
              //否则直接比较根节点和左孩子的大小
              if (temp >= arr[i])
                     break; //比较根和较大的孩子,如果根大,则说明满足了堆的要求。跳出循环遍历
              //myswap(arr[n], arr[i]);  //否则的话把较大的孩子节点和根节点相互交换,继续遍历较大孩子的子树是否满足堆。
              //直接交换是错误的,arr[n]的值变了。
              arr[n] = arr[i];//直接将大的结点赋给根节点 下次比较的还是和原来temp值相比较
              n = i;//把根节点变换为孩子节点,在继续循环遍历子树
       }
       arr[n] = temp;//把根结点的值补上。
}
void HeapSort1(int a[], int length)
{
       int *b;
       b = a - 1;    //这里为了把数组的下标从0开始提到从1开始,定义了一个指针,比数组地址小1;
       for (int i = length / 2; i > 0; i--)
       {
              HeapAdjust1(b, i, length);
              //print(a, length);
       }
       print(a, length);
       for (int i = length; i > 1;i--)
       {
              myswap(b[1], b[i]);
              HeapAdjust1(b, i, i-1);
              print(a, length);
       }

}

//自己写的代码===========================

int main() {
                 int H[10] = { 3,1,5,7,2,4,9,6,10,8 };
                cout << "初始值:" ;
                print(H, 10);
                HeapSort(H, 10);
                 //selectSort(a, 8);
                cout << "结果:" ;
                print(H, 10);

}

参考代码和自己代码相比较:

需要注意的地方:
在构造堆的时候,是逆层序遍历的顺序访问各子树根结点的,当根结点与左右孩子不满足堆条件时,把大(小)的那个孩子放在根结点上,在比较原根结点与大(小)孩子的子树是否构成堆,满足则继续遍历,否则在按同样的方法把子树重新构造成堆,这里有种递归的思想;
注意在交换结点位置时,各个结点的值,然后再以左右孩子构造堆时,结点值不能弄错;
效率:
时间复杂度:O(nlogn)

5.交换排序冒泡排序(Bubble Sort)

基本思想:
冒泡排序是一种交换排序,基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
这里写图片描述
冒泡排序是稳定排序

算法实现:

void bubbleSort(int a[], int n) {
                 for (int i = 0; i< n - 1; ++i) {
                                 for (int j = 0; j < n - i - 1; ++j) {
                                                 if (a [j] > a[j + 1])
                                                {
                                                                 int tmp = a [j]; a[j] = a[j + 1];  a [j + 1] = tmp;
                                                }
                                }
                }
}

//自己写的代码========================
void myswap( int * a, int * b )
{
        int temp = * a;
        * a = * b ;
        * b = temp ;
}

void BubbleSort( int a [], int length)
{
        for ( int i = 0 ; i < length - 1 ; i ++)
        {
               for ( int j = i+1; j < length-1-i ; j ++)
               {
                      if ( a [j] > a[ j+1 ])
                      {
                           myswap (&a [ j], &a [ j+1]);
                      }
               }
        }
}

//自己写的代码========================
参考代码和自己代码相比较:
一致

冒泡算法优化:
设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。
改进后的算法:

void Bubble_1 ( int r[], int n) {
     int i= n -1;  //初始时,最后位置保持不变
     while ( i> 0) {
          int pos= 0; //每趟开始时,无记录交换
          for (int j= 0; j< i; j++)
               if (r[j]> r[j+1]) {
                    pos= j; //记录交换的位置
                    int tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;
               }
          i= pos; //为下一趟排序作准备
     }
}
//自己写的代码========================
void myswap( int * a, int * b )
{
        int temp = * a;
        * a = * b ;
        * b = temp ;
}
void BubbleSort( int a [], int length)
{
         bool flag = true;
        for ( int i = 0 ; i < length - 1 ; i ++)
        {
               flag = flase;
               for ( int j = i+1; j < length-1-i ; j ++)
               {
                      if ( a [j] > a[ j+1 ])
                      {
                           flag = true;
                           myswap (&a [ j], &a [ j+1]);     
                      }
               }
               if(flag==false)
                    break;
        }
}
//自己写的代码========================

需要注意的地方:
边界条件;

效率:
时间复杂度:O(n^2)

6.交换排序快速排序(Quick Sort)

基本思想:
通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
1)选择一个基准元素,通常选择第一个元素或者最后一个元素,

2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。

3)此时基准元素在其排好序后的正确位置

4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。

快速排序的示例:

(a)一趟排序的过程:
这里写图片描述
(b)排序的全过程
这里写图片描述

 快速排序是不稳定的

算法实现:
递归实现

#include <iostream>
using namespace std;
void print(int a[], int n) {
       for (int j = 0; j<n; j++) {
              cout << a[j] << "  ";
       }
       cout << endl;
}
void swap(int *a, int *b)
{
       int tmp = *a;
       *a = *b;
       *b = tmp;
}
int partition(int a[], int low, int high)
{
       int privotKey = a[low];                             //基准元素
       while (low < high) {                                   //从表的两端交替地向中间扫描
              while (low < high  && a[high] >= privotKey) --high;  //从high 所指位置向前搜索,至多到low+1 位置。将比基准元素小的交换到低端
              swap(&a[low], &a[high]);
              while (low < high  && a[low] <= privotKey) ++low;
              swap(&a[low], &a[high]);
       }
       print(a, 10);
       return low;
}
void QuickSort(int a[], int low, int high) {
       if (low < high) {
              int privotLoc = partition(a, low, high);  //将表一分为二
              QuickSort(a, low, privotLoc - 1);          //递归对低子表递归排序
              QuickSort(a, privotLoc + 1, high);        //递归对高子表递归排序
       }
}
//自己写的代码========================
int Partition(int a[], int low, int high)
{
       int piovtkey = a[low];
       while (low < high)
       {
              while (piovtkey < a[high]&&low<high)
              {
                     high--;
              }
              a[low] = a[high];
              while (piovtkey > a[low]&&low<high)
              {
                     low++;
              }
              a[high] = a[low];
       }
       a[low] = piovtkey;
       print(a, 10);
       return low;

}
void QuickSort1(int a[], int low, int high)
{
       if (a == NULL)
              return;
       int pivot;
       if (low < high)
       {
              pivot = Partition(a, low, high);
              QuickSort1(a, low, pivot-1);
              QuickSort1(a, pivot+1, high);
       }
}
//自己写的代码========================
int main() {
       int a[10] = { 3,1,5,7,2,4,9,6,10,8 };
       cout << "初始值:";
       print(a, 10);
       QuickSort1(a, 0, 9);
       cout << "结果:";
       print(a, 10);
       return 0;
}

参考代码和自己代码相比较:
参考代码partition函数里,进行了多次交换,
自己的代码中,并不是交换,而是设立了一个哨兵,直接进行覆盖复制;
需要注意的地方:

效率:
时间复杂度:O(nlogn)

快速排序是通常被认为在同数量级(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按关键码有序或基本有序时,快排序反而蜕化为冒泡排序。为改进之,通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。快速排序是一个不稳定的排序方法。

7.归并排序(Merge Sort)

基本思想:
归并排序就是利用归并的思想实现的排序方法。它的原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到[n/2]个长度为2或1的有序子序列;再两两归并,…,如此重复,知道得到一个长度为n的有序序列为止,这种排序方法成为2路归并排序。
归并排序示例:

合并方法:
设r[i…n]由两个有序子表r[i…m]和r[m+1…n]组成,两个子表长度分别为n-i +1、n-m。
1.j=m+1;k=i;i=i; //置两个子表的起始下标及辅助数组的起始下标
2.若i>m 或j>n,转⑷ //其中一个子表已合并完,比较选取结束
3.选取r[i]和r[j]较小的存入辅助数组rf,如果r[i]

#include <iostream>
using namespace std;

typedef int ElemType;

void print(int a[], int n)
{
       for (int j = 0; j<n; j++)
       {
              cout << a[j] << "  ";
       }
       cout << endl;
}

//将r[i…m]和r[m +1 …n]归并到辅助数组rf[i…n]
void Merge(ElemType *r, ElemType *rf, int i, int m, int n)
{
       int j, k;
       for (j = m + 1, k = i; i <= m && j <= n; ++k){
              if (r[j] < r[i]) rf[k] = r[j++];
              else rf[k] = r[i++];
       }
       while (i <= m)  rf[k++] = r[i++];
       while (j <= n)  rf[k++] = r[j++];
       print(rf, n + 1);
}

void MSort(ElemType *r, ElemType *rf, int s, int t)
{
       ElemType rf2[10];
       if (s == t) r[s] = rf[s];
       else
       {
              int m = (s + t) / 2;          /*平分*p 表*/
              MSort(r, rf2, s, m);        /*递归地将p[s…m]归并为有序的p2[s…m]*/
              MSort(r, rf2, m + 1, t);      /*递归地将p[m+1…t]归并为有序的p2[m+1…t]*/
              Merge(rf2, rf, s, m + 1, t);   /*将p2[s…m]和p2[m+1…t]归并到p1[s…t]*/
       }
}
void MergeSort_recursive(ElemType *r, ElemType *rf, int n)
{   /*对顺序表*p 作归并排序*/
       MSort(r, rf, 0, n - 1);
}

//自己写的代码=====================
void Merge1(int a[],int res[], int low, int mid, int high)
{
       int i = low, j = mid +1, k = low;
       while (i <= mid&&j <= high)
       {
              if (a[i] <= a[j])
              {
                     res[k] = a[i];
                     k++;i++;
              }
              else
              {
                     res[k] = a[j];
                     k++;j++;
              }

       }
       if (i <= mid) //如果前半部分有元素比后半部分所有元素的值都大
       {
              while (k <= high)
              {
                     res[k] = a[i];
                     k++;i++;
              }
       }
       if (j <= high)
       {
              while (k <= high)    //如果后半部分有元素比前半部分所有元素的值都大
              {
                     res[k] = a[j];
                     k++;j++;
              }
       }
}

void myprint(int a[], int low, int high)
{
       for (int j = low; j<=high; j++)
       {
              cout << a[j] << "  ";
       }
       cout << endl;
}

void MergeSort1(int a[],int res[],int low,int high,int length)
{
       int mid = (low+high) / 2;
       int *temp = new int[length];     //定义一个辅助数组,用来存放每次归并的结果。

       if (low < high)
       {
              MergeSort1(a, temp, low, mid, length);   //把a的前半部分归并到res相应的位置中

              MergeSort1(a, temp, mid + 1, high,length);      //把a的后半部分归并到res相应的位置中

              Merge1(temp, res, low, mid, high);//吧数组a的前后两部分 有序的排到res中
              myprint(res, low,high);
       }
       else if (low==high)
       {
              res[low] = a[low];
       }
       delete[] temp;
}

//自己写的代码=====================

int main(){
       int a[10] = { 3, 1, 5, 7, 2, 4, 9, 6, 10, 8 };
       int b[10];
       //MergeSort_recursive(a, b, 10);
       MergeSort1(a, b, 0,9,10);
       print(b, 10);
       cout << "结果:" << endl;
       print(a, 10);

       return 0;
}

参考代码和自己代码相比较:

需要注意的地方:
算法除了要先开辟一个数组保存结果外,在归并的时候,归并的结果要先保存在一个辅助空间中,然后在对辅助空间的两个数组进行归并;
在Merge算法中,注意归并的时候,两个数组的指针都要遍历到最后一位;

效率:
时间复杂度:O(nlogn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值