![3c62f8b5e065c1b93b4d68776234d9f8.png](https://i-blog.csdnimg.cn/blog_migrate/928c791518cd5887b825bc8bc7b15966.jpeg)
要实现数组排序可视化,首先要明白“帧”是什么。对于原地排序,数组中元素的每一次交换,就对应了动画中的一帧。将交换后的数组的每一次的临时状态推入队列末尾(注意,推入的应该是当前数组的深拷贝),同时从队列的头部以固定的时间间隔拿元素进行渲染,就能将整个排序的过程可视化出来。
由于JS是单线程的,创建中间状态数组队列的过程和渲染过程要做到同时执行可能比较难(中间涉及到很复杂的异步调度),可以考虑先创建队列缓存住,再进行渲染。渲染可以考虑使用requestAnimationFrame方法。
实际设计中,考虑实现一个SwapAspect来封装数组的交换操作,这样对数组的中间状态保存对于实际的排序算法来说就是透明的了。
class SwapAspect {
constructor(frameFunc) {
if (!frameFunc || typeof frameFunc !== 'function')
throw new Error(frameFunc + ' is not a function!');
this.frameFunc = frameFunc;
this.queue = [];
this.counter = 0;
}
swap(a, i, j) {
if (i === j)
return;
const temp = a[i];
a[i] = a[j];
a[j] = temp;
this.queue.push(a.slice());
this.counter++;
}
clear() {
this.counter = 0;
this.queue.length = 0;
}
showProcess() {
const queue = this.queue;
const frameFunc = this.frameFunc;
const sortAnimationFrame = function () {
const a = queue.shift();
if (a) {
frameFunc(a);
requestAnimationFrame(sortAnimationFrame);
}
};
requestAnimationFrame(sortAnimationFrame);
return this.counter;
}
}
数组的形式可以用柱状图来表达,当然可以用Canvas直接手写实现;当然为了避免重新造轮子,也可以用现有的框架。这里使用Echarts来绘制柱状图。
const commonOption = {
backgroundColor: '#0f375f',
xAxis: {
type: 'category'
},
yAxis: {
type: 'value'
},
series: [{
type: 'bar',
itemStyle: {
normal: {
barBorderRadius: 5,
color: new echarts.graphic.LinearGradient(
0, 0, 0, 1,
[
{offset: 0, color: '#14c8d4'},
{offset: 1, color: '#43eec6'}
]
)
},
emphasis: {
color: new echarts.graphic.LinearGradient(
0, 0, 0, 1,
[
{offset: 0, color: '#2378f7'},
{offset: 0.7, color: '#2378f7'},
{offset: 1, color: '#83bff6'}
]
)
}
}
}],
animation: false
}
注意,在使用Echarts的时候要将animation设置为false,否则在requestAnimationFrame的时候会发生问题。
这样,就可以定义通用的排序方法:
//handle为Echarts实例对象,sortFunc为实际的排序方法
const sortWeaver = function (handle, sortFunc) {
const frameFunc = function (a) {
const option = {
series: {
data: a
}
};
handle.setOption(option);
};
const swapAspect = new SwapAspect(frameFunc);
return function (a) {
if (!Array.isArray(a))
return;
handle.clear();
handle.setOption(commonOption);
frameFunc(a);
sortFunc(a, swapAspect);
animationInProcessQueue.push(swapAspect);
swapAspect.showProcess();
};
};
冒泡排序:
const bubbleSort = sortWeaver(bubbleHandle, function (a, swapAspect) {
const len = a.length;
for (let i = len - 1; i > 0; i--) {
for (let j = 0; j < i; j++) {
if (a[j] > a[j + 1]) {
swapAspect.swap(a, j + 1, j);
}
}
}
});
选择排序:
const selectionSort = sortWeaver(selectionHandle, function (a, swapAspect) {
const len = a.length;
for (let i = len - 1; i > 0; i--) {
let maxJ = i;
for (let j = 0; j < i; j++) {
if (a[j] > a[maxJ]) {
maxJ = j;
}
}
swapAspect.swap(a, maxJ, i);
}
});
插入排序:
const insertionSort = sortWeaver(insertionHandle, function (a, swapAspect) {
const len = a.length;
for (let i = 1; i < len; i++) {
for (let j = i; j > 0 && a[j] < a[j - 1]; j--)
swapAspect.swap(a, j, j - 1);
}
});
快速排序:
const quickSort = sortWeaver(quickHandle, function (a, swapAspect) {
quickSort0(a, 0, a.length - 1, swapAspect);
});
const quickSort0 = function (a, left, right, swapAspect) {
if (left >= right)
return;
const pivotIndex = (left + right) >>> 1;
const pivotNewIndex = partition(a, left, right, pivotIndex, swapAspect);
quickSort0(a, left, pivotNewIndex - 1, swapAspect);
quickSort0(a, pivotNewIndex + 1, right, swapAspect);
};
const partition = function (a, left, right, pivotIndex, swapAspect) {
const pivot = a[pivotIndex];
swapAspect.swap(a, pivotIndex, right);
let storeIndex = left;
for (let i = left; i < right; i++) {
if (a[i] < pivot) {
swapAspect.swap(a, storeIndex++, i);
}
}
swapAspect.swap(a, storeIndex, right);
return storeIndex;
};
堆排序:
const heapSort = sortWeaver(heapHandle, function (a, swapAspect) {
const len = a.length;
for (let i = (len >>> 1) - 1; i >= 0; i--)
maxHeapify(a, i, len - 1, swapAspect);
for (let i = len - 1; i > 0; i--) {
swapAspect.swap(a, 0, i);
maxHeapify(a, 0, i - 1, swapAspect);
}
});
const maxHeapify = function (a, start, end, swapAspect) {
let parent = start;
let child = (parent << 1) + 1;
while (child <= end) {
if (child + 1 <= end && a[child] < a[child + 1])
child += 1;
if (a[parent] > a[child])
return;
else {
swapAspect.swap(a, parent, child);
parent = child;
child = (parent << 1) + 1;
}
}
};
梳排序:
const combSort = sortWeaver(combHandle, function (a, swapAspect) {
const len = a.length;
const shrinkFactor = 0.8;
let gap = len, swapped = true;
while (gap > 1 || swapped) {
if (gap > 1)
gap = Math.trunc(gap * shrinkFactor);
swapped = false;
for (let i = 0; i + gap < len; i++) {
if (a[i] > a[i + gap]) {
swapAspect.swap(a, i, i + gap);
swapped = true;
}
}
}
});
这里需要说明的是,选择排序的时间复杂度是
![4db04c255e20582df8fe5e18229c563a.png](https://i-blog.csdnimg.cn/blog_migrate/d9ad46c3a897dd780530cba3fd7d8d0e.jpeg)
项目在线演示地址:https://elasticdogs.com/sort/index.html
源码GitHub地址:https://github.com/YuyuZha0/in-place-sort-animation