鼠年学算法之 JS基础排序算法

马上就要过年了,趁着这段长假好好地研究一下基础的排序算法,先来看一张图
在这里插入图片描述

  • 稳定性:若是相邻的两个数字相等,排序后不会影响(交换)他们的位置
  • 不稳定性:相邻的两个数字相等,排序后有可能交换他们的位置
  • 内排序:所有排序都在内存中操作
  • 外排序:因为数据太大 ,需要把数据放在磁盘中,排序操作通过磁盘和内存的数据传输来运行
  • 时间复杂度:一个算法所耗费的时间
  • 空间复杂度:运行完一个程序所占内存的大小

注意:下面的排序实例,都是将排序方法定义在数组的prototype上,这样使用的时候即可直接使用.(点)运算符调用,如下

Array.prototype.methods = function(){
    
    
	//......
}

arr.methods()//使用点运算符调用

1.插入排序

实现原理(这里指升序排列)

  1. 选取数组的第二项与之前的数作比较,若之前的项 > 第二项,则交换位置并插入当前空位
  2. 选取第三项,与第三项之前的项两两比较,若是比较的项 > 第三项,则交换位置,当比较的项 < 第三项时,则插入当前空位
let arr = [1, -1, 2, 4, 8, 0, 3]

Array.prototype.insert = function(){
    
    
	for(let i = 1; i < this.length; i++){
    
    
		let putItem = this[i] //被提出的数组项
		
		for(let j = i - 1; j >= 0 && putItem < this[j]; j--){
    
     //将提出的项与之前的项两两比较,若满足条件,则交换项目            
			this[j+1] = this[j]
			this[j] = putItem 
		}
	}

}
arr.insert()
console.log(arr) //[-1, 0, 1, 2, 3, 4, 8]

在这里插入图片描述

2.冒泡排序

实现原理(这里指升序排列)

  1. 将数组的左右项目两两比较,将较大数字放在靠数组的右边。
  2. 上一步完成后,这时数组的最后一项就是数组的最大项目。
  3. 以此类推,排序操作完成后,数组升序排列
//冒泡排序
let arr = [0, -1, 1, 10, 4]
Array.prototype.sorty = function(){
    
    
	for (var i = 0; i < this.length-1; i++) {
    
    
		for(var j = 0; j < this.length-1; j++){
    
    
			if (this[j] > this[j+1]){
    
    
				console.log(arr)
				var temp = this[j]
				this[j] = this[j+1]
				this[j+1] = temp 
			};
		}
	};
}

arr.sorty()
console.log(arr)//[-1, 0, 1, 4, 10]

在这里插入图片描述

3.归并排序

