数据结构期末复习------排序汇总

值此期末复习阶段,回顾数据结构(C语言)第十章,内部排序汇总

一、直接插入排序

设置哨兵

void InsertSort(RecType r[],int n)
{
  int i,j;
  for(i=2;i<=n;i++){				//第一项可以认为已经排好,所以从第二项开始
    r[0]=r[i];							//复制哨兵,可以防止数组越界的问题
    j=i-1;									
    while(r[0].key<r[j].key)//依次向前移动
    {
      r[j+1]=r[j];
      j--;
    }												//结束时,r[0].key>r[j].key
    r[j+1]=r[0];						//将r[0]放回找到的位置
  }
}

分析:

  • 最好情况:原来的n个记录递增有序

    比较关键字n-1次

    移动关键字2(n-1)次(复制哨兵后又将哨兵复制回来)

  • 最坏情况:原来的n个记录递减有序

    比较关键字次数 O(n2)

    移动关键字次数 O(n2)

  • 平均移动记录的次数约为:O(n2)

  • 时间复杂度O(n2)

  • 空间复杂度O(1)

  • 该排序算法是稳定的

二、折半插入排序

折半的前提:在一个有序序列中插入新的记录

折半插入排序的过程中的折半查找的目的是查询插入点

折半插入排序=折半查找+插入

void BInsertSort(){				//对顺序表L做折半查找
  for(i=2;i<L.length;i++){
  	L.r[0]=L.r[i];				//r[0]可以做一个中间寄存量,折半插入排序
    low=1;high=i-1;				//第i个数是要排的数,所以从1到i-1查找i要插入的位置
    while(low<=high){			//结束循环条件,就是low>high(low=high+1)
      m=(low+high)/2;
      if(L.r[0].key<L.r[m].key)
        high=m-1;
      else
        low=m+1;
    }												
    for(j=i-1;j<=low;--j)//将low到i-1的所有数都向后移动一个长度
      L.r[j+1]=L.r[j];		
    L.r[high+1]=L.r[0];		//最后high+1或者low对应的位置就是要插入的位置
  }
}

三、希尔排序

  • 缩小增量排序

  • 插入排序中效率最高的一种排序方法

void ShellSort(int *a,int len){//希尔排序
  int temp=0;								//中间值
  int i=0;								
  int j=0;								
  int k=0;		
  for(i=(len/2);i>0;i=i/2){	//分组后一个组的长度
    for(j=i;j<len;j++){
      temp=a[0];
      r=j-i;
      while(r>=0&&temp<a[r]){
        a[r+i]=a[r];
        r=r-i;
      }
      a[r+i]=temp;
    }
  }
}

分析:

  • 空间复杂度:O(1)

    只占用了一个暂存单元

  • 时间复杂度:O(n(log2n)2)

  • 希尔排序是一个不稳定的排序方法(*)

四、冒泡排序

  • 改进前的冒泡排序
void bubble1(int a[],int n){
  int i,j,temp;					//temp作为中间量
  for(int i=0;i<j-1;i++){
    for(int j=0;j<n-1-i;j++){
      if(a[j]>a[j+1]){
        temp=a[j];			//交换
        a[j]=a[j+1];
        a[j+1]=temp;
      }
    }
  }
}
  • 改进后的冒泡排序
void bubblesort2(int a[],int n){
  int i,j,flag,temp;
   for(int i=0;i<j-1;i++){
    flag=1;
     for(int j=0;j<n-1-i;j++){
      if(a[j]>a[j+1]){
        temp=a[j];				//交换
        a[j]=a[j+1];
        a[j+1]=temp;
        flag=0;						//如果有交换,那么没有排好序
      }
    }
     if(flag==1)					//如果没有交换,说明已经有序,不需要再比较,直接退出
       break;
  }
}

分析:

  • 最好情况:带排序的文件已经是有序文件,只需要进行一趟排序,共计比较关键字的次数为n-1
  • 最坏情况:要经过n-1趟排序,所需总的比较关键字的次数为n(n-1)/2
  • 空间复杂度O(1)
  • 算法是稳定的

五、快速排序

基本思想:首先在r[1…n]中,确定一个r[i],经过比较和移动,使得r[i]左边的所有记录的关键字小于等于r[i].key,r[i]右边所有记录的关键字大于等于r[i].key。以r[i]为界,将文件划分为左右两个子文件,继续下去,使得每个文件只有一个记录为止。

void quksort(int r[],int low,int high){
  int x,i,j;
  if(low<high){
    i=low;j=high;x=r[i];		//把r[i]空了出来
    while(i!=j){
      while(i<j&&r[i]<x)j--;//从最右端开始找,找到第一个比x小的数
    	if(i<j){
        r[i]=r[j];i++;				//这个位置的数已经确定,从下一个继续
        while(i<j&&r[i]>x)i++;//从左端开始,找到第一个比x大的数
          if(i<j){
           r[j]=r[i];j--;			//同上
          }
        }
      }//	结束循环时,已经划分好两部分,即找到r[i]的位置,并且此时i=j
    r[i]=x;
    quksort(r,low,i-1);			//处理左边
    quksort(r,i+1,high);		//处理右边
   }
}
void quicksort(){
  quksort(r,1.n);
}

