数据结构与算法之排序(冒泡、选择、插入、希尔、归并、快速)(三)

简介: 数据结构与算法之排序(冒泡、选择、插入、希尔、归并、快速)

2.3.快速排序


快速排序是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。


需求:

排序前:{6, 1, 2, 7, 9, 3, 4, 5, 8}

排序后:{1, 2, 3, 4, 5, 6, 7, 8, 9}

排序原理:


1.首先设定一个分界值,通过该分界值将数组分成左右两部分;


2.将大于或等于分界值的数据放到到数组右边,小于分界值的数据放到数组的左边。此时左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值;


3.然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。


4.重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左侧和右侧两个部分的数据排完序后,整个数组的排序也就完成了


81fa904199294dbda4f114906cd4e1be.png


快速排序 API设计:


image.png


切分原理:


把一个数组切分成两个子数组的基本思想:


1.找一个基准值,用两个指针分别指向数组的头部和尾部;


2.先从尾部向头部开始搜索一个比基准值小的元素,搜索到即停止,并记录指针的位置;


3.再从头部向尾部开始搜索一个比基准值大的元素,搜索到即停止,并记录指针的位置;


4.交换当前左边指针位置和右边指针位置的元素;


5.重复2,3,4步骤,直到左边指针的值大于右边指针的值停止。


6d2c9e4c692d4e4f8fa6fe09f3558195.png

32d4826a70a342c385bf40697c709690.png


快速排序代码实现:


// 排序代码
public class Quick {
  public static void sort(Comparable[] a) {
    int lo = 0;
    int hi = a.length - 1;
    sort(a, lo,hi);
  }
  private static void sort(Comparable[] a, int lo, int hi) {
    if (hi<=lo){
      return;
    }
  //对a数组中,从lo到hi的元素进行切分
  int partition = partition(a, lo, hi);
  //对左边分组中的元素进行排序
  //对右边分组中的元素进行排序
  sort(a,lo,partition-1);
  sort(a,partition+1,hi);
  }
  public static int partition(Comparable[] a, int lo, int hi) {
    Comparable key=a[lo];//把最左边的元素当做基准值
    int left=lo;//定义一个左侧指针,初始指向最左边的元素
    int right=hi+1;//定义一个右侧指针,初始指向左右侧的元素下一个位置
    //进行切分
    while(true){
      //先从右往左扫描,找到一个比基准值小的元素
      while(less(key,a[--right])){//循环停止,证明找到了一个比基准值小的元素
        if (right==lo){
        break;//已经扫描到最左边了,无需继续扫描
      }
    }
    //再从左往右扫描,找一个比基准值大的元素
    while(less(a[++left],key)){//循环停止,证明找到了一个比基准值大的元素
    if (left==hi){
      break;//已经扫描到了最右边了,无需继续扫描
    }
  }
  if (left>=right){
    //扫描完了所有元素,结束循环
    break;
  }else{
    //交换left和right索引处的元素
    exch(a,left,right);
  }
}
  //交换最后rigth索引处和基准值所在的索引处的值
  exch(a,lo,right);
  return right;//right就是切分的界限
}
  /*
  数组元素i和j交换位置
  */
  private static void exch(Comparable[] a, int i, int j) {
    Comparable t = a[i];
    a[i] = a[j];
    a[j] = t;
  }
  /*
  比较v元素是否小于w元素
  */
  private static boolean less(Comparable v, Comparable w) {
    return v.compareTo(w) < 0;
  }
}
//测试代码
public class Test {
  public static void main(String[] args) throws Exception {
    Integer[] arr = {6, 1, 2, 7, 9, 3, 4, 5, 8};
    Quick.sort(arr);
    System.out.println(Arrays.toString(arr));
  }
}


快速排序和归并排序的区别:


快速排序是另外一种分治的排序算法,它将一个数组分成两个子数组,将两部分独立的排序。快速排序和归并排序是互补的:归并排序将数组分成两个子数组分别排序,并将有序的子数组归并从而将整个数组排序,而快速排序的方式则是当两个数组都有序时,整个数组自然就有序了。在归并排序中,一个数组被等分为两半,归并调用发生在处理整个数组之前,在快速排序中,切分数组的位置取决于数组的内容,递归调用发生在处理整个数组之后。


快速排序时间复杂度分析:


快速排序的一次切分从两头开始交替搜索,直到left和right重合,因此,一次切分算法的时间复杂度为O(n),但整个快速排序的时间复杂度和切分的次数相关。最优情况:每一次切分选择的基准数字刚好将当前序列等分


b2e3152b6be04fc3b71305f635f1e90b.png


如果我们把数组的切分看做是一个树,那么上图就是它的最优情况的图示,共切分了 logn次,所以,最优情况下快速排序的时间复杂度为O(nlogn);


最坏情况:每一次切分选择的基准数字是当前序列中最大数或者最小数,这使得每次切分都会有一个子组,那么总共就得切分n次,所以,最坏情况下,快速排序的时间复杂度为O(n^2);


