(1)バブルソートの実装

以下の疑似言語で記述されたバブルソートのアルゴリズムを実行した際、配列 a の各要素の値が、各ループの終了後にどのように変化するかをトレースしてください。入力配列 a は以下の通りです。

a = [5, 2, 8, 1, 9, 4]

アルゴリズム バブルソート(a)
入力: 整数配列 a[1...n] (n は配列の要素数)
出力: 昇順にソートされた整数配列 a

for i = 1 to n-1 do
  for j = 1 to n-i do
    if a[j] > a[j+1] then
      a[j] と a[j+1] を交換する
    end if
  end for
end for

以下の表に、各ループの終了後の a の値を示してください。 ij はループ変数です。 交換 の列は、そのループで要素の交換が行われたかどうかを示してください。

i j a[1] a[2] a[3] a[4] a[5] a[6] 交換
1 1 2 5 8 1 9 4
1 2 2 5 8 1 9 4
1 3
1 4
1 5
2 1
2 2
2 3
2 4
3 1
3 2
3 3
4 1
4 2
5 1
▶解答
i j a[1] a[2] a[3] a[4] a[5] a[6] 交換
1 1 2 5 8 1 9 4
1 2 2 5 8 1 9 4
1 3 2 5 1 8 9 4
1 4 2 5 1 8 4 9
1 5 2 5 1 8 4 9
2 1 2 1 5 8 4 9
2 2 2 1 5 4 8 9
2 3 2 1 5 4 8 9
2 4 2 1 5 4 8 9
3 1 1 2 5 4 8 9
3 2 1 2 4 5 8 9
3 3 1 2 4 5 8 9
4 1 1 2 4 5 8 9
4 2 1 2 4 5 8 9
5 1 1 2 4 5 8 9

バブルソートは、隣り合う要素を比較し、順序が逆であれば交換を繰り返すことでソートを行うアルゴリズムです。 外側のループ (i) は、ソート済みの部分の要素数を表し、内側のループ (j) は、ソートされていない部分の要素を走査します。 i が増えるごとに、ソート済みの要素数が増えていきます。 交換 が△の時は、そのループでは交換が行われていないことを意味します。これは、既にソートされている部分を示唆しており、アルゴリズムの効率性を向上させる工夫(最適化)を行う際に役立ちます。 最終的に、配列 a は昇順にソートされます。


(2)クイックソートの実装

以下の疑似コードは、クイックソートアルゴリズムの一部です。空欄(1)~(5)に当てはまる適切な選択肢を選んでください。 ピボットは配列の先頭要素とします。

アルゴリズム クイックソート(a, left, right)
入力: 整数配列 a, 開始インデックス left, 終了インデックス right
出力: a[left...right] が昇順にソートされた配列 a

もし left >= right ならば 終了する

pivot = a[left]
i = left + 1
j = right

while i <= j do
  while i <= right かつ a[i] <= pivot do
    i = i + (1)
  end while
  while j >= left + 1 かつ a[j] >= pivot do
    j = j + (2)
  end while
  もし i < j ならば
    a[i] と a[j] を交換する
  end if
end while

a[left] と a[j] を交換する

クイックソート(a, left, j + (3))
クイックソート(a, j + (4), right)

(1) a) -1 b) 1 c) 0 d) 2

(2) a) 1 b) -1 c) 0 d) 2

(3) a) 0 b) 1 c) -1 d) 2

(4) a) 0 b) 1 c) -1 d) 2

(5) このアルゴリズムの最悪計算量は? a) O(n log n) b) O(n) c) O(n^2) d) O(log n)

▶解答

(1) b) 1 i は配列の要素を順に見ていくので、インクリメントする必要があります。

(2) b) -1 j は配列の要素を逆順に見ていくので、デクリメントする必要があります。

(3) c) -1 左側のサブ配列の終了インデックスは j - 1 です。

(4) b) 1 右側のサブ配列の開始インデックスは j + 1 です。

(5) c) O(n^2) クイックソートは平均計算量が O(n log n) ですが、最悪の場合(既にソートされている配列など)は O(n^2) になります。


(3)二分探索のアルゴリズム

以下の疑似言語で記述された二分探索アルゴリズムを用いて、ソート済みの配列 a から値 target を検索します。 各ステップでの left, mid, right の値と、比較結果をトレースしてください。入力配列 a と検索対象 target は以下の通りです。

a = [2, 5, 7, 11, 13, 17, 19, 23, 29, 31]
target = 17

アルゴリズム 二分探索(a, target)
入力: 昇順にソートされた整数配列 a, 検索対象の整数 target
出力: target が a に存在する場合はそのインデックス、存在しない場合は -1

left = 0
right = a.length - 1

while left <= right do
  mid = (left + right) / 2
  if a[mid] == target then
    return mid
  else if a[mid] < target then
    left = mid + 1
  else
    right = mid - 1
  end if
end while

return -1

以下の表に、各ステップでの left, mid, right の値、a[mid] の値、比較結果、そして処理内容をまとめてください。

