大阪大学 情報科学研究科 情報工学 2010年8月実施 必須問題
Author
Description
配点: (1) 15点, (2) 24点, (3) 24点, (4-1) 13点, (4-2) 12点, (4-3) 12点
図1の ANSI-C 準拠である C 言語のプログラムは、53行目の整数型 (int 型) 配列 (array) dat で与えられる入力に対して、昇順 (ascending order) に整列 (sort) するプログラムである。以下の各問に答えよ。
#include <stdio.h>#define MAX 8
void display(int *arr) { int i; for(i=0; i<MAX; i++) printf("%d ", arr[i]); printf("\n");}
int arrange(int a, int b, int *arr) { int left, right, bd, temp; left=a; right=b; bd=arr[(a+b)/2];
while(1) { while(arr[left]<bd) { left++; if(left>b) break; } while(arr[right]>=bd) { right--; if(right<a) break; }
if(right<a) { arr[(a+b)/2]=arr[a]; arr[a]=bd; return a+1; } else if(left<=right) { temp=arr[left]; arr[left]=arr[right]; arr[right]=temp; left++; right--; } else { break; } } return(left);}
void sort(int a, int b, int *arr) { int k; if(b<=a) return; k=arrange(a, b, arr); display(arr); sort(a, k-1, arr); sort(k, b, arr);}
int main(void) { int dat[]={30, 50, 70, 40, 20, 80, 60, 10}; sort(0, MAX-1, dat); display(dat); return 0;}(1) このプログラムで実現されている整列方式 (sorting algorithm) の名称を答えよ。
(2) このプログラムの4行目の display 関数を、1回目に呼び出すときと、3回目に呼び出すときの、配列 dat の内容を記述せよ。
(3) このプログラムは、入力を指定する53行目の配列 dat によって動作が変化する。49行目の sort 関数の呼び出しにおいて、常に k と b が等しくなり、かつ25行目の処理が行われないように、配列 dat を並べ替えよ。
(4) 49行目の sort 関数の呼び出しにおいて、常に k と b が等しくなる配列 dat が与えられたときについて、以下の各小問に答えよ。
(4-1) 配列 dat の要素数を としたとき、プログラム実行の全体における sort 関数が呼び出される回数を求めよ。
(4-2) このような状況における、このプログラムが示す整列方式の時間計算量 (time complexity) を、整列要素数 を用いてオーダー表記 (order notation) で答えよ。
(4-3) これは、このプログラムが示す整列方式にとってどのような状況であるか、時間計算量の観点から簡潔に述べよ。
Kai
(1)
解答: クイックソート (Quick Sort)
(2)
解答:
- 1回目:
30, 10, 20, 40, 70, 80, 60, 50 - 3回目:
10, 20, 30, 40, 70, 80, 60, 50
解説:
arrange 関数は与えられた範囲の中央の要素 arr[(a+b)/2] をピボット (bd) とし、それ未満の要素を左側、それ以上の要素を右側に分割します。
初期状態の配列: 30, 50, 70, 40, 20, 80, 60, 10
1回目の sort(0, 7, dat) 呼び出し時の arrange(0, 7, dat):
- ピボット
bd = arr[3] = 40 leftは40未満の要素を探し、rightは40以上の要素を探して交換します。- 1回目の交換:
arr[1](50) とarr[7](10) を交換。配列は30, 10, 70, 40, 20, 80, 60, 50となる。 - 2回目の交換:
arr[2](70) とarr[4](20) を交換。配列は30, 10, 20, 40, 70, 80, 60, 50となる。 - この後、
leftは3 (40)、rightは2 (20) となりループを終了。leftであるk=3を返します。 - ここで1回目の
displayが呼ばれます。配列は30, 10, 20, 40, 70, 80, 60, 50。
次に sort(0, 2, dat) が呼ばれます(arrange(0, 2, dat)):
- ピボット
bd = arr[1] = 10 rightが-1まで減少しright < aの条件を満たし、25行目でarr[1]とarr[0]の値を操作します。結果的にarr[0]に 10、arr[1]に 30 が入ります。- 返り値は
k = 1。 - ここで2回目の
displayが呼ばれます。配列は10, 30, 20, 40, 70, 80, 60, 50。
次に左側 sort(0, 0, dat) が呼ばれて直ちにリターンし、右側 sort(1, 2, dat) が呼ばれます(arrange(1, 2, dat)):
- ピボット
bd = arr[1] = 30 leftは2 (20)、rightは2 (20) で停止し、arr[1](30) とarr[2](20) を交換します。- 返り値は
k = 2。 - ここで3回目の
displayが呼ばれます。配列は10, 20, 30, 40, 70, 80, 60, 50。
(3)
解答: 20, 40, 60, 80, 10, 50, 30, 70
解説:
k と b が常に等しくなるということは、arrange 関数の返り値である left が常に b になることを意味します。この時、分割後の右側の要素数が1つ(kからbなのでbのみ)、左側の要素数がそれ以外すべてという最も偏った分割が毎回の再帰で発生します。
また、25行目が実行されないためには、right < a が真にならない必要があります。
これを実現するためには、各再帰ステップにおいて、選択されるピボット arr[(a+b)/2] が、その部分配列 arr[a...b] における最大値であり、交換によって arr[b] の位置に移動し、残りの要素が左側に寄るようにする必要があります。
ソート済みの状態から逆順に操作を辿ることで、この特定の入力配列を構築できます。
(4-1)
解答: 回
解説:
k=b が常に成り立つため、部分配列のサイズ に対する sort(a, b) は、サイズ の sort(a, b-1) と、サイズ の sort(b, b) を呼び出します。
サイズ の最初の呼び出しから始まり、サイズ と再帰が深くなります。
各ステップで、サイズ1の部分問題(ベースケースで直ちに終了)が1回呼ばれます。
したがって、sort の呼び出し回数は、サイズが減っていく主流の呼び出しが 回、そこから派生するサイズ1の呼び出しが 回となり、合計で 回となります。
(4-2)
解答:
解説: 分割が常に最も不均等(要素数 と )に行われるため、要素数 の再帰方程式は となります。これを展開すると の時間がかかります。
(4-3)
解答: クイックソートにおいて、ピボットとして常に部分配列の最大値(または最小値)が選ばれ、分割が最も不均等に行われるため、再帰の深さが となり、性能が最悪(最悪時間計算量)となる状況である。