アメリカの大学で奮闘中

アメリカの大学でプログラミングを学ぶべくコンピューターサイエンス学部に留学したものの挫折し数学科に転部した著者が、自分の挫折ポイントを踏まえて数学、プログラミング、アルゴリズムついてできる限り分かりやすく解説してみるブログ。*できる限り記事の内容は技術的な間違いをしないように気をつけていますが、もし間違いがあれば教えていただけると助かります。

マージソート(Mergesort)をPythonで解説してみた

マージソートとはソート(並び替え)アルゴリズムの一種で、与えられたリストに対して一定のルールのもとで並び替えるアルゴリズムです。

 

具体的な例で考えていきましょう

 

具体例

arrに数字の入ったリストを仮定してみます。

arr = [12, 11, 13, 5, 6, 7]

これをマージソートを使うことで、

[5, 6, 7, 11, 12, 13]

と小さい順に並び替えていきます。

 

どうやって並び替えるのか(アルゴリズム)

 [12, 11, 13, 5, 6, 7]

↓ A

[12,11,13] [5,6,7]

↓ B, D

-------------------

[12,11] [13]

↓ C

[12][11] 

-------------------

-------------------

[5,6] [7]

↓ E

[5] [6]

-------------------

このようにリストを最後には一つの要素になるまで2分割していきます。

 

*次の操作ですが、下から順番で目で追ってください

 [ 5, 6, 7, 11, 12, 13]

↑ 

[11,12,13] [5,6,7]

↑ G  I

-------------------

[11,12] [13]

↑ F

[12][11] 

-------------------

-------------------

[5,6] [7]

↑ H

[5] [6]

-------------------

 

リストの最初の値から大小を比べて小さい順に左側に配置いて元のリストと同じ長さのソートされたリストに戻します。(要素が1つになるまで分解してから、大小を比較してリストに戻っているので、全てのマージ済のリストは小さい順になっている)

 

実際のコード

def mergeSort(arr, l, r):

   if l < r:

       # ①真ん中のインデックスを設定

       m = l+(r-l)//2

 

       # ②リストを左右に順番で分割

       mergeSort(arr, l, m)

       mergeSort(arr, m+1, r)

       

       # ③左右のリストをマージ(合併する)

       merge(arr, l, m, r)

 

 

def merge(arr, l, m, r):

        #準備編

    n1 = m - l + 1

    n2 = r - m

 

    # 全ての要素が0の新しいリストを2つ作る。

    L = [0] * (n1)

    R = [0] * (n2)

 

    # 上で作ったリストにarrのデータを前半と後半で分けて渡す

    for i in range(0, n1):

        L[i] = arr[l + i]

 

    for j in range(0, n2):

        R[j] = arr[m + 1 + j]

 

    # 全てのリストの操作の数を数える変数をセットアップする

    i, j, k = 0,0,l

 

   # 大小比較編

    # ここ以降で大小を比較して、小さい順になるように左右入れ替える

    # LもRもまたリストが空でない状態の時

    while i < n1 and j < n2:

        if L[i] <= R[j]:

            arr[k] = L[i]

            i += 1

        else:

            arr[k] = R[j]

            j += 1

        k += 1

 

    # Lのリストだけ空でない状態の時=Rのリストが空の時

    while i < n1:

        arr[k] = L[i]

        i += 1

        k += 1

 

    # Rのリストだけ空でない状態の時=Lのリストが空の時

    while j < n2:

        arr[k] = R[j]

        j += 1

        k += 1

    

 

 

#実際にリストを与えて関数を実行

arr = [12, 11, 13, 5, 6, 7]

mergeSort(arr, 0, len(arr)-1)

print(arr)

 

プログラムの解説

マージソートは主に2つのパートに別れています。1、リストを左右に分割に分ける。2、左右のリストをマージ(合併)して新しいリストに左右のリストから小さい順でならび変えて行きます。

 

1、リストを左右に分割に分ける

1番の機能はmergeSort関数が該当します。

recursion(再帰)と呼ばれるアルゴリズムが使われておりややこしいので念入りに解説できればと思います。

 

もしrecursion(再帰)をご存知でない人がいらっしゃればこちらを先に見てみてください。

 

astrostory.hatenablog.com

