アメリカの大学で奮闘中

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

写像(Mapping)とは何か、関数とは何か

アメリカの大学の2年生の秋学期、長かったCalculasのクラスが終わろうとしている時期に、数学の基礎となる証明法や集合理論、数学的記述方法のクラスが始まりました。

 

その時の自分は英語力を未熟で、数学は先生の板書かスライドの数式と教科書の公式を見比べることだけで理解をしていた自分には初めての数学の抽象的な説明を英語で受けることに戸惑い理解することを苦戦していました。

 

そしてまさにその初めて苦労したトピックが写像(Mapping)でした。

 

この記事では、アメリカで数学の授業を受ける方や数学の知識を英語でも知っておかないといけない人に向けて、写像、単射(injective)、全射(surjective)、全単射(bijective)を説明したいと思います。

 

そもそも写像とは?

この答えとしては厳密性や正確性を一旦無視して関数(function)のことだと思ってください。

 

ただし、ここで新たな関数の見方をして欲しいのです。

関数を矢印だと考えてください。さらに言うとAという集合の要素からBという集合の要素に対応させるイメージです。その際には以下のように記述します。

f:A → B

 

例としてf(x)=x+2という関数を考えます。これは言うまでもなくxという値に2を足す関数なのですが、実数の集合から別の実数の集合に対応していると無理やりかもですが言えます。(そもそも実数が集合なんだという方は後々集合についての記事を書く予定ですのでお楽しみに。)

 

この場合、実数R(集合A)から、実数R(集合B)に対応させているという意味を込めて以下のように記述します。集合Bは2を足しても実数であることは変わりないので実数Rです

f:R → R

 

*実数は英語でReal Numberといい、頭文字のRで記述します。

 

この→の性質を考えてみます。そして特徴的なものは名前がつけられています。それが単射(injective)、全射(surjective)、全単射(bijective) になります。

 

以下では先程の関数

f:R → R   f(x)= x+2

g:R → R   g(x)= x^2

を用いて説明していきたいと思います。

 

単射(injective)

単写は集合Aと集合Bの要素が一対一で対応している写像です。

なので、f(x) = 2xは単写になります。その一方でg(x)=x^2はg(x)=g(-x)=x^2と二対一になっているので単射(injective)ではありません。

 

全射(surjective)

全射は矢印が集合Aから集合Bの全ての要素にリーチできる状態になっていることを言います。

f(x)=x+2は、もし集合Bのyという要素が欲しければxについて解いてあげてx=y-2という実数を見つけることができるので全射です。一方でg(x)= x^2はもしg(x)の値がマイナスの時のxの値を探すことができないため、集合Aから集合Bの全ての要素にリーチできる状態ではないので全射ではありません。

 

全単射(bijective)

単写と全射の両方を満たすものを全単射と呼びます。なのでf(x)=x+2は全単射と言えます。

 

余談:写像と関数の違い

こちらのサイトには以下のように記述されていました。

mathlandscape.com

 

結論,両者の違いは基本的にないと思って差し支えないです。
強いて言えば,終域が数やそれに関連するものであるとき,「関数」といい,そうでないとき「写像」ということが多いです。

 

実際に大学の授業でも(少なくとも学部生のうちは)特に区別することなく理解していても問題ありませんでした。

Web開発かアプリ開発かまだ決め切れていないエンジニアはJavaScriptを学びなさい

少し攻めたタイトルになってしまったのですが、今回の記事ではプログラミングを学び始めたor就活でエンジニア就職をする予定だけど何を勉強すればいいか分からない人で、特にWEB開発とアプリ開発で迷っている人をターゲットにしています。

 

結論から言うと、迷っている人はJavaScriptを勉強すると良いと思います。理由はweb開発にもアプリ開発もJavaScriptで作ることができるからです。

 

そもそもJavaScriptとは

1995年にネットスケープコミュニケーションズのプログラマーだったブレンダン・アイク氏が開発した言語です。

 

HTML、CSS(WEBサイトの見た目を作る言語)で書かれた画面に動きをつけたりできます。例えばwebサイトのトップの画像が時間が経てば横にスライドして画像が変わるものとかありますよね。あのようなものはJavaScriptで実装できます。

 

また、3Dに動きをつけることもできることから、VRなどにも使われたりもあるようです。(ただもしVRの開発者などを目指す場合はJavaScriptより先に学ぶ優先度の高い技術があるとは思いますが)

 

WEB開発とJavaScript

上述した通りJavaScriptは画面に動きをつけるためのものだったのですが、今はサーバーサイドも書けるようなったため必要性がさらに増しています。

 

