質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

Q&A

解決済

2回答

946閲覧

クイックソートの処理の流れと実装内容が理解できません。

KIYZ

総合スコア17

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

1グッド

2クリップ

投稿2018/12/26 09:35

編集2018/12/26 12:12

前提・実現したいこと

1.関数に渡された配列の要素をクイックソートを用いて昇順に並び替えるプログラムの処理の流れと実装内容を理解しようとしています。
再帰処理の部分の実装内容を理解して全体の流れをイメージできるようになりたい。

2.プログラムの改変

理解したいソースコード

http://rosettacode.org/wiki/Sorting_algorithms/Quicksort#C

C

1#include <stdio.h> 2 3void quicksort(int *A, int len); 4 5int main (void) { 6 int a[] = {4, 65, 2, -31, 0, 99, 2, 83, 782, 1}; 7 int n = sizeof a / sizeof a[0]; 8 9 int i; 10 for (i = 0; i < n; i++) { 11 printf("%d ", a[i]); 12 } 13 printf("\n"); 14 15 quicksort(a, n); 16 17 for (i = 0; i < n; i++) { 18 printf("%d ", a[i]); 19 } 20 printf("\n"); 21 22 return 0; 23} 24 25void quicksort(int *A, int len) { 26 if (len < 2) return; 27 28 int pivot = A[len / 2]; 29 30 int i, j; 31 for (i = 0, j = len - 1; ; i++, j--) { 32 while (A[i] < pivot) i++; 33 while (A[j] > pivot) j--; 34 35 if (i >= j) break; 36 37 int temp = A[i]; 38 A[i] = A[j]; 39 A[j] = temp; 40 } 41 42 quicksort(A, i); // *1 43 quicksort(A + i, len - i); // *2 44}

自分が理解しやすいように書き直したソースコード

C

1#include <stdio.h> 2 3void swap_elms(int *array, int a, int b) 4{ 5 int buf; 6 7 buf = array[a]; 8 array[a] = array[b]; 9 array[b] = buf; 10} 11 12void quicksort(int *array, int len) 13{ 14 if (len < 2) return; 15 16 int pivot; 17 int large; 18 int small; 19 20 pivot = array[len / 2]; 21 large = 0; 22 small = len - 1; 23 while (1) 24 { 25 while (array[large] < pivot) 26 large++; 27 while (array[small] > pivot) 28 small--; 29 30 if (large >= small) 31 break; 32 33 swap_elms(array, large, small); 34 35 large++; 36 small--; 37 } 38 39 quicksort(array, large); // *1 40 quicksort(array + large, len - large); // *2 41} 42 43int main(void) 44{ 45 int i; 46 int size = 10; 47 int array[] = {4, 65, 2, -31, 0, 99, 2, 83, 782, 1}; 48 49 for (i = 0; i < size; i++) 50 printf("%d, ", array[i]); 51 printf("\n"); 52 53 quicksort(array, size); 54 55 for (i = 0; i < size; i++) 56 printf("%d, ", array[i]); 57 58 return (0); 59} 60

疑問

1.0「*1は配列内のピボット以下の値が集まった範囲をソートするために quicksort() を呼び出している。」という解釈で正しいのか。

1.1 「*1の quicksort() に渡している第一引数は、配列内のピボット以下の範囲の先頭の要素を指し、第二引数は配列内のピボット以下の範囲の最後の要素の次の要素を指している。」という解釈で正しいのか。

2.0「*2はピボット以上の値が集まった範囲をソートするために quicksort() を呼び出している。」という解釈で正しいのか。

