zoukankan      html  css  js  c++  java
  • 常见排序算法总结

    部分转自 http://blog.csdn.net/whuslei/article/details/6442755

    排序算法经过了很长时间的演变,产生了很多种不同的方法。对于初学者来说,对它们进行整理便于理解记忆显得很重要。每种算法都有它特定的使用场合,很难通用。因此,我们很有必要对所有常见的排序算法进行归纳。

         我不喜欢死记硬背,我更偏向于弄清来龙去脉,理解性地记忆。比如下面这张图,我们将围绕这张图来思考几个问题。

    image

         上面的这张图来自一个PPT。它概括了数据结构中的所有常见的排序算法。现在有以下几个问题:

         1、每个算法的思想是什么?
         2、每个算法的稳定性怎样?时间复杂度是多少?
         3、在什么情况下,算法出现最好情况 or 最坏情况?
         4、每种算法的具体实现又是怎样的?

         这个是排序算法里面最基本,也是最常考的问题。下面是我的小结。

    一,冒泡排序:

    1、简介:冒泡排序是最简单的排序,是刚学c语言时最早接触到的一个算法。

    他的思想就是,对待排序元素的关键字从后往前进行多遍扫描,遇到相邻两个关键字次序与排序规则不符时,就将这两个元素进行交换。这样关键字较小的那个元素就像一个泡泡一样,从最后面冒到最前面来

      2、时间复杂度 
         最好情况下:
    正序有序,则只需要比较n次。故,为O(n) 
          最坏情况下:  逆序有序,则需要比较(n-1)+(n-2)+……+1,故,为O(N*N)

      3、稳定性 
          排序过程中只交换相邻两个元素的位置。因此,当两个数相等时,是没必要交换两个数的位置的。所以,它们的相对位置并没有改变,冒泡排序算法是稳定的

    代码

     1 void BubbleSort(int a[], int n)
     2 {
     3     for(int i = 0 ; i < n; i++)
     4     {
     5         for(int j = n-1; j > i; j--)
     6         {
     7             if(a[j] < a[j - 1])
     8             {
     9                 swap(a[j], a[j - 1]);
    10             }
    11         }
    12     }
    13 }

    二、插入排序

    1、思想:如下图所示,每次选择一个元素K插入到之前已排好序的部分A[1…i]中,插入过程中K依次由后向前与A[1…i]中的元素进行比较。若发现发现A[x]>=K,则将K插入到A[x]的后面,插入前需要移动元素。

    image

         2、算法时间复杂度。 
            最好的情况下:正序有序(从小到大),这样只需要比较n次,不需要移动。因此时间复杂度为O(n) 
            最坏的情况下:逆序有序,这样每一个元素就需要比较n次,共有n个元素,因此实际复杂度为O(n­2
            平均情况下:O(n­2)

         3、稳定性。 
         理解性记忆比死记硬背要好。因此,我们来分析下。稳定性,就是有两个相同的元素,排序先后的相对位置是否变化,主要用在排序时有多个排序规则的情况下。在插入排序中,K1是已排序部分中的元素,当K2和K1比较时,直接插到K1的后面(没有必要插到K1的前面,这样做还需要移动!!),因此,插入排序是稳定的。

    代码

     1 void InsertSort(int a[], int n)
     2 {
     3    int i , j;
     4    for(i = 1 ; i < n; i++)
     5    {
     6        int tmp = a[i];
     7        for(j = i - 1; j >= 0; j--)
     8        {
     9            if(tmp < a[j])
    10            {   // 向后移动一位,因为a[i]的值赋给了k,所以直接赋值即可
    11                a[j+1] = a[j];
    12            }
    13            else break;
    14        }
    15        a[j+1] = tmp;
    16    }
    17 }

    三、希尔排序(插入排序)

         1、思想:希尔排序也是一种插入排序方法,实际上是一种分组插入方法。先取定一个小于n的整数d1作为第一个增量,把表的全部记录分成d1个组,所有距离为d1的倍数的记录放在同一个组中,在各组内进行直接插入排序;然后,取第二个增量d2(<d1),重复上述的分组和排序,直至所取的增量dt=1(dt<dt-1<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。    

         例如:将 n 个记录分成 d 个子序列:
           { R[0],   R[d],     R[2d],…,     R[kd] }
           { R[1],   R[1+d], R[1+2d],…,R[1+kd] }
             …
           { R[d-1],R[2d-1],R[3d-1],…,R[(k+1)d-1] }

         image
         说明:d=5 时,先从A[d]开始向前插入,判断A[d-d],然后A[d+1]与A[(d+1)-d]比较,如此类推,这一回合后将原序列分为d个组。<由后向前>

         2、时间复杂度。 
         最好情况:由于希尔排序的好坏和步长d的选择有很多关系,因此,目前还没有得出最好的步长如何选择(现在有些比较好的选择了,但不确定是否是最好的)。所以,不知道最好的情况下的算法时间复杂度。 
         最坏情况下:O(N*logN),最坏的情况下和平均情况下差不多。 
         平均情况下:O(N*logN)

         3、稳定性。 
         由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。(有个猜测,方便记忆:一般来说,若存在不相邻元素间交换,则很可能是不稳定的排序。)

    代码:

     1 void ShellSort(int a[], int n, int d)
     2 {
     3    int i,j,k,len;
     4    for( len = d; len >= 1; len--)// 步长
     5    {
     6        for( k = 0 ; k < len  ; k++)// 分组的起始位置
     7        {
     8           // 下面就是插入排序
     9           for(i = k + len ; i < n; i+=len)
    10           {
    11               int tmp = a[i];
    12               for(j = i - len; j >= 0; j-=len)
    13               {
    14                   if( tmp < a[j])
    15                      a[j+len] = a[j];
    16                   else break;
    17               }
    18               a[j+len] = tmp;
    19           }
    20        }
    21    }
    22 }

     四、快速排序

    快速排序采用了分治算法策略,它是冒泡排序的一种改进。

    基本思路是:把待排列的数据分为两个子列,从数列中挑出一个数作为基准,遍历其他数据,把小于它的放前面,大的放在基准的后面。之后,通过递归,将各个子序列划分为更小的序列,直到把小于基准值元素的子数列和大于基准值元素的字数列排序。

    快速排序示意图:

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<math.h>
     4 #include<algorithm>
     5 #include<iostream>
     6 using namespace std ;
     7 int a[100];
     8 int LEN;
     9 void QuikSort(int a[], int l, int r)
    10 {
    11     if(l < r )
    12     {
    13         int k = a[l];// 取第一个值为标量
    14         int head = l;
    15         int tail = r;
    16         while(head < tail)
    17         {
    18             // a[tail] >= k 必须是大于等于,否则当含有大量相等元素时,如(2,2,2,2,2)会出现死循环
    19             while(head < tail && a[tail] >= k)tail--;
    20             a[head] = a[tail];
    21             while(head <  tail && a[head] <= k)head++;
    22             a[tail] = a[head];
    23 
    24         }
    25         a[head] = k;
    26 
    27         QuikSort(a, l, head - 1);
    28         QuikSort(a, head+1, r);
    29 
    30     }
    31 }
    32 int main()
    33 {
    34 
    35     while(~scanf("%d",&LEN))
    36     {
    37         for(int i  = 0 ; i < LEN; i++)
    38             scanf("%d", &a[i]);
    39         QuikSort(a, 0, LEN - 1);
    40 
    41         for(int i = 0; i < LEN; i++)
    42             printf("%d ", a[i]);
    43 
    44     }
    45 
    46 
    47 }

     五、堆排序

    建堆是一个从下往上进行“筛选”的过程 (首先要把底部的建成小堆,前面调整是因为只有堆顶,其它都已经是堆了。当我建堆到堆顶是也是从堆顶往下筛选)(所以说建堆大范围是从下往上筛选,在添加该结点时,还得从该节点往下筛选确保添加该节点后还是堆)。
    如下图建堆过程:  从97 开始->65->38 ->49这是从下往上(大范围从下往上)。第二个图到65时又 65与13 调整了(从上往下调整)。当到49时也是49<-> 13  <-> 27所以也是从上之下调整(为了确保加入该结点后还是堆)。


     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<math.h>
     4 #include<algorithm>
     5 #include<iostream>
     6 using namespace std ;
     7 int a[100];
     8 int LEN;
     9 // 从底向上逐步调节每一个非叶子节点,使其符合大顶堆或者小顶堆的结构
    10 void HeapAdjust(int a[], int i, int n)
    11 {
    12      int l = 2*i;
    13      int r = 2*i + 1;
    14      int largest = i;
    15      if(l <= n&&a[i] < a[l])
    16         largest = l;
    17      if(r <= n && a[largest] < a[r])
    18         largest = r;
    19 
    20      if(largest != i)
    21      {
    22          int tmp = a[i];
    23          a[i] = a[largest];
    24          a[largest] = tmp;
    25          HeapAdjust(a, largest, n);
    26 
    27      }
    28 
    29 }
    30 void BuilMaxHeap(int a[])
    31 {
    32     for(int i = LEN/2; i >= 1; i--)
    33     {
    34         HeapAdjust(a, i, LEN);
    35     }
    36     printf("first max heap is:
    ");
    37     for(int  i = 1;  i <= LEN; i++)
    38             printf("%d ", a[i]);
    39     printf("
    ");
    40 
    41 }
    42 void HeapSort(int a[])
    43 {
    44     BuilMaxHeap(a);
    45     int n = LEN;
    46     int tmp;
    47     for(int i = LEN; i > 1; i--)
    48     {
    49         tmp = a[1];
    50         a[1] = a[i];
    51         a[i] = tmp;
    52         HeapAdjust(a, 1, i - 1);
    53     }
    54 }
    55 int main()
    56 {
    57 
    58     while(~scanf("%d",&LEN))
    59     {
    60         for(int i = 1 ; i <= LEN ; i++)
    61         {
    62             scanf("%d",&a[i]);
    63         }
    64         HeapSort(a);
    65         printf("after sort:
    ");
    66         for(int  i = 1;  i <= LEN; i++)
    67             printf("%d ", a[i]);
    68     }
    69 
    70 
    71 }
    View Code

    六、归并排序

    合并排序:

     合并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。合并排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。合并排序也叫归并排序。

    代码:

     
     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<math.h>
     4 #include<algorithm>
     5 #include <stack>
     6 #include<iostream>
     7 #include <stdexcept>
     8 #define maxn 1200
     9 using namespace std ;
    10 void MergeArray(int a[], int first, int mid, int last, int c[])
    11 {
    12     int i = first;
    13     int j = mid + 1;
    14     int k = first;
    15     while(i <= mid && j <= last)
    16     {
    17         if(a[i] <= a[j]) c[k++] = a[i++];
    18         else c[k++] = a[j++];
    19     }
    20 
    21     while(i <= mid)c[k++] = a[i++];
    22     while(j <= last)c[k++] = a[j++];
    23 
    24     for(int i = first; i <= last; i++)
    25         a[i] = c[i];
    26 }
    27 void MergeSort(int a[], int l, int r, int c[])
    28 {
    29    if(l >= r )return ;
    30    int mid = (l+r)/2;
    31    MergeSort(a, l, mid, c);
    32 
    33    MergeSort(a, mid+1, r, c);
    34 
    35    MergeArray(a, l, mid, r, c);
    36 
    37 }
    38 int main()
    39 {
    40     int a[] = {1,4,6,7,9,2,3,5,8,10};
    41     int c[100];
    42 
    43     MergeSort(a, 0, 9, c);
    44     for(int i = 0 ; i < 10; i++)
    45     {
    46         printf("%d ", a[i]);
    47     }
    48     printf("
    ");
    49 
    50 }
    View Code

     ,桶排序:

    假定:输入是由一个随机过程产生的[0, 1)区间上均匀分布的实数。将区间[0, 1)划分为n个大小相等的子区间(桶),每桶大小1/n:[0, 1/n), [1/n, 2/n), [2/n, 3/n),…,[k/n, (k+1)/n ),…将n个输入元素分配到这些桶中,对桶中元素进行排序,然后依次连接桶输入0 ≤A[1..n] <1辅助数组B[0..n-1]是一指针数组,指向桶(链表)。

    他的排序过程如图:

    桶排序只是用于关键字取值范围较小的情况,否则会因为所需箱子的数目太多而导致资源的浪费。这种排序的使用价值不大,他一般用于基数排序的一个中间过程。

    2,基数排序:

    基数排序是桶排序的一种改进和推广。它的基本思想是,先设立r个队列,队列编号分别为0~r-1,(r为关键字的基数),然后按照下面的规则对关键字进行“分配”和“收集”。

    1, 按照最低有效位的值,把n个关键字分配到上述的r个队列里,然后从小到达将各队列中关键字收集起来。

    2, 再按低次有效位的值把刚刚收集起来的关键字分配到r个队列中,重复收集工作。

    3, 重复上述分配和收集工作,直到最高的有效位。(也就是说,如果数位为d,则需要重复进行d次。d由所有元素中最长的一个元素的位数计量。)

    图示如下:

    上图过程,就是先按个位分配然后收集,再按十位,百位分配和收集,最后就得出排序结果。

    在C++中可以使用库函数lexicongraphical_compare()进行字典次序比较。

    每一趟分配的时间是O(n),所以总时间的开销为O(d(n+r)) = O(n),通常d,r为常数。空间负复杂度为O(n+r)。基数排序使用于采用链式结构存储结构的排序。

    计数排序:

    计数排序非常基础,他的主要目的是对整数排序并且会比普通的排序算法性能更好。例如,输入{1, 3, 5, 2, 1, 4}给计数排序,会输出{1, 1, 2, 3, 4, 5}。这个算法由以下步骤组成:

    1. 初始化一个计数数组,大小是输入数组中的最大的数。
    2. 遍历输入数组,遇到一个数就在计数数组对应的位置上加一。例如:遇到5,就将计数数组第五个位置的数加一。
    3. 把计数数组直接覆盖到输出数组(节约空间)。
    • 例子

    输入{3, 4, 3, 2, 1},最大是4,数组长度是5。

    建立计数数组{0, 0, 0, 0}。

    遍历输入数组:

    {3, 4, 3, 2, 1} -> {0, 0, 1, 0}
    {3, 4, 3, 2, 1} -> {0, 0, 1, 1}
    {3, 4, 3, 2, 1} -> {0, 0, 2, 1}
    {3, 4, 3, 2, 1} -> {0, 1, 2, 1}
    {3, 4, 3, 2, 1} -> {1, 1, 2, 1}

  • 相关阅读:
    js 对象合并
    python3 TypeError: 'str' does not support the buffer interface in python
    django rest framework 再撸体验
    linux shell输入重定向
    httpie 取代 curl
    wget 断点续传 & nginx文件服务器
    select2 demo
    vmare centos 6.8 minimal 无法上网
    protocol http not supported or disabled in libcurl apt-get
    python3 -pip
  • 原文地址:https://www.cnblogs.com/acSzz/p/5264056.html
Copyright © 2011-2022 走看看