分析:

  • 平均时间复杂度O(nlog2n)
  • 最坏情况下(基本有序),快排需要的比较次数和冒泡的比较次数相同,时间复杂度为O(n2)
  • 快排需要一个栈空间来实现递归,若每次将文件均匀的分成两部分,所需要的栈空间为O(log2n),即空间复杂度是O(log2n)
  • 快速排序是不稳定的
  • 快速排序不适于对原本有序或基本有序的记录进行排序
  • ”三者取中“的改进方案

六、选择排序

  1. 简单排序
void SelectSort(int r[],int n){
  int i,j,min;
  int x;
  for(i=1;i<n;i++){
    min=i;
    for(j=i+1;j<=n;j++)//找到i+1到n里所有数中,最小的数
      if(r[j]<r[min])
        min=j;
    if(min!=i){					//如果i不是最小的数,则交换,这样略去了多余的交换
      x=r[min];
      r[min]=r[i];
      r[i]=x;
    }
  }
}

分析:

  • 比较次数:O(n2)
  • 交换次数:
    • 最好情况:不移动记录
    • 最坏情况:每次都移动3个,共3(n-1)次
  • 时间复杂度:O(n2)
  • 空间复杂度:O(1)
  • 该算法是不稳定的(交换时,就不确定交换的顺序了,毕竟不是顺序后移)

  1. 树形选择排序

树形选择排序又称锦标赛排序,是一种按照锦标赛的思想进行排序的方法,可以用一颗有n个叶子结点的完全二叉树表示

每个内点是它左右孩子中的最小值

分析:

  • 需要另外开辟新的空间保存排序结果

  • 需要额外的n-1个内部节点,增加了内存开销

  • 将最小关键字改为极大数,再于兄弟结点比较属于多余

  • 树形选择排序一般不是用来排序,而是证明某些问题

  • 空间复杂度:O(n)

  • 时间复杂度:

    • 第一次选最小值,比较n-1次,以后每一次选最小值要比较n-1次,以后每选一次次小值,要比较log2n次,总的开销O(nlog2n)
  • 树形选择排序是不稳定的。


  1. 堆排序(Heap Sort)
  • 堆的定义(可以看作一个完全二叉树)

    • ki<=k2i且ki<=k2i+1 (小顶堆)

    • ki>=k2i且ki>=k2i+1 (大顶堆)

  • 两个问题
    1. 如何初始化一个序列为堆
    2. 堆顶元素被替换后,调整剩余元素成一个新的堆

堆调整的算法:

void HeapAdjust(int r[],int s,int m){//r中从1开始存放根节点,s是要调整的位置
  rc=r[s];							//保存调整的元素,空出s的位置
  for(j=2*s;j<=m;j*=2){
    if(j<m&&r[j]<r[j+1])//j<m表示s有右孩子j+1
      j++;							//计算s的具有较大值的孩子的序号
   											//这步结束时,j指向s的极大值的那个孩子
    if(rc>r[j])					//如果rc比极大孩子的值还要大,那么就不能继续下沉
      break;						//也就是找到了要插入的位置
    r[s]=r[j];					//没有找到要插入的位置,就将极大孩子值放到这个位置
    s=j;							
  }
  r[s]=rc;							//将rc填入到正确的位置,符合堆的定义的位置
}
  • 初始化一个大顶堆的过程需要依靠堆调整的过程
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
  • 堆排序是不稳定的排序
  • 堆排序对记录较少的文件不提倡,对较大的文件很有效

七、归并排序

把k个有序子文件合在一起,形成一个新的有序文件,同时归并k个有序子文件的过程称为k-路归并排序

2-路归并排序:归并2个有序子文件的排序

注:以下代码中,两个子文件在r中,由mid隔开

同时,这个算法在链表时使用过,当然也可以使用链式存储结构来尝试

void merge(int r[],int y[],int low,int mid,int high){
//归并两个有序子文件
  int k=i=low,j=mid+1;
  while(i<=mid&&j<=high){
    if(r[i]<r[j]){
      y[k]=r[i];
      i++;
    }
    else{
      y[k]=r[j];
      j++;
    }
    k++;
  }
  while(j<=high){
    y[k]=r[j];
    j++;k++;
  }
  while(i<=mid){
    y[k]=r[i];
    i++;j++;
  }
}
void mergepass(int r[],int y[],int s){
//一趟归并排序  
  int i=1;
  while(i+2*s-1<=n){						//两两归并唱的 为s的子文件
    merge(r,y,1,i+2-1,i+2*s-1);	//low,mid,high
    i=i+2*s;
  }
  if(i+s-1<n)										//如果最后还有大于1个子文件,进行归并
    merge(r,y,i,i+s-1,n);
  else{
    while(i<=n){								//如果最后的长度不够一个s,那么就将后面的直接复制过来,长度<=s时
      y[i]=r[i];
      i++;
    }
  }
}
void mergesort(int r,int n){
  int y[n+1];
  int s=1;										//子文件的初始长度为1
  while(s<n);{
    mergepass(r,y,s);
    s=2*s;										//将r归并到y中
    mergepass(y,y,s);
    s=2*s;										//再将y归并到r中
  }
}
  • 比较O(nlogn)次
  • 移动O(nlogn)个记录
  • 归并排序需要一个大小为n的辅助空间y
  • 归并排序是稳定的

