js中快速排序的优化历程

对sort的疑惑

自从es6出现后,我们不知不觉就用上了sort进行排序,毕竟人家又快又好。那么sort究竟怎么实现的呢?

参考了一下大佬的文章(三种快速排序以及快速排序的优化),我便悄悄拿起了键盘。

思路分析这些我就不写了,请移步大佬博客参照,由于大佬使用的java代码,我便用js来进行验证一下。

排序条件以及优化结果

根据大佬的分析,数组排序要面对的有以下四种情况,我们用1w个随机值为例进行一下测试,结果如下:

提示:由于每次随机值不一样,测试结果有一定出入,但大致应该差不多

数组情况随机数组升序数组降序数组重复数组解决的问题
固定基准值2929532920282解决随机数组的排序问题
随机基准值362011277解决有序数组的排序问题
三数取中301111256解决反向有序数组的排序问题
三数取中+插排301111263优化排序效率,1w可能看不太出来,100w个数据效果更明显
三数取中+插排+聚同228821解决重复数据较多时的排序问题
sort18829解决了以上的几个问题
  • 随机数组
    let arr1 = [];
    for(let i = 0; i < 10000; i++){
    	arr1.push(Math.floor(Math.random()*10000) )
    }
    
  • 升序数组
    let arr2 = [];
    for(let i = 0; i < 10000; i++){
    	arr2.push(i) 
    }
    
  • 降序数组
    let arr3 = [];
    for(let i = 10000; i > 0; i--){
    	arr3.push(i) 
    }
    
  • 重复数组
    let arr4 = [];
    for(let i = 0; i < 10000; i++){
    	arr4.push(Math.floor(Math.random()*10) )
    }
    
  • 比较方式,使用了performance.now来计算时间
    	let arrs = [arr1, arr2, arr3, arr4]
    	for(let i = 0; i< arrs.length; i++){
    		const a1 = performance.now();
    		sort( arrs[i] )
    		const a2 = performance.now();
    		console.log(a2 - a1);
    	}
    
快速排序优化1之固定基准值
	const sort = arr => {
		
	  if (arr.length < 2) return arr;
	  
	  // 固定基准值
	  let pivot = arr[0];

	  let left = [];
	  let right = [];
		
	  // 从1开始
	  for (let i = 1, total = arr.length; i < total; i++) {
	    if (arr[i] < pivot) left.push(arr[i]);
	    else right.push(arr[i]);
	  };
	  
	  return [
	    ...sort(left),
	    pivot,
	    ...sort(right)
	  ];
	};
快速排序优化2之随机基准值
	const sort = arr => {
				
	  if (arr.length < 2) return arr;
	  
	  // 使用随机基准值
	  let random = Math.floor(Math.random()*arr.length);
	  let pivot = arr[random];
	  arr.splice(random, 1);

	  let left = [];
	  let right = [];
	  
	  // 从0开始
	  for (let i = 0, total = arr.length; i < total; i++) {
	    if (arr[i] < pivot) left.push(arr[i]);
	    else right.push(arr[i]);
	  };
	  
	  return [
	    ...sort(left),
	    pivot,
	    ...sort(right)
	  ];
	};
快速排序优化3之三数取中
	const sort = arr => {
		
	  if (arr.length < 2) return arr;
	  
	  // 使用三数取中
	  let start = arr[0];
	  let middle = arr[Math.floor(arr.length/2)];
	  let end = arr[arr.length-1];
	  let pivot = middle;
	  
		if (start > middle && start < end) {
			pivot = start;
			arr.splice(0, 1)
		} else if (end > middle && end < start) {
			pivot = end;
			arr.splice(arr.length - 1, 1)
		} else if (pivot === middle) {
			arr.splice(Math.floor(arr.length / 2), 1)
		}

	  let left = [];
	  let right = [];
	
	  for (let i = 0, total = arr.length; i < total; i++) {
	    if (arr[i] < pivot) left.push(arr[i]);
	    else right.push(arr[i]);
	  };
	  
	  return [
	    ...sort(left),
	    pivot,
	    ...sort(right)
	  ];
	};