ステップ left right mid a[mid] 比較結果 (a[mid] と target) 処理内容
1 0 9 4 13 a[mid] < target left = mid + 1 (left = 5)
2
3
▶解答

二分探索は、ソート済みの配列に対して効率的に値を検索するアルゴリズムです。 配列の中央の要素 (a[mid]) と検索対象 (target) を比較し、targeta[mid] より小さければ左半分、大きければ右半分を探索範囲として再帰的に探索を行います。 この過程を、探索範囲がなくなるまで繰り返します。 target が見つかった場合はそのインデックスを返し、見つからない場合は -1 を返します。 この例では、3ステップで target (17) が見つかり、インデックス 5 が返されます。

ステップ left right mid a[mid] 比較結果 (a[mid] と target) 処理内容
1 0 9 4 13 a[mid] < target left = mid + 1 (left = 5)
2 5 9 7 19 a[mid] > target right = mid – 1 (right = 6)
3 5 6 5 17 a[mid] == target return 5

(4)線形探索のアルゴリズム

以下の疑似言語で記述された線形探索アルゴリズムを用いて、配列 a から値 target を検索します。 各ステップでの i の値と、比較結果をトレースしてください。 見つかった場合はそのインデックス、見つからない場合は -1 を返すものとします。入力配列 a と検索対象 target は以下の通りです。

a = [5, 2, 8, 1, 9, 4]
target = 9

アルゴリズム 線形探索(a, target)
入力: 整数配列 a, 検索対象の整数 target
出力: target が a に存在する場合はそのインデックス、存在しない場合は -1

for i = 0 to a.length - 1 do
  if a[i] == target then
    return i
  end if
end for

return -1

以下の表に、各ステップでの i の値、a[i] の値、比較結果 (a[i] と target)、そして処理内容をまとめてください。

ステップ i a[i] 比較結果 (a[i] と target) 処理内容
1 0 5 5 != 9 処理を継続
2 1 2 2 != 9 処理を継続
3 2
4 3
5 4 return 4
▶解答

線形探索 (Sequential Search) は、配列の先頭から順に要素を一つずつ調べ、検索対象の値と一致する要素が見つかるまで探索を続けるアルゴリズムです。 配列がソートされているかどうかに関係なく動作します。 この例では、target (9) は配列のインデックス 4 に存在するため、アルゴリズムは 4 を返します。

ステップ i a[i] 比較結果 (a[i] と target) 処理内容
1 0 5 5 != 9 処理を継続
2 1 2 2 != 9 処理を継続
3 2 8 8 != 9 処理を継続
4 3 1 1 != 9 処理を継続
5 4 9 9 == 9 return 4


(5)ハッシュ表探索法のアルゴリズムの実装

以下の疑似コードは、ハッシュ表を用いた探索アルゴリズムの一部です。ハッシュ表はサイズ10で、衝突処理にはチェーン法を用います。ハッシュ関数は h(key) = key mod 10 です。空欄(1)~(3)に当てはまる適切な選択肢を選んでください。

アルゴリズム ハッシュ表探索(hash_table, key)
入力: ハッシュ表 hash_table, 検索キー key
出力: key に対応する値、key が存在しない場合は null

index = h(key) // ハッシュ関数

node = hash_table[index]
while node != (1) do
  if node.key == key then
    return node.value
  end if
  node = node.(2)
end while

return (3)

選択肢:

(1) a) null b) 0 c) key d) index

(2) a) key b) next c) value d) previous

(3) a) node b) key c) null d) index

▶解答

(1) a) null チェーンの終端は null で示されます。ループはノードが存在する限り続きます。

(2) b) next チェーン法では、次のノードへのポインタは next で示されます。

(3) c) null キーが見つからない場合、null を返します。

(6)10進数の数値を2進数に変換するプログラム

以下の疑似言語で記述されたプログラムは、10進数の整数 decimal を2進数に変換し、その結果を文字列として返すものです。 decimal に 25 を入力した場合、プログラムの各ステップでの変数の値をトレースし、最終的な出力結果を答えてください。

アルゴリズム decimalToBinary(decimal)
入力: 10進数の整数 decimal
出力: 2進数の文字列 binary

binary = ""
while decimal > 0 do
  remainder = decimal mod 2
  binary = remainder + binary // 剰余を文字列の先頭に連結
  decimal = decimal / 2 // 整数除算
end while

return binary

以下の表に、各ステップでの decimal, remainder, binary の値をまとめます。 decimal / 2 は整数除算であることに注意してください。

ステップ decimal remainder binary
1 25 1 “1”
2
3
4
5
6
解答

最終的な出力結果: “11001”

ステップ decimal remainder binary
1 25 1 “1”
2 12 0 “01”
3 6 0 “001”
4 3 1 “1001”
5 1 1 “11001”
6 0 “11001”

このアルゴリズムは、繰り返し整数除算と剰余演算を用いて10進数を2進数に変換します。 decimal mod 2 は、decimal を 2 で割った余りを求めます。 この余りは、2進数の最下位桁になります。 decimal / 2 は、decimal を 2 で割った商を求めます。 この商を新たな decimal として、ループを繰り返すことで、2進数の各桁を順次求めます。 ループは decimal が 0 になるまで続きます。 剰余を文字列の先頭に連結することで、2進数の正しい順序で文字列が生成されます。


