当前位置:   article > 正文

图解比较几种排序算法:快速排序、归并排序、堆排序_快速排序归并排序堆排序比较

快速排序归并排序堆排序比较

图解比较几种排序算法:快速排序、归并排序、堆排序

快速排序

算法思路:

首先得到数组的第一个数,将比它小的放到它的左边,比它大的放到右边。然后递归操作就可以了。

图解:

在这里插入图片描述
1.以上一组数据,先从数字6开始,记录下数字6作为基准数字,即上图红色,在数组的末端数字9开始向前与6比较找到小于6的数字,9大于6,下一个,8大于6,再下一个,4小于6,这时将4移到6的位置。
在这里插入图片描述
2.接着从数组首端即此时第一个4所在的位置开始向后找到大于6的数字,一直到7发现大于6,这时将7移到4一开始时候的位置,即第二个4所在的位置。
在这里插入图片描述
3.此时发现两个数字7之间还有数字9没有进行比较,所以要重复进行以上两步操作。等到所有数字都比较过了,这时将第一个数字7的位置放入我们开始取得基准数字6。
在这里插入图片描述
4.这时数字6左边的都小于6,右边的都大于6。但是数字还没有顺序。我们只需要将6左边的数字和6右边的数字重复上面三步操作就可以了。也就是4,2,3和9,7,8,9分别再操作一次。一直递归下去直到完成排序。

代码:

public static void main(String[] args){
    int[] num = {6,2,3,1,7,4,8,9};
    int i=0;
    quickSort(i,num.length-1,num);
    for(i=0;i<num.length;i++){
        System.out.println(num[i]);
    }
}
private static void quickSort(int i,int j,int[] num){
    if (i<j){
        int begin=i,end=j;
        int q = num[i];
        while (i<j){
            while (num[j]>=q&&i<j){ j--; }
            num[i]=num[j];
            while (num[i]<q&&i<j){ i++; }
            num[j]=num[i];
        }
        num[i]=q;
        quickSort(begin,i-1,num);
        quickSort(i+1,j,num);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

总结:

快速排序每次将数组分为两份进行操作,这种分而治之的方法使得其平均时间复杂度为O(nlogn),所以快排的速度是非常快的,但是它不是稳定的,在极端情况下,比如一个近似有序的数组,因为快速排序是先将数组排序在进行递归,这就使得分而治之的方法不管用了,所以算法时间复杂度退化为O(n2) 。那有没有一种算法能使时间复杂度稳定为O(nlogn)呢,当然有,归并排序

归并排序

算法思路:

归并排序不同于快速排序的一点是,它会先递归分隔然后再排序合并。它会先分隔到只剩1到2个数字然后开始合并,合并过程中两边的数字都已经排好序了,所以合并的过程也比较简单,只要遍历两边数字保存到新的数组中就可以了。

图解

在这里插入图片描述
1.红色以上包括红色是分隔的过程,以下是合并的过程。分隔是就是简单的对半分,而在合并时我们发现两边的数都已经是有序的了,所以它们合并时非常简单,只要再定义一个数组,每次取两边数的最小值放入到新数组中就可以了。

代码:

public  static void main(String[] args){
    int[] num = {6,2,3,1,7,4,8,9};
    mergeSort(0,num.length-1,num);
    for(int i=0;i<num.length;i++){
        System.out.println(num[i]);
    }
}
/**
 *   先分成两份
 */
private  static void mergeSort(int a, int b, int[] num){
    if (a<b){
        int mid = (a+b)/2;
        mergeSort(a,mid,num);
        mergeSort(mid+1, b, num);
        merge(a,mid,b,num);
    }
}
/**
 *  再合并
 */
private static void merge(int a, int mid, int b, int[] num) {
    //此时a-b不是有序的,但是a-mid是有序的,mid+1-b是有序的
    int[] p = new int[b-a+1];
    int i=a,j=mid+1,k=0;
    while (i<=mid&&j<=b){
        if (num[i]<=num[j]){
            p[k++]=num[i++];
        }else {
            p[k++]=num[j++];
        }
    }
    while (i<=mid){
        p[k++]=num[i++];
    }
    while (j<=b){
        p[k++]=num[j++];
    }
    for (i=a;i<=b;i++) {
        num[i] = p[i - a];
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

总结:

归并排序先递归分隔所以它的时间复杂度能稳定在O(nlogn),这样看起来是比快速排序好一些,可是归并排序的名声却不如快速排序,这是为什么呢?我们知道判断算法的好坏要从时间、空间复杂度两个角度来看。很明显,快速排序我们只要额外申请1个int类型来保存基准值,而归并排序却要新建一个数组,所以快速排序的空间复杂度远远小于归并排序。这也是典型的空间换时间的算法。那又要接着问了,有没有空间时间复杂度都好的呢,答案也是肯定的,堆排序

堆排序

算法思路

堆排序是依靠堆来实现的,首先我们要明白堆分为大顶堆和小顶堆,大顶堆是指父节点大于其孩子节点,根节点最大,小顶堆相反。我们先把堆调整为大顶堆,然后只要将根节点与最后一个结点交换位置,接着要再次调整堆为大顶堆,这样就能使最大元素放到正确位置了。排除掉刚才移动的最大节点,将剩下的节点循环操作就可以了。因此难点只有如何调整堆了,看下面的图好理解些。

图解:

在这里插入图片描述
1.上面的为建立的堆,下面是对应的数组,最下面的数字为数组的下标。首先我们要明白二叉树的性质,第一个叶结点的下标为length/2,即整个数组长度为8,第一个叶节点9的下标为4;还有一个性质就是,如果一个节点下标为 i 且有子节点,那么其左子节点的下标为2i,右子节点的下标为2i+1。比如节点2的下标为1,其左子节点7的下标为2。
在这里插入图片描述
2.下面我们开始调整堆,从第一个非叶节点开始,也就是7开始。判断7的左右孩子节点是否大于它,如果大于将最大的那个与7交换。这里9大于7,所以这两个节点交换位置。接着我们还要判断交换后7所在的位置(即下标为7)是否还存在子节点,若存在那么还要重复第二步操作。我们要在初始化调整堆时从第一个非叶节点开始,而不是从根节点开始。这是因为从下到上的顺序可以保证等操作到上面结点时下面的节点都已经是父节点大于子节点了,不需要再调整了。
3.接着到第二个非叶节点,也就是3,其他操作和第二步相同。最后的调整结果如下图。
在这里插入图片描述

代码:

public  static void main(String[] args){
    int[] num = {6,2,3,1,7,4,8,9};
    //初始化堆
    for(int i=(num.length/2)-1;i>=0;i--){
        heapAdjust(i,num.length-1,num);
    }
    for(int i=0;i<num.length;i++){
        int q = num[0];
        num[0]=num[num.length-1-i];
        num[num.length-i-1]=q;
        heapAdjust(0,num.length-2-i,num);
    }
    for (int i=0;i<num.length;i++){
        System.out.println(num[i]);
    }
}

/**
 * 调整堆
 */
private static void heapAdjust(int i, int j, int[] num) {
    int p = num[i];
    int n = 2*i+1;
    while (n<=j){
        if (n+1<=j&&num[n]<num[n+1]){
            n++;
        }
        if (num[n]<=p){
            break;
        }else {
            num[i]=num[n];
            i=n;
            num[n]=p;
            n=2*n+1;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

总结:

建立堆的时间复杂度为O(n),调整堆的复杂度为O(logn),总体复杂度为O(nlogn)。这样看来堆排序在时间空间复杂度都非常好,同样的为啥它不如快排有名呢?那是因为为了维持大顶堆的特性,我们要频繁的对堆进行调整,而在很多时候是不需要的,所以它是不稳定的。而且堆排序是比快排稍微慢些的。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号