数据结构排序算法实验报告_数据结构与算法(8)(上):冒泡排序、插入排序和选择排序...

本文介绍了冒泡排序、插入排序和选择排序的原理、时间复杂度、空间复杂度和稳定性。冒泡排序在最好情况下时间复杂度为O(n),而插入排序和选择排序的时间复杂度在所有情况下都是O(n^2),但插入排序在实际操作中可能更高效。三种排序算法均为原地排序,其中冒泡排序和插入排序是稳定的,而选择排序则不是。
摘要由CSDN通过智能技术生成

a739cd132346e7b74edbf8b7a845fd92.png

(文中图片出自王争老师的课程:数据结构与算法之美,侵删)

今天主要学习冒泡排序,插入排序和选择排序。

我们分析一个排序算法主要从以下方面入手:

一.排序算法的执行效率

1.最好、最坏、平均情况时间复杂度

2.时间复杂度的系数、常数和低阶

3.比较次数和移动次数

二.排序算法的内存消耗

内存消耗主要通过空间复杂度来衡量,同时还引入一个新的概念--原地排序。原地排序算法特指空间复杂度是O(1)的排序算法。

三.排序算法的稳定性

衡量算法的一个重要因素就是稳定性。如果带排序元素中存在值相等的元素,经过排序之后,原有的相等元素顺序不变,成为稳定的排序算法。反之,则是不稳定的排序算法。

冒泡排序

冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。如下图所示:

70dd33f160e99534cb8068c7da8726d4.png
(图片出自王争老师的课程:数据结构与算法之美,侵删)

元素到达最后的状态,是经过了6次这样的冒泡操作(最后一次是和自己比较)。

f6cae6a4c1ccea8cdb1837758e49ae12.png
(图片出自王争老师的课程:数据结构与算法之美,侵删)

实际上,冒泡过程可以优化。当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作。如下图6个元素,值需要4次冒泡操作。

8aa59f55da370c5d6834a734263e65a0.png
(图片出自王争老师的课程:数据结构与算法之美,侵删)

因此,我们把它翻译成代码就是

public void bubbleSorts(int[] a,int n){
   if(n<=1) return;
   for(int i=0;i<n-1;i++){
      boolean flag = false;
      for(int j=0;j<n-i-1;j++){
           if(a[j] > a[j+1]){
              int temp = a[j];
              a[j] = a[j+1];
              a[j+1] = temp;
              flag = true;
           }else{
              break;
           }
      }
      if(!flag){
        break;
      }
   }  
}

通过冒泡排序的原理,我们可以确定:

冒泡排序是原地排序算法,它只涉及相邻元素的交换,需要常量级的内存空间,所以空间复杂度是O(1)。

冒泡排序是稳定的排序算法。在排序过程中,只有前后两个元素不等才会交换顺序,相等的时候不做交换,因此没有改变原有相等数据的顺序。

冒泡排序的时间复杂度,

假设待排序的元素本身就是有序的,那么只需要一次冒泡,即最好时间复杂度为O(n)。

假设待排序的元素本身是倒序的,那么就需要n次冒泡,时间复杂度为O(

)。

平均情况时间度的计算就稍微复杂一点。假设有n个数据的数组,就有n!种排列方式,不同的排列方式,对应的冒泡次数肯定不同。我们可以通过 有序度 逆序度 来进行分析。

有序度的情况是

a[i] <= a[j] ,  i<j;

如图所示,下图的有序度等于11.

7bf0be2f25432873fa37389aabb59845.png
(图片出自王争老师的课程:数据结构与算法之美,侵删)

逆序度的情况则是

a[i] > a[j], i < j;

一个完全有序的数组,有序度等于 n!=(n*(n-1))/2,以6个数据数组为例就是15。此时的有序度叫做满有序度。排序的过程即是减少逆序度,增加有序度,直到最后达到满有序度的过程。

逆序度 = 满有序度 - 有序度

以n个数据的数组为例,有序度为0的时候,需要进行 n*(n-1)/2次交换,平均交换次数取中间值n*(n-1)/4,那么平均时间复杂度就是O(

)。

插入排序

插入排序实现排序思想:

将需要的数据分为两个区间,已排序区间未排序区间。初始已排序区间只有一个元素,也就是第一个元素。插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。

如图所示,要排序的数据是 4,5,6,1,3,2,其中左侧为已排序区间,右侧是未排序区间。

582037a73cda05ac31339dc6dd923676.png
(图片出自王争老师的课程:数据结构与算法之美,侵删)

根据以上原理,我们可以用代码实现插入排序

public void insertSorts(int[] a,int n){
   if(n<=1) return;
   for(int i=1;i<n;i++){
      int data = a[i];
      int j = i-1;
      for(;j>=0;j--){
         if(a[j] > data){
           a[j+1] = a[j];
         }else{
           break;
         }
      }
      a[j+1] = data;
   }   
}

根据以上可以发现,插入排序和冒泡排序一样,是原地排序算法,同时也是稳定行排序算法,时间复杂度为O(

)。

选择排序

选择排序的原理和插入排序的原理类似,分为已排序区间未排序区间。不同的是,选择排序是在未排序区间里选择最小的元素,将其放在已排序区间的尾部。

16fe24a377539677f0feca518c32d5d4.png
(图片出自王争老师的课程:数据结构与算法之美,侵删)

根据可以写出下列代码:

public void selectSorts(int[] a,int n){
    if(n<=1) return;
    for(int i=0;i<n-1;i++){
        int min=i;
        for(int j=i+1;j<n;j++){
           if(a[j] < a[min]){
              min = j;
           }
        }
        int temp = a[i];
        a[i] = a[min];
        a[min] = temp;
    } 
 
}

首先,选择排序空间复杂度为 O(1),是一种原地排序算法。选择排序的最好情况时间复杂度、最坏情况和平均情况时间复杂度都为 O(n2)。

但是选择排序不是稳定排序,选择排序每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置,这样破坏了稳定性。因此一般在排序中,选择排序的效率是低于冒泡和插入排序的。

那冒泡排序和插入排序的效率哪个更高一点呢?

从我们写的代码来看

//冒泡
 int temp = a[j];
 a[j] = a[j+1];
 a[j+1] = temp;

//插入
 a[j+1] = a[j];

冒泡执行了三次交换,而插入只执行了一次,因此,理论上讲插入排序效率高于冒泡排序的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值