zoukankan      html  css  js  c++  java
  • 排序算法

    算法比较

    稳定性

    插入排序,冒泡排序,二路归并排序和基数排序是稳定的排序方法;

    选择排序,希尔排序,快速排序和堆排序是不稳定的排序方法;

    复杂度

    排序方法 平均时间 最坏情况 辅助空间
    插入排序 O(n^2) O(n^2) O(1)
    希尔排序 O(nlogn) O(nlogn) O(1)
    冒泡排序 O(n^2) O(n^2) O(1)
    快速排序 O(nlogn) O(n^2) O(logn)
    选择排序 O(n^2) O(n^2) O(1)
    堆排序 O(nlogn) O(nlogn) O(1)
    归并排序 O(nlogn) O(nlogn) O(n)
    基数排序 O(d(n+r)) O(d(n+r)) O(r+n)

    方法选择

    (1) 排序数据的规模n较大,关键字元素分布比较随机,并且不要求排序稳定性时,宜选用快速排序;

    (2) 排序数据规模n较大,内存空间又允许,并且有排序稳定性要求,宜采用归并排序;

    (3) 排序数据规模n较大,元素分布可能出现升序或者逆序的情况,并且对排序的稳定性不要求,宜采用堆排序或者归并排序;

    (4) 排序数据规模n较小,元素基本有序,或者分布也比较随机,并且有排序稳定性要求时,宜采用插入排序;

    (5) 排序数据规模n较小,对排序稳定性又不做要求时,宜采用选择排序;

    算法实现

    插入排序

    插入是个比较容易理解的排序算法,每次取下一个未排序的数,在前面已排序的部分从后往向前查找合适的位置,并将该数插入,而不满足排序条件的数字则需要不断的复制到下一位置,为待排序数字腾出位置;

     1 void insert_sort(int a[], int n)
     2 {
     3     int i = 0;
     4     int p = 0;
     5     int temp = 0;
     6 
     7     /* 认为第一个数字是有序的,从第二个数字开始 */
     8     for (i = 1; i < n; ++i)
     9     {
    10         temp = a[i]; /* 记录待插入数字 */
    11         p = i - 1; /* 从前面已经排好序的位置中选择 */
    12         
    13         /* 查找可插入位置,注意0位置也会进入循环,p可能为-1 */
    14         while (p >= 0 && temp < a[p])
    15         {
    16             a[p+1] = a[p]; /* 把不符合插入位置的数据后移 */
    17             p--; /* 继续向前查找 */
    18         }
    19         
    20         a[p+1] = temp; /* 待排序元素插入对应位置 */
    21     }
    22 }

    (1) 插入排序接住了一个temp作为辅助空间;

    (2) 插入排序是一个稳定的排序方法;

    (3) 最优情况:当序列是以有序状态输入时,达到最小比较次数,即n-1次比较,无需移动元素;

    最坏情况:当序列是以逆序状态输入时,达到最大比较次数n(n-1)/2,需要移动n(n-1)/2次元素;

    平均情况:最优与最坏情况的平均比较次数约为n^2/4次;

    综上:该算法的平均复杂度为O(n^2);

    选择排序

    选择排序是按照位置进行的,即从第1个到第n个位置,依次选择未排序部分最小的值放入,如果最小值不是当前位置的初始值,则需要进行数值对调;

     1 void select_sort(int a[], int n)
     2 {
     3     int i = 0;
     4     int j = 0;
     5     int min = 0;
     6     int temp = 0;
     7     
     8     /* 遍历序列,最后一个元素就不需要遍历了 */
     9     for (i = 0; i < n - 1; ++i)
    10     {
    11         /* 设置最小值为当前位置 */
    12         min = i;
    13         
    14         /* 从后面查找比当前最小值还小的值的位置 */
    15         for (j = i + 1; j < n; ++j)
    16         {
    17             /* 不断与前一个最小值进行比较,如果更小则更新位置 */
    18             /* 注意这里要使用每次的最小值a[min]与后面元素进行比较 */
    19             if (a[j] < a[min])
    20             {
    21                 min = j;
    22             }
    23         }
    24         
    25         /* 起始记录位置与最小值位置不一致,说明存在更小值,交换位置 */
    26         if (i != min)
    27         {
    28             temp = a[i];
    29             a[i] = a[min];
    30             a[min] = temp;
    31         }
    32     }
    33 }

    (1) 选择排序是不稳定排序;

    (2) 选择排序的交换次数,当为有序序列时最优,交换次数为0,当为逆序序列时最坏,交换次数为3(n-1),其中3为交换操作;

    (3) 选择排序的比较次数是不随序列是否有序的原始状态变化的,均为n(n-1)/2次比较;即算法的平均复杂度为O(n^2);

    冒泡排序

    冒泡排序的思想是未排序的部分从头到尾两两比较,若逆序则交换,每次排序后,最大的元素都会在序列尾端,然后再对前面未排序的部分进行起泡;

     1 /* 进一步优化,可以加flag判断是否交换,若无交换则排序完成 */
     2 /* 如下面注释掉的部分代码 */
     3 void bubble_sort(int a[], int n)
     4 {
     5     int i = 0;
     6     int j = 0;
     7     int temp = 0;
     8     //int flag = 0;
     9     
    10     /* 不断将最大数起泡到序列结尾,n-1次完成 */
    11     for (i = 0; i < n - 1; ++i)
    12     {
    13         /* 如果没有进行交换,则说明排序完成,直接退出 */
    14         //if (flag == 0)
    15         //{
    16         //    break;
    17         //}
    18         
    19         /* 每次排序都需要重置标记 */
    20         //flag = 0;
    21     
    22         /* 对尚未排好序的序列从头到尾进行起泡 */
    23         for (j = 0; j < n-1-i; ++j)
    24         {
    25             /* 交换数据 */
    26             if (a[j] > a[j+1])
    27             {
    28                 temp = a[j];
    29                 a[j] = a[j+1];
    30                 a[j+1] = temp;
    31                 
    32                 /* 有交换,则打标记 */
    33                 //flag = 1;
    34             }
    35         }
    36     
    37     }
    38 }

    (1) 冒泡排序是稳定的排序;

    (2) 分析使用flag的情况,如果序列有序时最优,只需要进行一次气泡过程即可,元素位置不变;如果序列逆序时最坏,需要进行n(n-1)/2比较;所以冒泡排序的评价时间复杂度为O(n^2);

    希尔排序

    对于插入排序和冒泡排序等,在序列基本有序的情况下,会得到更好的排序时间;希尔排序的思想是在进行上述排序之前,对元素进行移动使序列达到基本有序,从而减少比较和移动次数;希尔排序首先对所有元素按照一个gap为一组,组中元素小的往前移动,这样在达到最后一次排序之前,小的元素基本上都已经移动到前侧了,而最后一次gap=1的排序,可以认为是对基本有序的序列进行插入或冒泡排序,所需比较和移动次数大大减少;

     1 void shell_sort(int a[], int n)
     2 {
     3     int i = 0;
     4     int p = 0;
     5     int gap = 0;
     6     
     7     /* gap的规则,按照折半缩小,直到gap=1时,进行直接插入排序 */
     8     for (gap = n/2; gap > 0; gap /= 2)
     9     {
    10         /* 从gap开始,对属于每个gap范围的元素中小元素进行前移 */
    11         for (i = gap; i < n; ++i)
    12         {
    13             temp = a[i];
    14             p = i - gap;
    15             
    16             /* 前移位置查找 */
    17             while (p >= 0 && a[p] > temp)
    18             {
    19                 a[p+gap] = a[p];
    20                 p -= gap;
    21             }
    22             
    23             /* 插入合适位置 */
    24             a[p+gap] = temp;
    25         }
    26     }
    27 }

    (1) 希尔排序是不稳定的排序;

    (2) 希尔排序的平均时间复杂度为O(nlogn);

    (3) 希尔排序的gap取法,以及内部的移动方式也不是固定的;

    快速排序

    快速排序的思想是找一个基准元素,然后将序列中比基准元素小的放到左侧,大的放到右侧,然后在分别对基准元素左右的序列再次重复上述步骤;

     1 int partion(int a[], int low, int high)
     2 {
     3     /* 最左侧元素为基准值 */
     4     int t = a[low];
     5     
     6     /* 左右不相等则进行比对,相等则对调完毕 */
     7     while (low < high)
     8     {
     9         /* 从右侧向左侧查找小于基准值的元素 */
    10         while (low < high && a[high] >= t)
    11         {
    12             high--;
    13         }
    14         
    15         /* 较小值移动到左侧*/
    16         a[low] = a[high];
    17         
    18         /* 从左侧向右侧查找大于基准值的元素 */
    19         while (low < high && a[low] <= t)
    20         {
    21             low++;
    22         }
    23         
    24         /* 较大值移动到右侧 */
    25         a[high] = a[low];
    26     }
    27     
    28     /* 基准元素放到中间位置 */
    29     a[low] = t;
    30     
    31     return low;
    32 }
    33 
    34 
    35 void quick_sort(int a[], int low, int high)
    36 {
    37     int pivot = 0;
    38     
    39     if (low < high)
    40     {
    41         /* 分区 */
    42         pivot = partion(a, low, high);
    43         
    44         /* 分别对左右区域做排序 */
    45         quick_sort(a, low, pivot - 1);
    46         quick_sort(a, pivot + 1, high);
    47     }
    48 }

    (1) 快速排序是一种不稳定的排序;

    (2) 在序列有序的情况下,快排退化为冒泡排序,此时的时间复杂度最高,约为O(n^2);在划分中如果均为中位数时,时间复杂度为O(nlogn);但对于平均情况来说,快排仍然是最好的内排序方法;

    (3) 分界的基准元素取值方法有多种,通常是首元素,尾元素和中间元素;

    归并排序

    归并排序的思想是将待排序序列进行区域划分与合并,先将整个序列分成两个序列,然后其中的每个在分成两个,依次细分,然后对小区域进行归并,归并之后再对上层区域进行归并,最终得到有序序列;

     1 void merge(int a[], int low, int mid, int high, int temp[])
     2 {
     3     int i = low;
     4     int j = mid + 1;
     5     int k = 0;
     6     
     7     /* 两个区域都从头开始比较大小,合并到temp */
     8     while (i <= mid && j <= high)
     9     {
    10         if (a[i] <= a[j])
    11         {
    12             temp[k++] = a[i++];
    13         }
    14         else
    15         {
    16             temp[k++] = a[j++];
    17         }
    18     }
    19     
    20     /* 若其中一个区域有剩余元素,则直接并入 */
    21     while (i <= mid)
    22     {
    23         temp[k++] = a[i++];
    24     }
    25     
    26     while (j <= high)
    27     {
    28         temp[k++] = a[j++];
    29     }
    30     
    31     
    32     /* 将temp填入待排序列中 */
    33     for (i = 0; i < k; ++i)
    34     {
    35         a[low+i] = temp[i];
    36     }
    37 }
    38 
    39 
    40 void merge_sort(int a[], int low, int high, int temp[])
    41 {
    42     int mid = 0;
    43     
    44     if (low < high)
    45     {
    46         /* 取中位数 */
    47         mid = (low + high)/2;
    48         
    49         /* 分别对左右进行归并排序 */
    50         merge_sort(a, low, mid, temp);
    51         merge_sort(a, mid + 1, high, temp);
    52         
    53         /* 排序的合并过程 */
    54         merge(a, low, mid, high, temp);
    55     }
    56 }

    (1) 归并排序是一种稳定的排序算法;

    (2) 归并排序需要一个与待排序序列同样大小的空间;

    (3) 归并排序的时间复杂度为O(nlogn);

    堆排序

    堆排序的思想是把一个待排序序列看成一个近似完全二叉树,第一步是从最后一个非叶子节点开始一直到第一个节点,对序列进行调整,调整之后进行排序;排序首先将0位置的最大节点与最后一个元素相交换,然后对前面的0元素按照堆的规则进行调整;如上,直至全部元素排序结束;

     1 void heap_adjust(int a[], int i, int n)
     2 {
     3     int child = 0;
     4     int temp = 0;
     5     
     6     /* 对i节点进行调整 */
     7     while (i * 2 + 1 < n)
     8     {
     9         /* 找到做孩子节点 */
    10         child = 2 * i + 1;
    11         
    12         /* 如果存在右孩子,并且右孩子比较大,那么记录改成右孩子 */
    13         if (child < n - 1 && a[child + 1] > a[child])
    14         {
    15             child++;
    16         }
    17         
    18         /* 如果父节点大于等于最大的孩子节点,不需要调整 */
    19         if (a[i] >= a[child])
    20         {
    21             break;
    22         }
    23         /* 父节点小于孩子节点,则需要与孩子节点对调 */
    24         else
    25         {
    26             temp = a[i];
    27             a[i] = a[child];
    28             a[child] = temp;
    29         }
    30     
    31         /* 继续调整孩子节点 */
    32         i = child;
    33     }
    34 }
    35 
    36 void heap_sort(int a[], int n)
    37 {
    38     int i = 0;
    39     int temp = 0;
    40     
    41     
    42     /* 起始a[]认为是个数组形式表示的近似完全二叉树 */
    43     
    44     /* 从最后一个非叶子节点开始,向前逐步调整堆 */
    45     for (i = n / 2 - 1; i >= 0; --i)
    46     {
    47         heap_adjust(a, i, n);
    48     }
    49     
    50     /* 排序过程为第一个元素与最后一个未排序元素交换 */
    51     /* 然后调整前面未排序的部分,保证最大的首节点放到最后 */
    52     /* 调整之后,新的最大节点在根0位置 */
    53     for (i = n - 1; i > 0; --i)
    54     {
    55         /* 交换首元素和最后未排序元素 */
    56         temp = a[0];
    57         a[0] = a[i];
    58         a[i] = temp;
    59         
    60         /* 调整堆 */
    61         heap_adjust(a, 0, i);
    62     }
    63 }

    (1) 堆排序是一种不稳定的排序;

    (2) 堆排序的时间复杂度是O(nlogn);无论是最坏或者平均情况皆如此;

  • 相关阅读:
    Luogu 1080 【NOIP2012】国王游戏 (贪心,高精度)
    Luogu 1314 【NOIP2011】聪明的质检员 (二分)
    Luogu 1315 【NOIP2011】观光公交 (贪心)
    Luogu 1312 【NOIP2011】玛雅游戏 (搜索)
    Luogu 1525 【NOIP2010】关押罪犯 (贪心,并查集)
    Luogu 1514 引水入城 (搜索,动态规划)
    UVA 1394 And Then There Was One / Gym 101415A And Then There Was One / UVAlive 3882 And Then There Was One / POJ 3517 And Then There Was One / Aizu 1275 And Then There Was One (动态规划,思维题)
    Luogu 1437 [HNOI2004]敲砖块 (动态规划)
    Luogu 1941 【NOIP2014】飞扬的小鸟 (动态规划)
    HDU 1176 免费馅饼 (动态规划)
  • 原文地址:https://www.cnblogs.com/wanpengcoder/p/11768449.html
Copyright © 2011-2022 走看看