(7)2進数の加算を行うアルゴリズム

以下の疑似コードは、2進数を文字列として受け取り、2進数の加算を行うアルゴリズムです。空欄(1)~(3)に当てはまる適切な式または値を記述してください。 このアルゴリズムは、繰り上がりを考慮した加算を行います。

アルゴリズム binaryAddition(a, b)
入力: 2進数の文字列 a, b (同じ桁数とする)
出力: 2進数の文字列 result

n = a.length  // aとbは同じ桁数なので、どちらを使ってもよい
result = ""
carry = 0

for i = n - 1 downto 0 do  // 下位桁から処理
  digit_a = a[i] - '0'  // 文字を数値に変換
  digit_b = b[i] - '0'  // 文字を数値に変換
  sum = digit_a + digit_b + carry

  if sum == 0 then
    result = "0" + result
    carry = 0
  else if sum == 1 then
    result = "1" + result
    carry = 0
  else if sum == 2 then
    result = "0" + result
    carry = (1)
  else // sum == 3
    result = "1" + result
    carry = (2)
  end if
end for

if carry == 1 then
  result = "1" + result
end if

return result
▶解答

(1) 1 sumが2のとき、繰り上がり(carry)は1になります。

(2) 1 sumが3のとき、繰り上がり(carry)は1になります。

このアルゴリズムは、2進数の加算を下位桁から順次行います。各桁の加算結果と繰り上がりを考慮して、結果の文字列 result を構築します。

ループは、最上位桁まで処理した後、もし繰り上がりが残っていれば、最上位桁に1を追加します。

digit_adigit_b は、文字列 ab の各桁の数字を数値に変換しています。'0' を引くことで、文字 ‘0’ を数値 0 に変換しています。

sum は、現在の桁の2つの数字と繰り上がりを合計した値です。

if 文は、sum の値に応じて、結果の桁と繰り上がりを決定します。

(8)クラスとオブジェクト

以下の疑似言語は、Dog というクラスと、そのオブジェクト myDog を定義しています。空欄 (1)~(3) に適切なコードを記述してください。

クラス Dog {
  属性:
    名前: 文字列
    年齢: 整数

  メソッド:
    コンストラクタ(名前, 年齢) {
      この.名前 = 名前
      この.年齢 = 年齢
    }

    吠える() {
      出力 "ワン!"
    }

    年齢を表示する() {
      出力 "年齢は " + この.年齢 + " 歳です。"
    }

    年齢を設定する(新しい年齢) {
      (1)
    }
}

// Dogクラスのオブジェクトを作成
myDog = 新しい Dog("ポチ", 3)

// メソッドを呼び出す
myDog.吠える()
myDog.年齢を表示する()

// 年齢を変更する
myDog.年齢を設定する(5)

// 変更後の年齢を表示する
myDog.年齢を表示する()

// (2)  Catクラスを作成してください。属性は名前と種類、メソッドは鳴き声「ニャー」を出すメソッドを含めてください。
▶解答

(1) この.年齢 = 新しい年齢

メソッドは、オブジェクトの年齢属性を更新する役割を持っています。 この.年齢 = 新しい年齢 によって、オブジェクト自身の年齢属性 (この.年齢) を引数 新しい年齢 の値で更新します。

(2)

クラス Cat {
  属性:
    名前: 文字列
    種類: 文字列

  メソッド:
    コンストラクタ(名前, 種類) {
      この.名前 = 名前
      この.種類 = 種類
    }

    鳴き声を出す() {
      出力 "ニャー!"
    }
}

Cat クラスは、Dog クラスと同様に、属性とメソッドを持つクラスとして定義します。


(9)継承とポリモーフィズム

以下の疑似コードは、動物を表す Animal クラスとそのサブクラスである Dog クラスと Cat クラスを定義しています。 Animal クラスは抽象クラスであり、speak() メソッドは抽象メソッドとして定義されています。 Dog クラスと Cat クラスはそれぞれ speak() メソッドをオーバーライドしています。 Pet インターフェースは、pet() メソッドを定義しています。 Dog クラスと Cat クラスはそれぞれ Pet インターフェースを実装しています。 空欄 (1)~(3) に最も適切なコードを選択肢から選んでください。

インターフェース Pet {
  メソッド pet(): ヌル
}

抽象クラス Animal {
  属性:
    名前: 文字列

  メソッド:
    コンストラクタ(名前) {
      この.名前 = 名前
    }
    抽象メソッド speak(): ヌル
}

クラス Dog 継承 Animal 実装 Pet {
  メソッド:
    コンストラクタ(名前) {
      スーパークラスコンストラクタ(名前)
    }
    speak() {
      出力 この.名前 + "はワンワンと鳴きます。"
    }
    pet() {
      出力 この.名前 + "をなでなでします。"
    }
}