再帰は本当に何が起こっているかが分からないので、print()でその都度処理を可視化してあげると分かりやすいです。

 

なので、コードをこんな感じでmergeSort関数内にprint()文を入れてみました

 

def mergeSort(arr, l, r):

   if l < r:

       # ①真ん中のインデックスを設定

       m = l+(r-l)//2

       print("① Left", arr[l:m+1])

       print("① Right",arr[m+1:r+1])

       # ②リストを左右に順番で分割

       mergeSort(arr, l, m)

       print("②A Left",arr[l:m+1])

       print("②A Right",arr[m+1:r+1])

       mergeSort(arr, m+1, r)

       print("②B Left",arr[l:m+1])

       print("②B Right",arr[m+1:r+1])

       # ③左右のリストをマージ(合併する)

       merge(arr, l, m, r)

       print("I am done")

 

これでどのprint()文が実行され、リストの変化を見ることができます。

全体の実行結果

① Left [12, 11, 13]

① Right [5, 6, 7]

① Left [12, 11]

① Right [13]

① Left [12]

① Right [11]

②A Left [12]

②A Right [11]

②B Left [12]

②B Right [11]

I am done

②A Left [11, 12]

②A Right [13]

②B Left [11, 12]

②B Right [13]

I am done

②A Left [11, 12, 13]

②A Right [5, 6, 7]

① Left [5, 6]

① Right [7]

① Left [5]

① Right [6]

②A Left [5]

②A Right [6]

②B Left [5]

②B Right [6]

I am done

②A Left [5, 6]

②A Right [7]

②B Left [5, 6]

②B Right [7]

I am done

②B Left [11, 12, 13]

②B Right [5, 6, 7]

I am done

[5, 6, 7, 11, 12, 13]

 

I am doneで関数の全てが実行されている(merge関数が呼び出されているう)ことになるので、I am doneのメッセージで一旦分けて個別で解説したいと思います。

 

1つ目のブロック

① Left [12, 11, 13]

① Right [5, 6, 7]

① Left [12, 11]

① Right [13]

① Left [12]

① Right [11]

②A Left [12]

②A Right [11]

②B Left [12]

②B Right [11]

I am done

 

[12,11,13,5,6,7]の分解がされてます(A)

① Left [12, 11, 13]

① Right [5, 6, 7]

 

[12,11,13]の分解がされてます(B)

① Left [12, 11]

① Right [13]

 

[12,11]の分解がされてます(C)

① Left [12]

① Right [11]

 

[12],[11]と初めて要素が1つずつのリストになったので、1回目のmergeが行われてます。(F)

②A Left [12]

②A Right [11]

②B Left [12]

②B Right [11]

I am done

これにより[11,12]ができあがります。

 

2つ目のブロック

[11,12][13]のマージが行われてます(G)

②A Left [11, 12]

②A Right [13]

②B Left [11, 12]

②B Right [13]

I am done

これにより[11,12,13]ができあがります。

 

3つ目のブロック

②A Left [11, 12, 13]

②A Right [5, 6, 7]

① Left [5, 6]

① Right [7]

① Left [5]

① Right [6]

②A Left [5]

②A Right [6]

②B Left [5]

②B Right [6]

I am done

 

[5,6,7]の分解が行われてます(D)

① Left [5, 6]

① Right [7]

 

[5,6]の分解が行われてます(E)

① Left [5]

① Right [6]

 

[5],[6]が1つずつのリストになったので、3回目のmergeが行われてます。(H)

②A Left [5]

②A Right [6]

②B Left [5]

②B Right [6]

I am done

これにより[5,6]ができあがります。

 

つ目のブロック

[5,6][7]のマージが行われてます(I)

②A Left [5, 6]

②A Right [7]

②B Left [5, 6]

②B Right [7]

I am done

これにより[5,6,7]ができあがります。

 

5つ目のブロック

[11,12,13][5,6,7]のマージが行われてます(J)

②B Left [11, 12, 13]

②B Right [5, 6, 7]

I am done

これにより[5, 6, 7, 11, 12, 13]が出来上がります。

 

2、左右のリストをマージ(合併)する

2番の内容はmerge関数が該当します。

merge関数は主に準備編、大小比較編に別れます。

 