2.1 *2の quicksort() に渡している二つの引数はそれぞれ何を指しているのか。なぜ A + ilen - i となっているのか。(ここが一番理解できない部分ですので、初学者でも分かるように解説して頂けると幸いです。

3.0 このプログラムのピボット選択の部分を改変したいと思っています。配列の先頭、真ん中、最後の要素から中央値となる要素を選択してそれをピボットとして扱うにはどのような実装にすれば良いでしょうか。中央値を選択するための関数を作成して試行錯誤しましたが上手く実装できません。

ピボット選択時に3つの要素から中央値を選択するための関数

C

1int median3(int *array, int a, int b, int c) 2{ 3 if (array[a] < array[b]) 4 { 5 if (array[b] < array[c]) 6 return (b); 7 else if (array[c] < array[a]) 8 return (a); 9 return (c); 10 } 11 else 12 { 13 if (array[c] < array[b]) 14 return (b); 15 else if (array[a] < array[c]) 16 return (a); 17 return (c); 18 } 19}

よろしくお願い致します。

DrqYuto👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/12/26 11:21

「再帰1」というのは1回目の再帰という意味でしょうか?
KIYZ

2018/12/26 11:37

いいえ、 「// 再帰1」とコメントした行のコード(quicksort(array, large);) のことです。 「再帰2」も同じで、「n回目の再帰」という意味ではありません。 説明中に一言でこの行のコードを指せるようにするためにコメントしたのですが、却って分かりづらくなってしまったようですね。申し訳ありません。
KIYZ

2018/12/26 11:44

質問文とコード内のコメントを変更しました。 再帰1 => *1 再帰2 => *2
退会済みユーザー

退会済みユーザー

2018/12/26 11:49

あなたが書き直したソースコードはクイックソートではないです。
退会済みユーザー

退会済みユーザー

2018/12/26 12:03

すみません、というか元々がクイックソートじゃないです
episteme

2018/12/26 12:08

え? これがクイックソートじゃなかったら何なの?
退会済みユーザー

退会済みユーザー

2018/12/26 12:16

ごめんなさい、完全に自分が勘違いしていました。 下で回答します。
退会済みユーザー

退会済みユーザー

2018/12/26 14:07

大量に勘違い、読み間違いをしていました。 不快な思いをさせて大変申し訳ございません。 病院に行って頭を見てもらおうと思います。
guest

回答2

0

例えばここのように、ソートの動作を可視化して説明しているサイトなんかもありますので、理解の一助になれば。

投稿2018/12/27 00:07

tacsheaven

総合スコア13703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KIYZ

2019/01/04 03:17

ありがとうございます。
guest

0

ベストアンサー

勘違いして申し訳ございません。

疑問1.0)はい、そうです。
疑問1.1)はい、そうです。
疑問2.0)はい、そうです。
疑問2.1)第一引数がその配列の開始位置です。第二引数にはその配列の長さ**(開始位置に長さを足せばその配列の末尾がわかる)**を渡しています。
*1を抽象的に言うと「pivot以下の値が集まった配列をクイックソートしてください、その配列の開始位置はAでその配列の要素の数はiです」という意味です。つまり「A[0]からA[i]までの配列にpivot以下の値が格納されている、それをソートしてください」という意味です。
今、インデックス0からインデックスiまでの配列要素にpivot以下の値が集まっています。
インデックスlen-iからインデックスlenまでの配列要素にpivot以上の値が集まっています。
インデックスiからインデックスlenまでの配列要素にpivot以上の値が集まっています。
その時(pivot以上の値が格納されている配列の)開始位置はA+iで、その配列の長さはlen-iなので
quicksort(A + i, len - i); // *2 という関数の呼び方をしています。
つまり「A[i]からAlenまでの配列にpivot以上の値が格納されている、それをソートしてください」という意味です。
3.0)中央値を選ぶことはできません。中央値を選ぼうとする行為に時間がかかってしまうので。なので少ない計算量で中央値に近い値を選ぶことがポイントです。たとえば二つの値をランダムにとってその平均をpivotにするとか。
ごめんなさい、質問内容が読めてませんでした。その三つの値から中央値を選ぶことは可能です。
三つの値のとりかたはランダムに選ぶのもいいですし、質問者様がいうように先頭、真ん中、末尾の値でもいいと思います。
質問者様が実装した関数でpivotを選んでも上手く動作しました。
pivot部分を以下のように書き換えれば上手く動作すると思います

