排序——基本概述,插入排序

在前面的文章里系统的介绍了查找算法,从今天开始将进入排序章节。我们需要明白——排序,是为了更方便的查找,提高查找效率。本篇文章主要介绍排序的基本概念及最简单的直接插入排序。

一,基本概述

排序方法的分类

按数据存储介质内部排序和外部排序

比较器个数串行排序和并行排序

主要操作比较排序和基数排序

辅助空间原地排序和非原地排序

稳定性稳定排序和非稳定排序

自然性自然排序和非自然排序


比较器个数

若待排序记录都在内存中,称为内部排序;

若待排序记录一部分在内存,一部分在外存,则称为外部排序。

注:外部排序时,要将数据分批调入内存来排序,中间结果还要及时放入外存,显然外部排序要复杂得多。 

 主要操作

比较排序:用比较的方法

插入排序、交换排序、选择排序、归并排序

基数排序:不比较元素的大小,仅仅根据元素本身的取值确定其有序位置。

辅助空间

原地排序:辅助空间用量为O(1)的排序方法。

 (所占的辅助存储空间与参加排序的数据量大小无关)

非原地排序:辅助空间用量超过O(1)的排序方法。

稳定性

稳定排序:能够使任何数值相等的元素,排序以后相对次序不变。

非稳定性排序:不是稳定排序的方法。

排序的稳定性只对结构类型数据排序有意义。

例如:

     n个学生信息(学号、姓名、语文、数学、英语、总分)

     1、按数学成绩从高到低排序

     2、按照总分从高到低排序。

     3、总分相同的情况下,数学成绩高的排在前面

自然性

自然排序:输入数据越有序,排序的速度越快的排序方法。

非自然排序:不是自然排序的方法。


后面的文章将主要介绍

按数据存储介质内部排序和外部排序

比较器个数串行排序和并行排序

主要操作比较排序和基数排序

记录序列以顺序表存储 

# define MAXSIZE 20        //设记录不超过20个
typedef  int  KeyType ;       //设关键字为整型量(int型)
Typedef  struct {                  //定义每个记录(数据元素)的结构
    KeyType      key ;            //关键字 
    InfoType      otherinfo;    //其它数据项
}RedType ;
Typedef  struct {                          //定义顺序表的结构
  RedType  r [ MAXSIZE +1 ];   //存储顺序表的向量
 //r[0]一般作哨兵或缓冲区
  int length ;                                 //顺序表的长度
}SqList ;

 排序算法的好坏如何衡量?

时间效率:排序速度(比较次数与移动次数)

空间效率:占内存辅助空间的大小

二,直接插入排序

基本思想 :

        每步将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象适当位置,直到对象全部插入为止。

即边插入边排序,保证子序列中随时都是排好序的。

下面由图片来理解:

 插入排序是最简单的排序方法。

例: (13,6,3,31,9,27,5,11) 

排序过程:整个排序过程为n-1趟插入,即先将序列中第1个记录看成是一个有序子序列,然后从第2个记录开始,逐个进行插入,直至整个序列有序

插入排序的基本步骤:

R[1..i-1]中查找R[i]的插入位置,

R[1..j].key£ R[i].key< R[j+1..i-1].key;

R[j+1..i-1]中的所有记录均后移一个位置;

R[i] 插入到R[j+1]的位置上。 

具体的代码如下:

void InsertSort(SqList &L)
 {int i,j;
   for(i=2;i<=L.length;++i)
     if( L.r[i].key<L.r[i-1].key)//将L.r[i]插入有序子表
       { L.r[0]=L.r[i]; // 复制为哨兵
          L.r[i]=L.r[i-1];
          for(j=i-2; L.r[0].key<L.r[j].key;--j)
	            L.r[j+1]=L.r[j]; // 记录后移 
         L.r[j+1]=L.r[0]; //插入到正确位置
       }
 }

算法分析:

设对象个数为n,则执行n-1

比较次数移动次数与初始排列有关
 

最好情况下:

 每趟只需比较 1 次,不移动

 总比较次数为 n-1

for(i=2;i<=L.length;++i) 

if( L.r[i].key<L.r[i-1].key)

