当前位置: 首页 > news >正文

数据结构与算法之经典排序算法

一、简单排序

在我们的程序中,排序是非常常见的一种需求,提供一些数据元素,把这些数据元素按照一定的规则进行排序。比如查询一些订单按照订单的日期进行排序,再比如查询一些商品,按照商品的价格进行排序等等。所以,接下来我们要学习一些常见的排序算法。

1.1冒泡排序(Bubble Sort)

冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。

排序原理:

  1. 比较相邻的元素。如果前一个元素比后一个元素大,就交换这两个元素的位置
  2. 对每一对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素。最终最后位置的元素就是最大值

在这里插入图片描述

排序API设计:

类名Bubble
构造方法Bubble():创建Bubble对象
成员方法1. public static void sort(Comparable[] a):对数组a中的元素进行排序
2. private static boolean greater(Comparable v, Comparable w):判断v是否大于w,从小到大排序
3. private static void exch(Comparable[] a, int i, int j):交换数组中i和j位置的元素

代码实现:

Bubble

package com.itcast.algorithm.sort;/*** @Author: Juechen* @Date: 2024/4/30* @Description: 冒泡排序* @Version: 1.0**/
public class Bubble {/*** 对数组a中的元素进行排序*/public static void sort(Comparable[] a) {for (int i = a.length - 1; i > 0; i--) {for (int j = 0; j < i; j++) {if (greater(a[j], a[j + 1])) {exch(a, j, j + 1);}}}}/*** 判断v是否大于w,从小到大排序* < 0 则是从大到小排序** @param v* @param w* @return*/private static boolean greater(Comparable v, Comparable w) {return v.compareTo(w) > 0;}/*** 交换数组中i和j位置的元素** @param a* @param j* @param i*/private static void exch(Comparable[] a, int i, int j) {Comparable temp = a[i];a[i] = a[j];a[j] = temp;}
}

TestBubble

package com.itcast.algorithm.test;import com.itcast.algorithm.sort.Bubble;import java.util.Arrays;/*** @Author: Juechen* @Date: 2024/4/30* @Description: 测试冒泡排序* @Version: 1.0**/
public class TestBubble {public static void main(String[] args) {Integer[] arr = {4, 5, 6, 3, 2, 1};Bubble.sort(arr);System.out.println(Arrays.toString(arr));}
}

时间复杂度分析:

冒泡排序使用了双层for循环,其中内层循环的循环体是真正完成非序的代码,所以,我们分析冒泡排序的时间复杂度,主要分析一下内层循环体的执行次数即可。

在最坏情况下,也就是假如要排序的元素为{6,5,4,3,2,1}逆序,那么:

元素比较的次数为:

(N-1)+(N-2)+(N-3)+…+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;

元素交换的次数为:

(N-1)+(N-2)+(N-3)+…+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;

总执行次数为:

(N^2/2-N/2)+( N ^2/2-N/2)=N ^2-N;

按照大O推导法则,保留函数中的最高阶项那么最终冒泡排序的时间复杂度为O(N^2).


1.2选择排序(Selection Sort)

选择排序(Selection Sort)是一种更加简单直观的排序方法。

排序原理:

  1. 每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,如果当前索引处的值大于其他某个索引处的值,则假定其他某个索引处的值为最小值,最后可以找到最小值所在的索引
  2. 交换第一个索引处和最小值所在的索引处的值

在这里插入图片描述

排序API设计:

类名Selection
构造方法Selection():创建Selection对象
成员方法1. public static void sort(Comparable[] a):对数组a中的元素进行排序
2. private static boolean greater(Comparable v, Comparable w):判断v是否大于w,从小到大排序
3. private static void exch(Comparable[] a, int i, int j):交换数组中i和j位置的元素

代码实现:

Selection