クラス Cat 継承 Animal 実装 Pet {
  メソッド:
    コンストラクタ(名前) {
      スーパークラスコンストラクタ(名前)
    }
    speak() {
      出力 この.名前 + "はニャーニャーと鳴きます。"
    }
    pet() {
      出力 この.名前 + "をなでなでします。"
    }
}

動物リスト = 新しい リスト()
動物リスト.追加(新しい Dog("ポチ"))
動物リスト.追加(新しい Cat("タマ"))

for 各 動物 in 動物リスト do
  動物.speak()
  (1) // Petインターフェースのpet()メソッドを呼び出す記述を選んでください。
end for

// (2) Animalクラスに、共通の属性「種類」を追加するための記述を選んでください。


// (3)  Birdクラスを追加してください。Animalクラスを継承し、speak()メソッドで鳴き声を表示します。Petインターフェースは実装しません。

選択肢 (1):

a) 動物.pet()
b) 動物.speak()
c) 動物.名前
d) エラーが発生する

選択肢 (2): (Animalクラスの修正)

a) 属性: 種類: 文字列
b) メソッド: 種類を設定する(種類): ヌル
c) 属性: 種類: 数値
d) 変更不要

選択肢 (3): (Birdクラスの追加)

a) クラス Bird 継承 Animal { メソッド: speak() { 出力 “鳥はチーチーと鳴きます。” } }
b) クラス Bird 継承 Pet { メソッド: speak() { 出力 “鳥はチーチーと鳴きます。” } }
c) クラス Bird 実装 Animal { メソッド: speak() { 出力 “鳥はチーチーと鳴きます。” } }
d) クラス Bird { メソッド: speak() { 出力 “鳥はチーチーと鳴きます。” } }

▶解答

(1) a) 動物.pet() 動物Dog または Cat のインスタンスであり、どちらも Pet インターフェースを実装しているので、pet() メソッドを呼び出すことができます。 ポリモーフィズムによって、適切な pet() メソッドが実行されます。

(2) a) 属性: 種類: 文字列 Animal クラスに共通の属性を追加するには、属性を宣言する必要があります。文字列型が適切です。

(3) a) クラス Bird 継承 Animal { メソッド: speak() { 出力 “鳥はチーチーと鳴きます。” } } Bird クラスは Animal クラスを継承し、speak() メソッドを実装する必要があります。 Pet インターフェースは実装しないという問題文の条件を満たしています。

(10)単方向リストの基本的な操作(挿入、削除、検索)

以下の疑似コードは、単方向リストを表現しています。Node はノードを表し、data はノードのデータ、next は次のノードへのポインタです。List はリストを表し、head はリストの先頭ノードへのポインタです。 空欄 (1) と (2) に最も適切なコードを選択肢から選んでください。

クラス Node {
  属性:
    data: 整数
    next: Node
  コンストラクタ(data) {
    この.data = data
    この.next = ヌル
  }
}

クラス List {
  属性:
    head: Node
  コンストラクタ() {
    この.head = ヌル
  }

  メソッド:
    先頭に追加(data):
      新しいノード = 新しい Node(data)
      新しいノード.next = (1)
      この.head = 新しいノード

    削除(data):
      もし (この.head == ヌル):
        戻り値 偽 // リストが空

      もし (この.head.data == data):
        この.head = この.head.next
        戻り値 真

      現在のノード = この.head
      前のノード = ヌル
      繰り返す (現在のノード != ヌル and 現在のノード.data != data):
        前のノード = 現在のノード
        現在のノード = 現在のノード.next

      もし (現在のノード == ヌル):
        戻り値 偽 // データが見つからない

      前のノード.next = (2) // 削除処理
      戻り値 真

    検索(data): // 検索機能は省略
      ...
}

選択肢 (1):

a) ヌル
b) この.head
c) 新しいノード
d) この.head.next

選択肢 (2):

a) ヌル
b) 現在のノード
c) 前のノード
d) 現在のノード.next

▶解答

(1) b) この.head 新しいノードはリストの先頭に挿入されるため、next ポインタは現在の先頭ノード (この.head) を指す必要があります。

(2) d) 現在のノード.next 削除するノードの前のノード (前のノード) の next ポインタを、削除するノードの次のノード (現在のノード.next) に設定することで、削除するノードをリストから取り除きます。

(11)単方向リスト

単方向リストに関する以下の記述のうち、正しいものを全て選んでください。

a. メモリ効率が良い。ノードはデータと次のノードへのポインタのみを持つため、オーバーヘッドが少ない。

b. 任意のノードへのアクセスが高速である。インデックスを用いて直接アクセスできる。

c. ノードの挿入と削除が容易である。特定のノードの前後への挿入・削除は、ポインタの変更のみで済む。

d. リストの走査は一方向のみ可能である。逆方向への走査はできない。

e. ソート済みリストを維持することが容易である。挿入時に適切な位置にノードを挿入することで、ソートを維持できる。

f. メモリの断片化が起こりにくい。連続したメモリ領域を必要としないため。

▶解答

a, c, d, f

