七种排序

常用的排序算法的时间复杂度和空间复杂度

排序法

最差时间分析平均时间复杂度稳定度空间复杂度
冒泡排序O(n2)O(n2)稳定O(1)
快速排序O(n2)O(n*log2n)不稳定O(log2n)~O(n)
选择排序O(n2)O(n2)不稳定O(1)
二叉树排序O(n2)O(n*log2n)不一定O(n)

插入排序

O(n2)O(n2)稳定O(1)
堆排序O(n*log2n)O(n*log2n)不稳定O(1)
希尔排序OO不稳定O(1)

稳定性: 在排序之前,有两个数相等. 但是在排序结束之后,它们两个有可能改变顺序.

几种排序算法视频舞蹈讲解:

http://v.youku.com/v_show/id_XMzMyODk4NTQ4.html

快速排序  http://blog.csdn.net/v_JULY_v/article/details/6262915

思想:

快速排序是冒泡排序的改进版,是目前已知的最快的排序方法。

已知一组无序数据a[1]a[2]……a[n],需将其按升序排列。首先任取数据a[x]作为基准。比较a[x]与其它数据并排序,使a[x]排在数据的第k位,并且使a[1]~a[k-1]中的每一个数据<a[x]a[k+1]~a[n]中的每一个数据>a[x],然后采用分治的策略分别对a[1]~a[k-1]a[k+1]~a[n]两组数据进行快速排序。

稳定性:不稳定

时间复杂度:快速排序的最坏时间复杂度应为 O(n^2 ),最好时间复杂度为 O(n×lgn),平均时间复杂度为 O(n×lgn)。

空间复杂度:递归后所需栈空间为O(lgn) 。最坏情况下,递归树的高度为 O(n), 所需的栈空间为 O(n) 。

C语言代码实现:

// 快速排序的递归实现 

//快速排序

#include<stdio.h>

voidprintList(int *list,int n)

{

   int i;

   for(i=0;i<n;i++)

   {

      printf("%d   ",list[i]);

   }

   printf("\n");  

}

 

 

voidQuickSort(int list[],int startP,int endP)

{

 

   int i = startP , j = endP;

   int key = list[startP];

   if(i>j)//退出递归调用的条件

      return;

       

    while(i < j)

     {

             // 从后向前搜索

             while(j > i && list[j]>key)

             j--;

             if(i<j)

                 list[i++] = list[j];

                

              //printList(list,10);  

        

             // 从前向后搜索

             while(i < j && list[i]<key)

             i++;

             if(i<j)

             list[j--]=list[i];

 

         //printList(list,10);

     }

     list[i]=key;

     printList(list,10);

     QuickSort(list, startP,i-1);

     QuickSort(list, i+1, endP);

 

}

 

 

int main()

{

   int list[10]={4,6,2,8,4,9,0,3,5,4};

   int i;

   printList(list,10);

  

   QuickSort(list,0,9);

  

   printList(list,10);

   return 0;

}

 

堆排序

堆排序与快速排序, 归并排序一样都是时间复杂度为 O(N*logN)的几种常见排序方法。学习堆排序前,先讲解下什么是数据结构中的二叉堆。 

思想:

稳定性:

时间复杂度:

空间复杂度:

C语言代码实现:

 

 

 

归并排序

思想:归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。 

稳定性:稳定

时间复杂度:O(nlogn)

空间复杂度:

C语言代码实现:

//将有序数组a[]和b[]合并到c[]中 
void MemeryArray(int a[], int n, int b[], int m, int c[]) 

  int i, j, k; 
 
  i = j = k = 0; 
  while (i < n && j < m) 
  { 
    if (a[i] < b[j]) 
      c[k++] = a[i++]; 
    else 
      c[k++] = b[j++];   
  } 
 
  while (i < n) 
    c[k++] = a[i++]; 
 
  while (j < m) 
    c[k++] = b[j++]; 