package com.itcast.algorithm.sort;/*** @Author: Juechen* @Date: 2024/5/1* @Description: 选择排序* @Version: 1.0**/
public class Selection {/*** 对数组a中的元素进行排序*/public static void sort(Comparable[] a) {for (int i = 0; i < a.length; i++) {// 假设当前元素为最小值int min = i;// 遍历数组中的元素,找到最小值for (int j = i + 1; j < a.length; j++) {if (greater(a[min], a[j])) {min = j;}}// 将最小值与当前元素交换exch(a, i, min);}}/*** 比较v元素是否大于w元素* < 0 则是从大到小排序*/private static boolean greater(Comparable v, Comparable w) {return v.compareTo(w) > 0;}/*** 交换数组中的i和j两个元素*/private static void exch(Comparable[] a, int i, int j) {Comparable t = a[i];a[i] = a[j];a[j] = t;}
}

TestSelection

package com.itcast.algorithm.test;import com.itcast.algorithm.sort.Insertion;import java.util.Arrays;/*** @Author: Juechen* @Date: 2024/5/1* @Description: 测试选择排序* @Version: 1.0**/
public class TestSelection {public static void main(String[] args) {Integer[] arr = {4, 6, 8, 7, 9, 10, 2, 1};Insertion.sort(arr);System.out.println(Arrays.toString(arr));}
}

时间复杂度分析:

选择排序使用了双层for循环,其中外层循环完成了数据交换,内层循完成了数据比较,所以我们分别统计数据交换次数和数据比较次数:

数据比较次数:

(N-1)+(N-2)+(N-3)+…+2+1=((N-1)+1)*(N-1)/2=NA2/2-N/2;

数据交换次数 :

N-1

时间复杂度:N^2/2-N/2+( N-1) = N^2/2+N/2-1;

根据大O推导法则,保留最高阶项,去除常数因子,时间复杂度为O(N^2);


1.3插入排序(Insertion sort)

插入排序(Insertion sort)是一种简单直观且稳定的排序算法。

插入排序的工作方式非常像人们排序一手扑克牌一样。开始时,我们的左手为空并且桌子上的牌面朝下。然后,我们每次从桌子上拿走一张牌并将它插入左手中正确的位置。为了找到一张牌的正确位置,我们从右到左将它与已在手中的每张牌进行比较。

排序原理:

  1. 把所有的元素分为两组,已经排序的和未排序的;
  2. 找到未排序的组中的第一个元素,向已经排序的组中进行插入;
  3. 倒叙遍历已经排序的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插入元素,那么就把待插入元素放到这个位置,其他的元素向后移动一位;

在这里插入图片描述

排序API设计:

类名Insertion
构造方法Insertion ():创建Insertion 对象
成员方法1. public static void sort(Comparable[] a):对数组a中的元素进行排序
2. private static boolean greater(Comparable v, Comparable w):判断v是否大于w,从小到大排序
3. private static void exch(Comparable[] a, int i, int j):交换数组中i和j位置的元素

代码实现:

Insertion

package com.itcast.algorithm.sort;/*** @Author: Juechen* @Date: 2024/5/1* @Description: 插入排序* @Version: 1.0**/
public class Insertion {/*** 对数组a中的元素进行排序** @param a*/public static void sort(Comparable[] a) {// 从下标为1的元素开始,因为下标为0的元素默认有序for (int i = 1; i < a.length; i++) {// 从下标为i的元素开始,向前遍历,如果发现比它大的元素,则交换位置for (int j = i; j > 0; j--) {if (greater(a[j - 1], a[j])) {exch(a, j - 1, j);} else {break;}}}}/*** 比较v元素是否大于w元素*/private static boolean greater(Comparable v, Comparable w) {return v.compareTo(w) > 0;}/*** 交换数组中的i和j两个元素*/private static void exch(Comparable[] a, int i, int j) {Comparable t = a[i];a[i] = a[j];a[j] = t;}
}

TestInsertion

package com.itcast.algorithm.test;import com.itcast.algorithm.sort.Insertion;import java.util.Arrays;/*** @Author: Juechen* @Date: 2024/5/1* @Description: 测试插入排序* @Version: 1.0**/
public class TestInsertion {public static void main(String[] args) {Integer[] arr = {4, 3, 2, 10, 12, 1, 5, 6};Insertion.sort(arr);System.out.println(Arrays.toString(arr));}
}

时间复杂度分析:

插入排序使用了双层for循环,其中内层循环的循环体是真正完成排序的代码,所以,我们分析插入排序的时间复杂度,主要分析一下内层循环体的执行次数即可。

最坏情况,也就是待排序的数组元素为[12,10,6,5,4,3,2,1},那么:

比较的次数为 :

(N-1)+(N-2)+(N-3)+…+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;

交换的次数为:

(N-1)+(N-2)+(N-3)+…+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2:

总执行次数为:

(N^2/2-N/2)+(N ^2/2-N/2)=N ^2-N;

按照大O推导法则,保留函数中的最高阶项那么最终插入排序的时间复杂度为O(N^2).


二、高级排序

之前我们学习过基础排序,包括冒泡排序选择排序还有插入排序,并且对他们在最坏情况下的时间复杂度做了分析,发现都是O(N^2),而平方阶通过我们之前学习算法分析我们知道,随着输入规模的增大,时间成本将急剧上升,所以这些基本排序方法不能处理更大规模的问题,接下来我们学习一些高级的排序算法,争取降低算法的时间复杂度最高阶次幂。

2.1希尔排序(Shell Sort)

希尔排序是插入排序的一种,又称”缩小增量排序”,是插入排序算法的一种更高效的改进版本。

前面学习插入排序的时候,我们会发现一个很不友好的事儿,如果已排序的分组元素为{2,5,7,9,10},未排序的分组元素为{1,8},那么下一个待插入元素为1,我们需要拿着1从后往前,依次和10,9,7,5,2进行交换位置,才能完成真正的插入,每次交换只能和相邻的元素交换位置。那如果我们要提高效率,直观的想法就是一次交换,能把1放到更前面的位置,比如一次交换就能把1插到2和5之间,这样一次交换1就向前走了5个位置,可以减少交换的次数,这样的需求如何实现呢 ? 接下来我们来看看希尔排序的原理。

排序原理:

