ヒープソート(昇順)の手順
var data:Array = [4, 1, 6, 2, 9, 7, 3, 8];
ソート対象の配列を用意します。
配列を二分木として捉えます。
配列内の各要素の事をノードと呼び、下側に繋がっているノードの事を"子ノード"、上側に繋がっているノードの事を"親ノード"と呼びます。
例えば、ノード4の子はノード1とノード6になり、ノード1とノード6の親はノード4になります。
例えば、ノード4の子はノード1とノード6になり、ノード1とノード6の親はノード4になります。
各ノードは自分の子より値が必ず大きくなるように並べ替えます。(具体的なアルゴリズムは後述)
このような構造の事を"ヒープ"といい、二分木でヒープを組んでいるので"二分ヒープ"といいます。
このような構造の事を"ヒープ"といい、二分木でヒープを組んでいるので"二分ヒープ"といいます。
親は子より値が大きいので、一番上のノード(配列の0番目要素)に一番大きい値が入ることになります。
昇順にソートするので、一番大きい値を配列の末尾に移動させないといけません。
なので、先頭ノード(配列の先頭要素)と末尾ノード(配列の末尾要素)を入れ替えます。
なので、先頭ノード(配列の先頭要素)と末尾ノード(配列の末尾要素)を入れ替えます。
一番大きい値が配列の末尾に入るので、ノード9の位置が確定します。
ノードを入れ替えたことによってヒープ構造が崩れた可能性があります。
なので、確定した末尾ノードを除いた残りのノード群だけでヒープ構造を再築します。
なので、確定した末尾ノードを除いた残りのノード群だけでヒープ構造を再築します。
先頭ノードに一番大きい値が入っているので、末尾ノードと入れ替えます。
ここでいう末尾ノードというのは、[6]の事です。([7]は確定しているので除外)
ここでいう末尾ノードというのは、[6]の事です。([7]は確定しているので除外)
このように、
- (未確定ノード群だけで)ヒープを構築
- 先頭ノードと末尾ノードをを入れ替える
- 末尾ノードを確定
を繰り返すと、値が小さい順にノードが並び、ソートが完了します。
ヒープ構造の構築
- 子ノードの値が自分より大きければ、ノードを交換する。
- 子が二つある場合は、値が大きい方の子を選び、その子ノードの値が自分より大きければ、ノードを交換する。
- 交換によってノードの位置が変わった後、更に子ノードの値が自分より大きければ、ノードを交換する。
- つまり、子ノードの値 <= 親ノードの値になるまで交換し続ける。
この手順を(配列の)末尾~先頭の順番に適用していけば、ヒープ構造が構築できます。
プログラム
var left:int = parent * 2 + 1; // 左の子
var right:int = left + 1; // 右の子
親のインデックスがparent変数に入っているとすると、
- 左の子 = parent * 2 + 1
- 右の子 = parent * 2 + 2 (左の子 + 1)
でインデックスが取得できます。(インデックスが0から始まる場合)
private function heapSort(data:Array):void
{
ヒープを構築(子を持っているノード全て);
ループ
{
先頭ノードと末尾ノードを交換
ヒープを構築(先頭ノード);
}
}
メソッド内部のコードの流れです。
/**
*
* @param data ソート対象配列
* @param parent このparentインデックスのノードを適切な位置に移動させる
* @param length 未確定ノード数
*/
private function makeHeap(data:Array, parent:int, length:int):void
{
while (true)
{
// 左の子インデックスを取得
var child:int = parent * 2 + 1;
// 左の子インデックスがlength以上ということは子が無いので構築終了
if (child >= length) return;
// 左の子 + 1がlength未満なら右の子が存在する
// 左の子より右の子要素値が小さい場合 = 親と交換するのは左の子
// 左の子より右の子要素値が大きい場合 = 親と交換するのは右の子
if (child + 1 < length && data[child] < data[child + 1]) child++;
// 子のほうが値が大きい
if (data[parent] < data[child])
{
// 親と子を交換
var temp:int = data[parent];
data[parent] = data[child];
data[child] = temp;
// 交換したので、対象ノードのインデックスがchildに変わる
parent = child;
}
// 親のほうが値が大きいので交換する必要が無い。構築終了
else break;
// 親 > 子になるまでループ
}
}
ヒープを構築するmakeHeap()です。
private function heapSort(data:Array):void
{
// 下側のノード(配列の右側)から順番にヒープを構築
for (var y:int = (data.length - 2) / 2; y >= 0; y--)
{
makeHeap(data, y, data.length);
}
// lastは未確定ノード群の末尾インデックス
for (var last:int = data.length - 1; last >= 1; last--)
{
// 先頭ノードと末尾ノードを交換
var temp:int = data[0];
data[0] = data[last];
data[last] = temp;
// 交換したことによってヒープ構造が崩れた可能性がある
// なので先頭(0番目)ノードを構築し直す
makeHeap(data, 0, last);
}
}
- for (var y:int = (data.length - 2) / 2; y >= 0; y--)
子が無いノードは再構築(交換)する必要が無いので、子を持っているノード群の中で一番大きいインデックス値がyの初期値になります。
(要素数 - 2) / 2で、そのインデックスを参照できます。
(要素数 - 2) / 2で、そのインデックスを参照できます。
実行結果
flash on 2011-5-15 - wonderfl build flash online
検証用コード
package
{
import flash.display.Sprite;
public class Main extends Sprite
{
public function Main()
{
var data:Array = [4, 1, 6, 2, 9, 7, 3, 8];
heapSort(data);
trace(data);
}
private function heapSort(data:Array):void
{
for (var y:int = (data.length - 2) / 2; y >= 0; y--)
{
makeHeap(data, y, data.length);
}
for (var last:int = data.length - 1; last >= 1; last--)
{
var temp:int = data[0];
data[0] = data[last];
data[last] = temp;
makeHeap(data, 0, last);
}
}
private function makeHeap(data:Array, parent:int, length:int):void
{
while (true)
{
var child:int = parent * 2 + 1;
if (child >= length) return;
if (child + 1 < length && data[child] < data[child + 1]) child++;
if (data[parent] < data[child])
{
var temp:int = data[parent];
data[parent] = data[child];
data[child] = temp;
parent = child;
}
else break;
}
}
}
}
このwikiの更新情報RSS