第5回 配列とポインタの関係 - Yukihiro Iguchi, www

第2回 ソート
情報科学科
井口幸洋
[email protected]
2003/5/6
ソフトウェア実習I
1
目的
– データを並べ替える(ソート)技法を学ぶ
– ソートは極めて基本的で重要なアルゴリズム
– アルゴリズムによって処理速度が大幅に変わるこ
との体験をしてもらう
2003/5/6
ソフトウェア実習I
2
ソート方法のいろいろ
• バブルソート:1年生の時、学んだ方法.
メリットはほとんど無い.プロは使用しない.
• 単純挿入法
本日はこの3つの方法を
• ヒープソート
実装し,比較を行う.
• クイックソート
2003/5/6
ソフトウェア実習I
3
ソート方法のいろいろ
• バブルソート:1年生の時、学んだ方法.
メリットはほとんど無い.プロは使用しない.
• 単純挿入法
• ヒープソート
• クイックソート
2003/5/6
ソフトウェア実習I
4
単純挿入法
• 順序正しく並んだ列に新たなデータを正しい
位置に挿入
• n個のデータに対して計算量はO(n2).
2003/5/6
ソフトウェア実習I
5
単純挿入法のアルゴリズム
• a[1]~a[i-1]まで既に小さい順(昇順)に整列
されているとする.
• 挿入すべき値 q = a[i] とする.
• qをa[i-1]からa[1]の方向に向かって挿入すべ
き場所を探す.このとき,調べた値がqより大
きければ右に1個づつずらし,q以下になった
らそこに挿入して探索を抜け出る.
2003/5/6
ソフトウェア実習I
6
単純挿入法でのソートの例(1)
[1][2][3][4][5][6]
2 1 3 5 7 4
まず,a[1]までが整列されて
いると考える.
[1][2][3][4][5][6]
2 1 3 5 7 4
int i, j, q;
for (i = 2; i <= n; i++){
q = a[i];
j = i;
while(a[j-1] > q){
a[j] = a[j-1];
j--;
}
a[j] = q;
}
a[2]までを整列させる.
q=a[2]の内容をどこに挿入すれば
良いか考える.
a[1] > qなのでこれを1個右にずらし,そこに挿入!
2003/5/6
ソフトウェア実習I
7
単純挿入法でのソートの例(2)
[1][2][3][4][5][6]
1 2 3 5 7 4
a[2]までが整列されている.
[1][2][3][4][5][6]
1 2 3 5 7 4
int i, j, q;
for (i = 2; i <= n; i++){
q = a[i];
j = i;
while(a[j-1] > q){
a[j] = a[j-1];
j--;
}
a[j] = q;
}
a[3]までを整列させる.
q=a[3]の内容をどこに挿入すれば良いか考える.
a[2] < qなので今回は a[3] = q(つまり3)が挿入され,
見かけ上変化はない.
2003/5/6
ソフトウェア実習I
8
単純挿入法でのソートの例(3)
[1][2][3][4][5][6]
1 2 3 5 7 4
a[3]までが整列されている.
[1][2][3][4][5][6]
1 2 3 5 7 4
int i, j, q;
for (i = 2; i <= n; i++){
q = a[i];
j = i;
while(a[j-1] > q){
a[j] = a[j-1];
j--;
}
a[j] = q;
}
a[4]までを整列させる.
q=a[4]の内容をどこに挿入すれば良いか考える.
a[3] < qなので今回も a[4] = q(つまり5)が挿入され,
見かけ上変化はない.
2003/5/6
ソフトウェア実習I
9
単純挿入法でのソートの例(4)
[1][2][3][4][5][6]
1 2 3 5 7 4
a[4]までが整列されている.
[1][2][3][4][5][6]
1 2 3 5 7 4
int i, j, q;
for (i = 2; i <= n; i++){
q = a[i];
j = i;
while(a[j-1] > q){
a[j] = a[j-1];
j--;
}
a[j] = q;
}
a[5]までを整列させる.
q=a[5]の内容をどこに挿入すれば良いか考える.
a[4] < qなので今回も a[5] = q(つまり6)が挿入され,
見かけ上変化はない.
2003/5/6
ソフトウェア実習I
10
単純挿入法でのソートの例(5)
[1][2][3][4][5][6]
1 2 3 5 7 4
a[5]までが整列されている.
[1][2][3][4][5][6]
1 2 3 5 7 4
int i, j, q;
for (i = 2; i <= n; i++){
q = a[i];
j = i;
while(a[j-1] > q){
a[j] = a[j-1];
j--;
}
a[j] = q;
}
a[6]までを整列させる.
q=a[6]の内容をどこに挿入すれば良いか考える.
a[5]から順に大小関係を比較し,qの方が小さければ
データを右にずらしつつ、挿入位置をさぐる.
どのように動作するか検討してみてください.
2003/5/6
ソフトウェア実習I
11
単純挿入法の効率
• 計算の手数:O(n2)
• ほぼ整列しているデータ列に対しては効率
が高い.
• 効率を探ってみるときのヒント
– 小さい順にほぼ並んでいるデータ列
– 大きい順にほぼ並んでいるデータ列
– ランダムに並んでいるデータ列
2003/5/6
ソフトウェア実習I
12
ソート方法のいろいろ
• バブルソート:1年生の時、学んだ方法.
メリットはほとんど無い.プロは使用しない.
• 単純挿入法
• ヒープソート
• クイックソート
2003/5/6
ソフトウェア実習I
13
ヒープソート(heap sort)
• heap(整列二分木)を使用.
• 計算の手数は,O(n log n).
• ここでは次のことを学ぶ
– heap(整列二分木)とは何か?
– それをどう表現するか?(データ構造)
– これを使ってどうやってソートするのか?
2003/5/6
ソフトウェア実習I
14
親(parent)と子(child)
A
節点2の親
は節点1.
節点2は節
点1の子.
2003/5/6
F
1
E
2
ソフトウェア実習I
節点3の親
3 は節点1.
節点3は節
点1の子.
15
木(tree)
根(root)
節(node)
頂点(vertex)
A
C
B
D
節と節とを結ぶ線は
辺(edge).
E
F
G
H
葉(leaf)
2003/5/6
ソフトウェア実習I
16
木(tree)
level 0
X
level 1
A
level 3
W
R
F
B
2003/5/6
C
level 2
D
•節点(node)の集合と辺(edge)
の集合からなる.
•節点には名前が付けられたり,
他の情報を持つことがある.
•辺は2つの節点を結ぶ.
•道(パス,path)とは互いに異
なる節点の並びであり並びの
中で隣り合う節点は辺で結ば
れている.
•根とそれ以外の任意の一つ
の節点の間には一つだけ道が
ある.
ソフトウェア実習I
17
二分木(binary tree)
•節点(node)から出る辺が最大
で2個の木.
S
E
V
A
F
D
2003/5/6
L
C
ソフトウェア実習I
18
完全二分木(complete binary tree)と
配列による表現
S
E
V
4
1
A
2
F
5
L
3
6
k [1][2] [3] [4][5][6]
a[k]
S E A V F L
level 0 level 1
2003/5/6
•節点 i の親は i / 2で
求まる.
•節点 j の子は2j, 2j+1
で求まる.
•ただし,一番下のレベ
ルは右が空いている
場合があるので,どこ
まで節点があるかを記
憶する必要あり.
•例えば節点3の子
は6だけ.7は無い.
level 2
ソフトウェア実習I
19
完全二分木の配列表現上での操作
S
E
V
A
F
L
•一番下のレベルを除
いて,どのレベルも内
部節点が完全に埋
まっていて,一番下の
レベルにある内部節点
が左に詰められている
ものを完全二分木とこ
こでは定義する.
k [1][2] [3] [4][5] [6]
a[k]
S E A V F L
level 0 level 1
2003/5/6
level 2
ソフトウェア実習I
20
整列二分木(heap):
heap条件を満たした完全二分木
2
1
5
4
heap条件: 親の中身が子の中身より大きい
1
3
2
6
5
4
3
heap条件を満たしていない木の例
6
k [1][2][3][4][5][6]
a[k]
2 1 3 5 6 4
2003/5/6
ソフトウェア実習I
21
heap条件を満たすためのデータの入れ替え
2
1
5
4
1
3
2
6
5
4
3
6
k [1][2][3][4][5][6]
a[k]
2 1 3 5 6 4
2003/5/6
downheap(int a[], int j, int k)
{
int i, v;
v = a[j];
while (j <= k/2){
i = 2 * j;
if(i<k && a[i]<a[i+1]) i++;
if(v>=a[i])break;
a[j]=a[i]; j=i;
}
a[j] = v;
}
downheap(a, 3, 6);
downheap(a, 2, 6);
downheap(a, 1, 6);
ソフトウェア実習I
22
heap_sort内でのdownheapの呼び出し(1)
2
1
5
4
1
3
2
6
5
4
3
6
k [1][2][3][4][5][6]
a[k]
2 1 3 5 6 4
v=3
2003/5/6
節点3と節点6の
中身を比較
downheap(int a[], int j, int k)
{
int i, v;
v = a[j];
while (j <= k/2){
i = 2 * j;
if(i<k && a[i]<a[i+1]) i++;
if(v>=a[i])break;
a[j]=a[i]; j=i;
}
a[j] = v;
}
downheap(a, 3, 6);
子の方が大
downheap(a, 2, 6);
きいので交
downheap(a, 1, 6); 換
ソフトウェア実習I
23
heap_sort内でのdownheapの呼び出し(2)
2
1
5
4
1
4
2
6
5
3
3
6
k [1][2][3][4][5][6]
a[k]
v=1
2 1 4 5 6 3
downheap(int a[], int j, int k)
{
int i, v;
v = a[j];
while (j <= k/2){
i = 2 * j;
if(i<k && a[i]<a[i+1]) i++;
if(v>=a[i])break;
a[j]=a[i]; j=i;
}
a[j] = v;
}
downheap(a, 3, 6);
downheap(a, 2, 6);
downheap(a, 1, 6);
vと節点5(4と5の大き
2003/5/6
い方)との中身を比較ソフトウェア実習I
子の方が大
きいので交
換
24
heap_sort内でのdownheapの呼び出し(3)
2
6
5
4
1
4
2
1
5
3
3
6
k [1][2][3][4][5][6]
a[k]
v=2
2 6 4 5 1 3
downheap(int a[], int j, int k)
{
int i, v;
v = a[j];
while (j <= k/2){
i = 2 * j;
if(i<k && a[i]<a[i+1]) i++;
if(v>=a[i])break;
a[j]=a[i]; j=i;
}
a[j] = v;
}
downheap(a, 3, 6);
子の方が大
downheap(a, 2, 6);
きいので子
downheap(a, 1, 6); を親に移動
節点1と節点2(2と3の
2003/5/6
ソフトウェア実習I
大きい方)との中身を比較
25
heap_sort内でのdownheapの呼び出し(4)
6
5
5
4
1
4
2
1
5
3
3
6
k [1][2][3][4][5][6]
a[k]
v=2
6 6 4 5 1 3
downheap(int a[], int j, int k)
{
int i, v;
v = a[j];
while (j <= k/2){
i = 2 * j;
if(i<k && a[i]<a[i+1]) i++;
if(v>=a[i])break;
a[j]=a[i]; j=i;
}
a[j] = v;
}
downheap(a, 3, 6);
子の方が大
downheap(a, 2, 6);
きいので5
downheap(a, 1, 6); を上に移す
vと節点4(4と5の大きい
2003/5/6
ソフトウェア実習I
方)との中身を比較
26
heap_sort内でのdownheapの呼び出し(5)
6
5
2
4
1
4
2
1
5
3
3
6
k [1][2][3][4][5][6]
a[k]
v=2
6 5 4 2 1 3
2003/5/6
a[j]=v;
downheap(int a[], int j, int k)
{
int i, v;
v = a[j];
while (j <= k/2){
i = 2 * j;
if(i<k && a[i]<a[i+1]) i++;
if(v>=a[i])break;
a[j]=a[i]; j=i;
}
a[j] = v;
}
downheap(a, 3, 6);
downheap(a, 2, 6);
downheap(a, 1, 6);
ソフトウェア実習I
27
heapを使ったソート(関数heap_sort)
6
5
2
4
1
4
2
1
5
3
3
rootに最大値が入っている.
これを一番後ろと交換し,
一番後ろを取り除いた木の
整列二分木を作成すれば、
小さい順にデータを整列さ
せることができる.
6
k [1][2][3][4][5][6]
a[k]
6 5 4 2 1 3
2003/5/6
ソフトウェア実習I
28
heapを使ったソート(関数heap_sort)
3
5
2
4
1
4
2
1
5
6
3
rootに最大値が入っている.
これを一番後ろと交換し,
一番後ろを取り除いた木の
整列二分木を作成すれば、
小さい順にデータを整列さ
せることができる.
6
k [1][2][3][4][5][6]
a[k]
3 5 4 2 1 6
2003/5/6
ソフトウェア実習I
29
heapを使ったソート(関数heap_sort)
3
5
2
4
1
4
2
1
3
rootに最大値が入っている.
これを一番後ろと交換し,
一番後ろを取り除いた木の
整列二分木を作成すれば、
小さい順にデータを整列さ
せることができる.
5
k [1][2][3][4][5][6]
a[k]
3 5 4 2 1 6
2003/5/6
ここから前
だけ考える
downheap(a, 1, --k);
ソフトウェア実習I
30
heapを使ったソート(関数heap_sort)
5
3
2
4
1
4
2
1
3
rootに最大値が入っている.
これを一番後ろと交換し,
一番後ろを取り除いた木の
整列二分木を作成すれば、
小さい順にデータを整列さ
せることができる.
5
k [1][2][3][4][5][6]
a[k]
3 5 4 2 1 6
downheap(a, 1, --k);
以後同様に行えばよい!
2003/5/6
ここから前
だけ考える
ソフトウェア実習I
31
heap_sort内でのdownheapの呼び出し(5)
6
6
5
4
1
4
2
1
5
3
3
6
k [1][2][3][4][5][6]
a[k]
6 6 4 5 1 3
downheap(int a[], int j, int k)
{
int i, v;
v = a[j];
while (j <= k/2){
i = 2 * j;
if(i<k && a[i]<a[i+1]) i++;
if(v>=a[i])break;
a[j]=a[i]; j=i;
}
二回目のwhile
a[j] = v;
loop
}
downheap(a, 3, 6);
子の方が大
downheap(a, 2, 6);
きいので6
downheap(a, 1, 6); を上に移す
節点1と節点2(2と3の
2003/5/6
ソフトウェア実習I
大きい方)との中身を比較
32
ソート方法のいろいろ
• バブルソート:1年生の時、学んだ方法.
メリットはほとんど無い.プロは使用しない.
• 単純挿入法
• ヒープソート
• クイックソート
2003/5/6
ソフトウェア実習I
33
クイックソート
• 平均 O(n log n),最悪の場合 O(n2)の手間.
• 第1回の実習はこれを基にしている
• pivot(軸)を選び、これより左側にはpivot以
下のものを,右側にはpivot以上のものを集
める.2分割、4分割、8分割と再帰的にこの
作業をすすめる.
• 前回の資料を参考にして実際に簡単な例で
実行の様子を紙にかいて理解してからプロ
グラムをすること.
2003/5/6
ソフトウェア実習I
34
課題について
• 各ソート法の効率(スピードや手数)を計測する
• 実際の時間を計るには time コマンドを使えば良い
が、並べ替えるデータの個数が小さいと計測できな
い.大きくし、さらにforループなどで行うこと
• 入れ替えや代入の度にカウントアップする変数を用
意しこれによる手間の計測をすること
• 並べ替えデータについては3種類程度考えること
(なぜそれを選んだかの理由も必要)。
• データの個数nを大きくしていったときの変化の様
子もしらべること
2003/5/6
ソフトウェア実習I
35
リダイレクトとパイプライン
# a.out < file1
# a.out > file2
# a.out <file1 >file2
# cat file1 file2 | sort | uniq > kekka
# cat kekka
2003/5/6
ソフトウェア実習I
36