  1. 选定一个增长量h,按照增长量h作为数据分组的依据,对数据进行分组;
  2. 对分好组的每一组数据完成插入排序;
  3. 减小增长量,最小减为1,重复第二步操作。

在这里插入图片描述

排序API设计:

类名Shell
构造方法Shell():创建Shell对象
成员方法1. public static void sort(Comparable[] a):对数组a中的元素进行排序
2. private static boolean greater(Comparable v, Comparable w):判断v是否大于w,从小到大排序
3. private static void exch(Comparable[] a, int i, int j):交换数组中i和j位置的元素

代码实现:

Shell

package com.itcast.algorithm.sort;/*** @Author: Juechen* @Date: 2024/5/1* @Description: 希尔排序* @Version: 1.0**/
public class Shell {/*** 对数组a中的元素进行排序** @param a*/public static void sort(Comparable[] a) {// 1. 根据数组a的长度,确定增长量h的初始值int h = 1;while (h < a.length / 2) {h = 2 * h + 1;}// 希尔排序while (h >= 1) {// 排序// 2.1 找到待插入的元素for (int i = h; i < a.length; i++) {// 2.2 把待插入的元素插入到有序数列中for (int j = i; j >= h; j -= h) {if (greater(a[j - h], a[j])) {exch(a, j - h, j);} else {break;}}}// 减小h的值h = h / 2;}}/*** 比较v元素是否大于w元素*/private static boolean greater(Comparable v, Comparable w) {return v.compareTo(w) > 0;}/*** 交换数组中的i和j两个元素*/private static void exch(Comparable[] a, int i, int j) {Comparable t = a[i];a[i] = a[j];a[j] = t;}
}

TestShell

package com.itcast.algorithm.test;import com.itcast.algorithm.sort.Shell;import java.util.Arrays;/*** @Author: Juechen* @Date: 2024/5/1* @Description: 测试希尔排序* @Version: 1.0**/
public class TestShell {public static void main(String[] args) {Integer[] arr = {9, 1, 2, 5, 7, 4, 8, 6, 3, 5};Shell.sort(arr);System.out.println(Arrays.toString(arr));}
}

时间复杂度分析:

在希尔排序中,增长量h并没有固定的规则,有很多论文研究了各种不同的递增序列,但都无法证明某个序列是最好的,对于希尔排序的时间复杂度分析,在这里就不做分析了。

2.2归并排序(Merge Sort)

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列,即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

排序原理:

  1. 尽可能的一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是1为止。
  2. 将相邻的两个子组进行合并成一个有序的大组 ;
  3. 不断的重复步骤2,直到最终只有一个组为止。
    在这里插入图片描述

排序API设计:

类名Merge
构造方法Merge():创建Merge对象
成员方法1. public static void sort(Comparable[] a):对数组a中的元素进行排序
2. private static void sort(Comparable[] a, int lo, int hi):对数组a中从lo到hi之间的元素进行排序
3. private static void merge(Comparable[] a, int lo, int mid, int hi):对数组a中,从lo到hi为一组,从mid+1到hi为一组进行归并
4. private static boolean less(Comparable v, Comparable w):比较v元素是否小于w元素
成员变量1. private static Comparable[] assist:完成归并操作需要的辅助数组

代码实现:

Merge

package com.itcast.algorithm.sort;/*** @Author: Juechen* @Date: 2024/5/2* @Description: 归并排序* @Version: 1.0**/
public class Merge {// 归并所需要的辅助数组private static Comparable[] assist;/*** 比较v元素是否小于w元素*/private static boolean less(Comparable v, Comparable w) {return v.compareTo(w) < 0;}/*** 对数组a中的元素进行排序*/public static void sort(Comparable[] a) {// 1.初始化辅助数组assist = new Comparable[a.length];// 2.定义一个lo变量和hi变量,分别记录数组中最小的索引和最大的索引int lo = 0;int hi = a.length - 1;// 3.调用sort重载方法完成数组a中,从索引lo到索引hi的元素的排序sort(a, lo, hi);}/*** 对数组a中从lo到hi之间的元素进行排序*/private static void sort(Comparable[] a, int lo, int hi) {// 安全性校验if (hi <= lo) {return;}// 对lo到hi之间的数据进行分组int mid = lo + (hi - lo) / 2;// 分别对每一组数据进行排序sort(a, lo, mid);sort(a, mid + 1, hi);// 再把两个组中的数据进行归并merge(a, lo, mid, hi);}/*** 对数组a中,从lo到hi为一组,从mid+1到hi为一组进行归并*/private static void merge(Comparable[] a, int lo, int mid, int hi) {// 定义三个指针int p1 = lo;int p2 = mid + 1;int p3 = lo; // 记录辅助数组中的索引// 遍历,移动p1指针和p2指针,比较对应索引处的值,将较小的值放入辅助数组中while (p1 <= mid && p2 <= hi) {if (less(a[p1], a[p2])) {assist[p3++] = a[p1++];} else {assist[p3++] = a[p2++];}}// 如果p1的指针没有走完,那么顺序移动p1指针,把对应的元素放到辅助数组中while (p1 <= mid) {assist[p3++] = a[p1++];}// 如果p2的指针没有走完,那么顺序移动p1指针,把对应的元素放到辅助数组中while (p2 <= hi) {assist[p3++] = a[p2++];}// 把辅助数组中的元素拷贝到原数组中for (int i = lo; i <= hi; i++) {a[i] = assist[i];}}
}

TestMerge

package com.itcast.algorithm.test;import com.itcast.algorithm.sort.Merge;import java.util.Arrays;/*** @Author: Juechen* @Date: 2024/5/2* @Description: 测试归并排序* @Version: 1.0**/
public class TestMerge {public static void main(String[] args) {Integer[] arr = {8,4,5,7,1,3,6,2};Merge.sort(arr);System.out.println(Arrays.toString(arr));}
}

时间复杂度分析:

归并排序是分治思想的最典型的例子,上面的算法中,对a[lo…hi]进行排序,先将它分为a[lo…mid]和a[mid+1…hi]两部分,分别通过递归调用将他们单独排序,最后将有序的子数组归并为最终的排序结果。该递归的出口在于如果一个数组不能再被分为两个子数组那么就会执行merge进行归并,在归并的时候判断元素的大小进行排序。
在这里插入图片描述
用树状图来描述归并,如果一个数组有8个元素,那么它将每次除以2找最小的子数组,共拆log8次,值为3,所以树共有3层,那么自顶向下第k层有2^K个子数组,每个数组的长度为2 ^(3-K),归并最多需要2 ^(3-K)次比较。因此每层的比较次数为 2^k * 2^(3-k)=2N3,那么3层总共为 3*2^3。

假设元素的个数为n,那么使用归并排序拆分的次数为log2(n),所以共log2(n)层,那么使用log2(n)替换上面3* 2^3中的3这个层数,最终得出的归并排序的时间复杂度为 :log2(n)* 2^(log2(n))=log2(n)*n,根据大O推导法则,忽略底数,最终归并排序的时间复杂度为O(nlogn)

归并排序的缺点

需要申请额外的数组空间,导致空间复杂度提升,是典型的以空间换时间的操作。


2.3快速排序(Quick Sort)

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

排序原理:

  1. 首先设定一个分界值,通过该分界值将数组分成左右两部分 ;
  2. 将大于或等于分界值的数据放到到数组右边,小于分界值的数据放到数组的左边。此时左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值;
  3. 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
  4. 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左侧和右侧两个部分的数据排完序后,整个数组的排序也就完成了。

在这里插入图片描述

排序API设计:

类名Quick
构造方法Quick():创建Quick对象
成员方法1. public static void sort(Comparable[] a):对数组a中的元素进行排序
2. private static boolean less(Comparable v, Comparable w):判断v是否小于w,从小到大排序
3. private static void exch(Comparable[] a, int i, int j):交换数组中i和j位置的元素
4. private static void sort(Comparable[] a, int lo, int hi):对数组a中从lo到hi之间的元素进行排序
5. private static int partition(Comparable[] a, int lo, int hi):对数组a中,从索引lo到索引hi之间的元素进行分组,并返回分组界限对应的索引

代码实现:

Quick

package com.itcast.algorithm.sort;/*** @Author: Juechen* @Date: 2024/5/2* @Description: 快速排序* @Version: 1.0**/
public class Quick {/*** 比较v元素是否小于w元素*/private static boolean less(Comparable v, Comparable w) {return v.compareTo(w) < 0;}/*** 数组元素i和j交换位置*/private static void exch(Comparable[] a, int i, int j) {Comparable t = a[i];a[i] = a[j];a[j] = t;}/*** 对数组a中的元素进行排序*/public static void sort(Comparable[] a) {int lo = 0;int hi = a.length - 1;sort(a, lo, hi);}/*** 对数组a中从lo到hi之间的元素进行排序*/private static void sort(Comparable[] a, int lo, int hi) {if (hi <= lo) {return;}int partition = partition(a, lo, hi); // 返回的是分界值变换后的索引// 让左子组有序sort(a, lo, partition - 1);// 让右子组有序sort(a, partition + 1, hi);}/*** 对数组a中,从索引lo到索引hi之间的元素进行分组,并返回分组界限对应的索引*/private static int partition(Comparable[] a, int lo, int hi) {// 确定分界值Comparable key = a[lo];// 定义两个指针,分别指向待切元素的最小索引处和 最大索引处的下一个位置int left = lo;int right = hi + 1;// 开始切分while (true) {// 从左往右扫描,找到第一个大于等于key的元素while (less(a[++left], key)) {if (left == hi) {break;}}// 从右往左扫描,找到第一个小于key的元素while (less(key, a[--right])) {if (right == lo) {break;}}// 如果left和right相遇,则退出循环if (left >= right) {break;} else {// 交换left和right指向的元素exch(a, left, right);}}// 交换分界值exch(a, lo, right);return right;}
}

TestQuick

package com.itcast.algorithm.test;import com.itcast.algorithm.sort.Quick;import java.util.Arrays;/*** @Author: Juechen* @Date: 2024/5/3* @Description: 测试快速排序* @Version: 1.0**/
public class TestQuick {public static void main(String[] args) {Integer[] arr = {6, 1, 2, 7, 9, 3, 4, 5, 8};Quick.sort(arr);System.out.println(Arrays.toString(arr));}
}

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

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

时间复杂度分析:

快速排序的一次切分从两头开始交替搜索,直到left和right重合,因此,一次切分算法的时间复杂度为O(n),但整个快速排序的时间复杂度和切分的次数相关。

最优情况 : 每一次切分选择的基准数字刚好将当前序列等分。

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

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

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

三、排序稳定性

稳定性的定义

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

稳定性的意义

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

常见排序算法的稳定性

冒泡排序 :
只有当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]的时候才会交换位置,如果两个元素相等则不会交换位置,所以它并不会破坏稳定性,归并排序是稳定的。
快速排序:
快速排序需要一个基准值,在基准值的右侧找一个比基准值小的元素,在基准值的左侧找一个比基准值大的元素,然后交换这两个元素,此时会破坏稳定性,所以快速排序是一种不稳定的算法。

相关文章:

数据结构与算法之经典排序算法

一、简单排序 在我们的程序中&#xff0c;排序是非常常见的一种需求&#xff0c;提供一些数据元素&#xff0c;把这些数据元素按照一定的规则进行排序。比如查询一些订单按照订单的日期进行排序&#xff0c;再比如查询一些商品&#xff0c;按照商品的价格进行排序等等。所以&a…...

VSCode通过SSH连接虚拟机Ubuntu失败

问题说明 最近使用VSCode通过SSH连接Ubuntu&#xff0c;通过VSCode访问Ubuntu进行项目开发&#xff0c;发现连接失败 在VSCode中进行SSH配置 这些都没有问题&#xff0c;但在进行连接时候出现了问题&#xff0c;如下&#xff1a; 出现了下面这个弹窗 解决方法 发现当…...

在Codelab对llama3做Lora Fine tune微调

Unsloth 高效微调大模型的工具&#xff0c;通过Unsloth微调Llama3, Mistral, Gemma 速度提升2-5倍&#xff0c;内存减少70%&#xff01; Codelab 创建一个jupyter notebook 选择 T4 GPU 安装Fine tune 相关的lib %%capture import torch major_version, minor_version torch…...

KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记13 - STM32的SDIO学习5 - 卡的轮询读写擦

KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记13 - STM32的SDIO学习5 - 卡的轮询读写擦 一、前情提要二、目标三、技术方案3.1 读写擦的操作3.1.1 读卡操作3.1.2 写卡操作3.1.3 擦除操作 3.2 一些技术点3.2.1 轮询标志位的选择不唯一3.2.2 写和擦的卡状态查询3.2.3 写的速度 四、代…...

【C++】HP-Socket(三):UdpClient、UdpServer、UdpCast、UdpNode的区别

1、简述 UDP是无连接的&#xff0c;在UDP传输层中并没有客户端和服务端的概念。但是可以在应用层定义客户端和服务端&#xff0c;可以灵活的互换客户端和服务端&#xff0c;或者同时既是客户端也是服务端。 HP-Socket中在应用层定义了四种UDP组件&#xff1a;UdpClient、UdpS…...

java设计模式六 访问者

访问者模式&#xff08;Visitor Pattern&#xff09;是一种设计模式&#xff0c;它允许你将算法附加到对象结构中的各个元素上&#xff0c;而不必修改对象结构本身。它主要用于处理对象结构非常稳定&#xff0c;但频繁需要在此结构上执行不同操作的场景。访问者模式通过将操作移…...

中间件研发之Springboot自定义starter

Spring Boot Starter是一种简化Spring Boot应用开发的机制&#xff0c;它可以通过引入一些预定义的依赖和配置&#xff0c;让我们快速地集成某些功能模块&#xff0c;而无需繁琐地编写代码和配置文件。Spring Boot官方提供了很多常用的Starter&#xff0c;例如spring-boot-star…...

libcity笔记:添加新模型(以RNN.py为例)

创建的新模型应该继承AbstractModel或AbstractTrafficStateModel 交通状态预测任务——>继承 AbstractTrafficStateModel类轨迹位置预测任务——>继承AbstractModel类 1 AbstractTrafficStateModel 2 RNN 2.1 构造函数 2.2 predict 2.3 calculate_loss...

Ansible---自动化运维工具

一、Ansible概述 1.1 Ansible简介 Ansible是一款自动化运维工具&#xff0c;通过ssh对目标主机进行配置、应用部署、任务执行、编排调度等操作。它简化了复杂的环境管理和自动化任务&#xff0c;提高了工作效率和一致性&#xff0c;同时&#xff0c;Ansible的剧本(playbooks)…...

5.Git

Git是一个分布式版本控制工具&#xff0c;主要用于管理开发过程中的源代码文件&#xff08;Java类、xml文件、html文件等&#xff09;。通过Git仓库来存储和管理这些文件&#xff0c;Git仓库分为两种 本地仓库&#xff1a;开发人员自己电脑上的Git仓库远程仓库&#xff1a;远程…...

探索中位数快速排序算法:高效寻找数据集的中间值

在计算机科学领域&#xff0c;寻找数据集的中位数是一个常见而重要的问题。而快速排序算法作为一种高效的排序算法&#xff0c;可以被巧妙地利用来解决中位数查找的问题。本文将深入探讨中位数快速排序算法的原理、实现方法以及应用场景&#xff0c;带你领略这一寻找中间值的高…...

密码学《图解密码技术》 记录学习 第十五章

目录 十五章 15.1本章学习的内容 15.2 密码技术小结 15.2.1 密码学家的工具箱 15.2.2 密码与认证 15.2.3 密码技术的框架化 15.2.4 密码技术与压缩技术 15.3 虚拟货币——比特币 15.3.1 什么是比特币 15.3.2 P2P 网络 15.3.3地址 15.3.4 钱包 15.3.5 区块链 15.3.…...

如何在 Ubuntu 16.04 上为 Nginx 创建自签名 SSL 证书

简介 TLS&#xff0c;即传输层安全协议&#xff0c;及其前身SSL&#xff0c;即安全套接字层&#xff0c;是用于将普通流量包装在受保护的加密包装中的网络协议。 使用这项技术&#xff0c;服务器可以在服务器和客户端之间安全地发送流量&#xff0c;而不会被外部方拦截。证书…...

5.协议的编解码

本章内容其实没有多大难度&#xff0c;主要考察大家的细心程度.计算数据长度然后截取相应字节数组并按照协议进行解码&#xff0c;编码则反之。 1.基础消息的编解码 Override public BasicMessage decode(byte[] bytes) {int dataLength ByteUtil.bytesToInt(ByteUtil.extra…...

数据结构基础| 线性表

线性表 定义 没有元素则为空表 例子: 稀疏多项式的运算 图书信息管理系统 特点 线性结构 同类型 线性表的类型定义 1.基本操作: InitList(&L) 操作结果:构造空的线性表L DestroyList(&L) 初始化条件:线性表L存在 操作结果:销毁线性表L(线性表L不存在) Cle…...

嵌入式学习

笔记 作业 有如下结构体 struct Student{ char name[16]; int age; double math_score; double chinese_score; double english_score; double physics_score; double chemistry…...

sass-loader和node-sass与node版本的依赖问题

sass-loader和node-sass与node版本的依赖问题 没有人会陪你走到最后&#xff0c;碰到了便是有缘&#xff0c;即使到了要下车的时候&#xff0c;也要心存感激地告别&#xff0c;在心里留下空白的一隅之地&#xff0c;多年后想起时依然心存甘味。——林清玄 报错截图 报错信息 np…...

基于BP神经网络的QPSK解调算法matlab性能仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ........................................................................ for ij 1:leng…...

Linux服务器常用巡检命令

在Linux服务器上进行常规巡检是确保服务器稳定性和安全性的重要措施之一。以下是一些常用的巡检命令和技巧&#xff1a; 1. 查看系统信息 1.1 系统信息显示 命令&#xff1a;uname -a ​​​​ [rootlinux100 ~]# uname -a Linux linux100 4.15.0-70-generic #79-Ubuntu SMP…...

VSCode 配置 CMake

VSCode 配置 C/C 环境的详细过程可参考&#xff1a;VSCode 配置 C/C 环境 1 配置C/C编译环境 如果是 Windows 环境&#xff0c;需要安装 MingW。 方案一 可以去官网(https://sourceforge.net/projects/mingw-w64/)下载安装包。 注意安装路径不要出现中文。 打开 windows she…...

​《MATLAB科研绘图与学术图表绘制从入门到精通》示例:绘制德国每日风能和太阳能产量3D线图

在MATLAB中&#xff0c;要绘制3D线图&#xff0c;可以使用 plot3 函数。 在《MATLAB科研绘图与学术图表绘制从入门到精通》书中通过绘制德国每日风能和太阳能产量3D线图解释了如何在MATLAB中绘制3D线图。 购书地址&#xff1a;https://item.jd.com/14102657.html...

【信息系统项目管理师知识点速记】质量管理:控制质量

控制质量是为了评估绩效,确保项目输出完整、正确且满足客户期望,而监督和记录质量管理活动执行结果的过程。控制质量过程需要在整个项目期间开展,其目的是测量产品或服务的完整性、合规性和适用性,以确保项目达到主要干系人的质量要求。 12.5.1 输入 项目管理计划 质量管理…...

【云原生】Pod 的生命周期(一)

【云原生】Pod 的生命周期&#xff08;一&#xff09;【云原生】Pod 的生命周期&#xff08;二&#xff09; Pod 的生命周期&#xff08;一&#xff09; 1.Pod 生命期2.Pod 阶段3.容器状态3.1 Waiting &#xff08;等待&#xff09;3.2 Running&#xff08;运行中&#xff09;3…...

Golang | Leetcode Golang题解之第71题简化路径

题目&#xff1a; 题解&#xff1a; func simplifyPath(path string) string {stack : []string{}for _, name : range strings.Split(path, "/") {if name ".." {if len(stack) > 0 {stack stack[:len(stack)-1]}} else if name ! "" &am…...

Unreal游戏GPU性能优化检测模式全新上线

UWA已经在去年推出了针对于Unity项目的GPU性能优化工具&#xff0c;通过对GPU渲染性能、带宽性能以及各种下探指标&#xff0c;帮助Unity项目研发团队定位由GPU导致的发热耗电问题。这个需求在Unreal团队中也极为强烈&#xff0c;因此UWA将该功能移植到针对Unreal项目的GOT Onl…...

设计网页用什么软件

在设计网页时&#xff0c;可以使用多种软件来完成不同的任务。以下是一些常用的网页设计软件&#xff0c;以及它们的特点和用途。 1. Adobe Photoshop&#xff1a; Adobe Photoshop 是一款功能强大的图像编辑软件。在网页设计中&#xff0c;它常用于创建和编辑网页所需的图像、…...

⑪ - 测试工程师通识指南

📖 该文隶属 程序员:职场关键角色通识宝典✍️ 作者:哈哥撩编程(视频号同名) 博客专家全国博客之星第四名超级个体COC上海社区主理人特约讲师谷歌亚马逊演讲嘉宾科技博主极星会首批签约作者🏆 推荐专栏: 🏅 程序员:职场关键角色通识宝典🏅...

RabbitMQ知识点总结和复习

之前项目中用到RabbitMQ的场景主要是订单信息的传递&#xff0c;还有就是利用RabbitMQ的死信队列属性设置&#xff0c;实现延迟队列效果&#xff0c;实现超时支付取消功能&#xff0c;以及在两个不同项目中传递数据等场景。 最近几年的工作中都是一直用的RabbitMQ&#xff0c;…...

ContEA阅读笔记

Facing Changes: Continual Entity Alignment for Growing Knowledge Graphs 面对变化&#xff1a;不断增长的知识图谱的持续实体对齐 Abstract 实体对齐是知识图谱(KG)集成中一项基本且重要的技术。多年来&#xff0c;实体对齐的研究一直基于知识图谱是静态的假设&#xff…...

使用nvm切换nodejs版本

查看可以安装的版本&#xff1a; 使用nvm list显示已安装的nodejs版本&#xff1a; 选择一个版本下载&#xff1a; 切换对应的版本&#xff1a;...

买服饰网站建设/浏览广告赚佣金的app

题面 题意:给你一个矩阵,然后有很多的圆,这些圆可能相交着,一个或者几个就导致这个矩形被分割开了,就是从最下面的边到上面的边,连线被这些圆阻隔了,每一堆圆当做一个阻碍,问一共有几个阻碍 题解:看起来好难做啊!~!,我怎么知道一堆圆就把矩阵一横着的局域都占完了 哎然后猛然发…...

开关网站建设/无锡网站seo

本文代码基于 python3.6 和 pygame1.9.4。五子棋比起我之前写的几款游戏来说&#xff0c;难度提高了不少。如果是人与人对战&#xff0c;那么&#xff0c;电脑只需要判断是否赢了就可以。如果是人机对战&#xff0c;那你还得让电脑知道怎么下。我们先从简单的问题来看。开端画棋…...

免费招聘网站哪个好/什么软件可以找客户资源

PDF文件是一款比较安全的文件格式&#xff0c;有时候我们会将一些重要的文件格式转换成PDF文档&#xff0c;又或者是因为它的打印效果非常的好&#xff0c;那么如果想要将gif图片转换成PDF文档&#xff0c;应该怎么进行转换操作&#xff1f;gif图片转换成PDF文档方法是什么&…...

做游戏ppt下载网站有哪些/鄞州seo服务

你是否正准备选购一台电脑呢&#xff0c;处理品牌、外观和做工&#xff0c;电脑的硬件配置也通常是我们关注的重点&#xff0c;而如何通过看电脑CPU型号来了解性能好坏呢?CPU生产厂商会根据CPU产品的市场定位来给属于同一系列的CPU产品确定一个系列型号&#xff0c;从而便于分…...

宁波企业网站制作哪家好/影视后期培训班一般要多少钱

OS版本&#xff1a;Red Hat Enterprise Linux AS4/5 网上有很多关于linux下修改MAC地址的方法&#xff0c;大多依葫芦画瓢&#xff0c;似乎都没验证过&#xff0c;达不到修改的目的。 经过我的详细测试&#xff0c;最终成功解决了这个问题。 误区一&#xff1a; #ifconfig eth0…...

想学网站制作/小程序制作费用一览表

一、Array数组对象1、Array对象&#xff1a;使用单独的变量名来存储一系列的值。2、数组的创建&#xff1a;例&#xff1a;var myArray["Hello","yeleven","yeleven2"];3、数组的访问&#xff1a;通过指定数组名以及索引号码&#xff0c;你可以访…...