a. メモリ効率が良い。ノードはデータと次のノードへのポインタのみを持つため、オーバーヘッドが少ない。 これは正しいです。単方向リストのノードはデータと次のノードへのポインタしか持たないため、他のデータ構造と比べてメモリ使用量が少なく、メモリ効率が良いと言えます。

b. 任意のノードへのアクセスが高速である。インデックスを用いて直接アクセスできる。 これは誤りです。単方向リストは、先頭ノードから順に辿っていくしかノードにアクセスできないため、任意のノードへのアクセスはO(n)の計算量がかかります。配列のようなランダムアクセスはできません。

c. ノードの挿入と削除が容易である。特定のノードの前後への挿入・削除は、ポインタの変更のみで済む。 これは正しいです。挿入や削除は、該当ノードとその前後のノードのポインタを変更するだけで済みます。ただし、リストの中間への挿入・削除を行う場合は、該当ノードを探すのに時間がかかる場合があります。

d. リストの走査は一方向のみ可能である。逆方向への走査はできない。 これは正しいです。単方向リストは、ノードが次のノードへのポインタしか持たないため、逆方向への走査はできません。

e. ソート済みリストを維持することが容易である。挿入時に適切な位置にノードを挿入することで、ソートを維持できる。 これは、必ずしも容易とは言えません。ソート済みリストを維持するには、挿入時に適切な位置を見つけるための検索が必要となり、最悪の場合O(n)の計算量がかかります。 効率的なソート済みリストの維持には、より高度なデータ構造(例えば、平衡二分探索木)が適しています。

f. メモリの断片化が起こりにくい。連続したメモリ領域を必要としないため。 これは正しいです。単方向リストはノードが不連続なメモリ領域に配置されるため、メモリ断片化の問題が少ないです。

(12)双方向リスト

以下の疑似コードは、双方向リストのノードを表すNodeクラスです。空欄(1)と(2)に適切なコードを以下の選択肢から選んでください。

クラス Node {
  属性:
    data: 整数
    prev: Node  // 前のノードへのポインタ
    next: Node  // 次のノードへのポインタ

  コンストラクタ(data) {
    この.data = data
    この.prev = (1)
    この.next = (2)
  }
}

選択肢 (1):

a) この.next
b) ヌル
c) この.prev
d) data

選択肢 (2):

a) この.prev
b) ヌル
c) この.next
d) data

▶解答

(1) b) ヌル

(2) b) ヌル

双方向リストのノードは、前後のノードへのポインタ(prevnext)を持つ構造になっています。 新しいノードが作成された時点では、まだリストに挿入されていません。そのため、前後のノードへのポインタはどちらもヌルで初期化されます。 選択肢a, c, dは、それぞれnextポインタ、prevポインタ、data自身を指しており、新しいノード作成時の状態としては不適切です。

(13)双方向リストの特定のノードを削除するアルゴリズム

以下の疑似コードは、双方向リストから特定のデータを持つノードを削除する関数です。Nodeクラスは問題文で定義されているものとします(前の問題と同じ)。空欄(1)~(3)に適切なコードを以下の選択肢から選んでください。 削除対象のノードが存在しない場合は、何もせずを返します。

関数 削除ノード(リスト, data):
  現在のノード = リスト.head
  繰り返す (現在のノード != ヌル):
    もし (現在のノード.data == data):
      もし (現在のノード == リスト.head):
        リスト.head = 現在のノード.next
        もし (リスト.head != ヌル):
          リスト.head.prev = (1)
      さもなければ:
        現在のノード.prev.next = (2)
        現在のノード.next.prev = (3)
      戻り値 真
    現在のノード = 現在のノード.next
  戻り値 偽

選択肢 (1), (2), (3):

a) ヌル
b) 現在のノード.prev
c) 現在のノード.next
d) リスト.head
e) 現在のノード

▶解答

(1) a) ヌル
削除対象が先頭ノードの場合、新しい先頭ノードのprevポインタをヌルに設定する必要があります。 リストの先頭が空になる場合も考慮して、ヌルチェックを入れています。

(2) c) 現在のノード.next
削除対象がリストの中間ノードの場合、削除対象ノードの前方のノードのnextポインタを、削除対象ノードの後方のノードに設定します。

(3) b) 現在のノード.prev
同様に、削除対象ノードの後方のノードのprevポインタを、削除対象ノードの前方のノードに設定します。

(14)キュースタック操作

以下の疑似コードは、キューとスタックを用いたデータの処理を表しています。 キューqueueとスタックstackは、共に整数値を格納できるものとします。 enqueue(queue, x)はキューqueueに値xを追加し、dequeue(queue)はキューqueueの先頭要素を取り除き、その値を返します。push(stack, x)はスタックstackに値xを追加し、pop(stack)はスタックstackの一番上の要素を取り除き、その値を返します。 空のキューまたはスタックから要素を取り出そうとした場合はエラーとなります。

queue = 空のキュー
stack = 空のスタック

enqueue(queue, 10)
enqueue(queue, 20)
enqueue(queue, 30)

push(stack, dequeue(queue))
push(stack, 40)
push(stack, dequeue(queue))

enqueue(queue, pop(stack))
enqueue(queue, pop(stack))
enqueue(queue, 50)

