快速排序与代码实现

    快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。是一种分治思想的应用。

   快速排序是通过递归调用“切分”来实现的。

一、切分(partition)

   假设j是一个切分点,给定数组为a,切分点j要满足以下三个条件:

          1、对于j,a[j]已经排定

          2、a[lo]到a[j-1]都不大于a[j]

          3、a[j+1]到a[hi]都不小于a[j]

  切分的实现方法:首先取数组第一个元素为切分元素pivot=a[lo],之后用左指针从数组左端到数组右端扫描,直到找到一个不小于pivot的元素,同样用右指针从数组右端到左端扫描,找到一个不大于pivot的元素,之后交换两个指针元素的位置,如此下去直到两个指针相遇停止。这样就可以保证左指针左侧的元素都不大于pivot,右指针右侧的元素都不小于pivot,这样交换pivot与a[右指针],然后返回右指针,这样右指针的位置即为切分点,切分元素为pivot。

  举个例子,假设给定数组是a = [7 3 5 8 1 4 10 13],以第一个元素7为切分元素。

  可以看到最后切分元素的右边子数组的元素都大于切分元素,左边子数组的元素都小于切分元素,再对左右两个子数组分别切分,不断地重复这个过程,就能实现快速排序。

	private static int partition(int[] a, int lo, int hi) {
		// 取分割点,分割点左边元素都小于分割元素,右边元素都大于分割元素
		int i = lo, j = hi + 1;
		while (true) {
			// 由左及右寻找大于等于lo的元素位置
			while (a[++i] < a[lo]) if (i == hi) break;
			// 由右及左找到小于等于lo的元素位置
			while (a[--j] > a[lo]) if (j == lo)break;
			// 当左右指针相遇时停止扫描 
			if (i >= j) break;
			// 否则交换左右指针元素
			shuffle.swap(a, i, j);
		}
		// 交换切割元素与右指针元素
		shuffle.swap(a, lo, j);
		return j;
	}

二、快速排序

    前面说过快排就是递归调用切分来实现的,递归停止条件就是数组元素为1时返回,子问题是找到切分点,排序切分点左右子数组sort(lo,j-1)、sort(j+1, hi)。

    

private static void sort(int[] a, int lo, int hi) {
		if (hi <= lo) return;
		int j = partition(a, lo, hi); // 找到数组的分割点
		sort(a, lo, j-1);
		sort(a, j+1, hi);
	}

三、代码

 Java:

import java.util.Arrays;

public class QuickSort {
	// 实现快速排序
	public static void sort(int[] a) {
		shuffle.Shuffle(a); // 随机打乱输入数组,从而降低对输入的依赖
		sort(a, 0, a.length-1);
	}
	private static void sort(int[] a, int lo, int hi) {
		if (hi <= lo) return;
		int j = partition(a, lo, hi); // 找到数组的分割点
		sort(a, lo, j-1);
		sort(a, j+1, hi);
	}

	private static int partition(int[] a, int lo, int hi) {
		// 取分割点,分割点左边元素都小于分割元素,右边元素都大于分割元素
		int i = lo, j = hi + 1;
		while (true) {
			// 由左及右寻找大于等于lo的元素位置
			while (a[++i] < a[lo]) if (i == hi) break;
			// 由右及左找到小于等于lo的元素位置
			while (a[--j] > a[lo]) if (j == lo)break;
			// 当左右指针相遇时停止扫描 
			if (i >= j) break;
			// 否则交换左右指针元素
			shuffle.swap(a, i, j);
		}
		// 交换切割元素与右指针元素
		shuffle.swap(a, lo, j);
		return j;
	}
	public static void main(String[] args) {
		int[] a = {3,2,6,7,1};
		sort(a);
		System.out.println(Arrays.toString(a));
		
		
		

	}

}

python:

class Sort:
    def __init__(self, nums):
        self.nums = nums
    def sort(self, nums):
        length = len(nums)
        print("所给数组为:%s"%nums)
        self.quicksort(nums, 0, length-1)
    def quicksort(self, nums, lo, hi):
        if(lo >= hi): return
        part = self.partition(nums, lo, hi)
        self.quicksort(nums, lo, part-1)
        self.quicksort(nums, part+1, hi)
    def partition(self, nums, lo, hi):
        print(nums[lo:hi+1])
        left = lo + 1
        right = hi
        while True:
            while (nums[lo] < nums[left]):
                if (left == hi):
                    break
                left += 1
            while (nums[lo] > nums[right]):
                if (right == lo):
                    break
                right -= 1
            if(left >= right):
                break
            self.exch(nums, left, right)
        self.exch(nums, lo, right)
        print("切分点为: %d"% nums[right])
        return right
    def exch(self, nums, left, right):
        temp = nums[left]
        nums[left] = nums[right]
        nums[right] = temp

四、时间复杂度分析

    参考:https://www.cnblogs.com/fengty90/p/3768827.html

    最好情况:选择切分点时每次都将数组切分的很均匀,如果要排序的数组有n个元素,那么递归树的深度即为\left \lfloor log2n \right \rfloor+1 (这个不理解可以回忆一下完全二叉树的深度),即需要递归log2n次,第一次Partiation应该是需要对整个数组扫描一遍,做n次比较。然后,获得的枢轴将数组一分为二,那么各自还需要T(n/2)的时间(注意是最好情况,所以平分两半)。于是不断地划分下去因此最好情况下快排的时间复杂度为O(nlogn)

    最坏情况:

当待排序的序列为正序或逆序排列时,且每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。此时需要执行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置,因此比较次数为 ,最终其时间复杂度为O(n^2)。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值