可以看出合并有序数列的效率是比较高的,可以达到 O(n)。 解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了? 可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,
可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。 
下面给出了代码。 
// 归并排序 
// By MoreWindows (http://blog.csdn.net/MoreWindows) 
bool MergeSort(int a[], int n) 

  int *pTempArray = new int[n]; 
  if (p == NULL) 
    return false; 

  mergesort(a, 0, n - 1, pTempArray); 
  delete[] pTempArray; 
  return true; 

void mergesort(int a[], int first, int last, int temp[]) 

  if (first < last) 
  { 
    int mid = (first + last) / 2; 
    mergesort(a, first, mid, temp);        //左边有序 
    mergesort(a, mid + 1, last, temp); //右边有序 
    mergearray(a, first, mid, last, temp); //再将二个有序数列合并 
  } 

//将有二个有序数列a[first...mid]和a[mid...last]合并。 
void mergearray(int a[], int first, int mid, int last, int temp[]) 

  int i = first, j = mid + 1; 
  int m = mid,      n = last; 
  int k = 0; 

  while (i <= m && j <= n) 
  { 
    if (a[i] < a[j]) 
      temp[k++] = a[i++]; 
    else 
      temp[k++] = a[j++]; 
  } 
   
  while (i <= m) 
    temp[k++] = a[i++]; 
   
  while (j <= n) 
    temp[k++] = a[j++]; 
   
  for (i = 0; i < k; i++) 
    a[first + i] = temp[i]; 

归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。 

 

 

冒泡排序

思想:已知一组无序数据a[1]a[2]……a[n],需将其按升序排列。首先比较a[1]a[2]的值,若a[1]大于a[2]则交换两者的值,否则不变。再比较a[2]a[3]的值,若a[2]大于a[3]则交换两者的值,否则不变。再比较a[3]a[4],依此类推,最后比较a[n-1]a[n]的值。这样处理一轮后,a[n]的值一定是这组数据中最大的。再对a[1]~a[n-1]以相同方法处理一轮,则a[n-1]的值一定是a[1]~a[n-1]中最大的。再对a[1]~a[n-2]以相同方法处理一轮,依此类推。共处理n-1轮后a[1]a[2]……a[n]就以升序排列了。

稳定性:稳定

时间复杂度:O(n2)

空间复杂度:O(1)

C语言代码实现:

//冒泡排序1 
void BubbleSort1(int a[], int n) 

  int i, j; 
 
  for (i = 0; i < n; i++) 
    for (j = 1; j < n - i; j++) 
      if (a[j - 1] > a[j]) 
        Swap(a[j - 1], a[j]); 

下面对其进行优化,设置一个标志,如果这一趟发生了交换,则为true,否则为
false。明显如果有一趟没有发生交换,说明排序已经完成。 
//冒泡排序2 
void BubbleSort2(int a[], int n) 

  int j, k; 
  bool flag; 
   
  k = n; 
  flag = true; 
  while (flag) 
  { 
    flag = false; 
    for (j = 1; j < k; j++) 
      if (a[j - 1] > a[j]) 
      { 
        Swap(a[j - 1], a[j]); 
        flag = true; 
      } 
    k--; 
  } 

}
再做进一步的优化。如果有100个数的数组,仅前面10个无序,后面90个都已排
好序且都大于前面10个数字,那么在第一趟遍历后,最后发生交换的位置必定小
于10,且这个位置之后的数据必定已经有序了,记录下这位置,第二次只要从数
组头部遍历到这个位置就可以了。 
//冒泡排序3 
// By MoreWindows (http://blog.csdn.net/MoreWindows) 
void BubbleSort3(int a[], int n) 

  int j, k; 
  int flag; 
   
  flag = n; 
  while (flag > 0) 
  { 
    k = flag; 
    flag = 0; 
    for (j = 1; j < k; j++) 
      if (a[j - 1] > a[j]) 
      { 
        Swap(a[j - 1], a[j]); 

        flag = j; 
      } 
  } 

 

 

选择排序

