希尔排序、快速排序、归并排序的实现分析以及时间复杂度

编程入门 行业动态 更新时间:2024-10-10 14:28:39

<a href=https://www.elefans.com/category/jswz/34/1769823.html style=希尔排序、快速排序、归并排序的实现分析以及时间复杂度"/>

希尔排序、快速排序、归并排序的实现分析以及时间复杂度

高级排序

    • 希尔排序
    • 快速排序
    • 归并排序

希尔排序

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

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

需求:

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

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

排序原理:

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

图示:

增长量h的确定:增长量h的值每一固定的规则,我们这里采用以下规则:

int h=1
while(h<5){
h=2h+1;//3,7
}
//循环结束后我们就可以确定h的最大值;
h的减小规则为:
h=h/2

测试代码:

public class ShellTest {public static void main(String[] args) {Integer[] integers = {7, 6, 5, 0, 4, 3, 120, 2, 1, 100, -100};Shell.sort(integers);System.out.println(Arrays.toString(integers));}
}

核心代码:

public class Shell {public static void sort(Comparable[] a) {//1.根据a的数组长度,确定增长量h的初始值int h = 1;while (h < a.length / 2) {h = 2 * h + 1;}// 16        //2.希尔排序    {4, 5, 6, 3, 2, 1,9,5,10,596,452,56,3,5,6,9}while (h >= 1) {//排序//2.1找到待插入的元素for (int i = h; i < a.length; i++) {//2.2把待插入的元素插入到有序数列中for (int j = i; j >= h; j -= h) {//待插入的元素是a[j],比较a[j]和a[j-h]if (greater(a[j - h], a[j])) {//交换元素exch(a, j - h, j);} else {//待插入元素已经找到了合适的位置,结束循环break;}}}//减小h的值h = h / 2;}}public static boolean greater(Comparable v, Comparable w) {return v.compareTo(w) > 0;}public static void exch(Comparable[] a, int i, int j) {Comparable temp;temp = a[i];a[i] = a[j];a[j] = temp;}
}

希尔排序的时间复杂度分析:
这个确实有点难度,公认的时间复杂度为O (n^ 1.3),具体就不展开说了,有会的兄弟可以留言已下。

我自己测试大数据量的时候,希尔排序的性能确实高于插入排序。

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

快速排序

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

需求:

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

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

排序原理:

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

图示:

切分原理:

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

  1. 找一个基准值,用两个指针分别指向数组的头部和尾部;
  2. 先从尾部向头部开始搜索一个比基准值小的元素,搜索到即停止,并记录指针的位置;
  3. 再从头部向尾部开始搜索一个比基准值大的元素,搜索到即停止,并记录指针的位置;
  4. 交换当前左边指针位置和右边指针位置的元素;
  5. 重复2,3,4步骤,直到左边指针的值大于右边指针的值停止。

图示:

测试代码:

public class Quick02Test {public static void main(String[] args) {Integer[] integers = {7, 6, 5, 0, 4, 3, 120, 2, 1, 100, -100};Quick02 quick02 = new Quick02();quick02.sort(integers, 0, integers.length - 1);for (Integer integer : integers) {System.out.println(integer);}}
}

核心代码:

public class Quick02 {public void sort(Comparable[] arr, int left, int right) {if (left >= right) {return;}int par = par(arr, left, right);sort(arr, left, par - 1);sort(arr, par + 1, right);}public int par(Comparable[] arr, int left, int right) {int hi = right + 1;int lo = left;Comparable temp = arr[left];while (true) {while (arr[--hi].compareTo(temp) > 0) {if (hi <= left) {break;}}while (temp.compareTo(arr[++lo]) > 0) {if (lo >= right) {break;}}if (lo >= hi) {break;} else {exch(arr, lo, hi);}}exch(arr, hi, left);return hi;}private void exch(Comparable[] arr, int hi, int left) {Comparable temp = arr[hi];arr[hi] = arr[left];arr[left] = temp;}
}

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

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

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

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

最优情况:

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

最坏情况:

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

平均情况:

每一次切分选择的基准数字不是最大值和最小值,也不是中值,这种情况我们也可以用数学归纳法证明,快速排序的时间复杂度为O(nlogn)。

稳定性:

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

归并排序

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

需求:

排序前:

{8,4,5,7,1,3,6,2}

排序后:

{1,2,3,4,5,6,7,8}

排序原理:

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

图示:

归并原理:

测试代码:

public class Merge02Test {public static void main(String[] args) {Integer[] arr = {9, 8, 7, 6, 0, 5, 4, 0, -100};Merge02 merge02 = new Merge02();merge02.sort(arr);for (int i = 0; i < arr.length; i++) {System.out.println(arr[i]);}}
}

核心代码:

public class Merge02 {private static Comparable[] assist;public void sort(Comparable[] arr) {assist = new Comparable[arr.length];sort(arr, 0, arr.length - 1);}public void sort(Comparable[] arr, int lo, int hi) {if (lo >= hi) {return;}int mid = lo + (hi - lo) / 2;sort(arr, lo, mid);sort(arr, mid + 1, hi);merge(arr, lo, mid, hi);}public void merge(Comparable[] arr, int lo, int mid, int hi) {int i = lo;int p1 = lo;int p2 = mid + 1;while (p1 <= mid && p2 <= hi) {if (arr[p1].compareTo(arr[p2]) > 0) {assist[i++] = arr[p2++];} else {assist[i++] = arr[p1++];}}while (p1 <= mid) {assist[i++] = arr[p1++];}while (p2 <= hi) {assist[i++] = arr[p2++];}for (int index = lo; index <= hi; index++) {arr[index] = assist[index];}}
}

归并排序时间复杂度分析:

归并排序是分治思想的最典型的例子,上面的算法中,对a[lo…hi]进行排序,先将它分为a[lo…mid]和a[mid+1…hi] 两部分,分别通过递归调用将他们单独排序,最后将有序的子数组归并为最终的排序结果。该递归的出口在于如果 一个数组不能再被分为两个子数组,那么就会执行merge进行归并,在归并的时候判断元素的大小进行排序。

用树状图来描述归并,如果一个数组有8个元素,那么它将每次除以2找最小的子数组,共拆log8次,值为3,所以 树共有3层,那么自顶向下第k层有2k个子数组,每个数组的长度为2(3-k),归并最多需要2^(3-k)次比较。因此每层 的比较次数为 2^k * 2(3-k)=23,那么3层总共为 32^3。 假设元素的个数为n,那么使用归并排序拆分的次数为log2(n),所以共log2(n)层,那么使用log2(n)替换上面32^3中 的3这个层数,最终得出的归并排序的时间复杂度为:log2(n)* 2^(log2(n))=log2(n)*n,根据大O推导法则,忽略底 数,最终归并排序的时间复杂度为O(nlogn);

归并排序的缺点:

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

稳定性:

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

更多推荐

希尔排序、快速排序、归并排序的实现分析以及时间复杂度

本文发布于:2023-06-28 07:05:01,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/923572.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:希尔   复杂度   快速   时间

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!