x = dequeue(queue)
y = dequeue(queue)
z = dequeue(queue)

// x, y, z の値は?

選択肢:

a) x = 10, y = 20, z = 50
b) x = 20, y = 10, z = 50
c) x = 10, y = 40, z = 50
d) x = 20, y = 40, z = 50
e) x = 30, y = 20, z = 40

▶解答

e) x = 30, y = 20, z = 40

プログラムの実行をステップごとに追跡します。

  1. enqueue(queue, 10), enqueue(queue, 20), enqueue(queue, 30): キューqueueには、10, 20, 30 の順で要素が入ります。
  2. push(stack, dequeue(queue)): キューqueueの先頭要素10が取り出され、スタックstackにプッシュされます。 スタックstackの状態は[10]となります。
  3. push(stack, 40): スタックstackに40がプッシュされます。 スタックstackの状態は[10, 40]となります。
  4. push(stack, dequeue(queue)): キューqueueの先頭要素20が取り出され、スタックstackにプッシュされます。 スタックstackの状態は[10, 40, 20]となります。
  5. enqueue(queue, pop(stack)): スタックstackの一番上の要素20が取り出され、キューqueueに追加されます。 キューqueueの状態は[30, 20]となります。
  6. enqueue(queue, pop(stack)): スタックstackの一番上の要素40が取り出され、キューqueueに追加されます。 キューqueueの状態は[30, 20, 40]となります。
  7. enqueue(queue, 50): キューqueueに50が追加されます。 キューqueueの状態は[30, 20, 40, 50]となります。
  8. x = dequeue(queue): キューqueueの先頭要素30が取り出され、xに代入されます。 x = 30
  9. y = dequeue(queue): キューqueueの先頭要素20が取り出され、yに代入されます。 y = 20
  10. z = dequeue(queue): キューqueueの先頭要素40が取り出され、zに代入されます。 z = 40

(15)2つのベクトルのコサイン類似度を計算するプログラム

以下の疑似言語のプログラムは、2つのベクトル v1v2 のコサイン類似度を計算します。 v1v2 は、同じ次元数の数値ベクトルで、要素数は n とします。 プログラム実行後の similarity の値を求めてください。

関数 コサイン類似度(v1, v2, n):
  dot_product = 0
  magnitude_v1 = 0
  magnitude_v2 = 0

  // 内積の計算
  i = 0 から n-1 まで繰り返す:
    dot_product = dot_product + v1[i] * v2[i]

  // v1 の大きさの計算
  i = 0 から n-1 まで繰り返す:
    magnitude_v1 = magnitude_v1 + v1[i] * v1[i]
  magnitude_v1 = √magnitude_v1

  // v2 の大きさの計算
  i = 0 から n-1 まで繰り返す:
    magnitude_v2 = magnitude_v2 + v2[i] * v2[i]
  magnitude_v2 = √magnitude_v2

  // コサイン類似度の計算 (0除算対策あり)
  もし (magnitude_v1 * magnitude_v2 == 0):
    similarity = 0
  さもなければ:
    similarity = dot_product / (magnitude_v1 * magnitude_v2)

  戻り値 similarity

v1 = [1, 2, 3]
v2 = [4, 0, 2]
n = 3

similarity = コサイン類似度(v1, v2, n)
▶解答

プログラムをステップごとに追跡し、各変数の値の変化を記録します。

内積の計算:

dot_product = 1 * 4 + 2 * 0 + 3 * 2 = 10

v1 の大きさの計算:

magnitude_v1 = √(1*1 + 2*2 + 3*3) = √(1 + 4 + 9) = √14

v2 の大きさの計算:

magnitude_v2 = √(4*4 + 0*0 + 2*2) = √(16 + 0 + 4) = √20

コサイン類似度の計算:

magnitude_v1 * magnitude_v2 = √14 * √20 = √280
similarity = 10 / √280 ≈ 0.5976

(16)ニュートン法を用いて方程式の解を求めるアルゴリズム

以下の疑似言語プログラムは、ニュートン法を用いて方程式 f(x) = x² – 2 = 0 の解(√2)を求めるものです。 初期値 x₀ = 1.0、許容誤差 ε = 0.01 とします。 f'(x) は f(x) の導関数です。 プログラム実行後、変数 x の値は何になりますか? また、ループは何度実行されますか?

関数 ニュートン法(f, f_dash, x0, epsilon):
  x = x0
  繰り返す:
    x_new = x - f(x) / f_dash(x)
    もし (abs(x_new - x) < epsilon):
      戻り値 x_new
    x = x_new

関数 f(x):
  戻り値 x*x - 2

関数 f_dash(x):
  戻り値 2*x

x0 = 1.0
epsilon = 0.01
x = ニュートン法(f, f_dash, x0, epsilon)
print x // xの値を出力
print ループ回数 // ループ回数を表示
▶解答

ニュートン法は、方程式の解を反復的に求める数値解法です。 以下のステップでプログラムを追跡します。

ステップ1: 初期値設定

  • x = x0 = 1.0