サーバーサイドのプログラムとは、サーバ側から行う処理に対して必要なプログラムのことですが、具体例としては、検索単語を入れたらその結果を返すとか、データーベース(データを保存するところ)に問い合わせデータを入れるとか、同じドメインの別のwebページに飛ばす(ルーティング)などになります。

 

なのでJavaScriptと、Node.jsやReactなどのライブラリー、フレームワークさえできればweb開発が可能です。

 

アプリ開発とJavaScript

ここでいうアプリはモバイルアプリを想定しています。

 

元々iosのアプリはObjective-C(現在はSwift)、AndroidはJavaで別々で書かなくてはいけなかったのですが、最近は1つの言語でios用、Android用の両方を開発できるようになりました。

 

その言語がJavaScriptである開発ツールをここでシェアできればと思います。

 

Expo

ExpoはFacebookが開発したReactNativeやその開発ツールです。JavaScriptでios、Androidのモバイルアプリの開発を行えます。

 

expo.dev

 

Monaca

Monacaは日本の会社が提供している開発ツールで、web上のエディタで開発ができることと、公式サイトが日本語であることからお手軽なところがメリットです。

 

ja.monaca.io

 

最後に

実は僕もモバイル開発にJavaScriptが使えることをここ最近知った身で、もっと早く知っていたら身を引き締めてJavaScriptの勉強ができたのにと感じていました。もし読者がJavaScript勉強中の方でしたらより一層モチベーションを上げて勉強していただけたら幸いです。またどの言語を学ぶか検討中の方は判断材料として参考にしていただければと思います。僕もまだまだ学習中の身ですが、需要がありそうでしたら普段のアルゴリズムや数学に加えてこのような実践的な開発についても書いていこうと思います。

 

Pythonの文法が分かって、新たにJavaScriptを勉強したい方向けの記事

 

astrostory.hatenablog.com

 

 

参考文献

 

JavaScriptの歴史【紆余曲折を経たプログラミング言語】 – 株式会社ライトコード

 

【2020年版】人気のNode.jsライブラリ・フレームワーク厳選まとめ | あしたのチームTechBlog

マルコフ連鎖②~グラフを考える~

前回の記事の続きです。

 

astrostory.hatenablog.com

 

この記事ではマルコフ連鎖をグラフとして見ていきます。なお、今回も例は前回の記事の天気の例を利用します。

 

簡単に再度説明しますと

 

晴れ、曇り、雨という3つの状態があり、そのいずれかの状態から別の状態に遷移します。

遷移の確率はそれぞれいかになります。

晴れ→晴れの確率をp1、曇り→晴れの確率をq1、雨→晴れの確率をr1

晴れ→曇りの確率をp2、曇り→曇りの確率をq2、雨→曇りの確率をr2

晴れ→雨の確率をp3、曇り→雨の確率をq3、雨→雨の確率をr3

 

グラフとして見る

マルコフ連鎖をグラフとして見ると以下のようになります。

 

f:id:Astrostory:20220217155251p:plain

 

グラフの特徴として、マルコフ連鎖はループを含む(かもしれない)有向グラフになります。

 

遷移行列

グラフには隣接行列という、行列にそれぞれのノードをとってどことどこのノードがつながっているかの情報を0と1であらわして、隣接状況を表現する隣接行列というものがありました。

 

もしご存知でない方はこちらもご参考ください。

astrostory.hatenablog.com

 

マルコフ連鎖にもそのような行列が存在します。マルコフ連鎖の隣接行列は、状態の遷移を表すため遷移行列と呼ばれ、隣接しているか否かの0,1でなく、0か遷移する時の確率で示します。

 

天気の例での遷移確率は以下のようになります。

   A   B   C

A   p1 q1  r1

B   p2 q2 r2

C   p3 q3 r3

 

これ今ある状態(行)から次(tからt+1)に遷移する状態(列)の確率をまとめていますが、この行列式を二乗すればtからt+2の遷移行列になり、この行列式を三乗するとtからt+3の遷移行列になります。

 

これってプログラムで扱うには便利な性質だと思いませんか?

 

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

マルコフ連鎖①〜グラフ理論と確率〜

マルコフ連鎖というグラフ理論を用いた確率の考え方があり、これはプログラムの実装も可能で便利だと思うのでこの記事で詳しく説明できればと思います。

 

いつものように数学的な厳密性は少し欠けていても分かりやすい記事を目指します。

 

マルコフ連鎖とは