思想:已知一组无序数据a[1]a[2]……a[n],需将其按升序排列。首先比较a[1]a[2]的值,若a[1]大于a[2]则交换两者的值,否则不变。再比较a[1]a[3]的值,若a[1]大于a[3]则交换两者的值,否则不变。再比较a[1]a[4],依此类推,最后比较a[1]a[n]的值。这样处理一轮后,a[1]的值一定是这组数据中最小的。再将a[2]a[3]~a[n]以相同方法比较一轮,则a[2]的值一定是a[2]~a[n]中最小的。再将a[3]a[4]~a[n]以相同方法比较一轮,依此类推。共处理n-1轮后a[1]a[2]……a[n]就以升序排列了。

稳定性:不稳定

时间复杂度:O(n2)

空间复杂度:O(1)

C语言代码实现:

直接选择排序无疑是最容易实现的,下面给出代码: 
// 直接选择排序 
// By MoreWindows (http://blog.csdn.net/MoreWindows) 
void Selectsort(int a[], int n) 

  int i, j, nMinIndex; 
  for (i = 0; i < n; i++) 
  { 
    nMinIndex = i; //找最小元素的位置 
    for (j = i + 1; j < n; j++) 
      if (a[j] < a[nMinIndex]) 
        nMinIndex = j; 
 
    Swap(a[i], a[nMinIndex]); //将这个元素放到无序区的开头 
  } 

在这里,要特别提醒各位注意下 Swap()的实现,建议用: 
inline void Swap(int &a, int &b) 

  int c = a; 
  a = b; 
  b = c; 

笔试面试时考不用中间数据交换二个数,很多人给出了 
inline void Swap1(int &a, int &b) 

  a ^= b; 
  b ^= a; 
  a ^= b; 


在网上搜索下,也可以找到许多这样的写法。不过这样写存在一个隐患,如果
a, b指向的是同一个数,那么调用 Swap1()函数会使这个数为 0。如: 
  int i = 6; 
  Swap1(i, i); 
  printf("%d\n", i); 
当然谁都不会在程序中这样的写代码,但回到我们的 Selectsort(),如果 a[0]就是
最小的数,那么在交换时,将会出现将 a[0]置0 的情况,这种错误相信调试起来
也很难发现吧,因此建议大家将交换二数的函数写成: 
inline void Swap(int &a, int &b) 

  int c = a; 
  a = b; 
  b = c; 

 或者在Swap1()中加个判断,如果二个数据相等就不用交换了: 
inline void Swap1(int &a, int &b) 

  if (a != b) 
  { 
    a ^= b; 
    b ^= a; 
    a ^= b; 
  } 

 

 

插入排序

