排序算法(二)-归并排序、快速排序

一:归并排序

归并排序:如果要排序一个数组,我们先把数组从中间分成前后倆个部分,对前后2部分分别排序,再将排好序的2个部分合并在一起,这样整个数组就变成有序的了。
在这里插入图片描述
我们通过上面的图解过程可以看出,我们先将一个大的数组分解成一个一个的子问题,再把子问题进行合并,得到最后的结果。这其实就是分治思想。 这其实和我们之前说的递归很相似。其实归并排序使用的就是分治思想,通过递归来实现。基于上面的图解我们试着写出归并排序的代码实现。
通过之前我们知道,写出递归代码的2个主要要素
一:找到递推表达式
二:找到终止条件
其实通过上面的图解过程我们很容易获得递推表达式:
递推公式:mergeSort(p,q) = 合并(mergeSort(p,s),mergeSort(s+1,q));
其中mergeSort(p,q) 表示对数组p,q的下标部分进行归并排序,而下标s我们可以使用p与q的中间位置,即:s = (p+q)/2;得到mergeSort(p,s)与mergeSort(s+1,q)后,我们需要将mergeSort(p,s)与mergeSort(s+1,q)进行合并,合并即对2个有序的子数组合并成一个有序的数组。通过这个我们很容易知道终止条件是p>=q。
有了递归的2个主要要素,我们很容易得到归并排序的代码实现:

// a标识排序的数组 p下标的开始位置  q下标的结束位置
public static int[] mergeSort(int[] a , int p , int q){
    int[] result = new int[q-p+1];
    // 只有一个元素
    if (p == q){
        result[0] = a[p];
        return result;
    }
    int s = (p+q)/2;
    int[] left = mergeSort(a,p,s);
    int[] right = mergeSort(a,s+1,q);
    // 合并2个有序数组
    merge(result,left,right);
    return result;
}

 // 合并2个有序数组
    public static void merge(int[] result , int[] left , int [] right){
        int leftSize = left.length;
        int rightSize = right.length;
        int leftIndex = 0;
        int rightIndex = 0;
        int resultIndex = 0;
        while(leftIndex < leftSize || rightIndex < rightSize){
            if (leftIndex >= leftSize){
                result[resultIndex] = right[rightIndex];
                rightIndex++;
            } else if (rightIndex >= rightSize){
                result[resultIndex] = left[leftIndex];
                leftIndex++;
            }else{
                if (right[rightIndex] > left[leftIndex] ){
                    result[resultIndex] = left[leftIndex];
                    leftIndex++;
                }else{
                    result[resultIndex] = right[rightIndex];
                    rightIndex++;
                }
            }
            resultIndex++;
        }
    }

现在我们来看看排序算法的三个要素:
首先我们很容易知道归并排序是稳定的,因为用到额外的空间存储临时数组,故空间复杂度是O(n);
接下来我们看看时间复杂度: 对于n长度的数组进行归并排序我们容易得到 T(n) = 2*T(n/2) + n,其中n为合并2个有序数组。
如此递归我们可得到 T(n) = 2^k *T(n/2^k) + k * n 当n/2^k = T(1) = 1;可以得到k = logn 从而时间复杂度O(nlogn)。

二:快速排序

快速排序我们一般称为快排,在排序算法用的相对比较多,现在让我们看看如何实现快排。
快速排序核心思想:对一个数组进行排序,选择数组中任意一个元素作为分区点pivot,接着遍历数组将大于分区点pivot的元素放在右侧,将小于分区点放到左侧。将分区点pivot放在中间,此时分成3个部分,小于分区点,分区点,大于分区点。
在这里插入图片描述从上述的图解过程我们可以看出,归并是从下到上的过程,而快速排序是一个从上到下的过程。
在快速排序中,对于每次的快排,我们可以将小于分区点的数据放入一个数组,将大于分区点的数据放入另一个数组中,最后合并到数组中.其过程如下:
在这里插入图片描述
通过上面的过程,我们可以虽然可以实现快速排序的过程,但是明显使用了额外的内存空间。
那么快速排序便不是原地排序了,如何实现快速排序的原地排序实现呢?
其实我们可以借助选择排序的思想,将数组分成[p,r-1]与[r,q]2个部分,其中[p,r-1]的部分为处理的部分,[r,q]为未处理部分,我们每次取[r,q]中的一个元素与分区点pivot进行比较,如果小于分区点,则加入[p,r-1]的末尾。但是我们知道数组中插入一个元素是极其耗时,那么如何解决这个问题呢?
在这里插入图片描述
通过图解我们可以使用指针i与j,从数组的起始位置,如果j的值大于分区点的值, j 指针加一, i 位置不变,直到 j 的值小于等于分区点的值,交换 i 与 j 的值,并且 i 与 j 同事加一, 一直到遍历完数组。
通过上述过程,利用递归,很容易实现快速排序的原地排序的代码:

/*
* result:待排序数组  p 起始位置  q 终止位置
*/
public static void quickSort(int[] result , int p , int q ){
    // 选取最后一个为分区点
    int pivot = result[q];
    int i = p ;
    int j = p;
    while( j < q  ){
        if(result[j] < pivot){
            int temp = result[i];
            result[i] = result[j];
            result[j] = temp;
            i++;
        }
        j++;
    }
    // 交换 i 与 q 的值
    int temp = result[i];
    result[i] = pivot;
    result[q] = temp;
    //  小于分区点 递归执行 直到 长度为1
    if (i-1 > p){
        quickSort(result,p,i-1);
    }
    // 大于分区点 递归执行 直到 长度为1
    if (i+1 < q){
        quickSort(result,i+1,q);
    }
}

我们来看看快排的性能问题,快排是一个原地排序算法,并且快排不是稳定的,快速也是将一个数组分成近似相同的2个分组,故它的时间复杂度与归并排序相同为O(nlongn)

三:总结

接下来我们对我们学习的排序算法进行一个简单的总结

排序算法时间复杂度空间复杂度稳定性
冒泡排序O(n^2)O(1)稳定
选择排序O(n^2)O(1)稳定
选择排序O(n^2)O(1)不稳定
归并排序O(nlongn)O(n)稳定
快速排序O(nlongn)O(1)不稳定
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值