快速排序优化4之三数取中+插排
	function insertArr(arr){
		for(let i = 1 , len = arr.length; i < len ; i++){
			let preIndex = i - 1;
			let current = arr[i];
			while(preIndex >= 0 && current < arr[preIndex] ){
				arr[preIndex+1] = arr[preIndex]
				preIndex--;
			}
			arr[preIndex+1] = current;
		}
		return arr;
	}
			
	const sort = arr => {

		if (arr.length < 2) return arr;

		// 当数组长度小于10用插入排序
		if (arr.length < 10) {
			return insertArr(arr);
		}

		// 使用三数取中
		let start = arr[0];
		let middle = arr[Math.floor(arr.length / 2)];
		let end = arr[arr.length - 1];
		let pivot = middle;

		if (start > middle && start < end) {
			pivot = start;
			arr.splice(0, 1)
		} else if (end > middle && end < start) {
			pivot = end;
			arr.splice(arr.length - 1, 1)
		} else if (pivot === middle) {
			arr.splice(Math.floor(arr.length / 2), 1)
		}

		let left = [];
		let right = [];


		for (let i = 0, total = arr.length; i < total; i++) {
			if (arr[i] < pivot) {
				left.push(arr[i]);
			} else {
				right.push(arr[i]);
			}
		};

		return [
			...sort(left),
			pivot,
			...sort(right)
		];
	};
快速排序优化5之三数取中+插排+聚同

	function getMiddle(a, b, c) {
		var min = Math.min(a, b, c);
		var max = Math.max(a, b, c);
		var middle = a + b + c - min - max;
		return middle;
	}
	
	function insertArr(arr){
		for(let i = 1 , len = arr.length; i < len ; i++){
			let preIndex = i - 1;
			let current = arr[i];
			while(preIndex >= 0 && current < arr[preIndex] ){
				arr[preIndex+1] = arr[preIndex]
				preIndex--;
			}
			arr[preIndex+1] = current;
		}
		return arr;
	}
			
	const sort = arr => {

		if (arr.length < 2) return arr;

		// 当数组长度小于10用插入排序
		if (arr.length < 10) {
			return insertArr(arr);
		}

		// 使用三数取中
		let start = arr[0];
		let middle = arr[Math.floor(arr.length / 2)];
		let end = arr[arr.length - 1];
		let pivot = getMiddle(start, middle, end);
		
		let left = [];
		let right = [];
		let alike = [];

		for (let i = 0, total = arr.length; i < total; i++) {
			if (arr[i] < pivot) {
				left.push(arr[i]);
			} else if (arr[i] > pivot) {
				right.push(arr[i]);
			} else {
				alike.push(arr[i])
			}
		};

		return [
			...sort(left),
			...alike,
			...sort(right)
		];
	};
结语

使用最后一层优化后,结果依然和原生sort有一定的差距,但是大致相差不是远。本想用100w个数据来测试,但是前两种方法直接让控制台死机,但只要能得出结果就ok,可见直接用快速排序并不能达到最佳效果。

追加总结1

今天对第五种方案进行了一些优化,去掉了多余 if-else 的判断。并且重新对 原生 js 的 Array.prototype.sort 进行测试,它的表现依旧非常出色。在写 sort 的时候,我也发现了一个非常重要的注意事项,那就是一定要记着 return 0,这个方法其实就是为了解决高浓度重复数组的排序问题。效果等同,甚至优于聚同方案

写法如下:

arr.sort((a, b) => {
	if( a > b ){
		return 1;
	}else if( a < b ){
		return -1;
	}else{
		// 等同聚同方案
		return 0
	}
})

参考文献

https://blog.csdn.net/insistgogo/article/details/7785038.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值