マルコフ連鎖とはt(時間)が離散的なマルコフ過程の一種です。

 

マルコフ過程とは、次に起こる事象が今と過去の状態に依存することなく、現在の状態のみで決まる確率過程のことをいいます。

 

現在も状況のみで決まるというポイントが確率の表記を簡単に、コンピューターサイエンスに応用しやすくしているポイントだと思います。

 

実際にマルコフ連鎖を表現する数式は以下のように記述することができます。

P{xn = axm = am, m < n} P{xn = axn1 = an1}

(n-1を現在としてみれば)t =nでx=aとなる状態に来るまでt=m(0<=m<=n)の過程があったにも関わらず、その確率はt=n-1のみの条件付き確率になっている。すなわち現在の状態によって未来の状態が決まる確率という式になっているかと思います。

 

t=n+1の確率をt=nの時の状態で決まるというふうに書いても大丈夫です。

 

表記はいくつか方法があるかと思います。

 

例えば

𝑃 (𝑋𝑛+1 = 𝑗 | X𝑛 = 𝑖, X𝑛−1 = 𝑖𝑛−1,...,X0 = 𝑖0) = 𝑝(𝑖,𝑗)

t=0~nの時にi_0 ~ i_nの状態経てXn+1の時にjになるのは、iとjのみの要素で決まるという意味の式です。

 

具体例

 

例えば、マルコフ連鎖の説明でよく出てくる天気の例を出したいと思います。

晴れの事象をA、曇りの事象をB、雨の事象をCとし、

晴れ→晴れの確率をp1、曇り→晴れの確率をq1、雨→晴れの確率をr1とします。

 

そうすると、

次の日腫れる確率は以下になる。

今日晴れの時 ... p・An

今日曇りの時は ... q・Bn

今日雨の時は ... r・Cn

 

なので、このように3つの状態から晴れになる遷移をするマルコフ連鎖の確率Pn+1は以下のように表されます。

Pn+1 = p1・An + q1・Bn + r1・Cn

 

さらに

晴れ→曇りの確率をp2、曇り→曇りの確率をq2、雨→曇りの確率をr2

晴れ→雨の確率をp3、曇り→雨の確率をq3、雨→雨の確率をr3とすると

 

次の日曇りになる確率と次の日雨になる確率は以下のようになる。

Qn+1 = p2・An + q2・Bn + r2・Cn

Rn+1 = p3・An + q3・Bn + r3・Cn

 

このような確率過程をマルコフ連鎖といいます。

 

グラフ理論ともう少し絡めてマルコフ連鎖を実装する時に役立つ知識を紹介している記事も興味のある方はどうぞ。

astrostory.hatenablog.com

 

クイックソート(Quicksort)を解説してみた by Python

 

クイックソートとはデータの集合をある決まりに従って並び替えるソートアルゴリズムの一種です。クイックソートはクイックと付いているだけあって早く処理できる方法になります。

 

今回は

array = [5,4,6,9,2,7,8,1,3]    →   array = [1,2,3,4,5,6,7,8,9]

arrayの中身をリストを数字の小さい順に並び替えるクイックソートを考えます。

 

ちなみにもしリスト型のメソッドを使うなら array.sort() で小さい順に並び変わります。

 

アルゴリズム

簡単にまとめると

基準値を決めてそれより小さいグループ大きいグループに分ける、小さいグループはさらに小さいグループとそれよりは大きいグループに分け、大きいグループはさらに大きいグループとそれよりは小さいグループに分けていく。それを繰り返して、グループの要素が一個になるまで行い、その全てのグループを連結し直すと並び替えられたリストになっている。

 

f:id:Astrostory:20220207161154p:plain

 

リストを分割(quicksort 関数)

1、元与えられたリストでPartition関数を呼び出して分割する

 

2、分割された左側(数が小さい方)と右側(数が大きい方)でまた別々にPartition関数を呼び出す。

 

3、2番がリストの要素の数が1つになる手前まで繰り返される(if l>rの条件)

 

 

リストを並び替え(Partition 関数)

1、pivotというデータの大小を比べる基準になる変数をソートするリストの中から決定します。

リストの最初や真ん中、最後のデータかリストのはじめのデータをpivotとしていたり、任意の3つのデータのメジアンをpivotとしているコードもみたことがあります。

 

2、pivotより左側のデーったはpivotのそれより小さく、右側のデータはpivotのそれより大きいことをループを使ってチェックしていきます。

 

3、左側のデータの方が大きかったり、右側の方が小さかった場合、それら二つをスワッピング(交換)します。

 