归并排序与之前的排序方式相比会难以理解一些,先来看一张图
在这里插入图片描述
实现原理(这里指升序排列):分为两部分("分""治"

  • 第一部分:分

像上面图片所述的,将一个多数项的数组分为两个数组,直到单项数字

  • 第二部分:治

将单项数字依次组合成多个只有两项的小数组,并排序,这时数组的左边就是最小的数字,之后再进行合并数组,并排序。直到排序完毕。

详细视频讲解点击此处

//这里定义的函数,可以将拆分开的数组进行排序
function merge(left, right){
    
    
	var newArr = []
	var i = 0;
	var j = 0;
	while(i < left.length && j < right.length){
    
     //比较数组的头部,并将符合条件的数字推入新数组
		if(left[i] < right[j]){
    
    
			newArr.push(left[i++])
		}else{
    
    
			newArr.push(right[j++])
		}
	}
	//上面循环完毕后,会出现其中一个数组还有残留的数项(剩余的数项可以直接推入新数组)
	//下面将剩下的数都推入新数组
	while(i < left.length) {
    
    newArr.push(left[i++])};
	while(j < right.length) {
    
    newArr.push(right[j++])}
	return newArr
}

写完后可以自己代入两个数组试一试
"治"实现了,下面来实现"分"

function Merge_sort(arr){
    
    
	if(arr.length == 1) return arr 
	//当数组长度为一时,代表 "分" 成功,返回当前数项 
	let point = Math.floor(arr.length/2)
	let leftArr = arr.slice(0, point)
	let rightArr = arr.slice(point)
	return Merge(Merge_sort(leftArr),Merge_sort(rightArr)) //递归,直到数组长度唯一
}

console.log(Merge_sort([0, -1, 1, 10, 4, -4, 800]))
//left: [-1]  right: [1]
//left: [0]   right: [-1, 1]
//left: [10]  right: [4]
//left: [-4]  right: [800]
//lrft: [4, 10]  right: [-4, 800]
//left: [-1, 0, 1]  right: [-4, 4, 10, 800]
// [-4, -1, 0, 1, 4, 10, 800]

从打印的数据也可以看出,将拆分的单项数字,合并成两项的数组比较大小并排序,再合并成多项,以此类推。最后两项数组合并为一项并排序完毕。
在这里插入图片描述

4.快速排序

快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高,因此经常被采用,也经常会到各大公司的面试题见到。同时它也是具有一定难度的排序算法。

实现原理(这里指升序排列)

  1. 先从数列中取出一个数作为基准数(这里取第一位)。
  2. 将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边
  3. 再对左右区间重复第二步(递归调用),直到各区间只有一个数。

详细的步骤点击
建议看比较官方的网站讲解,博主看了几个视频,感觉讲得全都不一样。

function quick_sort(arr, start, end){
    
    
	let i = start
	let j = end
	let temp = arr[i] //取出基准数
	while(i<j){
    
    
		while(i < j && arr[j] >= temp) j-- // 从右向左找第一个小于temp的数
		if(i < j){
    
    
			arr[i] = arr[j]
		}
		while(i < j && arr[i] < temp) i++ //从左向右找第一个大于temp的数
		if(i < j){
    
    
			arr[j] = arr[i]
		}
		console.log(arr)
	}
	arr[i] = temp // 将基准数放入数组中
	//上面的代码执行完后,基数右边都是比基数小的数,基数左边都是比基数大的数字
	
	//下面的判断语句将基数左右两边的数字继续相同的操作(递归调用)
	if (i > start) quick_sort(arr, start, i - 1);
	if (j < end) quick_sort(arr, j + 1, end);
	return arr
}

let a = quick_sort([2, 3, 1, -1, -1, 56, 3, 5], 0, 7)
console.log(a) //[-1, -1, 1, 2, 3, 3, 5, 56]

5.选择排序

经过几天的基础算法学习,简单的算法也已经可以看完原理,自己实现了。

实现原理(这里指升序排列)

  1. 选择数列中最小的数字,放到数列的第一个位置,
  2. 再排除第一个数字(最小的数字),选择这个数列中的最小的数字,放到数列第二位中(既第二位为最小的数字)
  3. 重复完毕后,排序完毕

简单来说,就是找到最小值并将其放置在第一位,接着找到第二小的值并将其放在第二位,以此类推。

详细教学视频请戳

Array.prototype.choose_sort = function(){
    
    

	for(let i = 0; i < this.length; i++){
    
     //规定整体循环的次数,并规定比较参照数(i)
		for (let j = i + 1; j < this.length; j++) {
    
     //将 i 与数组后的每一项比较,满足条件则交换
			if (this[i] > this[j]) {
    
    
				let temp = this[i];
			    this[i] = this[j];
			    this[j] = temp;
			};
		};
	}
	return this
}

console.log([1, 3, 4, 4, -1, 3].choose_sort()) //[-1, 1, 3, 3, 4, 4]

6.希尔排序

希尔排序又叫做缩小增量排序,是上面讲到的直接插入排序的改进版。
希尔排序的平均排序性能也要优于插入排序

实现原理(这里指升序排列)

  1. 选取一个增量(gep),这里选取数组长度的一半。增量会逐次缩小
  2. 根据增量,将数列分为不同的子序列,分别对子序列插入排序
  3. 最后当增量为 1 时,进行最后一次插入排序。完毕

点击视频查看详细教学

希尔排序与很多排序不同,它使用增量来分割子序列,理解需要花费一些时间。
在实现这个排序时,因为开始没有理解透彻,编写代码时,也踩了许多坑。

下面的代码是我所理解还没透彻时,编写的问题代码

function shell(arr){
    
    
	let gep = Math.floor(arr.length/2) 

	for (; gep > 0; gep = Math.floor(gep/2)) {
    
     //规定增量,并分割子序列
		for (let j = gep; j < arr.length; j++) {
    
     // 将子序列进行插入排序
			let i = j - gep
			
			while(i >= 0 && arr[i] > arr[j]){
    
    
				let temp = arr[i]
				arr[i] = arr[j]
				arr[j] = temp
			}
		};
	};
	console.log(arr)
}

shell([2,5,1,5,6,23,33,2,1,10])  //[1, 1, 2, 5, 2, 5, 6, 10, 23, 33]

上述代码的输出结果并没有将数组排序成功。
找了许久资料和问题后发现,问题出在子序列的插入排序上。
上面的代码只是将子序列数字两两比较(类似于冒泡排序),没有使用插入排序,也并不满足冒泡排序的条件。当然就无法排序成功

下面是改良之后的代码

function shell2(arr){
    
    
	let gep = Math.floor(arr.length/2) // 声明增量
	for (; gep > 0; gep = Math.floor(gep/2)) {
    
     //规定增量,并分割子序列
	 
		for (let j = gep; j < arr.length; j++) {
    
     
			let i = j 
			let temp = arr[j]  //选取子序列的第二位进行插入排序
			
			while(i - gep >= 0 && arr[i - gep] > temp){
    
    
				arr[i] = arr[i - gep]
				i = i - gep
				arr[i] = temp				
			}
		};
	};
	console.log(arr)
}

shell2([2,5,1,5,6,23,33,2,1,10]) //[1, 1, 2, 2, 5, 5, 6, 10, 23, 33]

上面第七行的temp = arr[i],是在while循环体外进行缓存的,之前踩了大坑,错误的将它缓存至while内部,每一次缓存arr[i]准备交换数字时,因为之前的循环可能将arr[i] 替换了位置,导致排序错误

7.计数排序

对于已经确定范围的数列,使用计数排序是明智且高效的选择。(一般用作范围小于100的排序,最高效)
计数排序也是用于 确定范围的整数的线性时间排序算法

实现原理(这里指升序排列)

  1. 规定索引数组的长度(为数列中最大值的数字 + 1)
  2. 计算数列中每个数字出现的次数,并计入索引数组中
  3. 根据索引数组,将排序好的数列推入一个空数组

计数排序的概念有一些抽象,刚刚开始我也是一头雾水。
但是多看了几遍视频将原理弄明白之后,还是很容易实现的

讲解视频请点击

function count_sort(arr, maxValue){
    
     
	let len = arr.length
	let bucketArr = new Array(maxValue + 1) //以数组的最大值设置bucketArr的长度
	let resultArr = []

	for (let i = 0; i < len; i++) {
    
       //判断原数组中每个元素出现的个数,并累加
		if (!bucketArr[arr[i]]) bucketArr[arr[i]] = 0 ;
		bucketArr[arr[i]]++ 
	};
	
	for (var j = 0; j < len; j++) {
    
      // 将bucketArr中的排序,填充至resultArr
	 		while(bucketArr[j] > 0){
    
      
			resultArr.push(j)
			bucketArr[j]--
		}
	};
	console.log(resultArr)
}

count_sort([2,2,2,5,5,2,6,8,9,9,1,3], 9) //[1, 2, 2, 2, 3, 5, 5, 6, 8, 9, 9]

猜你喜欢

转载自blog.csdn.net/pspxuan/article/details/104060095
今日推荐