36ca7d43101b4a4280f46003997a0453.png

平均情况:每一次切分选择的基准数字不是最大值和最小值,也不是中值,这种情况我们也可以用数学归纳法证明,快速排序的时间复杂度为O(nlogn),由于数学归纳法有很多数学相关的知识,容易使我们混乱,所以这里就不对平均情况的时间复杂度做证明了


2.4.排序的稳定性


数组arr中有若干元素,其中A元素和B元素相等,并且A元素在B元素前面,如果使用某种排序算法排序后,能够保证A元素依然在B元素的前面,可以说这个该算法是稳定的


1572d6ca1fa54ad3b5c5754e32dc9e4b.png

稳定性的意义:


如果一组数据只需要一次排序,则稳定性一般是没有意义的,如果一组数据需要多次排序,稳定性是有意义的。例如要排序的内容是一组商品对象,第一次排序按照价格由低到高排序,第二次排序按照销量由高到低排序,如果第二次排序使用稳定性算法,就可以使得相同销量的对象依旧保持着价格高低的顺序展现,只有销量不同的对象才需要重新排序。这样既可以保持第一次排序的原有意义,而且可以减少系统开销


第一次按照价格从低到高排序:


image.png


第二次按照销量进行从高到低排序:


image.png


常见排序算法的稳定性:


冒泡排序:


只有当arr[i]>arr[i+1]的时候,才会交换元素的位置,而相等的时候并不交换位置,所以冒泡排序是一种稳定排序算法。


选择排序:


选择排序是给每个位置选择当前元素最小的,例如有数据{5(1),8 ,5(2), 2, 9 },第一遍选择到的最小元素为2,所以5(1)会和2进行交换位置,此时5(1)到了5(2)后面,破坏了稳定性,所以选择排序是一种不稳定的排序算法。


插入排序:


比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么把要插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。


希尔排序:


希尔排序是按照不同步长对元素进行插入排序 ,虽然一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。


归并排序:


归并排序在归并的过程中,只有arr[i]<arr[i+1]的时候才会交换位置,如果两个元素相等则不会交换位置,所以它并不会破坏稳定性,归并排序是稳定的。


快速排序:


快速排序需要一个基准值,在基准值的右侧找一个比基准值小的元素,在基准值的左侧找一个比基准值大的元素,然后交换这两个元素,此时会破坏稳定性,所以快速排序是一种不稳定的算法。






目录
相关文章
数据结构|排序总结(1)|直接插入排序
数据结构|排序总结(1)|直接插入排序
|
27天前
|
存储 搜索推荐 算法
【数据结构】八大排序之计数排序算法
【数据结构】八大排序之计数排序算法
11 4
|
27天前
|
搜索推荐 算法
【数据结构】八大排序之归并排序算法
【数据结构】八大排序之归并排序算法
20 5
|
27天前
|
搜索推荐 算法 编译器
【数据结构】八大排序之快速排序算法
【数据结构】八大排序之快速排序算法
35 4
|
29天前
|
算法 Python
数据结构与算法 经典排序方法(Python)
数据结构与算法 经典排序方法(Python)
24 0
|
1月前
|
传感器 算法 计算机视觉
基于肤色模型和中值滤波的手部检测算法FPGA实现,包括tb测试文件和MATLAB辅助验证
该内容是关于一个基于肤色模型和中值滤波的手部检测算法的描述,包括算法的运行效果图和所使用的软件版本(matlab2022a, vivado2019.2)。算法分为肤色分割和中值滤波两步,其中肤色模型在YCbCr色彩空间定义,中值滤波用于去除噪声。提供了一段核心程序代码,用于处理图像数据并在FPGA上实现。最终,检测结果输出到&quot;hand.txt&quot;文件。
|
1月前
|
机器学习/深度学习 算法 计算机视觉
基于yolov2深度学习网络的视频手部检测算法matlab仿真
基于yolov2深度学习网络的视频手部检测算法matlab仿真
|
1月前
|
算法
【MATLAB】语音信号识别与处理:移动中位数滤波算法去噪及谱相减算法呈现频谱
【MATLAB】语音信号识别与处理:移动中位数滤波算法去噪及谱相减算法呈现频谱
23 2
|
1月前
|
算法
【MATLAB】语音信号识别与处理:一维信号NLM非局部均值滤波算法去噪及谱相减算法呈现频谱
【MATLAB】语音信号识别与处理:一维信号NLM非局部均值滤波算法去噪及谱相减算法呈现频谱
40 1
|
6天前
|
机器学习/深度学习 人工智能 算法
基于DCT和扩频的音频水印嵌入提取算法matlab仿真
本文介绍了结合DCT和扩频技术的音频水印算法,用于在不降低音质的情况下嵌入版权信息。在matlab2022a中实现,算法利用DCT进行频域处理,通过扩频增强水印的隐蔽性和抗攻击性。核心程序展示了水印的嵌入与提取过程,包括DCT变换、水印扩频及反变换步骤。该方法有效且专业,未来研究将侧重于提高实用性和安全性。