最坏情况下:

 第 i 趟比较i次,移动i+1

if( L.r[i].key<L.r[i-1].key)       {
   L.r[0]=L.r[i]; // 复制为哨兵
   L.r[i]=L.r[i-1];
   ……
   L.r[j+1]=L.r[0]; //插入到正确位置 }

 下面是一段完整的直接插入排序c语言代码:

#include <stdio.h>

// 插入排序函数,参数为待排序数组arr和数组长度n
void insertion_sort(int arr[], int n) {
    int i, key, j;
    // 从第二个元素开始遍历数组
    for (i = 1; i < n; i++) {
        key = arr[i]; // 当前要插入的元素
        j = i - 1; // 已排序部分的最后一个元素的索引

        // 将大于key的元素向后移动一位
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j = j - 1;
        }
        // 将key插入到正确的位置
        arr[j + 1] = key;
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6}; // 待排序数组
    int n = sizeof(arr) / sizeof(arr[0]); // 计算数组长度

    insertion_sort(arr, n); // 调用插入排序函数对数组进行排序

    printf("Sorted array: "); // 输出排序后的数组
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    printf(" ");

    return 0;
}

三,折半插入排序

基本思想:

为了减少关键字的比较次数,我们在直接插入的基础上引入折半插入排序。

我们可以结合之前的折半查找来理解

折半查找

 代码如下:

void BInsertSort(SqList &L) {
    // 遍历顺序表,从第二个元素开始
    for (int i = 2; i <= L.length; ++i) {
        // 将当前元素作为待插入元素
        L.r[0] = L.r[i];
        int low = 1, high = i - 1, m;
        // 使用二分查找法确定待插入元素在已排序部分的正确位置
        while (low <= high) {
            m = (low + high) / 2;
            if (L.r[0].key < L.r[m].key) {
                high = m - 1;
            } else {
                low = m + 1;
            }
        }
        // 将正确位置及其右侧的所有元素向后移动一位,为待插入元素腾出空间
        for (int j = i - 1; j >= high + 1; --j) {
            L.r[j + 1] = L.r[j];
        }
        // 将待插入元素插入到正确的位置
        L.r[high + 1] = L.r[0];
    }
} // BInsertSort

算法分析:

折半查找比顺序查找快,所以折半插入排序就平均性能来说比直接插入排序要快。

它所需要的关键码比较次数与待排序对象序列的初始排列无关,仅依赖于对象个数。在插入第 i 个对象时,需要经过 ë log2i û +1  次关键码比较,才能确定它应插入的位置  

 折半插入排序相较于直接插入排序:

减少了 比较次数 ,但没有减少 移动次数
平均性能优于直接插入排序
时间复杂度为 o(n2)
空间复杂 度为 o(1)
是一种 稳定 的排序方法

四,希尔排序 

基本思想:

        先将整个待排记录序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。

ü dk 值较大 ,子序列中 对象较少 ,速度 较快
ü dk 逐渐 变小 ,子序列中 对象变多 ,但大多数 对象已基本有序 ,所以排序速度仍然很快。

 希尔排序的技巧:

        子序列不是简单“逐段分割”

        将相隔增量dk的记录组成一个子序列

        dk逐趟缩短(如依次取5,3,1),直到dk=1为止

 希尔排序的优点:

        小元素跳跃式前移

        最后一趟增量为1时基本有序

        平均性能优于直接插入排序

希尔排序算法主程序的代码:

dk值依次装在dlta[t]中

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

 某一趟的排序操作:

void   ShellInsert(SqList &L,int dk) {
//对顺序表L进行一趟增量为dk的Shell排序,dk为步长因子
for(i=dk+1;i<=L.length; ++ i)//开始将r[i] 插入有序增量子表
      if(r[i].key < r[i-dk].key) {         
       r[0]=r[i];//暂存在r[0]
       for(j=i-dk; j>0 &&(r[0].key<r[j].key); j=j-dk)
           r[j+dk]=r[j];//关键字较大的记录在子表中后移
        r[j+dk]=r[0];//在本趟结束时将r[i]插入到正确位置
       }
}

算法分析:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值