冒泡排序(Bubble Sort)
今天在某贴子上看到一个问题,如何使用动画的形式描述冒泡排序,下面的答案有点让人模棱两可,大多数都是文字描述思路,光有思路不够,作为程序员还要动手写出来才可以。冒泡排序虽然是一种基础简单的排序,但是对于刚入门的小伙伴来说也需要一点点理解,为了帮助大家理解冒泡排序,特意写了一遍关于冒泡排序文章,并详细解答其中每一步的意义,供大家参考。
一、什么是冒泡排序
1.1定义
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
1.2 思路
- 比较相邻元素,如果前一个比后一个大或者小,就交换他们两个,下面我们都按照升序来说,也就是前一个比后一个大的情况
- 对每一个相邻元素都做出同样的比较操作,从开始到结尾,一轮排序过后,队尾的一定是队列里最大的元素
- 针对所有的元素重复做以上操作,除了最后一个,因为最后一个没有和他比较的一对了
- 重复上面的1、2、3一直到排序完成
1.3 代码
function bubbleSort(arr) {
for(let i = 0; i < arr.length - 1; i++) {
for(let j = 0; j < arr.length - 1 - i; j++) {
if(arr[j] > arr[j+1]) {
[arr[j], arr[j+1]] = [arr[j+1], arr[j]]
}
}
}
return arr
}
const arr = [1,2,5,2,3,6,3]
console.log(bubbleSort(arr)) // [1, 2, 2, 3, 3, 5, 6]
1.4 解析
上面的代码可能有的小伙伴没看明白,比较抽象,下面我来解答一下为什么这样写
1. 为什么外层是arr.length - 1
答:这是一种优化手段,因为最后一次只有一个数字,不需要比较,例如
升序排序 [4,1,3,5, 2]
第一轮
[4,1,3,5,2] => [1,4,3,5,2] => [1,3,4,5,2] => [1,3,4,5,2] => [1,3,4,2,5]
第二轮 最后一项已经冒泡排好了,因此内部排序不用管最后一项了
[1,3,4,2] => [1,3,4,2] => [1,3,4,2] => [1,3,2,4]
第三轮
[1,3,2] => [1,3,2] => [1,2,3]
第四轮
[1,2] => [1,2]
第五轮 // 这就是我们外层优化掉的-1
[1]
2. 为什么内层arr.length - i
答:也是优化手段,因为经过外层i次排序,我们就会产生i个排好顺序的数字放在队尾,所以每次不需要管队尾的i个数字
3. 为什么内层arr.length - i之后还要 - 1
答:同样是优化手段,因为每次内部排序的时候,我们排到倒数第二项的时候,就会和倒数第一项去比较,产生出排序结果,这个时候如果再比较最后一项和最后一项之外的项(不存在undefined),就会没有意义,所以我们只比较arr.length - 1 - i,
也就是比较到第i次排序的倒数第二项即可产生排序结果,跳出内部排序,进入下一轮外部排序
4. 我们也发现,冒泡排序是一种稳定排序,当有两个相同项时,是不会进入到调换顺序的逻辑的
5. 该算法的复杂度为T(n) = O(n*n) => O(n²)
二、如何用动画的形式表现出来
动画效果
冒泡排序.GIF
1.1 思路解析
使用HTML去表现出排序过程,就是把排序的状态和DOM同步,要想实现动画的效果,就需要等待动画结束再进行排序,核心就是要暂停排序,同步DOM,循环往复
。
我们知道在for循环内部是无法进行异步操作的,但是现在的需求又是要把过程表现出来,我们有一种思路,是把这个过程保存下来,再通过某种手段进行回放,这种从代码上看不够直观,我们想要直观地展现出来就必须要进行异步操作,怎么办呢,我们能不能让线程停一会儿呢?
答案是可以的,generator函数可以帮我们解决暂停代码的问题,但是释放下一次的执行不太方便,所以我们需要用ES7中的async/await来解决这个问题,现在我们只需要实现一个sleep函数即可
。
废话不多说,直接上代码
js部分依赖了方便的jQuery,同学们自己找一个就可以了,这里就不贴了。
1.2 代码
css部分:
#app {
text-align: center;
}
.sort-box {
background-color: #333;
height: 400px;
width: 600px;
margin: 0 auto;
}
.container {
position: relative;
height: 100%;
background-color: #333;
}
.item {
background-color: #aaa;
transition: all 0.3s;
height: 80px;
width: 30px;
position: absolute;
bottom: 0px;
text-align: center;
}
.item span {
color: #fff;
position: absolute;
top: -20px;
left: 50%;
transform: translateX(-50%);
text-align: center;
}
.item-cur {
background-color: #efefef;
}
.item-next {
background-color: #ff7f12;
}
.item-finish {
background-color: #5f3;
}
h4 {
color: #fff;
font-size: 18px;
}
h3 {
margin: 0;
padding: 10;
color: #fff;
font-size: 20px;
}
HTML结构:
第1轮
sort
JS部分:
class BubbelSort {
constructor(arr) {
this.arr = arr
this.initData(this.arr)
this.myBubbleSortHTML(this.arr)
}
sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
initItem(num, left, width, height, index) {
let template =
`${num}`;
$('.container').append(template)
}
initData(arr) {
const maxNum = Math.max.call(...arr)
const baseHeight = 80 / 100
const innerWidth = 1 / arr.length - 0.01
let result = arr.map((item, index, arr) => {
let obj = {}
obj.height = item / maxNum * baseHeight
obj.width = innerWidth
obj.left = innerWidth * index + index * 0.01
obj.index = index
obj.num = item
obj.baseHeight = baseHeight
obj.innerWidth = innerWidth
return obj
})
this.initHTML(result)
}
initHTML(infoArr) {
infoArr.forEach(item => {
this.initItem(item.num, item.left, item.width, item.height, item.index)
})
}
async myBubbleSortHTML(arr) {
for (let i = 0; i < arr.length - 1; i++) {
$('h3').html(`第${i+1}轮`)
for (let j = 0; j < arr.length - 1 - i; j++) {
await this.sleep(130)
this.changeInfo(arr[j], arr[j + 1], j, j + 1)
if (arr[j] > arr[j + 1]) {
await this.sleep(130)
this.changeTip(arr[j], arr[j + 1], j, j + 1, true);
await this.sleep(130);
this.changeDom(j, j + 1);
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
this.removeAllCss(j, j + 1)
} else {
await this.sleep(130)
this.changeTip(arr[j], arr[j + 1], j, j + 1, false)
}
if (j + 1 === arr.length - 1 - i) {
this.finishCss(arr.length - 1 - i)
}
}
}
$('h4').html(`全部数组排序完毕`)
return arr
}
changeTip(itemJ, itemK, j, k, flag) {
if (flag) {
$('h4').html(`${itemJ}比${itemK}要大,开始交换`)
this.removeCurCss(j, k)
this.addNextCss(j, k)
} else {
$('h4').html(`${itemJ}不比${itemK}大,不需要交换`)
this.removeCurCss(j, k)
}
}
changeInfo(itemJ, itemK, j, k) {
$('h4').html(`比较${itemJ}和${itemK}`)
this.addCurCss(j, k)
}
addCurCss(j, k) {
$(`.item-${j}`).addClass('item-cur')
$(`.item-${k}`).addClass('item-cur')
}
removeCurCss(j, k) {
$(`.item-${j}`).removeClass('item-cur')
$(`.item-${k}`).removeClass('item-cur')
}
addNextCss(j, k) {
$(`.item-${j}`).addClass('item-next')
$(`.item-${k}`).addClass('item-next')
}
removeAllCss(j, k) {
$(`.item-${j}`).removeClass('item-next')
$(`.item-${k}`).removeClass('item-next')
}
changeDom(j, k) {
const leftJ = $(`.item-${j}`).css('left')
const leftK = $(`.item-${k}`).css('left')
$(`.item-${j}`).css('left', leftK)
$(`.item-${k}`).css('left', leftJ)
$(`.item-${j}`).addClass(`changing-j`)
$(`.item-${k}`).addClass(`changing-k`)
$('.changing-j').addClass(`item-${k}`).removeClass(`changing-j item-${j}`)
$('.changing-k').addClass(`item-${j}`).removeClass(`changing-k item-${k}`)
}
finishCss(j) {
$(`.item-${j}`).addClass(`item-finish`)
}
}
new BubbelSort([1, 3, 2, 5, 16, 13, 7, 4, 9, 1, 2, 12, 2, 1, 3, 2, 5, 16, 13])
最后
相信看到这的同学已经理解了冒泡排序是如何工作的,后面我会抽时间更新别的排序,帮大家理解十大经典排序的工作原理,谢谢大家看完别忘了点个赞~