4、3の操作の終わったリストを[pivotとそれより前の要素を含むリスト]、[pivott以降の用を含むリスト]の二つに分割して、再帰させ1−3をまた繰り返されます。

最後に要素が1つしかないリストになるまで繰り返されます。

 

コード

def quicksort(arr):

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

 

def qs(arr, l, r):

    if l >= r:

        return

    p = partition(arr, l, r)

    qs(arr, l, p - 1)

    qs(arr, p + 1, r)

 

def partition(arr, l, r):

    print("arr",arr[l:r+1])

    pivot = arr[r]

    print("pivot is", pivot)

    i = l - 1

    for j in range(l, r):

        if arr[j] < pivot:

            print("In-Small", arr[j])

            i += 1

            print("Swap point", i)

            arr[i], arr[j] = arr[j], arr[i]

    arr[i + 1], arr[r] = arr[r], arr[i + 1]

    return i + 1

 

 

a1 = [5, 4, 6, 9, 2, 7, 8, 1, 3]

quicksort(a1)

 

実行結果とその解説

実行結果

arr [5, 4, 6, 9, 2, 7, 8, 1, 3]

pivot is 3

In-Small 2

Swap point 0

In-Small 1

Swap point 1

arr [2, 1]

pivot is 1

arr [9, 5, 7, 8, 4, 6]

pivot is 6

In-Small 5

Swap point 3

In-Small 4

Swap point 4

arr [5, 4]

pivot is 4

arr [8, 9, 7]

pivot is 7

arr [9, 8]

pivot is 8

解説

1個目の分割と並び替え

arr [5, 4, 6, 9, 2, 7, 8, 1, 3]

pivot is 3

In-Small 2

Swap point 0

In-Small 1

Swap point 1

まだ分割する前なので、与えられた配列は一番最初のリストそのものです。

pivot(大小を比べる基準の数)は一番右の3が選ばれ、3以下の1,2は左のインデックス0と1の場所の値(5,4)と交換させられます。(arr[i], arr[j] = arr[j], arr[i])

そして最後にpivotの3を交換した次の値(インデックス2)に配置して1つ目の分割は終わります。( arr[i + 1], arr[r] = arr[r], arr[i + 1])

リストの分かれ方は以下になります。

[2,1] | 3 | [9,5,7,8,4,6]

 

2個目の分割と並び替え

arr [2, 1]

pivot is 1

1つ目の分割の結果の左側が最初再帰にかけられます。

pivotは一番右の1が選ばれますが、1がリストの中で一番小さいため交換は起こらず、pivotと先頭の数が交換させられます。

リストの分かれ方は以下になります。

1 | [2]

 

3つ目の分割と並び替え

arr [9, 5, 7, 8, 4, 6]

pivot is 6

In-Small 5

Swap point 3

In-Small 4

Swap point 4

1つ目の分割の結果の右側が再帰にかけられています。

pivotは一番右の6が選ばれます。5と4が6より小さいため、リストのインデックス3,4に小さい数が集められ、pivotより小さいグループ、pivot、pivotより大きいグループに分けられます。

*arrは常に要素全体を持ったリストであり、rとlの引数でインデックスを操作して左右を分割しているため、この3つ目の分割と並び替えは前回のリストの右側に当たるため、[1,2,3,〇,〇]の○に該当する場所が数を入れ替える場所になります。

リストの分かれ方は以下になります。

1 | [2] | 3 | [5,4] | 6 | [8,9,7]

 

4、5つ目の分割と並び替え

arr [5, 4]

pivot is 4

 

arr [8, 9, 7]

pivot is 7

これらは先ほどの結果の [5,4] と[8,9,7]をさらに並び替え&分割している様子になります。

2番目の分割と並び替えと全く同じことが行われているので分かりやすいかと思います。

リストの分かれ方は以下になります。

1 | [2] | 3 | 4 | [5] | 6 | 7 |[9,8]

 

6つ目の分割と並び替え

arr [9, 8]

pivot is 8

[9,8]のリストを並び変え&分割を行っております。

リストの分かれ方は以下になります。

1 | [2] | 3 | 4 | [5] | 6 | 7 | 8 | [9]

これにより、1~9まで順番に並んだことになります。

 

参考文献

英語ですが、図と実際のコードを動画で解説しておりとても分かりやすくておすすめです。

A Complete Overview of Quicksort (Data Structures & Algorithms #11) - YouTube

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

マージソート(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つ前が最後のノードになるように接続しています

 

最後に

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