再帰クイックソートの可視化
2007-02-06


いやなブログ - JavaScript でソートアルゴリズムを可視化」より。何も考えずに再帰処理のクイックソートの様子を逐次描画しようとするとこうなります。

function quickSort(data, begin, end, log) {
  if (begin >= end) return data;

  var pivotPos = begin;
  var pivot = data[pivotPos];

  for (var i = begin + 1; i < end; i++) {
    if (data[i] < pivot) {
      var temp = data[i];
      data[i] = data[pivotPos + 1];
      data[pivotPos + 1] = data[pivotPos];
      data[pivotPos] = temp;
      pivotPos++;
    }
  }

  log(data);

  quickSort(data, begin, pivotPos, log);
  quickSort(data, pivotPos + 1, end, log);
  return data;
}

var array = [9, 5, 7, 3, 2, 4, 0, 1, 6, 8];
quickSort(array, 0, array.length, print);
// 5,7,3,2,4,0,1,6,8,9
// 3,2,4,0,1,5,7,6,8,9
// ...
// 0,1,2,3,4,5,6,7,8,9

しかし、ブラウザ上でアニメーション表示するためには、setTimeout などのタイマーを使う必要があります。ところが、タイマーを使って処理を分けると、その処理が終わった後で元の処理に戻ることができません。上のクイックソートでいうと、データの前半を並び替えた後、戻って後半を並び替えないといけないため、そのままではアニメーション表示ができないのです。

ではどうするか。戻らなくてはならないのが問題ならば、戻らなければいいのです。呼び出し先から戻って続きの処理をやるのではなく、続きの処理 (継続) も呼び出し先に丸投げしてしまいましょう。JavaScript ではクロージャが使えるので、続きの処理をまとめたクロージャを引数として渡すことができます。このような書き方を継続渡しスタイル (CPS, continuation passing style) というそうです。「ヒビノキロク - Javascriptで継続渡し」を参考に上のクイックソートを継続渡しスタイルに書き換えてみます。

function quickSortCPS(data, begin, end, log, cont) {
  if (begin >= end) return cont(data);

  var pivotPos = begin;
  var pivot = data[pivotPos];

  for (var i = begin + 1; i < end; i++) {
    if (data[i] < pivot) {
      var temp = data[i];
      data[i] = data[pivotPos + 1];
      data[pivotPos + 1] = data[pivotPos];
      data[pivotPos] = temp;
      pivotPos++;
    }
  }

  log(data);

  return quickSortCPS(data, begin, pivotPos, log,
    function (partiallySortedData) {
      return quickSortCPS(partiallySortedData, pivotPos + 1, end, log,
        function (entirelySortedData) {
          return cont(entirelySortedData);
        });
    });
}

function nop() {}
quickSort(array, 0, array.length, nop, print);
// 0,1,2,3,4,5,6,7,8,9

return 文に式が指定されていますが、この返り値は意味を持ちません。処理結果は関数呼び出しの返り値として受け取るのではなく、継続として渡したクロージャが引数として受け取ります。

さて、ここまで来れば後は簡単です。返す値に意味がない、すなわち戻る必要がなくなったのですから、大手を振ってタイマーが使えます。Gecko では setTimeout の引数として関数実行時の引数も渡せるので、以下のように書き換えられます。

function quickSortCPSWithTimer(data, begin, end, interval, log, cont) {
  ...

  return setTimeout(quickSortCPSWithTimer, interval,
    data, begin, pivotPos, interval, log,
    function (partiallySortedData) {
      return setTimeout(quickSortCPSWithTimer, interval,
        partiallySortedData, pivotPos + 1, end, interval, log,
        function (entirelySortedData) {
          return cont(entirelySortedData);
        });
    });
}

function plot(data) {
  ...
}
quickSortCPSWithTimer(array, 0, array.length, 20, plot, nop);

これで 20 ミリ秒ごとにソート途中のデータがプロットされることになります。実際は IE だと setTimeout で引数を渡せないので、Gecko の setTimeout と同じ動作をする関数 doLater を使います。

function doLater(func, interval) {
  var args = Array.prototype.slice.call(arguments, 2);
  return setTimeout(function () { func.apply(this, args); }, interval);
}

以上をまとめた


続きを読む

[JavaScript]

コメント(全0件)
コメントをする


記事を書く
powered by ASAHIネット