選択ソート
時間計算量
- 時間計算量
- コンピュータが問題を解く際、入力データ量の増加に対して、必要な処理のステップ数(計算や操作回数)がどう変化するかを指し示す。
- O(1), O(n), O(nlogn), O(n2)などがあり、O(1)が一番効率高い。例は以下のよう。
- 配列のアクセス
- for文
- Quickソート
- 選択ソート、ダブルFor文
選択ソート
- 選択ソート
- 未整列のデータ列から最小値(または最大値)を毎回選択し、整列済みの列の末尾に移動させる単純な並べ替えアルゴリズム
実装ー最大の値を探索
- 最大の値を探索
- 最大値を想定すること。
- スターポイントのための1個目のFor文
- 比較のための2個目のFor文
- ダブルFor文なので、O(n2)になる
int[] abc = { 0, 1, 7, 2, 13, 15, 6 };
int max = abc[0];
for (int i = 0; i < abc.length; i++) {
for (int j = 1 + i; j < abc.length; j++) {
if (max <= abc[j]) {
max = abc[j];
}
}
}
- go langではJavaと宣言とか、データ型を決める方法が違う
- 関数も大文字にする必要がある。
- パラメータも Arrayじゃなくて、Sliceタイプを使うようになる
func FindMaxValue(arr []int) int {
max := arr[0]
for i := range arr {
for j := i + 1; j < len(arr); j++ {
if max < arr[j] {
max = arr[j]
}
}
}
return max
}
実装ー最小値を探索
- 最小限の値を探索
- 上と同じ。ただ以上の方向が変わっただけ
int[] abc = { 3, 1, 7, 2, 13, 15, 6 };
int min = abc[0];
for (int i = 0; i < abc.length; i++) {
for (int j = 1 + i; j < abc.length; j++) {
if (min >= abc[j]) {
min = abc[j];
}
}
}
- go lang
func FindMinValue(arr []int) int {
min := arr[0]
for i := range arr {
for j := 1 + i; j < len(arr); j++ {
if min > arr[j] {
min = arr[j]
}
}
}
return min
}
実装ー配列を反転させる
- 配列を反転させることではSwappingの技術が必要
- SwappingはJavaではTemp変数が必要
- 要素にアクセスするためには、要素 - 1が必要。
- そこで i を引く理由は後半から逆に行きながらSwappingするからだ
- 時間計算量は n/2なので O(n)になる
- n / 2になる理由は前半だけを見て後半と比較すると後半の要素はループが不要
int[] abc = {0,1,7,2,4,13,6};
for(int i = 0; i < abc.length / 2; i++) {
int temp = abc[i];
abc[i] = abc[abc.length - 1 -i];
abc[abc.length - 1 -i] = temp;
}
func ReverseArray(arr []int) {
for i := 0; i < len(arr)/2; i++ {
arr[i], arr[len(arr)-1-i] = arr[len(arr)-1-i], arr[i]
}
}
- ただ、不変性が重要なので、不変性を担保するために、新しいオブジェクトを返すことにする
- 今回はプリミティブ型なので、Cloneでも問題はないが、参照型としたら、シャローコピーになってしまう。
- ディープコピーにするためにはFor文でループしながら1つづつコピーするしかない
public int[] returnReversedArray(int[] arr) {
int newArr = arr.clone();
for(int i = 0; i < newArr.length / 2; i++) {
int temp = newArr[i];
newArr[i] = newArr[newArr.length - 1 -i];
newArr[newArr.length - 1 -i] = temp;
}
}
Integer[] newArr = new Integer[arr.length];
for (int i = 0; i < arr.length; i++) {
newArr[i] = arr[i].clone(); //オブジェクトの時
}
- プリミティブ型はただのシャロ―コピーもディープコピーと同じ効果が現れる
- makeはJavaに例えれば、newと同じ
- Copyは以下のFor文と同じ効果
func ReturnNewReverseArray(arr []int) []int {
newArray := arr //ーーー>シャロ―コピー
newArray := make([]int, len(arr)) //ーーー>ディープコピー
copy(newArray, arr)
/*for i := range arr {
newArray[i] = arr[i]
}*/
for i := 0; i < len(newArray)/2; i++ {
newArray[i], newArray[len(arr)-1-i] = newArray[len(newArray)-1-i], newArray[i]
}
return newArray
}
実装ー選択ソート昇順
- 選択ソート昇順
- If文で比較の対象が abc[i] じゃなくて、abc[minValueIdx]になる理由は、実際に最小値を見つけたとき、minValueIdxが変わるからだ
public static void main(String[] args) throws Exception {
int[] abc = { 3, 1, 7, 2, 13, 15, 6 };
int minValueIdx = abc[0];
for (int i = 0; i < abc.length; i++) {
for (int j = 1 + i; j < abc.length; j++) {
if (abc[minValueIdx] > abc[j]) {
minValueIdx = j;
}
}
int temp = abc[i];
abc[i] = abc[minValueIdx];
abc[minValueIdx] = temp;
}
}
実装ー選択ソート降順
- 選択ソート降順
- if文の方向だけ変えればいい
public static void main(String[] args) throws Exception {
int[] abc = { 3, 1, 7, 2, 13, 15, 6 };
int maxValueIdx = abc[0];
for (int i = 0; i < abc.length; i++) {
for (int j = 1 + i; j < abc.length; j++) {
if (abc[maxValueIdx] <= abc[j]) {
maxValueIdx = j;
}
}
int temp = abc[i];
abc[i] = abc[maxValueIdx];
abc[maxValueIdx] = temp;
}
}
- 不変性を担保するため、新しい配列を返す
public static void main(String[] args) throws Exception {
int[] abc = { 3, 1, 7, 2, 13, 15, 6 };
int[] sortedArray = abc.clone();
int maxValueIdx = sortedArray[0];
for (int i = 0; i < sortedArray.length; i++) {
for (int j = 1 + i; j < sortedArray.length; j++) {
if (sortedArray[maxValueIdx] <= sortedArray[j]) {
maxValueIdx = j;
}
}
int temp = sortedArray[i];
sortedArray[i] = sortedArray[maxValueIdx];
sortedArray[maxValueIdx] = temp;
}
System.out.println(Arrays.toString(sortedArray));
}
- go lang
func SelectionSort(arr []int) []int {
maxIndex := 0
newArr := make([]int, len(arr))
copy(newArr, arr)
for i := range newArr {
for j := i + 1; j < len(newArr); j++ {
if newArr[maxIndex] < newArr[j] {
maxIndex = j
}
}
newArr[i], newArr[maxIndex] = newArr[maxIndex], newArr[i]
}
return newArr
}
深く探求ー文字列を反転させる
- 1週目は数字を反転させた
- 今回は文字列だが、回帰と違いを理解するために、For文で書く
- 文字列 ー> 配列列の場合
public static StringBuilder reverseStr(String str) {
StringBuilder reverse = new StringBuilder();
for (int i = str.length() - 1; i >= 0; i--) {
reverse.append(str.charAt(i));
}
return reverse;
}
- 数字を反転させる方法と同じく、半分だけ見ればいいので、StringBuilderを利用したほうよりはパフォーマンスが高い
- method callがない
- リサイジングがない
- インデックスが有効なのかチェックが走らない
- 時間計算量は以下になる
- toCharArray()にコピー -> O(n)
- swap ループ -> O(n/2)
- new String(arr) -> 再びコピー O(n)
public static String reverse(String s) {
char[] arr = s.toCharArray();
for (int i = 0; i < arr.length / 2; i++) {
char temp = arr[i];
arr[i] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = temp;
}
return new String(arr);
}
- JAVA apiは以下のようだ
- 実は以下が一番パフォーマンスが高いと認められる
- 時間計算量は以下になる
- StringBuilder()にコピー -> O(n)
- swap ループ -> O(n/2)
- new String(arr) -> 再びコピー O(n)
- 配列を手動でいじる時と時間計算量は変わらない
- しかし、JVMのレベルでの最適化を踏まえると、JAVAのAPIを使ったほうがいい
public static void main(String[] args) {
char[] newArray = { 'a', 'b', 'c', 'd', 'e', 'f' };
String reversed = new StringBuilder()
.append(newArray)
.reverse()
.toString();
}
- 以下がJAVAのAPIであるStringBuilderのreverseメソッドの実装部だ
- 文字列に関して、文字列は必ず負数であるため、(n-1) » 1 は (n-1) / 2 と同じだ
- ただ、CPU単位で踏まえると、効率が違う
- » 1 はCPUの観点ですごく簡潔な1つの命令
- ただの移動だけで済む
- / 2 はすごく難しい1つの命令
- 商を計算する
- 余りを計算する
- 負の数を正しく処理する
- オーバーフローを回避する
- » 1 はCPUの観点ですごく簡潔な1つの命令
- 現代JVMはそういった部分で最適化をしてくれる可能性が高いが、昔はそうでなかったので、まだああいう書き方が残っている
public AbstractStringBuilder reverse() {
byte[] val = this.value;
int count = this.count;
int n = count - 1;
if (isLatin1()) {
for (int j = (n-1) >> 1; j >= 0; j--) {
int k = n - j;
byte cj = val[j];
val[j] = val[k];
val[k] = cj;
}
} else {
StringUTF16.reverse(val, count);
}
return this;
}
- go lang
- JavaはUTFー16に文字列を保存しているので、charAt()を使ったら、自動的にByte単位で計算してそのインデックスの文字列を引っ張てくる
- go langにはそんなのがないので、Byte単位で全部変換する
- 0 ~ 255からが1Byteの範囲であるので、その中で文字ことに決まった数字があるので、1Byteの中で97が入ってきたら、それは ‘a’に変換される
- 同じく 2Byteは 16Bitなので、2の16乗ー1まで数字を格納する
func ReverseString1(s string) string {
strbuilder := strings.Builder{}
for i := len(s) - 1; i >= 0; i-- {
strbuilder.WriteByte(s[i])
}
return strbuilder.String()
}
振り返り
- ダブルFor文でjの値をどう設定していけばいいのかわかなかった
//最初
for (int i = 0; i < abc.length; i++) {
for (int j = 1/*ここどうすればいい? */; j < abc.length; j++) {
}
}
//AI参考
for (int i = 0; i < abc.length; i++) {
for (int j = 1 + i; j < abc.length; j++) {
}
}
- 位置を入れ替えるためには、Indexが必要だったのにそれに気づかなかった
//最初
int maxValue = sortedArray[0];
for (int i = 0; i < sortedArray.length; i++) {
for (int j = 1 + i; j < sortedArray.length; j++) {
if (sortedArray[maxValueIdx] <= sortedArray[j]) {
maxValue = sortedArray[j];
}
}
}
//AI参考
int maxValueIdx = sortedArray[0];
for (int i = 0; i < sortedArray.length; i++) {
for (int j = 1 + i; j < sortedArray.length; j++) {
if (sortedArray[maxValueIdx] <= sortedArray[j]) {
maxValueIdx = j;
}
}