在线体验
设计思路
1. 排序算法部分
快速排序的思想就是在序列中选择一个支点pivot, 让小于pivot的元素放在它的左边,不小于(说大于的话不准确)它的元素放在它的右边。
visualgo.net 的实现,维护三个序列,s1, s2, unknown
s1中存放小于的元素,s2中存放不小于支点的元素,unknown是未排序序列(具体的区分在动画中很清楚)
function partition(a, i, j) {
let m = i, p = a[i];
for (let k = i + 1; k <= j; k++) {
if (a[k] < p) {
m++;
swap(a, m, k);
}
}
swap(a, m, i);
return m;
}
function quickSort(a, low, high) {
if (low < high) {
int m = partition(a, low, high);
quickSort(a, low, m - 1);
quickSort(a, m + 1, high);
}
}
2. 动画部分
排序的每一步操作存储在未来发生的回调函数执行队列中,全局变量 step 代表操作步数
假设每一步动画时延为1s,
那么在未来的0s, 1s, 2s, ..., step * 1s 将会在页面上看到具体的排序操作,
如果没有CSS的过渡效果,那么整个过程将会显得十分生硬。
例如我将第一个节点的位置与第3个节点的位置交换,
在 0s - 1s 有一段时间间隔,设置 transiton: all 1s 就能看到过渡效果。
完整代码
< script>
let step = 0 ;
let nodeList = document. getElementsByClassName ( 'num' ) ,
reloadBtn = document. querySelector ( '.reload' ) ,
sortBtn = document. querySelector ( '.sort' ) ;
let initColor = 'rgb(173, 216, 230)' ,
sortedColor = 'rgb(255, 165, 0)' ,
pivoColor = 'rgb(255, 255, 0)' ,
s1Color = 'rgb(60, 179, 113)' ,
s2Color = 'rgb(153, 50, 204)' ,
activeColor = 'rgb(220, 20, 60)' ;
let colorMap = {
'sorted' : sortedColor,
's1' : s1Color,
's2' : s2Color,
'init' : initColor,
'pivo' : pivoColor,
'active' : activeColor
} ;
let showConsoleNode = document. querySelector ( '.showConsole' ) ;
let arr = [ ] , timeout = 0.4 , queue = [ ] ;
function handleChange ( ) {
let selectOps = document. querySelector ( '#speed-control' ) ;
let selectIdx = selectOps. selectedIndex;
let val = selectOps. options[ selectIdx] . value;
timeout = Number ( val) ;
console. log ( timeout)
}
function swapPosition ( node1, node2) {
[ node1. style. left, node2. style. left] = [ node2. style. left, node1. style. left] ;
}
function setBgColor ( nodeArr, color) {
for ( let i = 0 ; i < nodeArr. length; i++ ) {
nodeArr[ i] . style. background = color;
}
}
function swap ( arr, i, j) {
[ arr[ i] , arr[ j] ] = [ arr[ j] , arr[ i] ] ;
}
function createFrameForChangeColor ( arr, step, nodeType) {
setTimeout ( ( ) => {
console. log ( 'select:' + arr[ 0 ] . innerText) ;
setBgColor ( arr, colorMap[ nodeType] ) ;
} , step * timeout * 1.6 * 1000 ) ;
}
function createFrameForSwapPos ( node1, node2, step) {
setTimeout ( ( ) => {
console. log ( `swap ${ node1. innerText} , ${ node2. innerText} ` ) ;
swapPosition ( node1, node2)
} , step * timeout * 1.6 * 1000 )
}
function resetColorForPartOfArr ( arr, l, r, m, step) {
setTimeout ( ( ) => {
for ( let i = l; i <= r; i++ ) {
let node = nodeList[ arr[ i] . nodeIndex] ;
if ( i !== m) {
setBgColor ( [ node] , initColor) ;
} else {
setBgColor ( [ node] , sortedColor) ;
}
}
} , step * timeout * 1.6 * 1000 ) ;
}
function resetColorForAll ( step) {
setTimeout ( ( ) => {
for ( let i = 0 ; i < nodeList. length; i++ ) {
let node = nodeList[ arr[ i] . nodeIndex] ;
setBgColor ( [ node] , initColor) ;
}
} , step * timeout * 1.6 * 1000 ) ;
}
function partition ( a, i, j) {
let m = i, p = a[ i] ;
createFrameForChangeColor ( [ nodeList[ a[ i] . nodeIndex] ] , step++ , 'pivo' ) ;
for ( let k = i + 1 ; k <= j; k++ ) {
createFrameForChangeColor ( [ nodeList[ a[ k] . nodeIndex] ] , step++ , 'active' ) ;
if ( a[ k] . val < p. val) {
m++ ;
createFrameForChangeColor ( [ nodeList[ a[ k] . nodeIndex] ] , step++ , 's1' ) ;
createFrameForSwapPos ( nodeList[ a[ m] . nodeIndex] , nodeList[ a[ k] . nodeIndex] , step++ ) ;
swap ( a, m, k) ;
} else {
createFrameForChangeColor ( [ nodeList[ a[ k] . nodeIndex] ] , step++ , 's2' )
}
}
createFrameForSwapPos ( nodeList[ a[ m] . nodeIndex] , nodeList[ a[ i] . nodeIndex] , step++ ) ;
swap ( a, m, i) ;
resetColorForPartOfArr ( arr, i, j, m, step++ ) ;
return m;
}
function quickSort ( a, low, high) {
if ( low <= high) {
let m = partition ( a, low, high) ;
quickSort ( a, low, m - 1 ) ;
quickSort ( a, m + 1 , high) ;
}
}
function init ( ) {
let fragment = document. createDocumentFragment ( ) ;
for ( let i = 0 ; i < 10 ; i++ ) {
let div = document. createElement ( 'div' ) ,
num = parseInt ( Math. random ( ) * 10 + 1 ) ;
arr. push ( {
val: num,
nodeIndex: i,
} ) ;
div. className = 'num' ;
div. style. height = ` ${ num * 20 } px` ;
div. style. left = ` ${ i * 50 } px` ;
div. style. bottom = '0' ;
div. style. background = initColor;
div. style. transition = `left ${ timeout} s, background ${ timeout * 0.6 } s` ;
div. innerText = num;
fragment. append ( div) ;
}
document. querySelector ( '.animate-contanier' ) . append ( fragment) ;
}
function reload ( ) {
arr = [ ] ;
for ( let i = 0 ; i < 10 ; i++ ) {
let div = nodeList[ i] ,
num = parseInt ( Math. random ( ) * 10 + 1 ) ;
arr. push ( {
val: num,
nodeIndex: i,
} ) ;
div. style. left = ` ${ i * 50 } px` ;
div. style. bottom = '0' ;
div. style. height = ` ${ num * 20 } px` ;
div. innerText = num;
}
}
init ( ) ;
document. querySelector ( '.sort' ) . addEventListener ( 'click' , function ( ) {
quickSort ( arr, 0 , 9 , 0 ) ;
resetColorForAll ( step)
} )
< / script>