思想:已知一组升序排列数据a[1]a[2]……a[n],一组无序数据b[1]b[2]……b[m],需将二者合并成一个升序数列。首先比较b[1]a[1]的值,若b[1]大于a[1],则跳过,比较b[1]a[2]的值,若b[1]仍然大于a[2],则继续跳过,直到b[1]小于a数组中某一数据a[x],则将a[x]~a[n]分别向后移动一位,将b[1]插入到原来a[x]的位置这就完成了b[1]的插入。b[2]~b[m]用相同方法插入。(若无数组a,可将b[1]当作n=1的数组a

稳定性:稳定

时间复杂度:O(n2)

空间复杂度:O(1)

C语言代码实现:

下面给出严格按照定义书写的代码(由小到大排序): 
void Insertsort1(int a[], int n) 

  int i, j, k; 
  for (i = 1; i < n; i++) 
  { 
    //为a[i]在前面的a[0...i-1]有序区间中找一个合适的位置 
    for (j = i - 1; j >= 0; j--) 
      if (a[j] < a[i]) 
        break; 
 
    //如找到了一个合适的位置 
    if (j != i - 1) 
    { 
      //将比a[i]大的数据向后移 
      int temp = a[i]; 
      for (k = i - 1; k > j; k--) 
        a[k + 1] = a[k]; 
      //将a[i]放到正确位置上 

      a[k + 1] = temp; 
    } 
  } 

这样的代码太长了,不够清晰。现在进行一下改写,将搜索和数据后移这二个步
骤合并。即每次a[i]先和前面一个数据a[i-1]比较,如果a[i] > a[i-1]说明a[0…i]也
是有序的,无须调整。否则就令j=i-1,temp=a[i]。然后一边将数据a[j]向后移动一
边向前搜索,当有数据a[j]<a[i]时停止并将temp放到a[j + 1]处。 
void Insertsort2(int a[], int n) 

  int i, j; 
  for (i = 1; i < n; i++) 
    if (a[i] < a[i - 1]) 

    { 
      int temp = a[i]; 
      for (j = i - 1; j >= 0 && a[j] > temp; j--) 
        a[j + 1] = a[j]; 
      a[j + 1] = temp; 
    } 



 

 

 

希尔排序

思想:已知一组无序数据a[1]a[2]……a[n],需将其按升序排列。发现当n不大时,插入排序的效果很好。首先取一增量d(d<n),将a[1]a[1+d]a[1+2d]……列为第一组,a[2]a[2+d]a[2+2d]……列为第二组……a[d]a[2d]a[3d]……列为最后一组依此类推,在各组内用插入排序,然后取d'<d,重复上述操作,直到d=1

该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,
待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是
很高的,因此希尔排序在时间效率上比前两种方法(冒泡排序,直接插入排序)有较大提高。 

稳定性:不稳定

时间复杂度:O(nlogn)~O(n2),平均时间复杂度大致是O(n√n)

空间复杂度:O(1)

C语言代码实现:

下面给出严格按照定义来写的希尔排序
void shellsort1(int a[], int n) 


  int i, j, gap; 
 
  for (gap = n / 2; gap > 0; gap /= 2)    //步长 
    for (i = 0; i < gap; i++)                  //按组排序       
    { 
      for (j = i + gap; j < n; j += gap)     
      { 
        if (a[j] < a[j - gap]) 
        { 
          int temp = a[j]; 
          int k = j - gap; 
          while (k >= 0 && a[k] > temp) 
          { 
            a[k + gap] = a[k]; 
            k -= gap; 
          } 
          a[k + gap] = temp; 
        } 
      } 

}


很明显,上面的shellsort1代码虽然对直观的理解希尔排序有帮助,但代码量太大
了,不够简洁清晰。因此进行下改进和优化,以第二次排序为例,原来是每次从
1A到1E,从2A到2E,可以改成从1B开始,先和1A比较,然后取2B与2A比较,
再取1C与前面自己组内的数据比较…….。这种每次从数组第gap个元素开始,每
个元素与自己组内的数据进行直接插入排序显然也是正确的。 
void shellsort2(int a[], int n) 

  int j, gap; 
   
  for (gap = n / 2; gap > 0; gap /= 2) 
    for (j = gap; j < n; j++)      //从数组第gap个元素开始 
      if (a[j] < a[j - gap])   //每个元素与自己组内的数据进行直接插入排序         
      { 
        int temp = a[j]; 
        int k = j - gap; 
        while (k >= 0 && a[k] > temp) 
        { 
          a[k + gap] = a[k]; 
          k -= gap; 
        } 
  a[k + gap] = temp; 


再将直接插入排序部分用《白话经典算法系列之二 直接插入排序的三种实现》
中直接插入排序的第三种方法来改写下: 
//希尔排序 
// By MoreWindows (http://blog.csdn.net/MoreWindows) 
void shellsort3(int a[], int n) 

  int i, j, gap; 
 
  for (gap = n / 2; gap > 0; gap /= 2) 
    for (i = gap; i < n; i++) 
      for (j = i - gap; j >= 0 && a[j] > a[j + gap]; j -= gap) 
        Swap(a[j], a[j + gap]); 

这样代码就变得非常简洁了。 
 
 附注:上面希尔排序的步长选择都是从 n/2 开始,每次再减半,直到最后为 1。
其实也可以有另外的更高效的步长选择,如果读者有兴趣了解,请参阅维基百科
上对希尔排序步长的说明: 
http://zh.wikipedia.org/wiki/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F 
 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值