準備編

準備編で定義(用意)している変数は以下になります。

n1, n2, L, R, i, j, k 

 

n1とn2は左右のリストの要素の数を関数の引数を使って計算します。

 

LとRでは左右のリストを新たに作ります。

print([0]*(Int型の値))

を実行して貰えばわかりますが、[0,0, ..., 0]のような0要素が(Int型の値)個入ったリストができます。

 

i,j,kは何回左(i)、右(j)、arr[k]の操作を行ったかを記憶しておくためのものです。

 

大小比較編

LとRはそれぞれ小さい順に並び変えられた要素が入っています。

 

実質大小を比較しているのは3つのwhileのうち初めの「LもRもまたリストが空でない状態の時」の条件ついたwhile文です。

 

if L[i] <= R[j]:

この条件はi番目のL(左)のリストの要素がR(右)のリストの要素より小さいか等しいという意味です。左の方が小さいことになるので、左の要素をリストにいれます。(等しい場合はどっちを入れてもいいのでスルー)

else:

それ以外ということは、i番目のR(左)のリストの要素がL(左)のリストの要素より小さいという意味ので、右のリストの値を入れます

 

残りの2つのwhileはどちらかのリストが空になった時に使われます。

例えば、L=[2,3], R=[1,5] → L=[], R=[5], arr=[1,2,3]のような場合。

この例だと3番目のwhile以下の処理が実行されます。

 

参考文献

Python Program for Merge Sort - GeeksforGeeks

 

 

にほんブログ村 教育ブログ プログラミング教育へ
にほんブログ村

(連結リスト)線形リストと循環リスト

*以前の連結リストの記事の続きです。

astrostory.hatenablog.com

 

今回は前回の連結リストの続きです。前回は片方向リストと双方向リストの解説を行いました。その時に使った例の連結リストは線形リストでした。

 

連結リストには線形リストと循環リストがあります。線形リストの方が簡単なので今までの記事は全て連結リストを使っていましたが、今回は循環リストについて書きたいと思います。

 

循環リストとは

循環リストは簡単にいうと線形リストの線が円になっているリストのことです。具体的には線形リストの最後のノードが先頭に接続している状態のリストです。

 

循環リスト(片方)

図を見て頂けると分かりますが、最後のノードがheadに結びついているのが分かるかと思います。

 

f:id:Astrostory:20220131114916p:plain

循環リスト(片方向)

 

実際のコードはこちらです。

 

class CircularLinkedList:

    class Node:

        def __init__(self, payload):

            self.payload = payload

            self.next = None

 

    

    def __init__(self):

        self.head = None

 

    def addNode(self, newpayload):

        newnode = CircularLinkedList.Node(newpayload)

 

        if self.head == None:

            self.head = newnode

            self.head.next = self.head

            return

            

        else:   #最後のノードに連結するケース

            runner = self.head

            while not runner.next == self.head:

                runner = runner.next

            runner.next = newnode

            runner.next.next = self.head

 

links = CircularLinkedList()

links.addNode(26)

links.addNode(99)

links.addNode(127)

links.addNode(42)

links.addNode(367)

 

違いが分かりやすいように、線形リストと違うポイントをこのように色付けしました。

 

基本のコードはさほど変わりますが何点か違いがありますね。この違いは最後のノードが先頭に接続する故に変わる違いです。

 

2個目の赤線はwhile文で最後のノードまでrunnerを進めるためのコードですが、循環リストには最後のノードの次は何もない(None)ではなく、headになっています。なのでself.headに辿り着くまでrunnerを回した一個前が最後のノードということになります。

 

最後の赤線はrunnerの次の次のノードをself.headを接続するという意味です。runnnerが最後のノードで、その次に新しくnewnodeを入れたため新しい最後のノードができました。ここで思い出して欲しいのが循環リストは最後のノードが先頭に接続している状態のリストでしたね。なので、最後のノードの次はheadに持ってきます。

 

最初の赤線は循環リストは最後のノードが先頭に接続している状態のリストという条件を満たすためにあります。ちなみに。このコードがないと while not runner.next == self.head: でエラーが起こります。(体験談w)

 

双方向循環リスト

