【アルゴリズム 挿入ソート】ざっくりわかるシェルスクリプト12

挿入ソート

挿入ソートとは、未整列の要素を一つずつつまみ上げて、整列済みの列の適切な位置に挿入していくアルゴリズムです。
挿入ソートは、選択ソートと異なり、整列したいデータ列以外の一時記憶領域を用意しなくて良いという特徴があります。
対象データ列が短い場合などに効果的に利用されます。
人間に並べ替えを行わせるとまっさきに思いつく方法として親しまれていたりもします。

がんばれ挿入ソート!

プログラムソース

この章で使っているプログラムソースは以下にあります。
02_3InsertionSort.sh 一般的な配列の挿入ソート
02_3Eval_InsertionSort.sh 擬似的な2次元配列で実装した挿入ソート

挿入ソートの処理手順

1.配列の先頭から小さい順に並べる。
2.先頭から2つの値を比較して小さい方を1番目に、大きい方を2番目に置く。
3.3番目の値を取り出し、1番め、2番目と順に比較し、適切な位置(左から小さい順に並ぶよう)に挿入する。
4.4番目以降も同様に、n番目の値を取り出して先頭からn-1番目まで順番に比較し、適切な位置に挿入します。
5.上記の操作を末尾の値まで繰り返すことで、先頭が最も小さく末尾が最も大きい数値の列が得られる。

n番目の値を挿入する際、

  • n番目の値が整列済みの列の中で最も小さければ、先頭の値との1回の比較で挿入位置が決定できます。

ところが、

  • n番目の値が整列済みの列の中で最も大きければ、整列済みの値の数(n-1回)だけ比較を繰り返さなければなりません(ここが挿入ソートの最大の弱点)

がんばれ挿入ソート!

挿入ソートのアルゴリズム

配列の要素が整列済みに近い状態ならば高速に整列を完了できる(最良計算時間はO(N))
逆順に並んでいる場合はとてつもない回数の比較が必要(最悪計算時間はO(N^2))となってしまう。
この欠点をある程度緩和したアルゴリズムとしてシェルソート(Shell sort)がある。

選択ソートのように一時的な要素の値を保管する領域を確保する必要はないことが挿入ソートのメリット。
選択ソートはヒープソートを学ぶための一里塚と言われているが、同様に挿入ソートは、シェルソートを学ぶための一里塚との位置づけとも言われている。

Bash/シェルスクリプトで実装した一般的な配列で実装した挿入ソート。

##
# <>insertionSort()
# 挿入ソート
function insertionSort(){
  for((out=1;out<nElems;out++)){
    local tmp=$((array[out]));
    for((in=out;in>0 && array[in-1]>tmp;in--)){
      array[in]=$((array[in-1]));
    }
    array[in]=$tmp;
  }
}

bash/シェルスクリプトによる疑似2次元配列の実装

