如何用HTML制作一个冒泡排序的动画

冒泡排序(Bubble Sort)

今天在某贴子上看到一个问题,如何使用动画的形式描述冒泡排序,下面的答案有点让人模棱两可,大多数都是文字描述思路,光有思路不够,作为程序员还要动手写出来才可以。冒泡排序虽然是一种基础简单的排序,但是对于刚入门的小伙伴来说也需要一点点理解,为了帮助大家理解冒泡排序,特意写了一遍关于冒泡排序文章,并详细解答其中每一步的意义,供大家参考。

一、什么是冒泡排序

1.1定义

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

1.2 思路
  1. 比较相邻元素,如果前一个比后一个大或者小,就交换他们两个,下面我们都按照升序来说,也就是前一个比后一个大的情况
  2. 对每一个相邻元素都做出同样的比较操作,从开始到结尾,一轮排序过后,队尾的一定是队列里最大的元素
  3. 针对所有的元素重复做以上操作,除了最后一个,因为最后一个没有和他比较的一对了
  4. 重复上面的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])

最后

相信看到这的同学已经理解了冒泡排序是如何工作的,后面我会抽时间更新别的排序,帮大家理解十大经典排序的工作原理,谢谢大家看完别忘了点个赞~

你可能感兴趣的:(如何用HTML制作一个冒泡排序的动画)