線形リストに片方向リストと双方向リストがあるように、循環リストにも片方向リストと双方向リストがあります。

 

双方向の循環リストは片方向の巡回リストに逆方向のルートもついている状態です。

 

f:id:Astrostory:20220131121826p:plain

循環リスト(双方向)

図がごちゃごちゃしてて見にくいのですが、最後のノードから先頭に矢印があるだけでなく先頭から最後のノードに逆向きの矢印があるのも分かるかと思います。

 

実際のコードです

class CircularDoubleLL:

    class Node:

        def __init__(self, payload):

            self.payload = payload

            self.next = None

            self.prev = None

    

    def __init__(self):

        self.head = None

 

    def addNode(self, newpayload):

        newnode = CircularDoubleLL.Node(newpayload)

 

        if self.head == None:

            self.head = newnode

            self.head.next = self.head

            self.head.prev = self.head

            return

            

        else:   #最後のノードに連結するケース

            runner = self.head

            while not runner.next == self.head:

                runner = runner.next

            runner.next = newnode

            runner.next.prev = runner

            runner.next.next = self.head

            self.head.prev = runner.next

 

links = CircularDoubleLL()

links.addNode(26)

links.addNode(99)

links.addNode(127)

links.addNode(42)

links.addNode(367)

 

双方向の循環リストのために新しく足した場所をこれで色付けしました

 

最初のオレンジ線は箇所は双方向にするために後ろのノードの情報を入れるためのprevを用意

 

2つ目のオレンジ線は循環リストは最後のノードが先頭に接続している状態のリストという条件を満たすために入れてあります。言うまでもないですが、ここは初期状態なのでself.headが先頭であり、最後のノードでもあります。

 

最後の2つのオレンジ線

上は新しいノードを連結するためにrunner.nextにnewnodeを入れた後、新しい最後のノードから1つ前のノードを接続しており

下は先頭の1つ前が最後のノードになるように接続しています

 

最後に

少し駆け足になりましたが、こちらが循環リストの説明です。もし分かりにくい箇所があればコメントで質問して頂けるとそちらの回答もこの記事あるいは別記事で載せたいと思うでの是非お送りください。

 

 

片方向リストと双方向リストを大学生が解説してみた by python

以前の記事で連結リストの実装を簡単に紹介しました。

 

astrostory.hatenablog.com

 

実を言うと前回は連結リストの中でも一番シンプルで簡単な線形リストの片方向連結リストの解説を行いました。

 

ですが実際は連結リストはいくつかの種類があり、今回の記事ではそれらを解説したいと思います。

 

 

片方向リスト vs 双方向リスト

以前の記事(上に貼っている記事)で紹介した連結リストは片方向リストでした。片方向リスト先頭があってそこから最後のノードに向かって順番に接続している状態です。

 

それに対して双方向リスト先頭から最後のノードという向きだけでなく、最後から先頭の向きもノードが接続している状態です。

 

*ノードは下の図の箱のことです。

f:id:Astrostory:20220129133820p:plain

片方向連結リストの図解

f:id:Astrostory:20220129135036p:plain

双方向連結リスト

 

前回の記事のおさらいですが、(片方向)連結リストは先頭から最後のノードまでの繋がりを(最後のノードを除く)各々全てのノードが次のノードの情報を持つことで表現していました。

 

なので、双方向リストは次のノードの情報に加えて前のノードの情報も持つことで前後の繋がりを表現しています。

 

 

双方向リストを実際に実装してみましょう。

 

class LinkedList:

    class Node:

        def __init__(self, payload):

            self.payload = payload

            self.next = None

            self.prev = None

    

    def __init__(self):

        self.head = None

 

    def addNode(self, newpayload):

        newnode = LinkedList.Node(newpayload)

 

        if self.head == None:

            self.head = newnode

            return

            

        else:   #最後のノードに連結するケース

            runner = self.head

            while runner.next != None:

                runner = runner.next

            runner.next = newnode

            runner.next.prev = runner

 

links = LinkedList()

links.addNode(26)

links.addNode(99)

links.addNode(127)

links.addNode(42)

links.addNode(367)

 

*わかりやすくするために双方向リストにするために片方向リストから追加したコードをこの書体に変えています

 

コードとしては、