八、基数排序

基数排序进分析关键字自身每位的值,通过分配、回收进行处理。

从地位到高位,依次排序,产生有序序列

  • 设有效数字位d位,需要d趟分配、回收
  • 每趟分配运算时间O(n)
  • 收集:基数为rd,即rd个队列,从rd个队列中收集,运算时间O(rd)
  • 一趟分配、运算的时间O(n+rd),时间复杂度O(d*(n+rd))
  • 基数排序是稳定的
  • 辅助空间:每个队列首尾2个指针,共2rd个指针

内部排序的比较

一、时间性能

  • O(nlogn):快速排序、堆排序、归并排序

    快排是被认为是最快的排序方法,后两者,比较,在n值较大的情况下,归并排序较堆排序更快

  • O(n2):插入排序、冒泡排序、选择排序

    插入排序最为常用,选择排序过程中记录移动次数最少

  • O(n):基数排序

  1. 待排序的记录序列安关键字顺序有序时,应尽量避免快速排序

  2. 简单排序的三种方法中,冒泡排序效率最低

二、空间性能

  • O(1):所有简单排序方法(插入、冒泡、选择)和堆排序
  • O(logn):快速排序(递归程序执行过程中栈所需的辅助空间)
  • O(n):归并排序和基数排序

三、稳定性

  • 不稳定:希尔排序、快速排序、直接选择和堆排序(4个)
  • 稳定:除以上排序、以上设计的排序均是稳定的

一般来说,排序的过程中所进行的比较操作和交换数据仅发生在相邻的记录之间,没有大布距的数据调整,则排序方法是稳定的

四、总结

  • 排序的记录个数较小时,选择插入排序法,如果一个记录的数据项较多,占用空间大,应该降低移动次数,选用选择排序法,但若有“有序”倾向时,慎用快速排序

  • 快速排序和归并排序在n值较小时的性能不及直接插入排序

  • 基数排序的时间复杂度是O(d*n),适合n很大,d较小的情况

  • 如果安最次位优先进行排序时,必须宣统稳定的排序方法(也就是,含有多个关键字时)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 对于北邮-2021-软件工程-期末复习.rar,其实际内容应该包含了北邮软件工程课程的期末复习相关资料。这些资料可能包括了课堂讲义、考试重点、习题解答、历年试卷等内容,有助于帮助学生复习和准备考试。此外,该压缩文件可能也包含了一些软件工程方面的技术文献、著作或工具等资源,供学生自行查阅和学习。 软件工程是计算机科学的一个重要支,旨在研究关于软件的开发、管理和维护等方面的最佳实践和方法。在学习软件工程的过程中,需要不断运用各种工具和技术,例如需求析、设计模式、测试驱动开发、代码版本管理等。因此,掌握软件工程技能对于未来从事软件开发的工程师和研究者来说都是非常重要的。 之,北邮-2021-软件工程-期末复习.rar是一个包含了软件工程课程复习相关资料的压缩文件,对于学习软件工程的同学们来说,它是一份宝贵的资源。在复习过程中,我们需要认真审核其中的内容,结合课堂实际情况,制定个人化的复习计划,不断加强基础知识的掌握,并结合实际应用,提高自己的软件开发能力。 ### 回答2: 北邮-2021-软件工程-期末复习.rar文件是北邮2021年软件工程课程的期末复习资料压缩包。其中包含了软件工程课程相关的学习资料和复习材料。在这个压缩包中,可能包括课件、PPT、习题和答案、教材、参考书籍、课程大纲等等。这些材料都是为了帮助学生进行软件工程课程的有效学习和备考复习所准备的。 该压缩包的目的是为学生提供一个集中、整理好的资源库,方便学生在期末复习阶段能够迅速找到所需资料。通过使用该压缩包,学生可以更加高效地进行软件工程知识的巩固和回顾,更好地理解和掌握课程中的关键概念和技术。 对于学生而言,打开这个压缩包后,可以根据自己的需要选择具体的学习材料进行查阅和使用。同时,也可以根据课程大纲或者考试的重点,有针对性地进行复习,并通过做习题巩固知识。 之,北邮-2021-软件工程-期末复习.rar是一份为学生提供软件工程课程复习资料的压缩包,通过使用其中的学习资料,学生可以更加高效地进行软件工程知识的复习和巩固,为期末考试做好准备。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值