void insert ( struct node *p, elementtype x ) { struct node *n; n = ( struct node * ) malloc ( sizeof ( struct node ) ); n -> element = x; n -> next = ___①___p->next; /* 元のリストの続きを n につなぐ */ ___②___p->next = n; /* p の次を n に張り替える */}
解答
①: p -> next ②: p -> next
void insert ( struct node *p, elementtype x ) { struct node *n; n = ( struct node * ) malloc ( sizeof ( struct node ) ); n -> element = x; n -> next = p -> next; /* ① */ p -> next = n; /* ② */}
解説
①と②の順番が命。
①で「n の次」に「p の元々の次」を退避しておいてから、②で「p の次」を n に張り替える。
逆に②を先にやると p 以降のリストが迷子になり、①で参照できなくなる。まず後ろを確保してから、前を書き換える。これがポインタ操作の鉄則。
問3: リストからの削除 delete
p が指すノードの次のノードを削除する。
void delete ( struct node *p ) { if ( ___①___p->next != NULL ) { p -> next = ___②___p->next->next; }}
解答
①: p -> next ②: p -> next -> next
void delete ( struct node *p ) { if ( p -> next != NULL ) { p -> next = p -> next -> next; }}
link reverse2 ( link p ) { link head, oldp, keep; head = p; oldp = ___①___; /* 1つ前のノード(最初は無い) */ p = p -> next; /* 注目ノードへ */ while ( p != NULL ) { keep = oldp; oldp = p; p = p -> next; oldp -> next = ___②___; /* 注目ノードの次を1つ前へ向ける */ } head -> next = ___③___; /* head の次に最後尾を繋ぐ */ return head;}
解答
①: NULL ②: keep ③: oldp
link reverse2 ( link p ) { link head, oldp, keep; head = p; oldp = NULL; p = p -> next; while ( p != NULL ) { keep = oldp; oldp = p; p = p -> next; oldp -> next = keep; } head -> next = oldp; return head;}
void quicksort( recordtype a[], int l, int r ){ int i; if ( l < r ){ i = ___①___partition ( a, l, r ); /* pivot の位置を決める */ quicksort( a, l, i-1 ); /* 左部分列を再帰 */ quicksort( a, i+1, r ); /* 右部分列を再帰 */ }}int partition( recordtype a[], int l, int r ){ int i, j; recordtype v; v = a[l]; i = l; j = r+1___②___; /* j は右端の1つ外から */ do { do { i++; } while( a[i].key < v.key ); /* pivotより大きい値を探す */ do { j--; } while( a[j].key > v.key ); /* pivotより小さい値を探す */ if ( i < j ){ ___③___swap ( &a[i], &a[j] ); /* 交差していなければ入れ替え */ } } while ( ___④___j<i ); /* 交差するまで繰り返す */ a[l] = a[j]; a[j] = v; /* pivot を正しい位置へ */ return ___⑤___j;}
解答
①: partition ②: r + 1 ③: swap ④: j > i ⑤: j
void quicksort( recordtype a[], int l, int r ){ int i; if ( l < r ){ i = partition( a, l, r ); quicksort( a, l, i-1 ); quicksort( a, i+1, r ); }}int partition( recordtype a[], int l, int r ){ int i, j; recordtype v; v = a[l]; i = l; j = r + 1; do { do { i++; } while( a[i].key < v.key ); do { j--; } while( a[j].key > v.key ); if ( i < j ){ swap( &a[i], &a[j] ); } } while ( j > i ); a[l] = a[j]; a[j] = v; return j;}
void quicksort2( recordtype a[], int n ){ int i, last; if( n <= 1 ) return; /* 終端条件 */ ___①___swap ( &a[0], &a[ rand() % n ] ); /* pivot を乱数で選び先頭へ */ last = 0; /* pivotより小さい値の右端 */ for( i = 1; i < n; i++ ){ if ( a[i].key < a[0].key ){ ___②___swap ( &a[++last], &a[i] ); /* last の次に詰めて入れ替え */ } } ___①___swap ( &a[0], &a[last] ); /* pivot を正しい位置へ */ quicksort2( a, last ); /* 左部分列 */ quicksort2( ___③___a+last+1 , n - last - 1 ); /* 右部分列 */}
解答
①: swap ②: swap ③: a + last + 1
void quicksort2( recordtype a[], int n ){ int i, last; if( n <= 1 ) return; swap( &a[0], &a[ rand() % n ] ); last = 0; for( i = 1; i < n; i++ ){ if ( a[i].key < a[0].key ){ swap( &a[++last], &a[i] ); } } swap( &a[0], &a[last] ); quicksort2( a, last ); quicksort2( a + last + 1, n - last - 1 );}
void pushdown( recordtype a[], int first, int last ){ int r = first, k = 2 * r; while( k <= last ){ if( k < last && a[k].key < a[k+1].key ){ k++; } if( a[r].key >= a[k].key ){ break; } swap( &a[r], &a[k] ); r = k; k = 2 * r; }}void heapsort( recordtype a[], int n ){ int i; for( i = n/2; i >= 1; i-- ){ pushdown( a, i, n ); } for( i = n; i >= 2; i-- ){ swap( &a[1], &a[i] ); pushdown( a, 1, i-1 ); }}
/* x を k ビット右へシフトし、その左 j ビットを取り出す */int bits( int x, int k, int j ){ return ( x >> k ) & ___①___;}void radixsort( recordtype a[], int l, int r, int b ){ int i, j; if ( l < r && b >= 0 ){ i = l; j = r; do { while ( ___②___ == 0 && i < j ){ i++; } /* 左から0を探す */ while ( ___③___ == 1 && i < j ){ j--; } /* 右から1を探す */ if ( i != j ){ swap( &a[i], &a[j] ); } } while ( j != i ); if ( bits(a[r].key, b, 1) == 0 ){ j++; } radixsort( a, l, j-1, b-1 ); /* 0の部分を再帰 */ radixsort( a, j, r, b-1 ); /* 1の部分を再帰 */ }}
int bits( int x, int k, int j ){ return ( x >> k ) & ~( ~0 << j );}void radixsort( recordtype a[], int l, int r, int b ){ int i, j; if ( l < r && b >= 0 ){ i = l; j = r; do { while ( bits( a[i].key, b, 1 ) == 0 && i < j ){ i++; } while ( bits( a[j].key, b, 1 ) == 1 && i < j ){ j--; } if ( i != j ){ swap( &a[i], &a[j] ); } } while ( j != i ); if ( bits(a[r].key, b, 1) == 0 ){ j++; } radixsort( a, l, j-1, b-1 ); radixsort( a, j, r, b-1 ); }}
void mergesort ( recordtype a[], int l, int r ){ int i, j, k, m; recordtype b[LIMIT]; if ( l < r ){ m = ___①___; /* 中央を求める */ mergesort ( a, l, m ); /* 左半分を再帰 */ mergesort ( a, ___②___m+1, r ); /* 右半分を再帰 */ for ( i = m; i >= l; i-- ){ b[i] = a[i]; } /* 左半分をそのままコピー */ i = l; for ( j = m+1; j <= r; j++ ){ b[___③___r-j] = a[j]; } /* 右半分を逆順にコピー */ j = r; for ( k = l; k <= r; k++ ){ /* 両端からマージ */ if ( ___④___ ){ a[k] = b[i++]; } /* 左が小さければ左から */ else { a[k] = b[j--]; } /* 右が小さければ右から */ } }}
解答
①: ( r + l ) / 2 ②: m + 1 ③: r - ( j - (m+1) ) ④: b[i].key < b[j].key
void mergesort ( recordtype a[], int l, int r ){ int i, j, k, m; recordtype b[LIMIT]; if ( l < r ){ m = ( r + l ) / 2; mergesort ( a, l, m ); mergesort ( a, m+1, r ); for ( i = m; i >= l; i-- ){ b[i] = a[i]; } i = l; for ( j = m+1; j <= r; j++ ){ b[ r - ( j - (m+1) ) ] = a[j]; } j = r; for ( k = l; k <= r; k++ ){ if ( b[i].key < b[j].key ){ a[k] = b[i++]; } else { a[k] = b[j--]; } } }}
解説
分割は「真ん中で切って、それぞれを再帰」だけ。本体はマージ。
左半分は b の左半分にそのまま、右半分は b の右半分に逆順でコピーする。すると b の左端 i と右端 j から小さい方を取り出していくだけで、どちらが先に尽きても正しく併合できる。
int search( elementtype x, elementtype a[], int N ){ int i; a[N] = x; /* 番兵:末尾に x をセット */ for ( i = 0; ___①___; i++ ) ; /* 必ず見つかる(番兵で停止) */ return ___②___; /* 番兵で止まったら見つからなかった */}
解答
①: a[i] != x ②: i < N
int search( elementtype x, elementtype a[], int N ){ int i; a[N] = x; for ( i = 0; a[i] != x; i++ ) ; return i < N;}
解説
番兵なしの素朴な実装は while ( i < N && a[i] != x ) で、ループごとに3つの演算(i<N, a[i]!=x, i++)がある。
末尾に x を置くと「必ず見つかる」ので、i < N のチェックを外して条件 1 つにできる。見つかった場所が N なら番兵に当たった=見つからなかった、N 未満なら本当に見つかった。
比較回数は最悪 N 回、成功時平均 N/2 回。
問18: 二分探索 binary search
整列済みの配列が前提。中央と比較して、探索範囲を半分ずつに絞る。
int search( elementtype x, elementtype a[], int N ){ int l, r, m; l = 0; r = N-1; while ( l < r ){ m = ___①___; /* 中央を求める */ if ( x < a[m] ){ r = ___②___; /* 前半側を探索 */ } else if ( x > a[m] ){ l = ___③___; /* 後半側を探索 */ } else { l = r = m; /* 見つかった → 範囲を収束 */ } } return ( l == r && a[l] == x );}
解答
①: ( l + r ) / 2 ②: m - 1 ③: m + 1
int search( elementtype x, elementtype a[], int N ){ int l, r, m; l = 0; r = N-1; while ( l < r ){ m = ( l + r ) / 2; if ( x < a[m] ){ r = m - 1; } else if ( x > a[m] ){ l = m + 1; } else { l = r = m; } } return ( l == r && a[l] == x );}
解説
中央 m と x を比較し、x が小さければ左半分(r = m-1)、大きければ右半分(l = m+1)に絞る。
一致したら l = r = m で範囲を 1 点に収束させ、ループを抜ける。最後に「候補が 1 つに絞られて、かつ一致」か確認。
int search( elementtype x, elementtype a[], int N ){ int l, r, m; l = 0; r = N-1; while ( l < r ){ m = ___①___; /* 内挿:値の比から位置を推定 */ if ( x < a[m] ){ r = m - 1; } else if ( x > a[m] ){ l = m + 1; } else { l = r = m; } } return ( l == r && a[l] == x );}
解答
①: l + ( x - a[l] ) * ( r - l ) / ( a[r] - a[l] )
int search( elementtype x, elementtype a[], int N ){ int l, r, m; l = 0; r = N-1; while ( l < r ){ m = l + ( x - a[l] ) * ( r - l ) / ( a[r] - a[l] ); if ( x < a[m] ){ r = m - 1; } else if ( x > a[m] ){ l = m + 1; } else { l = r = m; } } return ( l == r && a[l] == x );}
解説
二分探索と違うのは m の計算式だけ。あとは完全に同じコード。
「x が a[l] と a[r] の間のどこら辺にあるか」を比で推定する:( M - l ) : ( r - l ) = x - a[l] : ( a[r] - a[l] )。辞書で「か」を引くとき、頭からではなく「か行あたり」を開く感覚。
理論上の計算量は O(log log N) と二分探索より速いが、m の計算コストが高いので N が大きくないと実用上は二分探索に負ける。