理解快速排序分治法实现js

function quickSort(arr) {
	if (!arr || arr.length<2) {
		return
	}
	quickSortMain(arr, 0, arr.length-1)
}

function quickSortMain(arr, L, R) {
	if (L<R) {
		// 随机取个值下标的数当作中间值指标,并和最后一个值交换位置
		// 交换的目的是方便设置左边界,在midBorder函数中
		// Math.random()生成[0,1)的随机数
		// Math.random()*(R-L)生成0 ~ R-L长度的随机数
		// 但是得到的随机数是在[0, R-L)上的,取不到R-L
		// 所以我们+1就可以了
		// 为什么要用这种方式
		// 很简单,因为L不一定是0
		// 如果L是5,R是10,我应该是在5~10中取
		// 直接使用只能取到[0,6),而不是想要的[5,11)
		// 所以可以得到想要的随机数的区间公式
		// min+(Math.floor(Math.random()*(max-min+1)))= [min, max+1) 
		// 其中max>min
		// 还有个问题就是为什么要随机取一个值呢
		// 如果不取随机数,那么最坏情况必然是O(n^2)
		// 由于在计算机中随机数是等概率的
		// 中间值可能刚好在中间,也可能在最坏的位置
		// 但是一平均下来,会把时间复杂度收敛到O(nlogn)
		// 过程我也不会证明,大家知道就行了
		swap(arr, L + ((Math.random()*(R-L+1))>>0), R)
		// 通过midBorder把数组变成如下形式
		// [小于中间值序列,等于中间值序列,大于中间值序列]
		const border = midBorder(arr, L, R)
		quickSortMain(arr, L, border[0] - 1)
		quickSortMain(arr, border[1] + 1, R)
	}
}

function midBorder(arr, L, R) {
	let small = L - 1  // 小于中间值的序列右边界,下面简称右边界
	let big = R // 大于中间值的序列左边界,下面简称左边界
	// L是当前位置,如果没有和大于中间值的序列边界重合继续分类
	while ( L < big ) {
		// 当前值小于中间值,划分到小于序列,
		// 操作就是交换两者位置,并且右边界右移一位,当前位置右移一位
		if (arr[L] < arr[R]) {
			swap(arr, ++small, L++)
		} else if (arr[L] > arr[R]) { 
		// 当前值大于中间值,划分到大于序列,
		//左边界左移一位,当前位置不动,
		//因为右边的值是未访问过的,等下次循环再次判断
			swap(arr, --big, L)
		} else { // 当前值等于中间值,继续往后看
			L++
		}
	}
	// 把中间值换回中间相等序列
	// 当循环结束后,L 和 big相等,
	// 这个位置的值肯定是大于等于中间值的,直接交换即可
	swap(arr, big, R)
	// 返回大于,小于,中间值的序列的左右边界下标
	return [small+1, big]
}

function swap(arr, a, b) {
	let tmp = arr[a]
	arr[a] = arr[b]
	arr[b] = tmp
	
	// 交换两个数也可以使用异或运算,从而不用新建一个tmp变量
	// 异或:自己异或自己为0,任何数和0异或都为自己
	// a = x
	// b = y
	// a = a ^ b // a此时为 x^y
	// b = a ^ b // b此时为 x^y^y = x ^ 0 = x
	// a = a ^ b // a此时为 x^y^x = y ^ 0 = y
	// 但是前提是a,和b在内存中是两个独立的空间,不然会把自己变成0
	// 所以保险的方式还是上面的交换方式
}


const arr = [6,5,3]
quickSort(arr)
console.log(arr)

midBorder函数的功能
假设有一个数组[6,5,3,4,4,3,1,2]
为了演示完整的功能,我们假设取到的随机值下标对应的值为第一个3
当然其他值也可以,可以自己推一推
quickSortMain中先交换了一次位置
所以进入midBorder时
数组为[6,5,2,4,4,3,1,3]

一开始
小于中间值3的序列的边界在-1,
大于中间值3的序列的边界在7,
这里为什么左边界是设置为7而不是8
因为上边交换了位置到最后,该位置是已知的
并且指针重合后,7位置的数,必定小于等于在重合位置的数

当前位置在0
当前位置没有和大于序列的边界重合时循环判断
6>3
左边界左扩(先--),即交换下标6,和当前下标的值
由于是从左往右看的,所以不知道交换过来的值是什么情况
所以当前下标不动,等下一次循环再判断

当前位置0,左边界-1,右边界6
1<3
右边界右扩(先++),即交换下标0和当前位置的数
左边的数是已知的,所以当前位置右移(后++)

。。。。。。

3==3
当前位置直接右移

。。。。。。

完成循环后
交换重合位置的数和末尾的数
得到
[1,2,3,3,4,5,6,4]
我们的中间值是3
左边界的下标为1
右边界的下标为3
左边界的下标+1为中间值3序列的左边界
右边界下标为中间值3序列的右边界
返回这个数组即[2,3]

接下来又会递归0~14~7的子数组
const border = midBorder(arr, L, R)
quickSortMain(arr, L, border[0] - 1)
quickSortMain(arr, border[1] + 1, R)
重复上述步骤
直到有序

下图演示一下midBorder内部功能
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值