(片方向)連結リストはnextという変数で次のノードの情報を持っていましたが、双方向リストはそれに加えてprev(previouの略)という変数で前の情報を持っています。

 

それにより双方向リストでは先頭から最後のノードまで、最後のノードから先頭までの向きのルートも接続させることはできます。

 

連結リストの続きの記事が気になる方はこちらから

 

astrostory.hatenablog.com

 

 

参考文献

学術論文ではアウトですが..

ご勘弁を

連結リスト - Wikipedia

Gale-shapleyアルゴリズム

Gale-shapleyアルゴリズムはマッチングに関するアルゴリズムです。

 

合コンでの男女のマッチング、研修医と病院のマッチングなどのグループAとグループB、そして全ての参加者がそのマッチング対象のグループの参加者に対して順位がある場合、Gale-shapleyアルゴリズムが役立ちます。

 

これらのマッチングでは、安定マッチングと呼ばれる一対一になっている状態を目指します。

 

言葉だけの説明は難しいと思うので、実際の問題を使って解説します。

 

男女の合コンのマッチングを考えます。

 

A ... 1,2,3

B ... 2,1,3

C ... 2,3,1

 

1 ... B, A, C 

2 ... A, B, C

3 ... C, B, A

 

男性最良マッチング

男性の希望順位ができる限り反映されるマッチング(最良)を考えます。ちなみに、英語ではoptimalと言います。

 

男性の最良マッチングは男性の希望順位が反映されやすいように男性から告白します。

 

そうすると、女性1、女性2、女性3は以下のように男性がアプローチしてきます。

女性1...A

女性2...B,C

女性3...

 

でも女性2は二人の人が来ているので、自分の順位から高い方を選びます。女性2はA->B->Cの順番で希望しているので、Bを選択。

 

女性2とBがマッチしたので、Cは他の女性にアプローチします。Cの順番は2,3,1なので、女性3にアプローチします。

 

これでカップルは{(1,A)(2,B)(3,C)}とマッチングによって誕生しました。

 

ここで全員が安定マッチングしたことが分かります。

 

 

女性最良マッチング

女性の希望順位ができる限り反映されるマッチング(最良)を考えます。ちなみに、英語ではoptimalと言います。

 

上の男性マッチングの女性と男性を逆にするイメージです。

 

そうすると、男性A、男性B、男性Cは以下のように女性がアプローチしてきます

 

男性A ... 2

男性B ... 1

男性C ... 3

 

これにより{(A,2)(B,1)(C,3)}のカップルが生まれました。

 

一対一になっていることから安定マッチングになっていることも理解できます。

 

 

Spanning Tree(全域木)とは何か

今回の記事ではグラフ理論のSpanning Tree(全域木)を扱います。

 

まずそもそもグラフとは?の人はこちらの記事から入られることをお勧めします。

 

astrostory.hatenablog.com

 

 

また今回は分かりやすくするために無向グラフを使います。

全域木

全域木はかなり平たくいうと、全ての頂点が辺で結ばれている状態を満たすグラフのことを言います

 

全域木は元々のグラフの補グラフ(subgraph)になっています。

 

ちなみに、Theoremで全ての連結グラフは全域木を持つことが保証されています。

 

最小全域木

最小全域木は重り付グラフの時に、全域木のトータルの重りの和が最小になるグラフです。

 

最小全域木を求めるアルゴリズム

最小全域木を求めるアルゴリズムは主に2つです。Kruskal's Algorithm(クラスカルのアルゴリズム)とPrime Algorithm(プライムのアルゴリズム)です。

 

これらのアルゴリズムを使う際は、一旦全ての辺を消します。

 

Kruskal's Algorithm(クラスカルのアルゴリズム)

Kruskal's Algorithm(クラスカルのアルゴリズム)は全ての辺(edge)の中から小さいものを順に書き足していき、全ての頂点が結ばれるまで続けます。

f:id:Astrostory:20220101103358p:plain

 

Prime Algorithm(プライムのアルゴリズム)

Prime Algorithm(プライムのアルゴリズム)はある任意の一点からスタートして、今まで通った頂点から出ている辺(edge)の中でweight(重り)が一番小さいものを選んで線をひきます。

 

