• 排序算法


    算法比较

    稳定性

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

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

    复杂度

    排序方法 平均时间 最坏情况 辅助空间
    插入排序 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);无论是最坏或者平均情况皆如此;

  • 相关阅读:
    Dive into ML
    tensorflow中模型的保存
    scope 命名方法
    Tensorflow中的Seq2Seq全家桶
    梯度消失与梯度爆炸问题
    Tensorflow 模型的保存、读取和冻结、执行
    训练神经网络的处方
    About Feature Scaling and Normalization
    论文中绘制神经网络的工具
    前端 OSS 自动化部署脚本
  • 原文地址:https://www.cnblogs.com/wanpengcoder/p/11768449.html
Copyright © 2020-2023  润新知