算法概述
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
主要参数
平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 排序方式 | 稳定性 |
O(n²) | O(n²) | O(n²) | O(1) | In-place | 不稳定 |
往期回顾
本次动画还是沿用了前面我们封装好的一些函数,唯一不同的地方就是排序算法不同。如果没有看过我前面文章的小伙伴,建议先去看一下往期的该系列博客。
公共的内容我这里就直接贴出代码,不再进行讲解。
#warp .active{
background-color: #da4d66;
color: #ffffff;
}
#warp .undetermined{
background-color: green;
color: #ffffff;
}
#warp .complete{
background-color: #00bcd4;
color: #ffffff;
}
var warp = document.getElementById('warp')
var point = [] //保存所有的节点
//创建dom类
let Dom = function(height, left){
this.dom = null
this.height = height
this.bottom = 0
this.left = left
this.createDom()
}
Dom.prototype = {
createDom: function(){ //创建节点
var ele = document.createElement('div')
ele.style.height = this.height + "%"
ele.style.left = this.left
ele.innerHTML = `<span>${this.height}</span>`
warp.appendChild(ele)
this.dom = ele
return this //返回this是为可以使用链式调用
},
addClass: function(name){ //添加样式类
this.dom.classList.add(name)
return this
},
removeClass: function(name){ //移除样式类
this.dom.classList.remove(name)
return this
},
move(position,pixel){ //移动位置
this.dom.style[position] = pixel
this[position] = pixel
return this
}
}
//交换数组中的两个位置
Array.prototype.arrExchange = function(p1, p2){
let v = this[p1]
this[p1] = this[p2]
this[p2] = v
return this
}
过程分析
在实现动画前,还是先来分析一下排序的过程
let array = [75,55,83,33,94,49,40,28,42,37,68]
let len = array.length
let temp
for (let i = 1; i < len; i++) {
temp = array[i]
let j = i - 1
while(array[j] > temp && j >= 0){
array[ j + 1] = array[j]
j--
}
array[j + 1] = temp
}
细心的朋友可以注意到,选择排序的时间复杂度永远都是 O(n²)。对于一个随机的数组,无论它初始状态是什么顺序,即便是初始状态就排好了顺序。对于选择排序来说,它的每轮循环必须都要从头到尾遍历一遍,即使在过程中找到了更小的元素,但是无法确定的是后面是否还有更小的元素。
动画实现
这对于我们实现这个动画有什么好处呢,选择排序它与插入排序不同,我们是可以明确的知道在选择排序中,内层循环的次数。可以利用这一点来确定每轮外层循环应该设置的延迟时间是多少。老办法用一个time变量用来累加每次内层循环的时间。
let len = array.length
let minIndex,temp,time = 0
for (let i = 0; i < len - 1; i++) {
setTimeout(() => {
minIndex = i
for (let j = i + 1; j < len; j++) {
setTimeout(() => {
if(array[j] < array[minIndex])
minIndex = j
}, (j-i-1)*500);
}
array = array.arrExchange(i, minIndex)
}, time);
time += (len - i + 1)*500 + 200
}
起始时time为0,直接开始第一轮的循环,第二轮循环必须要在第一轮完成后开始,第一轮循环共涉及到 len 个元素,每进行一轮就会递减一个元素,就应为 (len - i)*500。可以看到上面是(len - i + 1)*500 + 200 ,由于最后一个元素比较完成后,要进行位置互换,互换时间我在这里也设置的为500ms,所以这里要加1,这里+200ms是为了使每轮之间稍微有点间隔的余地。凡是都要留条后路。
let len = array.length
let minIndex,temp
for (let i = 0; i < len - 1; i++) {
setTimeout(() => {
minIndex = i
operation.changeStyle(minIndex, 'undetermined')
for (let j = i + 1; j < len; j++) {
setTimeout(() => {
operation.changeStyle(j, 'active')
operation.changeStyle(minIndex, 'undetermined')
if(array[j] < array[minIndex]){
minIndex = j
}
}, (j-i-1)*500);
}
setTimeout(() => {
array = array.arrExchange(i, minIndex) //数组中交换位置
operation.changePosition(i, 'left', 50*(minIndex - i) ) //视觉上交换位置
operation.changePosition(minIndex, 'left', -(50*(minIndex - i)))
point.arrExchange(i, minIndex) //实际的dom节点交换,一定要与数组同步,否则会影响后面的效果
point[i].addClass('complete')
}, (len - i)*500);
}, time);
time += (len - i + 1)*500 + 200
}
与冒泡排序和插入排序的动画还有一点不同,选择排序在比较过程中只对节点颜色上进行改变,只有当每轮结束后才会进行交换位置。这里一定要注意节点交换位置的时间,在上面已经得出了时间,其实就是 (len - i)*500 。
在前面的博客讲到过,这里的位置交换仅仅是视觉上的交换,不要忘记动画交换后对真实的dom节点进行交换,使其与排序数组中的顺序一直,否则后面的动画效果会让你抓头。
关注公众号《前端筱园》,回复“算法动画”获取完整源码
个人网站:www.dzyong.top