ステップ2: 1回目の反復

  • x_new = x - f(x) / f_dash(x) = 1.0 - (1.0*1.0 - 2) / (2 * 1.0) = 1.0 - (-1) / 2 = 1.5
  • abs(x_new - x) = abs(1.5 - 1.0) = 0.5 > epsilon (許容誤差より大きいので、ループは続きます)
  • x = x_new = 1.5

ステップ3: 2回目の反復

x_new = x - f(x) / f_dash(x) = 1.5 - (1.5*1.5 - 2) / (2 * 1.5) = 1.5 - (0.25) / 3 = 1.41666...

abs(x_new - x) = abs(1.41666... - 1.5) ≈ 0.0833 < epsilon (許容誤差より小さいので、ループは終了します)
関数 ニュートン法x_new の値、つまりおよそ 1.41666... を返します。


(17)ニューラルネットワークの基本構造

以下の説明は、単純な3層ニューラルネットワーク(入力層、隠れ層、出力層)を示しています。 各層の役割について、正しい記述を選択肢から選んでください。

入力層: 3つのノード(x1, x2, x3)を持つ。
隠れ層: 2つのノードを持つ。各ノードは入力層のノードからの重み付き和とバイアスを受け取る。活性化関数はシグモイド関数とする。
出力層: 1つのノードを持つ。隠れ層のノードからの重み付き和とバイアスを受け取る。活性化関数はシグモイド関数とする。

選択肢:

a) 入力層は、入力データを受け取り、特徴量を抽出する。隠れ層は、入力データの特徴量をさらに抽象化し、出力層は最終的な予測結果を出力する。

b) 入力層は、入力データを受け取り、重みとバイアスを調整する。隠れ層は、入力データの特徴量を抽出する。出力層は、最終的な予測結果を出力する。

c) 入力層は、入力データを受け取り、重みとバイアスを調整する。隠れ層は、最終的な予測結果を出力する。出力層は、入力データの特徴量を抽出する。

d) 入力層は、入力データの特徴量を抽出する。隠れ層は、入力データを受け取り、重みとバイアスを調整する。出力層は、最終的な予測結果を出力する。

▶解答

a) 入力層は、入力データを受け取り、特徴量を抽出する。隠れ層は、入力データの特徴量をさらに抽象化し、出力層は最終的な予測結果を出力する。

解説:

ニューラルネットワークは、複数の層から構成されるモデルです。各層の役割は以下の通りです。

  • 入力層 (Input Layer): 外部から与えられた生のデータ(入力データ)を受け取ります。この層自体は計算を行わず、単にデータを次の層へ伝えます。 「特徴量抽出」という表現は、厳密には次の隠れ層で行われる処理ですが、入力層がデータの受け渡しを行うことで、間接的に特徴量抽出のプロセスを始める役割を担っていると言えるため、選択肢aは適切です。
  • 隠れ層 (Hidden Layer): 入力層から受け取ったデータを基に、特徴量を抽出・変換します。 各ノードは、入力層からの信号の重み付き和とバイアスを加算し、活性化関数(この問題ではシグモイド関数)を通すことで、非線形変換を行います。 この非線形変換により、複雑なパターンを学習することができます。 隠れ層は、入力データの特徴量をさらに抽象化し、より高次元の表現に変換します。
  • 出力層 (Output Layer): 隠れ層から受け取った情報を基に、最終的な予測結果を出力します。 出力層の活性化関数は、予測するタスクによって異なります(例えば、分類問題ではソフトマックス関数、回帰問題では恒等関数など)。 この問題ではシグモイド関数を用いているため、0から1の間の値を出力し、確率などを表現するのに適しています。

選択肢b、c、dは、各層の役割が誤っています。 特に、重みとバイアスの調整は、学習過程で行われるものであり、各層の固有の役割ではありません。 重みとバイアスは、ネットワーク全体の学習を通して調整され、各層の出力に影響を与えます。

(18)シンプルなニューラルネットワークを用いた分類

以下の説明は、単純な2層ニューラルネットワーク(入力層、出力層)を用いて、手書き数字(0~9)の画像を分類する問題を解くためのモデルを示しています。
入力層のニューロン数は784個で、各ニューロンは画像のピクセル1つに対応します。
出力層のニューロン数は10個で、各ニューロンは数字0~9のいずれかに対応します。
活性化関数にはソフトマックス関数を使用します。
このモデルについて、正しい記述を選択肢から選んでください。

選択肢:

a) このモデルは、各ピクセルの値を重み付けして合計し、その結果を直接出力層に渡し、各数字の確率を出力する。

b) このモデルは、入力層から出力層への重み行列を用いて、入力画像の特徴を抽出し、各数字の確率を出力する。

c) このモデルは、入力画像を784次元のベクトルとして表現し、各ピクセルの値をそのまま出力層に入力する。

d) このモデルは、入力層のニューロン数を10個に減らすことで、計算量を削減できる。

▶解答

b) このモデルは、入力層から出力層への重み行列を用いて、入力画像の特徴を抽出し、各数字の確率を出力する。

解説:

この問題は、シンプルな2層ニューラルネットワーク(入力層と出力層のみ)を用いた画像分類を理解しているかを問うています。 選択肢を一つずつ見ていきましょう。

a) このモデルは、各ピクセルの値を重み付けして合計し、その結果を直接出力層に渡し、各数字の確率を出力する。
誤り: これは単純な線形モデルであり、複雑なパターン認識には不向きです。 ニューラルネットワークは、重み行列を用いて非線形変換を行うことで、より複雑な特徴を学習します。

b) このモデルは、入力層から出力層への重み行列を用いて、入力画像の特徴を抽出し、各数字の確率を出力する。
正解: これが正しい記述です。 入力層の784個のニューロンの値は、重み行列によって10個の出力ニューロンに伝播されます。 重み行列は学習によって調整され、入力画像の特徴を効果的に捉えるように学習されます。 ソフトマックス関数は、出力層の各ニューロンの値を確率に変換します。

c) このモデルは、入力画像を784次元のベクトルとして表現し、各ピクセルの値をそのまま出力層に入力する。
誤り: これは、重み行列を用いない単純なモデルであり、複雑なパターン認識には不向きです。 重み行列を用いることで、入力画像の特徴を効果的に抽出できます。

d) このモデルは、入力層のニューロン数を10個に減らすことで、計算量を削減できる。
誤り: 入力層のニューロン数は、入力画像のピクセル数に対応しており、削減することはできません。 入力層のニューロン数を減らすと、重要な情報が失われ、分類精度が低下します。

(19)標準偏差の計算

以下のデータセットの標準偏差を計算してください。小数点以下第2位を四捨五入すること。標準偏差は、データの散らばり具合を表す指標であり、以下の手順で計算されます。

  1. 平均値の算出: データセットのすべての数値を合計し、データ数で割ることで平均値を求めます。
  2. 分散の算出: 各データと平均値との差を二乗し、それらの合計をデータ数で割ることで分散を求めます。
  3. 標準偏差の算出: 分散の平方根を求めることで標準偏差を求めます。

データセット: {10, 12, 15, 18, 20}

選択肢:

a) 3.69
b) 4.00
c) 4.58
d) 5.00

▶解答

a) 3.69

解説:

問題文に記載されている手順に従って、標準偏差を計算します。

1. 平均値の算出:

データセットの合計は 10 + 12 + 15 + 18 + 20 = 75 です。データ数は 5 です。したがって、平均値 (μ) は 75 / 5 = 15 です。

2. 分散の算出:

各データと平均値との差を二乗し、合計します。

(10 – 15)² = 25
(12 – 15)² = 9
(15 – 15)² = 0
(18 – 15)² = 9
(20 – 15)² = 25

合計は 25 + 9 + 0 + 9 + 25 = 68 です。

分散 (σ²) は、この合計をデータ数で割ることで求めます。 σ² = 68 / 5 = 13.6 です。

3. 標準偏差の算出:

標準偏差 (σ) は、分散の平方根です。 σ = √13.6 ≈ 3.6878 です。

小数点以下第2位を四捨五入すると、3.69 となります。

(20)標準偏差がデータ分析において重要な理由

データ分析において、標準偏差が重要な指標である理由として、最も適切なものを以下の選択肢から選んでください。

選択肢:

a) データセットの最大値と最小値の差を直接的に示すため。

b) データセットの平均値からのデータのばらつき具合を定量的に示すため。

c) データセットの個々のデータ値を正確に表現するため。

d) データセットの中央値を正確に計算するため。

▶解答

b) データセットの平均値からのデータのばらつき具合を定量的に示すため。

解説:

選択肢を一つずつ検討してみましょう。

a) データセットの最大値と最小値の差を直接的に示すため。
誤り: 最大値と最小値の差は、データの範囲(レンジ)を示しますが、データのばらつき具合全体を正確に反映しているとは言えません。 例えば、データが平均値を中心に集中している場合と、平均値から大きく離れた値が少数存在する場合では、範囲は同じでもばらつき具合は大きく異なります。

b) データセットの平均値からのデータのばらつき具合を定量的に示すため。
正解: 標準偏差は、データが平均値からどれだけ散らばっているかを数値で表す指標です。 標準偏差が大きいほど、データのばらつきが大きく、平均値からのずれが大きいことを意味します。 データ分析において、データのばらつきを理解することは非常に重要です。 例えば、製品の寸法のばらつきを評価したり、投資のリスクを評価したりする際に、標準偏差は重要な指標となります。

c) データセットの個々のデータ値を正確に表現するため。
誤り: 標準偏差は、個々のデータ値そのものを表現するものではありません。 標準偏差は、データ全体のばらつき具合を要約した指標です。

d) データセットの中央値を正確に計算するため。
誤り: 中央値は、データセットをソートした際に中央に位置する値です。 標準偏差は、中央値の計算には直接関係しません。

結論:

標準偏差は、データのばらつきを定量的に示す重要な指標であり、データ分析において様々な場面で活用されます。 平均値だけではデータの分布状況を完全に把握することはできません。 標準偏差を併せて考慮することで、より正確なデータ分析が可能になります。