トップページ > アルゴリズム > ヒープソート



ヒープソート(昇順)の手順

var data:Array = [4, 1, 6, 2, 9, 7, 3, 8];
 
ソート対象の配列を用意します。


配列を二分木として捉えます。

配列内の各要素の事をノードと呼び、下側に繋がっているノードの事を"子ノード"、上側に繋がっているノードの事を"親ノード"と呼びます。
例えば、ノード4の子はノード1とノード6になり、ノード1とノード6の親はノード4になります。


各ノードは自分の子より値が必ず大きくなるように並べ替えます。(具体的なアルゴリズムは後述)
このような構造の事を"ヒープ"といい、二分木でヒープを組んでいるので"二分ヒープ"といいます。

親は子より値が大きいので、一番上のノード(配列の0番目要素)に一番大きい値が入ることになります。


昇順にソートするので、一番大きい値を配列の末尾に移動させないといけません。
なので、先頭ノード(配列の先頭要素)と末尾ノード(配列の末尾要素)を入れ替えます。

一番大きい値が配列の末尾に入るので、ノード9の位置が確定します。


ノードを入れ替えたことによってヒープ構造が崩れた可能性があります。
なので、確定した末尾ノードを除いた残りのノード群だけでヒープ構造を再築します。


先頭ノードに一番大きい値が入っているので、末尾ノードと入れ替えます。
ここでいう末尾ノードというのは、[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で、そのインデックスを参照できます。


実行結果

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;
			}
		}
	}
}
 

|新しいページ|検索|ページ一覧|RSS|@ウィキご利用ガイド | 管理者にお問合せ
|ログイン|