满堂花醉三千客,一剑霜寒十四州。这篇文章主要讲述太震撼了!我把七大JS排序算法做成了可视化!!!太好玩了!相关的知识,希望能为你提供帮助。
前言文章转载:乐字节
大家好,我是林三心。写这篇文章是有原因的,偶然我看到了一个java的50种排序算法的可视化的视频,但是此视频却没给出具体的实现教程,于是我心里就想着,我可以用javascript + canvas去实现这个酷炫的效果。每种排序算法的动画效果基本都不一样哦。例如冒泡排序是这样的
文章图片
实现思路 想实现的效果从封面可以看到,无论是哪种算法,一开始都是第一张图,而最终目的是要变成第二张图的效果
文章图片
文章图片
极坐标讲实现思路之前,我先给大家复习一下高中的一个知识——极坐标。哈哈,不知道还有几个人记得他呢?
- O:极点,也就是原点
- ρ:极径
- θ:极径与X轴夹角
- x = ρ * cosθ,因为x / ρ = cosθ
- y = ρ * sinθ,因为y / ρ = sinθ
文章图片
那我们想实现的结果,又跟极坐标有何关系呢?其实是有关系的,比如我现在有一个排序好的数组,他具有37个元素,那我们可以把这37个元素转化为极坐标中的37个点,怎么转呢?
const arr = [
0, 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 30, 31, 32, 33, 34, 35, 36
]
我们可以这么转:
- 元素对应的索引index * 10 -> 角度θ(为什么要乘10呢,因为要凑够360°嘛)
- 元素对应的值arr[index] -> 极径ρ
(0 ->
θ = 00°,ρ = 0) (1 ->
θ = 10°,ρ = 1) (2 ->
θ = 20°,ρ = 2) (3 ->
θ = 30°,ρ = 3)
(4 ->
θ = 40°,ρ = 4) (5 ->
θ = 50°,ρ = 5) (6 ->
θ = 60°,ρ = 6) (7 ->
θ = 70°,ρ = 7)
(8 ->
θ = 80°,ρ = 8) (9 ->
θ = 90°,ρ = 9) (10 ->
θ = 100°,ρ = 10) (11 ->
θ = 110°,ρ = 11)
(12 ->
θ = 120°,ρ = 12) (13 ->
θ = 130°,ρ = 13) (14 ->
θ = 140°,ρ = 14) (15 ->
θ = 150°,ρ = 15)
(16 ->
θ = 160°,ρ = 16) (17 ->
θ = 170°,ρ = 17) (18 ->
θ = 180°,ρ = 18) (19 ->
θ = 190°,ρ = 19)
(20 ->
θ = 200°,ρ = 20) (21 ->
θ = 210°,ρ = 21) (22 ->
θ = 220°,ρ = 22) (23 ->
θ = 230°,ρ = 23)
(24 ->
θ = 240°,ρ = 24) (25 ->
θ = 250°,ρ = 25) (26 ->
θ = 260°,ρ = 26) (27 ->
θ = 270°,ρ = 27)
(28 ->
θ = 280°,ρ = 28) (29 ->
θ = 290°,ρ = 29) (30 ->
θ = 300°,ρ = 30) (31 ->
θ = 310°,ρ = 31)
(32 ->
θ = 320°,ρ = 32) (33 ->
θ = 330°,ρ = 33) (34 ->
θ = 340°,ρ = 34) (35 ->
θ = 350°,ρ = 35)
(36 ->
θ = 360°,ρ = 36)
文章图片
有没有发现,跟咱们想实现的最终效果的轨迹很像呢?
文章图片
随机打散那说完最终的效果,咱们来下想想如何一开始先把数组的各个元素打散在极坐标上呢?其实很简单,咱们可以先把生成一个乱序的数组,比如
const arr = [
25, 8, 32, 1, 19, 14, 0, 29, 17,
6, 7, 26, 3, 30, 31, 16, 28, 15,
24, 10, 21, 2, 9, 4, 35, 5, 36,
33, 11, 27, 34, 22, 13, 18, 23, 12, 20
]
然后还是用上面那个规则,去转换极坐标
- 元素对应的索引index * 10 -> 角度θ(为什么要乘10呢,因为要凑够360°嘛)
- 元素对应的值arr[index] -> 极径ρ
(25 ->
θ = 00°,ρ = 25) (8 ->
θ = 10°,ρ = 8) (32 ->
θ = 20°,ρ = 32) (1 ->
θ = 30°,ρ = 1)
(19 ->
θ = 40°,ρ = 19) (14 ->
θ = 50°,ρ = 14) (0 ->
θ = 60°,ρ = 0) (29 ->
θ = 70°,ρ = 29)
(17 ->
θ = 80°,ρ = 17) (6 ->
θ = 90°,ρ = 6) (7 ->
θ = 100°,ρ = 7) (26 ->
θ = 110°,ρ = 26)
(3 ->
θ = 120°,ρ = 3) (30 ->
θ = 130°,ρ = 30) (31 ->
θ = 140°,ρ = 31) (16 ->
θ = 150°,ρ = 16)
(28 ->
θ = 160°,ρ = 28) (15 ->
θ = 170°,ρ = 15) (24 ->
θ = 180°,ρ = 24) (10 ->
θ = 190°,ρ = 10)
(21 ->
θ = 200°,ρ = 21) (2 ->
θ = 210°,ρ = 2) (9 ->
θ = 220°,ρ = 9) (4 ->
θ = 230°,ρ = 4)
(35 ->
θ = 240°,ρ = 35) (5 ->
θ = 250°,ρ = 5) (36 ->
θ = 260°,ρ = 36) (33 ->
θ = 270°,ρ = 33)
(11 ->
θ = 280°,ρ = 11) (27 ->
θ = 290°,ρ = 27) (34 ->
θ = 300°,ρ = 34) (22 ->
θ = 310°,ρ = 22)
(13 ->
θ = 320°,ρ = 13) (18 ->
θ = 330°,ρ = 18) (23 ->
θ = 340°,ρ = 23) (12 ->
θ = 350°,ρ = 12)
(20 ->
θ = 360°,ρ = 20)
文章图片
实现效果综上所述,咱们想实现效果,也就有了思路
- 1、先生成一个乱序数组
- 2、用canvas画布画出此乱序数组所有元素对应的极坐标对应的点
- 3、对乱序数组进行排序
- 4、排序过程中不断清空画布,并重画数组所有元素对应的极坐标对应的点
- 5、直到排序完成,终止画布操作
文章图片
开搞!!!咱们,做事情一定要有条有理才行,还记得上面说的步骤吗?
- 1、先生成一个乱序数组
- 2、用canvas画布画出此乱序数组所有元素对应的极坐标对应的点
- 3、对乱序数组进行排序
- 4、排序过程中不断清空画布,并重画数组所有元素对应的极坐标对应的点
- 5、直到排序完成,终止画布操作
生成乱序数组咱们上面举的例子是37个元素,但是37个肯定是太少了,咱们搞多点吧,我搞了这么一个数组nums:我先生成一个0 - 179的有序数组,然后打乱,并塞进数组nums中,此操作我执行4次。为什么是0 - 179,因为0 - 179刚好有180个数字
身位一个程序员,我肯定不可能自己手打这么多元素的啦。。来。。上代码
let nums = []
for (let i = 0;
i <
4;
i++) {
// 生成一个 0 - 179的有序数组
const arr = [...Array(180).keys()] // Array.keys()可以学一下,很有用
const res = []
while (arr.length) {
// 打乱
const randomIndex = Math.random() * arr.length - 1
res.push(arr.splice(randomIndex, 1)[0])
}
nums = [...nums, ...res]
}
经过上面操作,也就是我的nums中拥有4 * 180 = 720个元素,nums中的元素都是0 - 179范围内的
canvas画乱序数组画canvas之前,肯定要现在html页面上,编写一个canvas的节点,这里我宽度设置1000,高度也是1000,并且背景颜色是黑色
<
canvasstyle="background: #000;
">
<
/canvas>
上面看到了,极点(原点)是在坐标正中间的,但是canvas的初始原点是在画布的左上角,我们需要把canvas的原点移动到画布的正中间,那正中间的坐标是多少呢?还记得咱们宽高都是1000吗?那画布中心点坐标不就是(500, 500),咱们可以使用canvas的ctx.translate(500, 500)来移动中心点位置。因为咱们画的点都是白色的,所以咱们顺便把ctx.fillStyle设置为white
文章图片
const canvas = document.getElementById(\'canvas\')
const ctx = canvas.getContext(\'2d\')
ctx.fillStyle = \'white\' // 设置画画的颜色
ctx.translate(500, 500) // 移动中心点到(500, 500)
那到底该怎么画点呢?按照之前的,其实光计算出角度θ和极径ρ是不够的,因为canvas画板不认这两个东西啊。。那canvas认啥呢,他只认(x, y),所以咱们只要通过角度θ和极径ρ去算出(x, y),就好了,还记得前面极坐标的公式吗
- x = ρ * cosθ,因为x / ρ = cosθ
- y = ρ * sinθ,因为y / ρ = sinθ
const CosandSin = []
for (let i = 0;
i <
360;
i++) {
const jiaodu = i / 180 * Math.PI
CosandSin.push({ cos: Math.cos(jiaodu), sin: Math.sin(jiaodu) })
}
这时候又有新问题了,咱们一个圆上的整数角度只有0° - 359°这360个整数角,但是nums中有720个元素啊,那怎么分配画布呢?很简单啊,一个角度上画2个元素,那不就刚好 2 * 360 = 720
行,咱们废话不多说,开始画初始散点吧。咱们也知道咱们需要画720个点,对于这种多个相同的东西,咱们要多多使用面向对象这种编程思想
// 单个长方形构造函数
function Rect(x, y, width, height) {
this.x = x // 坐标x
this.y = y // 坐标y
this.width = width // 长方形的宽
this.height = height // 长方形的高
}// 单个长方形的渲染函数
Rect.prototype.draw = function () {
ctx.beginPath() // 开始画一个
ctx.fillRect(this.x, this.y, this.width, this.height) // 画一个
ctx.closePath() // 结束画一个
}const CosandSin = []
for (let i = 0;
i <
360;
i++) {
const jiaodu = i / 180 * Math.PI
CosandSin.push({ cos: Math.cos(jiaodu), sin: Math.sin(jiaodu) })
}function drawAll(arr) {
const rects = [] // 用来存储720个长方形
for (let i = 0;
i <
arr.length;
i++) {
const num = arr[i]
const { cos, sin } = CosandSin[Math.floor(i / 2)] // 一个角画两个
const x = num * cos // x = ρ * cosθ
const y = num * sin // y = ρ * sinθ
rects.push(new Rect(x, y, 5, 3)) // 收集所有长方形
}
rects.forEach(rect =>
rect.draw()) // 遍历渲染
}
drawAll(nums) // 执行渲染函数
来页面中看看效果吧。此时就完成了初始的散点渲染
文章图片
边排序边重画其实很简单,就是排序一次,就清空画布,然后重新执行上面的渲染函数drawAll就行了。由于性能原因,我先把drawAll封装成一个Promise函数
function drawAll(arr) {
return new Promise((resolve) =>
{
setTimeout(() =>
{
ctx.clearRect(-500, -500, 1000, 1000) // 清空画布
const rects = [] // 用来存储720个长方形
for (let i = 0;
i <
arr.length;
i++) {
const num = arr[i]
const { cos, sin } = CosandSin[Math.floor(i / 2)] // 一个角画两个
const x = num * cos // x = ρ * cosθ
const y = num * sin // y = ρ * sinθ
rects.push(new Rect(x, y, 5, 3)) // 收集所有长方形
}
rects.forEach(rect =>
rect.draw()) // 遍历渲染
resolve(\'draw success\')
}, 10)
})
}
然后咱们拿一个排序算法例子来讲一讲,就拿个冒泡排序来讲吧
async function bubbleSort(arr) {
var len = arr.length;
for (var i = 0;
i <
len;
i++) {
for (var j = 0;
j <
len - 1 - i;
j++) {
if (arr[j] >
arr[j + 1]) {//相邻元素两两对比
var temp = arr[j + 1];
//元素交换
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
await drawAll(arr) // 一边排序一边重新画
}
return arr;
}
然后在页面里放一个按钮,用来执行开始排序
<
button >
开始排序<
/button>
document.getElementById(\'btn\').onclick = function () {
bubbleSort(nums)
}
效果如下,是不是很开心哈哈哈!!!
文章图片
完整代码这是完整代码
<
canvasstyle="background: #000;
">
<
/canvas>
<
button >
开始排序<
/button>
复制代码
const canvas = document.getElementById(\'canvas\')
const ctx = canvas.getContext(\'2d\')
ctx.fillStyle = \'white\' // 设置画画的颜色
ctx.translate(500, 500) // 移动中心点到(500, 500)let nums = []
for (let i = 0;
i <
4;
i++) {
// 生成一个 0 - 180的有序数组
const arr = [...Array(180).keys()]
const res = []
while (arr.length) {
// 打乱
const randomIndex = Math.random() * arr.length - 1
res.push(arr.splice(randomIndex, 1)[0])
}
nums = [...nums, ...res]
}// 单个长方形构造函数
function Rect(x, y, width, height) {
this.x = x // 坐标x
this.y = y // 坐标y
this.width = width // 长方形的宽
this.height = height // 长方形的高
}// 单个长方形的渲染函数
Rect.prototype.draw = function () {
ctx.beginPath() // 开始画一个
ctx.fillRect(this.x, this.y, this.width, this.height) // 画一个
ctx.closePath() // 结束画一个
}const CosandSin = []
for (let i = 0;
i <
360;
i++) {
const jiaodu = i / 180 * Math.PI
CosandSin.push({ cos: Math.cos(jiaodu), sin: Math.sin(jiaodu) })
}function drawAll(arr) {
return new Promise((resolve) =>
{
setTimeout(() =>
{
ctx.clearRect(-500, -500, 1000, 1000) // 清空画布
const rects = [] // 用来存储720个长方形
for (let i = 0;
i <
arr.length;
i++) {
const num = arr[i]
const { cos, sin } = CosandSin[Math.floor(i / 2)] // 一个角画两个
const x = num * cos // x = ρ * cosθ
const y = num * sin // y = ρ * sinθ
rects.push(new Rect(x, y, 5, 3)) // 收集所有长方形
}
rects.forEach(rect =>
rect.draw()) // 遍历渲染
resolve(\'draw success\')
}, 10)
})
}
drawAll(nums) // 执行渲染函数async function bubbleSort(arr) {
var len = arr.length;
for (var i = 0;
i <
len;
i++) {
for (var j = 0;
j <
len - 1 - i;
j++) {
if (arr[j] >
arr[j + 1]) {//相邻元素两两对比
var temp = arr[j + 1];
//元素交换
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
await drawAll(arr) // 一边排序一边重新画
}
return arr;
}document.getElementById(\'btn\').onclick = function () {
bubbleSort(nums) // 点击执行
}
正片开始!!!首先说明,哈哈
- 我是算法渣渣
- 每种算法排序,动画都不一样
- drawAll放在不同地方也可能有不同效果
async function bubbleSort(arr) {
var len = arr.length;
for (var i = 0;
i <
len;
i++) {
for (var j = 0;
j <
len - 1 - i;
j++) {
if (arr[j] >
arr[j + 1]) {//相邻元素两两对比
var temp = arr[j + 1];
//元素交换
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
await drawAll(arr) // 一边排序一边重新画
}
return arr;
}document.getElementById(\'btn\').onclick = function () {
bubbleSort(nums) // 点击执行
}
文章图片
选择排序
async function selectionSort(arr) {
var len = arr.length;
var minIndex, temp;
for (var i = 0;
i <
len - 1;
i++) {
minIndex = i;
for (var j = i + 1;
j <
len;
j++) {
if (arr[j] <
arr[minIndex]) {//寻找最小的数
minIndex = j;
//将最小数的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
await drawAll(arr)
}
return arr;
}
document.getElementById(\'btn\').onclick = function () {
selectionSort(nums)
}
文章图片
插入排序
async function insertionSort(arr) {
if (Object.prototype.toString.call(arr).slice(8, -1) === \'Array\') {
for (var i = 1;
i <
arr.length;
i++) {
var key = arr[i];
var j = i - 1;
while (j >
= 0 &
&
arr[j] >
key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
await drawAll(arr)
}
return arr;
} else {
return \'arr is not an Array!\';
}
}
document.getElementById(\'btn\').onclick = function () {
insertionSort(nums)
}
文章图片
堆排序
async function heapSort(array) {
if (Object.prototype.toString.call(array).slice(8, -1) === \'Array\') {
//建堆
var heapSize = array.length, temp;
for (var i = Math.floor(heapSize / 2) - 1;
i >
= 0;
i--) {
heapify(array, i, heapSize);
await drawAll(array)
}//堆排序
for (var j = heapSize - 1;
j >
= 1;
j--) {
temp = array[0];
array[0] = array[j];
array[j] = temp;
heapify(array, 0, --heapSize);
await drawAll(array)
}
return array;
} else {
return \'array is not an Array!\';
}
}
function heapify(arr, x, len) {
if (Object.prototype.toString.call(arr).slice(8, -1) === \'Array\' &
&
typeof x === \'number\') {
var l = 2 * x + 1, r = 2 * x + 2, largest = x, temp;
if (l <
len &
&
arr[l] >
arr[largest]) {
largest = l;
}
if (r <
len &
&
arr[r] >
arr[largest]) {
largest = r;
}
if (largest != x) {
temp = arr[x];
arr[x] = arr[largest];
arr[largest] = temp;
heapify(arr, largest, len);
}
} else {
return \'arr is not an Array or x is not a number!\';
}
}
document.getElementById(\'btn\').onclick = function () {
heapSort(nums)
}
文章图片
快速排序
async function quickSort(array, left, right) {
drawAll(nums)
if (Object.prototype.toString.call(array).slice(8, -1) === \'Array\' &
&
typeof left === \'number\' &
&
typeof right === \'number\') {
if (left <
right) {
var x = array[right], i = left - 1, temp;
for (var j = left;
j <
= right;
j++) {
if (array[j] <
= x) {
i++;
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
await drawAll(nums)
await quickSort(array, left, i - 1);
await quickSort(array, i + 1, right);
await drawAll(nums)
}
return array;
} else {
return \'array is not an Array or left or right is not a number!\';
}
}
document.getElementById(\'btn\').onclick = function () {
quickSort(nums, 0, nums.length - 1)
}
文章图片
基数排序
async function radixSort(arr, maxDigit) {
var mod = 10;
var dev = 1;
var counter = [];
for (var i = 0;
i <
maxDigit;
i++, dev *= 10, mod *= 10) {
for (var j = 0;
j <
arr.length;
j++) {
var bucket = parseInt((arr[j] % mod) / dev);
if (counter[bucket] == null) {
counter[bucket] = [];
}
counter[bucket].push(arr[j]);
}
var pos = 0;
for (var j = 0;
j <
counter.length;
j++) {
var value = https://www.songbingjia.com/android/null;
if (counter[j] != null) {
while ((value = counter[j].shift()) != null) {
arr[pos++] = value;
await drawAll(arr)
}
}
}
}
return arr;
}
document.getElementById(/'btn\').onclick = function () {
radixSort(nums, 3)
}
文章图片
希尔排序
async function shellSort(arr) {
var len = arr.length,
temp,
gap = 1;
while (gap <
len / 5) {//动态定义间隔序列
gap = gap * 5 + 1;
}
for (gap;
gap >
0;
gap = Math.floor(gap / 5)) {
for (var i = gap;
i <
len;
i++) {
temp = arr[i];
for (var j = i - gap;
j >
= 0 &
&
arr[j] >
temp;
j -= gap) {
arr[j + gap] = arr[j];
}
arr[j + gap] = temp;
await drawAll(arr)
}
}
return arr;
}
document.getElementById(\'btn\').onclick = function () {
shellSort(nums)
}
文章图片
参考
- 排序算法参考:十大经典排序算法总结(JavaScript描述)
总结【太震撼了!我把七大JS排序算法做成了可视化!!!太好玩了!】如果你觉得此文对你有一丁点帮忙,点个赞,鼓励一下作者。或者可以加入我的摸鱼裙 想进学习裙,摸鱼裙,请点击这里摸鱼
推荐阅读
- 鸿蒙分包与Android app组件化
- 重新学习C语言day06
- Java开发工程师进阶篇-必掌握的常用设计模式
- You-Get开源在线下载神器,搭配python更加丝滑(文中案例演示)
- JavaScript小技巧
- Springboot集成JUnit5优雅进行单元测试
- Java详解注释&关键字&常量&变量&标识符
- go实现进程监控并启动
- .NET 5 支持 Azure Functions OpenAPI 扩展啦