c

1 int index; 2 index = median3(A,0,len/2,len); 3 int pivot = A[index]; 4 5

処理の流れを理解しやすくする為にprint文を仕込んでみました。
これを実行してみてください。

c

1#include <stdio.h> 2 3void quicksort(int *A, int len, int deep); 4void printspace(int deep); 5 6int main (void) { 7 int a[] = {4, 65, 2, -31, 0, 99, 2, 83, 782, 1,3}; 8 int n = sizeof a / sizeof a[0]; 9 printf("%d\n", n); 10 11 int i; 12 for (i = 0; i < n; i++) { 13 printf("%d ", a[i]); 14 } 15 printf("\n"); 16 17 quicksort(a, n, 0); 18 19 for (i = 0; i < n; i++) { 20 printf("%d ", a[i]); 21 } 22 printf("\n"); 23 24 return 0; 25} 26 27void quicksort(int *A, int len, int deep) { 28 29 int k; 30 printspace(deep); 31 printf("配列の中身:"); 32 for (k = 0; k < len; k++) { 33 printf("%d ", A[k]); 34 } 35 printf("\n"); 36 37 38 39 if (len < 2){ 40 printspace(deep); 41 printf("配列の要素数が1以下なのでソート終了 要素の値:%d 深さ:%d\n",A[0], deep); 42 return; 43 } 44 45 int pivot = A[len / 2]; 46 47 48 int i, j; 49 for (i = 0, j = len - 1; ; i++, j--) { 50 while (A[i] < pivot) i++; 51 while (A[j] > pivot) j--; 52 53 if (i >= j) break; 54 55 int temp = A[i]; 56 A[i] = A[j]; 57 A[j] = temp; 58 } 59 60 printspace(deep); 61 printf("pivot:%d以下の配列leftをクイックソートする\n", pivot); 62 quicksort(A, i, deep+1); 63 printspace(deep); 64 printf("pivot:%d以上の配列rightをクイックソートする\n", pivot); 65 quicksort(A + i, len - i, deep+1); 66 printspace(deep); 67 printf("配列の中身(ソート後):"); 68 for (k = 0; k < len; k++) { 69 printf("%d ", A[k]); 70 } 71 printf("\n"); 72 73 74} 75 76void printspace(int deep){ 77 int i; 78 for(i=0;i<deep;i++){ 79 printf("-----"); 80 } 81} 82

「-----」このprint出力で再帰の深さを示しています。
pivot:99以上の配列rightをクイックソートする
-----配列の中身:782 99 ★これがソート前の配列の中身
-----pivot:99以下の配列leftをクイックソートする
----------配列の中身:99
----------配列の要素数が1以下なのでソート終了 要素の値:99 深さ:2
-----pivot:99以上の配列rightをクイックソートする
----------配列の中身:782
----------配列の要素数が1以下なのでソート終了 要素の値:782 深さ:2
-----配列の中身(ソート後):99 782 ★これがソート後の配列の中身

投稿2018/12/26 12:26

編集2018/12/26 14:43
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KIYZ

2019/01/04 03:12

返事が遅くなってしまい申し訳ありません。 大変ご丁寧なご説明をして頂き誠にありがとうございます。 順を追って説明して頂いたお陰で疑問が解けました。 疑問3.0に関しては、 median3() がインデックスを返す関数であるにも関わらず、 pivot = median3(array, 0, len / 2, len); としていた事が原因で問題が生じていましたが、 pivot = array[median3(array, 0, len / 2, len)]; とすることで問題なく動作しました。 また、ここまで処理の流れや再帰深度を分かりやすく可視化できるprintf()デバッグは行ったことが無かったので、そちらも大変勉強になりました。今後活用させて頂きます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問