これを全ての頂点(node, vertex)が連結されるまで続けます。

 

f:id:Astrostory:20220101102747p:plain

最小全域木はできる限り合計の重りの和が小さくしたまま全ての頂点との経路が作れます。この特徴を使って実社会の問題解決に役立ててもらえればと思います。

アメリカの大学でコンピューターサイエンスを学ぶ前に知りたかったこと

僕はアメリカの大学でコンピューターサイエンス学部に入り、その後他の学部に転部しました。

 

転部した理由はシンプルに挫折したことと、学べる内容が自分の思っていたものと違ったからです。

 

その経験を元に僕がアメリカの大学に入る前に知りたかったことを書きたいと思います。

 

抽象度の高いアルゴリズムへの耐性はあるかい?

大学にもよると思いますが、一番最初にプログラミング言語そのものを学習した後、次に学ぶものはアルゴリズムやデータ構造になります。

 

アルゴリズムとはプログラムを書く際にどういう風な手順で計算をさせるかのことです。

 

例えば、RPGゲームで10歩先に落ちているみかんを拾うことをアルゴリズムに落とし込むとします。

 

一歩前に進む → 一歩前に進む →  ...  → 一歩前に進む → 地面にあるものを拾う

 

になります。

 

ですが残念ながらシステムを組む場合、これがベストの状態とは言えません。なぜなら"一歩前に進む"が多用されており、コードも読みにくく、もし他の場所で3歩先に落ちているみかんを拾うアルゴリズムが必要になった時コードを使い回せないからです。

 

この場合、以下のように考えると尚コードの行数も異なりスマートです。

n=10

n回 "一歩前に進む"を繰り返す → 地面にあるものを拾う

 

 

などのようにコードを効率化したり、有名なアルゴリズムを理解したりが必要になります。上記のものであればゲームのキャラクターがただ歩くものをイメージできるのでハードルが下がりますが、実際にコードを書くときは目に見えないものを自分でイメージしたり、あるいはイメージできずに理解してコードを書いていく必要があります。

 

以下が典型的なアルゴリズムやデータ構造になりますので、興味がある方はご覧ください。

astrostory.hatenablog.com

 

 

astrostory.hatenablog.com


astrostory.hatenablog.com

 

プログラミングを学ぶ理由は?

大学でコンピューターサイエンスを学びたい理由はなんでしょうか?

 

僕の知る限り以下の3パターンかなと思います。

1、プログラムを書くことがとことん好きな人

2、プログラミングを通して何かを作りたい人

3、コンピューターサイエンス学部を出ると就職に困らず高給取りになれと聞きつけた人

 

1の人は人並み苦労はするかもしれませんが好きなことをできているので、楽しみながら苦労を超えていけばいいと思います。

 

3の人もコンピューターサイエンス学部卒が経歴としてはいいので、将来のお金や安定性をモチベーションに頑張れるなら、それでいいと思います。

 

問題は2の人です。何かを作りたいかは今明確にできるなら明確にしておくことをおすすめします。

 

例えば、もしwebサイト、スマートフォンアプリを作りたい場合、コンピューターサイエンス学部を卒業する際にはそれらの作り方を学ぶことはできません。ただし、コンピューターサイエンスの学位を取れる人なら独学ですぐ作れるようになると思います。

 

コンピューターサイエンス学部ではあくまで、できる限りライブラリーやAPIを使わずかっちりしたシステムを作る勉強をします。

 

僕の大学なら、webサイトやモバイルアプリの作り方を勉強したい人はDigital Media Arts学部のWeb Design Concentrationなどにいくと具体的にどうやって作るかを勉強できます。

 

他の例で、ある学際領域×ITで何かをしたい場合もコンピューターサイエンスの学位は必須ではないかもしれません。

 

大学によってはBioinfomatics(生物×IT)のような学部があったりするので、そういうところに入るとミスマッチが起こらず済むかもしれません。

 

人によってはシステムそのものからガッチリ勉強したい人もいると思うので、一概には言えませんが、コンピューターサイエンス学部の最初の授業でプログラムの書き方さえ身につけてしまえばそれ以外は自分の作りたい、応用したいものにフォーカスすることが早道かもしれません。

 

卒業できるならしておこう!