#######################################
# 02_3InsertionSort.shを少しだけオブジェクティブに
# aRray[0].getValue() で値を取得できるように改変した
# 配列にIDと値を入れるだけのbashスクリプト
#######################################
##
# グローバル変数
declare -i nElems=0;
#
# <>display()  
# 配列を表示
function display(){
  for((i=0;i<nElems;i++)){
    echo -n "aRray[$i]  \
    ID: " $( aRray[$i].getID ) " \
    Value:" $( aRray[$i].getValue ) ; 
    echo "";
  }
}
##
# <>setValue() 
# セッター
function setValue() {
  #今後挿入や置き換えに備えてnElemsとは別の変数を用意しておく
  local Elem="$1";      
  local value="$2";
	eval "aRray[$Elem].getValue()      { echo "$value"; }"
}
##
# <>setID()
# セッター
function setID(){
  #今後挿入や置き換えに備えてnElemsとは別の変数を用意しておく
  local Elem="$1";      
  local ID="$2";
	eval "aRray[$Elem].getID()         { echo "$ID"; }"
}
##
# <> insert
# 配列の要素に値を代入
function insert(){
  local ID=$1;          #100からの連番
  local value=$2;       #配列に代入される要素の値
  setID     "$nElems"    "$ID";      #IDをセット
  setValue  "$nElems"    "$value";   #Valueをセット
  ((nElems++));
}
##
# <> set Array
# 配列を作成
function setArray(){
  local N=$1;           #すべての要素数
  local ID=100;         #100からの連番
  local value;          #配列に代入される要素の値
  for((i=0;i<N;i++)){
    value=$( echo $RANDOM );
    insert $((ID++)) $value;
  }
}
##
# <>bubbleSort() 
# バブルソート
# URL:https://www.youtube.com/watch?v=xli_FI7CuzA
function bubbleSort(){
  local tmp_id;
  local tmp_value;
  for((i=nElems;i>0;i--)){
    for((j=0;j<i-1;j++)){
      if(($(aRray[$j].getValue)>$(aRray[$((j+1))].getValue)));then
        # 交換
        tmp_id=$(aRray[$j].getID);
        tmp_value=$(aRray[$j].getValue);
        setID     "$j"    $(aRray[$((j+1))].getID);      #IDをセット
        setValue  "$j"    $(aRray[$((j+1))].getValue);   #Valueをセット
        setID     $((j+1))    $tmp_id;      #IDをセット
        setValue  $((j+1))    $tmp_value;   #Valueをセット
        # 交換
      fi 
    } 
  }  
}
## <>selectionSort()
# 選択ソート
# URL:https://www.youtube.com/watch?v=g-PGLbMth_g
function selectionSort(){
  local tmp_id;
  local tmp_value;
  for((i=0;i<nElems;i++)){
    min=$i;
    for((j=i+1;j<nElems;j++)){
      if(($(aRray[$min].getValue)>$(aRray[$j].getValue)));then
        min=$j;
      fi
    }
    # 交換
    tmp_id=$(aRray[$min].getID);
    tmp_value=$(aRray[$min].getValue);
    setID     "$min"    $(aRray[$i].getID);      #IDをセット
    setValue  "$min"    $(aRray[$i].getValue);   #Valueをセット
    setID     $i    $tmp_id;      #IDをセット
    setValue  $i    $tmp_value;   #Valueをセット
    # 交換
  }
}
##
## <>insertionSort()
# 挿入ソート
# URL:https://www.youtube.com/watch?v=DFG-XuyPYUQ
function insertionSort(){
  local tmp_id;
  local tmp_value;
  local in;
  for((out=1;out<nElems;out++)){
    tmp_id=$(aRray[$out].getID);
    tmp_value=$(aRray[$out].getValue);
    in=$out;
    while (( in>0 )) && (( $(aRray[$((in-1))].getValue)>tmp_value ));do
      setID     "$in"    $(aRray[$((in-1))].getID);      #IDをセット
      setValue  "$in"    $(aRray[$((in-1))].getValue);   #Valueをセット
      in=$((in-1));
    done 
    setID     "$in"    $tmp_id;      #IDをセット
    setValue  "$in"    $tmp_value;   #Valueをセット
  } 
}
# <>execSort()
# メインルーチン
function execSort(){
  local N=$1;
  setArray $N;    #配列をセット
  echo "修正前"
  display;
  #bubbleSort;     #バブルソート
  #selectionSort;  #選択ソート
  insertionSort;
  echo "修正後"
  display;
}
##
# 実行
time execSort 10;
exit;

実行結果

bash-5.1$ bash 02_3Eval_InsertionSort.sh
修正前
aRray[0]      ID:  100      Value: 16343
aRray[1]      ID:  101      Value: 11323
aRray[2]      ID:  102      Value: 1381
aRray[3]      ID:  103      Value: 15343
aRray[4]      ID:  104      Value: 28067
aRray[5]      ID:  105      Value: 27342
aRray[6]      ID:  106      Value: 32195
aRray[7]      ID:  107      Value: 15563
aRray[8]      ID:  108      Value: 24240
aRray[9]      ID:  109      Value: 28649
修正後
aRray[0]      ID:  102      Value: 1381
aRray[1]      ID:  101      Value: 11323
aRray[2]      ID:  103      Value: 15343
aRray[3]      ID:  107      Value: 15563
aRray[4]      ID:  100      Value: 16343
aRray[5]      ID:  108      Value: 24240
aRray[6]      ID:  105      Value: 27342
aRray[7]      ID:  104      Value: 28067
aRray[8]      ID:  109      Value: 28649
aRray[9]      ID:  106      Value: 32195

