内部排序|青训营

150 阅读5分钟

排序所涉及操作:

  • 比较
  • 移动

插入排序

直接插入排序

  • 排序策略:在有序表的恰当处插入一个新元素,并保持该有序表的有序性。即插入第i个元素时,1~i-1个元素已按关键字排序。
image.png

这里,r[0]的作用是监视哨兵。

(1)监视表头(结束)

(2)中间变量单元使用

把需要排序的表分为三部分:监视哨r[0]、已有序紫色表、待插入的橙色表。

  • 直接排序插入示例:   数组{48,38,65,97,76,13,27,49}

“49”已经有序,将“38”放入r[0]待插入 从“48”开始向左逐一比较,<则向右移动已有序的数值,直至≤某值(此处是≤r[0]),则将“38”插入 插入“38”后向右继续遍历待插入数值,重复上述步骤 插入“13” <则向右移动已有序的数值,直至≤某值 <则向右移动已有序的数值,直至≤某值 ...... 直至≤某值(此处是≤r[0]),则将“13”插入

  • 代码实现:
void InsertSort()
    {
      for(i = 2; i<=n;i++) //从第二个元素开始比较
      {
        r[0] = r[i]; //r[0] 为监视哨
        j = i-1;
        while(r[0].key < r[j].key)
         {
            r[j+1] = r[j];
            j--;
         }//记录后移,边移动边找插入位置
         r[j+1] = r[0];
      }
    }
  • 算法分析

(1)时间分析: 用移动和比较次数可作为衡量时间复杂性的标准

正序时:比较次数 image.png,移动次数0

逆序时:比较次数 image.png,移动次数 image.png

所以时间复杂度T(n)=O(n2)

(2)空间分析: 在移动时不需要另增加存储空间,所以空间复杂度S(n)=O(1)

(3)稳定性: 由于是在线性表中逐一比较,所以该算法是稳定的。

希尔排序

  • 基本思想:将待排序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”,再对全体进行一次直接插入排序 子序列的构成不是简单地“逐段分割”,而是将相隔某个“增量”的记录组成一个子序列

  • 示例:

1.第一趟是59、36;20、98;17、11;......两两进行直接插入排序。此时增量为8

2.第二趟“缩小增量”,此时增量为4。36、28、59、65;20、14、98、41;......每组分别进行直接插入排序

3.由此不断缩小分组增量,分别进行直接插入排序

选择排序

简单选择排序

  • 基本思想:从n-i+1(i=1,2,...,n-1)个记录中选出最小关键字,并和第i个记录交换 将{ 76,38,85,97,76,13,27,49}用简单选择排序方法进行排序。 image.png

堆排序

堆是一个完全二叉树。其中每个结点的关键字都大于其孩子结点的关键字。

堆的数组表示法

堆的调整:(1)根的左右孩子比较(找出较大值)(2)父子比较(父>子,逆序则交换)

堆排序需要解决的两个问题:

(1)如何由一个无序序列建成一个堆 将序列看成是完全二叉树,最后一个非终端结点是第┕n/2┘(向下取整)个元素,从该元素开始堆的调整,逐步往上调整其他非终端节点

(2)如何在输出堆顶元素之后,调整剩余元素为堆 以队中最后一个元素替代输出的堆顶元素,自上而下进行堆的调整

交换排序

冒泡排序

  • 简述基本思想:两两比较,逆序交换,一趟排序没有交换则排序结束。
  • 个人理解:不如称之为“沉底排序”,每趟排序关键字大的像石块沉底。下一趟则是“次大”的石块沉底叠到上一次石块上。

快速排序

  • 基本思想:首先确定一个关键字,通过一趟排序将代拍记录分割成独立的两部分,一部分均小于关键字、另一部分均大于关键字;后再分别对两部分记录重复上述步骤。
int Partition(int i, int j){
    r[0] = r[i];
    x = r[i].key;
    while(i < j){
        while(i < j && r[j].key >= x) j--;
        r[i] = r[j];
        while(i < j && r[i].key <= x) i++;
        r[j] =r[i];
    }
    r[i] = r[0];
    return i;
}

以上代码是对r[i]...r[j]中的记录进行一趟排序,将它们分成两部分。使:[...这部分的值<=x...] x [...这部分的值>=x...]

用子表的第一个记录作界点记录,第一个while循环从表的两端交替地向中间扫描 将比界点记录小的记录交换到低端,将比界点记录大的记录交换到高端。最后返回界点所在位置

void QuickSort(int low, int high){
    if(low < high){
        k = Partition(low, high);
        QuickSort(low, k-1);
        QuickSort(k+1, high);
    }
}

以上对记录序列r[low]..r[high]进行快速排序, 用if语句保证长度大于1,随后将r[i]...r[j]分解成两部分,分别对左子表和右子表做快速排序。

归并排序

基本思想:n个记录看成n个长度为1的有序子序列,随后两两归并直至得到长度为n的有序序列——2路归并排序

示例

void merge(int h, int m, int n){
    int i = h;
    int j = m+1;
    k = h;
    while(i <= m && j <= n){
        if(r[i].key <= r[j].key) s[k++] = r[i++];
        else s[k++] = r[j++];
    }
    while(i <= m) s[k++] = r[i++];
    while(j <= n) s[k++] = r[j++];
    for(i = h; i < n; i++) r[i] = s[i];
}

将两个有序表r[h]...r[m]和r[m+1]...r[n]归并为一个新的有序表r[h]...r[n]。设置i、j和k指示器的初值

随后将数值小的元素发在数组s中。后面两个while循环分别表示指示器j、i数值已经越界,随后使用for循环将有序表s赋给r

基数排序

  • 基本思想

以10进制数为例。 对于d位的关键字(待排数据都是d位,不足者高位补0),从第d位(最低位)开始排序,直至第1位止。 也即先让个位有序,再使十位有序,...... 它是一种按位进行的排序方法。 image.png

  • 例:对{278,109,63,930, 589,184,505,269,8,83 }用基数排序法进行排序。

初始状态: 278,109,063,930,589,184,505,269, 008, 083

第一趟排序后(个位有序): 930, 063,083,184,505,278, 008,109,589, 269

第二趟排序后(在个位有序的基础上使拾位有序):505,008,109,930, 063,269 , 278,083,184,589

第三趟排序后(在拾位有序的基础上使百位有序):008,063,083,109,184,269,278,505,589,930

各种内部排序对比