1~3のいずれの人でも、できるならコンピューターサイエンス学部を卒業した方がいいとは思います。

 

まず潰しが効くからです。アメリカの高給なソフトウェアエンジニアの仕事につきたい時、コンピューターサイエンス学部卒の学歴はめちゃくちゃ有利ですし、選考でアルゴリズムやデータベースのコーディングテストもあります。

 

もう一つの理由はやっぱり知っておくことに越したことはないからです。

 

僕の体験談ですが、とあるゲームを作った時コードがかなり長くなってしまい、ずっともっと短く書けるのではだったり、うまい処理の方法があるのではと思いつつも、それがあるのかないのかすら分からずコードを書き続けました。

 

上みたいに、ループの知識がないと"前に進む"を10回書かないといけないのです。それがベストな書き方かどうかも知らずに。

 

 

最後の理由で僕はコンピューターサイエンス学部を去った後もアルゴリズムは勉強しており、このブログはその学びのアウトプットの場でもあります。

 

少し長ったらしい文章になりましたがお役に立てれば幸いです。

 

 

 

 

 

 

Geometry 相似の証明

中学の時に習ったであろう、2つの角が同じなら相似を証明していきます。

 

事前準備

そもそも相似とは

そもそも相似(similarity)の定義は...

大学で使っているテキストブックによると...

 

Two triangles are similar if their corresponding angles are equal and if their sides are in proportion.

(もし対応する角(全て)が等しくて、辺(全て)の比が等しい時、2つの三角形を相似という。)

 

それを元にして考えると、合同な三角形の場合対応する角は全て等しく、全ての辺も1:1で同じ比率になるため、合同な三角形は相似と言えます。

 

今回証明は割愛させてもらいますが、

もし△ABC~△DEFならば、△DEF~△ABC

もし△ABC~△DEFかつ△DEF~△GHIならば、△ABC~△GHI

は使っていいこととします。

 

三角形と内部の平行線

f:id:Astrostory:20201104033420p:plain

図1

次の証明の際に、"もし三角形がいずれかの辺の平行線で分割されたならば、できた三角形は元の三角形と相似である"この性質を使うため、先にここで証明しておきます。

 

証明)

△ABCとDE||BCが与えられているとする。

∠DAE = ∠BAC

∠ADE = ∠ABC, ∠AED =∠ACB (平行線の同位角)

△ABCと△ADEの対応している3つの角は全て等しい

 

平行線と比の関係より

CE:AE = BD:AD  すなわち、CE/AE = BD/AD

よって、CE/AE + AE/AE = AC/AE = BD/AD + AD/AD = AB/AD ...①

 

DF||ACを使って、同様に

BF/FC + FC/FC = BC/FC = BD/AD + AD/AD = AB/AD ...②

 

四角形DFCEが平行四辺形なので、DE=FC 

②をBC/DE =AB/AD ...③と書き換えることができる

 

①、③より、BC/DE = AB/AD = AC/CE

よって、△ABCと△ADEの対応している3つの辺の比は全て等しい

 

以上より、△ABCと△ADEは相似  □

 

 

実際の証明

それでは、今回の目的の3つの角の均さや全ての辺の割合の均一さを説明しなくても、対応する角2つの等しさで事足りることを証明していきたいと思います。

 

上の準備で赤色のところはこれからの証明で使う内容ですので、適時参照してください。

 

f:id:Astrostory:20201104041719p:plain

図2

証明)

△ABCと△ADEと∠A = ∠D、∠B = ∠E、∠C = ∠Fが与えられているとする

 

1) AB = DEの時、△ABCと△ADEは合同な三角形であるため相似

 

2)AB ≠ DEの時

AB>DE, AC>DFと仮定すると、AB、AC上にAG= DE,AH=DFを満たすGとHをとることができる。線を書き足して線分GHを作る。

 

△AGHと△DEFは合同である(∠A = ∠D,AG= DE,AH=DFより) 

∠AGH =∠DEF。

∠B = ∠Eであるため、∠AGH =∠DEF=∠B(∠ABC)

 

同位角が等しいため、GH||BC

先ほどの証明を使って,△ABCと△AGHは相似

△AGHと△DEFは合同(相似)であるため、,△ABCと△DEFは相似 □