real	0m0.145s
user	0m0.062s
sys	0m0.082s
bash-5.1$

Valueの値がソートされているのが見てわかると思います。

処理コスト

挿入ソートの処理コストはバブルソートや選択ソートに負けず劣らず非常に高いわけですが、まずはバブルソート、選択ソートに続き、遅さNo.2を争う挿入ソートの実装ができたわけです。次回からは、さらにもう少しだけ効率的なソート手法の紹介をします。

処理コストを可視化(いずれ何がどうなのかはわかります)

「ざっくり」シリーズのご紹介

【アルゴリズム 再帰】ざっくりわかるシェルスクリプト15
https://suzukiiichiro.github.io/posts/2022-10-07-01-algorithm-recursion-suzuki/
【アルゴリズム キュー】ざっくりわかるシェルスクリプト14
https://suzukiiichiro.github.io/posts/2022-10-06-01-algorithm-queue-suzuki/
【アルゴリズム スタック】ざっくりわかるシェルスクリプト13
https://suzukiiichiro.github.io/posts/2022-10-06-01-algorithm-stack-suzuki/
【アルゴリズム 挿入ソート】ざっくりわかるシェルスクリプト12
https://suzukiiichiro.github.io/posts/2022-10-05-01-algorithm-insertionsort-suzuki/
【アルゴリズム 選択ソート】ざっくりわかるシェルスクリプト11
https://suzukiiichiro.github.io/posts/2022-10-05-01-algorithm-selectionsort-suzuki/
【アルゴリズム バブルソート】ざっくりわかるシェルスクリプト10
https://suzukiiichiro.github.io/posts/2022-10-05-01-algorithm-bubblesort-suzuki/
【アルゴリズム ビッグオー】ざっくりわかるシェルスクリプト9
https://suzukiiichiro.github.io/posts/2022-10-04-01-algorithm-bigo-suzuki/
【アルゴリズム 2次元配列編】ざっくりわかるシェルスクリプト8
https://suzukiiichiro.github.io/posts/2022-10-03-01-algorithm-eval-array-suzuki/
【アルゴリズム 配列準備編】ざっくりわかるシェルスクリプト7
https://suzukiiichiro.github.io/posts/2022-10-03-01-algorithm-array-suzuki/
【アルゴリズム 配列編】ざっくりわかるシェルスクリプト6
https://suzukiiichiro.github.io/posts/2022-09-27-01-array-suzuki/
【grep/sed/awkも】ざっくりわかるシェルスクリプト5
https://suzukiiichiro.github.io/posts/2022-02-02-01-suzuki/
【grep特集】ざっくりわかるシェルスクリプト4
https://suzukiiichiro.github.io/posts/2022-01-24-01-suzuki/
【はじめから】ざっくりわかるシェルスクリプト3
https://suzukiiichiro.github.io/posts/2022-01-13-01-suzuki/
【はじめから】ざっくりわかるシェルスクリプト2
https://suzukiiichiro.github.io/posts/2022-01-12-01-suzuki/
【はじめから】ざっくりわかるシェルスクリプト1
https://suzukiiichiro.github.io/posts/2022-01-07-01-suzuki/

【TIPS】ざっくりわかるシェルスクリプト
https://suzukiiichiro.github.io/posts/2022-09-26-01-tips-suzuki/

書籍の紹介

【アルゴリズム スタック】ざっくりわかるシェルスクリプト13

【アルゴリズム スタック】ざっくりわかるシェルスクリプト13

【アルゴリズム 選択ソート】ざっくりわかるシェルスクリプト11

【アルゴリズム 選択ソート】ざっくりわかるシェルスクリプト11