数据结构——排序基础

169 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

内部排序分类

内部排序方法分为五类:交换排序、选择排序、插入排序、归并排序和基数排序。

  • 交换排序:交换排序是对无序区中记录的关键字两两进行比较,若逆序则交换,直到关键字之间不存在逆序为止。冒泡排序和快速排序是交换排序中最典型的两个方法。
  • 选择排序:选择排序是在无序区中选出关键字最小的记录,置于有序区的最后,直到全部记录有序。简单选择排序和堆排序是选择排序中最典型的两个方法。
  • 插入排序:插入排序是将无序区中的一个记录插入至有序区,使得有序区的长度加1,直到全部记录有序。直接插入排序和希尔排序是插入排序中最具代表性的两个方法。
  • 归并排序:归并排序是不断将两个或两个以上有序区合并成一个有序区,直到全部记录有序。
  • 基数排序:基数排序是和前面所述各类排序方法完全不同的一种排序方法,不需要进行记录关键字的比较

内部排序按时间复杂度可分为3类:

  1. 简单的排序方法,时间复杂度为O(n^2);
  1. 先进的排序方法,时间复杂度为O(nlogn);
  1. 基数排序,时间复杂度为O(n)。

直接插入排序

从R[i-1]起向前进行顺序查找

设置哨兵结点R[0],记录R[i]的位置

要点:每次都从后往前查找,查找的同时记录向后移动

void InsertionSort ( RcdSqList &L ) {
  // 对顺序表 L 作直接插入排序。
   for ( i=2; i<=L.length; ++i ) 
       if (L.rcd[i].key < L.rcd[i-1].key) {
            L.rcd[0] = L.rcd[i];          // 复制为监视哨
            for (j=i-1; L.rcd[0].key < L.rcd[j].key;  -- j)
                L.rcd[j+1] = L.rcd[j];       // 记录后移
            L.rcd[j+1] = L.rcd[0];     // 插入到正确位置
       }
} // InsertSort

希尔排序

希尔排序是将整个待排记录序列,按增量d划分成d个子序列,再将d个子序列进行直接插入排序

每完成一次分组排序的d都减半

一趟希尔排序:

void ShellInsert(RcdSqList &L, int dk) { // 对顺序表L作一趟希尔排序,增量为dk
   int i, j;
   for(i = 1; i<=L.length-dk; ++i)
      if(L.rcd[i+dk].key < L.rcd[i].key) { // 需将L.rcd[i+dk]插入有序序列
         L.rcd[0] = L.rcd[i+dk];  // 暂存在L.rcd[0] 
         j = i+dk;
        do{ j-=dk; L.rcd[j+dk] = L.rcd[j]; // 记录后移
        }while(j-dk>0 && L.rcd[0].key<L.rcd[j-dk].key); // 判断是否需要继续移动
        L.rcd[j] = L.rcd[0]; // 插入 
     }
}

void ShellInsert ( RcdSqList &L, int dk ) {
   for ( i=dk+1; i<=n; ++i )
      if ( L.rcd[i].key< L.rcd[i-dk].key) {
        L.rcd[0] = L.rcd[i];            // 暂存在R[0]
        for(j=i-dk; j>0&&L.rcd[0].key<L.rcd[j].key;
                j-=dk)
           L.rcd[j+dk] = L.rcd[j]; 
                            //记录后移,查找插入位置
        L.rcd[j+dk] = L.rcd[0];   // 插入
      } // if
} // ShellInsert

希尔排序:

void ShellSort(RcdSqList &L, int d[], int t) {
   // 按增量序列d[0..t-1]对顺序表L作希尔排序
   int k;
   for( k = 0; k<t; ++k )
      ShellInsert(L, d[k]); //一趟增量为d[k]的插入排序 
} 

基数排序

多关键字排序

关键字是分主次的

有高位优先排序和低位优先排序

  • 高位优先:

    • 先按最主位关键字k0进行排序,得到若干子序列,其中每个子序列中的记录都含相同的k0值
    • 接着分别就每个子序列按关键字k1进行排序,致使k1值相同的记录构成长度更短的子序列;
    • 依次重复,直至就当前得到的各个子序列按km-1 进行从小到大进行排序;
    • 最后由这些子序列依次相连所得序列便是排序的最后结果。
  • 低位优先排序:

    • 先按最低位关键字进行排序 ;
    • 接着按次低位关键字实施排序 ;
    • 最后按最主位关键字进行排序。
    • 与高位优先排序不同,其排序过程中不产生子序列,每趟都是对整个序列进行排序。

基数排序

将记录的关键字看成由m个关键字复合而成,每个关键字可能取r个值,则只要从最低位关键字起,先按关键字的不同值将记录“分配”到r个子序列,再按从小到大将各子序列依次首尾相接“收集”在一起,如此重复m趟,最终完成整个记录序列的排序。按这种方法实现的排序称为基数排序。(其中基数r指的是“关键字”的取值范围 )

若关键字k是字符串,则可把字符串中的每一个字母看成一个关键字,即r = 26。

计数基数排序

  • 数据类型定义

    • typedef struct {
        KeysType *keys;     // 关键字
        ……                         // 其它数据项
      } KeysRcdType;    // 基数排序中的记录类型
      
      typedef struct {
      KeysRcdType *rcd;       // 0号位置做哨兵
      int length;                   // 顺序表长度
      int size;                         // 顺序表容量
      int digitNum;              // 关键字位数
      int radix;                       // 关键字基数
      } KeysSqList; // 顺序表类型
      

关键要点:引入三个数组

  • 数组count用于统计关键字的r种取值的个数。count[i]是对值i的计数;
  • 数组pos用于确定各子序列的起始位置。pos[i]是值为i的子序列的起始位置。
  • 数组rcd1与rcd类型相同。在各趟收集中,第一趟从数组rcd收集到rcd1,第二趟从rcd1收集到rcd
  • 如此交替进行,若总趟数为奇数,最后还需将排序结果从数组rcd1复制回rcd。

\