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

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

新規登録して質問してみよう
ただいま回答率
85.48%
アルゴリズム

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

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

1000閲覧

ノードを使ったヒープ構築のプログラムを作成したいです

wagashi_157

総合スコア51

アルゴリズム

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

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2021/11/04 14:09

編集2021/11/04 14:11

前提・実現したいこと

各ノードにID(整数)とコスト(整数)のペアが保持され, 親ノードの保持する「コスト」が子ノードの保持する「コスト」より小さいという性質を持つヒープを構築したいです。以下の要領に従って実装したいです。

  1. id(int 型)とcost(int 型)をメンバとした構造体node を定義する.
  2. node を型とする大きさ30の配列node data を(大域変数として)用意する.
  3. node data の要素の初期値として,各要素のIDは配列のインデックス番号と同

じ値に(ID は0から29),コストは0から100までのランダムな値とする.
(これを関数gen node dataで行う.)
4. 上で説明したように,(また,ヒープソートでヒープを構築したように)コストに
関したヒープを構築する.(これを関数make heapで行う。)
5.(適切にヒープが構築されているかを確認するため)配列の各要素の値(IDとコ
スト)を出力する.(これを関数print heap で行う.)

発生している問題

down_heap()とmake_heap()を作成したのですが, エラーは出なかったものの昇順にソートできていません。どこに原因があるのかが分からないので教えてほしいです。ノードの使い方にも慣れていないので教えてほしいです。

id: 0, cost: 30 id: 1, cost: 16 id: 2, cost: 23 id: 3, cost: 69 id: 4, cost: 13 id: 5, cost: 74 id: 6, cost: 96 id: 7, cost: 13 id: 8, cost: 89 id: 9, cost: 92 id: 10, cost: 86 id: 11, cost: 6 id: 12, cost: 12 id: 13, cost: 56 id: 14, cost: 10 id: 15, cost: 84 id: 16, cost: 78 id: 17, cost: 82 id: 18, cost: 73 id: 19, cost: 52 id: 20, cost: 95 id: 21, cost: 29 id: 22, cost: 75 id: 23, cost: 33 id: 24, cost: 24 id: 25, cost: 79 id: 26, cost: 21 id: 27, cost: 20 id: 28, cost: 48 id: 29, cost: 77 id: 0, cost: 1 id: 16, cost: 23 id: 3, cost: 4 id: 13, cost: 74 id: 6, cost: 7 id: 13, cost: 89 id: 9, cost: 10 id: 86, cost: 6 id: 12, cost: 13 id: 56, cost: 10 id: 15, cost: 84 id: 16, cost: 78 id: 17, cost: 82 id: 18, cost: 73 id: 19, cost: 52 id: 20, cost: 95 id: 21, cost: 29 id: 22, cost: 75 id: 23, cost: 33 id: 24, cost: 24 id: 25, cost: 79 id: 26, cost: 21 id: 27, cost: 20 id: 28, cost: 48 id: 29, cost: 77 id: 0, cost: 0 id: 0, cost: 0 id: 0, cost: 540410064 id: 0, cost: 0 id: 0, cost: 0

該当のソースコード

C++

1#include<iostream> 2#include<random> 3using namespace std; 4 5#define N 30 6#define MAX_COST 100 7 8struct node { 9 int id; 10 int cost; 11}; 12 13node node_data[N]; 14 15void gen_node_data() 16{ 17 mt19937 mt{random_device{}()}; 18 uniform_int_distribution<int> dist(0,MAX_COST); 19 20 for (int i=0; i<N; i++) { 21 node_data[i].id = i; 22 node_data[i].cost = dist(mt); 23 } 24} 25 26void swap(int a[],int i,int j) 27{ 28 int t; 29 t=a[i]; 30 a[i]=a[j]; 31 a[j]=t; 32} 33 34void down_heap(int a[],int leaf,int root) 35{ 36 int i=2*root; 37 while (i<=leaf) { 38 if (i<leaf&&a[i+1]>a[i]) 39 i++; 40 if (a[root]>=a[i]) 41 break; 42 swap(a,root,i); 43 root=i; 44 i=root*2; 45 } 46} 47 48void make_heap(int a[],int asize) 49{ 50 int i; 51 for (i=asize/2-1; i>=0; i--) { 52 down_heap(a,i,asize); 53 } 54 for (i=asize-1; i>=1; i--) { 55 int t; 56 t=a[0]; 57 a[0]=a[i]; 58 a[i]=t; 59 down_heap(a,0,i-1); 60 } 61} 62 63bool check_rightmost(int i) 64{ 65 int x = i+2; 66 while (x>1) { 67 if (x%2==1) return 0; 68 else x=x/2; 69 } 70 return 1; 71} 72 73void print_heap() 74{ 75 for (int i=0; i<N; i++) { 76 cout << "id: "; 77 cout << node_data[i].id; 78 cout << ", cost: "; 79 cout << node_data[i].cost; 80 81 if (check_rightmost(i)) cout << endl; 82 else cout << " "; 83 } 84 cout << endl; 85} 86 87int main() 88{ 89 gen_node_data(); 90 print_heap(); 91 92 for (int i=0; i<N; i++) { 93 make_heap(&node_data[i].cost,N) 94 } 95 print_heap(); 96 97 return 0; 98}

試したこと

main関数でmake_heapの()内は構造体であると同時にnode_data[i]と設定し, &をつけたらエラーは出なかったのですが, &を付けないとエラーが出ました。

補足情報(FW/ツールのバージョンなど)

C++17を使用しています。

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

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

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

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

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

guest

回答2

0

ベストアンサー

長文になりますがご了承ください。

1. 構造体型の配列の扱い

C++

1void make_heap(int a[],int asize) 2//... 3int main() 4{ 5//... 6 for (int i=0; i<N; i++) { 7 make_heap(&node_data[i].cost,N) 8 } 9//... 10}

wagashi_157さんのコードを見るに、node_dataの各要素のcostだけを取り出し、int型配列として関数に渡そうとしているようですが、そのようなことはできません。(そうしたい場合は、別に配列を作ってfor文でcostの値をコピーしてから渡す必要があります。)
関数にはnode_dataをそのまま渡し、関数内では引数をnode a[]として配列を受け取り、a[i].costのように関数内でcostを参照してください。swap関数, down_heap関数も同様です。

(修正例)

C++

1void make_heap(node a[],int asize) //node型配列として受け取る 2//... 3int main() 4{ 5//... 6 make_heap(node_data,N); //node型配列のまま渡す 7//... 8}

なお、この箇所で&をつけるとエラーが発生しない理由は、C言語では生配列を引数として渡すときは、配列そのものではなく、配列の先頭の要素を示すポインタを渡しているからです。
今回はint a[]を引数にしているので、実際に関数に渡されるのはint *(int型変数へのポインタ)型になります。一方、&は変数へのポインタのアドレスを返す演算子なので、&node_data[i].costの型もint *になります。したがって、コンパイルエラーは発生しません。

ポインタやアドレスについて詳しく知りたい場合は、こちらの記事が参考になると思います。
C++ポインタまとめ - Qiita

2. down_heap関数の処理

C++

1void down_heap(int a[],int leaf,int root) 2{ 3 int i=2*root; 4 while (i<=leaf) { 5 if (i<leaf&&a[i+1]>a[i]) 6 i++; 7 if (a[root]>=a[i]) 8 break; 9 swap(a,root,i); 10 root=i; 11 i=root*2; 12 } 13}

ループ(スワップ)の条件は「rootよりも子のほうがcostが小さい」です。これはwhile文で判断するには条件が複雑なので、while (true)とし、whileループ内でrootと子の大小を比較します。そして、rootよりも子のほうが小さい場合はスワップし、rootのほうが小さい場合はbreak文でループを抜けます。また、rootが子を持つかどうかの判断も必要なため、asizeを引数にします。(leaf引数は削除)

なお、ノードiの親は(i - 1) / 2左の子はi * 2 + 1、右の子はi * 2 + 2です。ここを間違えると大変なことになります。子の取得は頻繁に行うので、関数にしておくと便利です。

(修正例)

C++

1//ノードiの左の子を返す関数 2int left(int i) 3{ 4 return i * 2 + 1; 5} 6//ノードiの右の子を返す関数 7int right(int i) 8{ 9 return i * 2 + 2; 10} 11 12void down_heap(node a[], int asize, int root) 13{ 14 int smallest; //rootと子ノードのうち最もcostが小さいノード 15 while (true) 16 { 17 smallest = root; 18 if (left(root) < asize && a[left(root)].cost < a[smallest].cost) 19 smallest = left(root); //rootが左の子を持ち、かつ左の子がsmallestよりも小さいとき 20 if (right(root) < asize && a[right(root)].cost < a[smallest].cost) 21 smallest = right(root); //rootが右の子を持ち、かつ右の子がsmallestよりも小さいとき 22 23 if (smallest == root) 24 break; //rootが最小ならループを抜ける 25 else 26 { 27 //子のほうが小さいならスワップする 28 swap(a, root, smallest); 29 root = smallest; 30 } 31 } 32}

3. make_heap関数の処理

C++

1void make_heap(int a[],int asize) 2{ 3 int i; 4 for (i=asize/2-1; i>=0; i--) { 5 down_heap(a,i,asize); 6 } 7 for (i=asize-1; i>=1; i--) { 8 int t; 9 t=a[0]; 10 a[0]=a[i]; 11 a[i]=t; 12 down_heap(a,0,i-1); 13 } 14}

ヒープの構築(ビルドヒープ)は、葉でないノードについて降順に((N/2-1)番目から順番に0番目まで)ダウンヒープを行うだけです

(修正例)

void make_heap(node a[], int asize) { for (int i = asize / 2 - 1; i >= 0; i--) { down_heap(a, asize, i); } }

4. main関数の処理

C++

1int main() 2{ 3 gen_node_data(); 4 print_heap(); 5 6 for (int i=0; i<N; i++) { 7 make_heap(&node_data[i].cost,N) 8 } 9 print_heap(); 10 11 return 0; 12}

make_heap関数内でヒープの構築は完結しているので、make_heap関数は1回呼び出すだけで良いです。あと、些細なことですがmake_heap(&node_data[i].cost,N)の末尾に;が足りません。

(修正例)

C++

1int main() 2{ 3 gen_node_data(); 4 print_heap(); 5 6 make_heap(node_data, N); 7 print_heap(); 8 9 return 0; 10}

5. まとめ

コード全体の修正例は以下の通りです。(print_heap関数での配列の表示形式を少し変えました)

C++

1#include <iostream> 2#include <random> 3using namespace std; 4 5#define N 30 6#define MAX_COST 100 7 8struct node 9{ 10 int id; 11 int cost; 12}; 13 14node node_data[N]; 15 16void gen_node_data() 17{ 18 mt19937 mt{random_device{}()}; 19 uniform_int_distribution<int> dist(0, MAX_COST); 20 21 for (int i = 0; i < N; i++) 22 { 23 node_data[i].id = i; 24 node_data[i].cost = dist(mt); 25 } 26} 27 28void swap(node a[], int i, int j) 29{ 30 node t; 31 t = a[i]; 32 a[i] = a[j]; 33 a[j] = t; 34} 35 36int left(int i) 37{ 38 return i * 2 + 1; 39} 40int right(int i) 41{ 42 return i * 2 + 2; 43} 44 45void down_heap(node a[], int asize, int root) 46{ 47 int smallest; 48 while (true) 49 { 50 smallest = root; 51 if (left(root) < asize && a[left(root)].cost < a[smallest].cost) 52 smallest = left(root); 53 if (right(root) < asize && a[right(root)].cost < a[smallest].cost) 54 smallest = right(root); 55 56 if (smallest == root) 57 break; 58 else 59 { 60 swap(a, root, smallest); 61 root = smallest; 62 } 63 } 64} 65 66void make_heap(node a[], int asize) 67{ 68 for (int i = asize / 2 - 1; i >= 0; i--) 69 { 70 down_heap(a, asize, i); 71 } 72} 73 74bool check_rightmost(int i) 75{ 76 int x = i + 2; 77 while (x > 1) 78 { 79 if (x % 2 == 1) 80 return 0; 81 else 82 x = x / 2; 83 } 84 return 1; 85} 86 87void print_heap() 88{ 89 cout << "(id/cost)\n"; 90 for (int i = 0; i < N; i++) 91 { 92 cout << node_data[i].id << "/" << node_data[i].cost << " "; 93 if (check_rightmost(i)) 94 cout << endl; 95 } 96 cout << endl 97 << endl; 98} 99 100int main() 101{ 102 gen_node_data(); 103 print_heap(); 104 105 make_heap(node_data, N); 106 print_heap(); 107 108 return 0; 109} 110

(出力例)

(id/cost) 0/29 1/87 2/72 3/52 4/31 5/76 6/1 7/54 8/38 9/69 10/74 11/100 12/44 13/4 14/30 15/25 16/63 17/95 18/48 19/74 20/57 21/54 22/83 23/65 24/39 25/22 26/73 27/97 28/2 29/97 (id/cost) 6/1 15/25 28/2 8/38 4/31 25/22 13/4 3/52 18/48 20/57 21/54 24/39 12/44 0/29 14/30 7/54 16/63 17/95 1/87 19/74 9/69 10/74 22/83 23/65 11/100 5/76 26/73 27/97 2/72 29/97

もしヒープについてよく分かっていないようでしたら、下記のサイトを見ることをおすすめします。ヒープの他にも様々なアルゴリズムが載っており、アルゴリズムの様子をアニメーションで説明しているので分かりやすいです。疑似コードも載っているので、コードを書く際にも役立つでしょう。
アルゴリズムビジュアル大事典

投稿2021/11/05 07:44

編集2021/11/05 08:07
luuguas

総合スコア492

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

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

0

どこに原因があるのかを調べるのには、時間がかかりそうです。

手元にヒープソートのコードがあったので、こんなものを書いてみました。

C++

1#include <iostream> // cin, cout, endl 2#include <utility> // swap 3#include <random> // mt19937, random_device, uniform_int_distribution 4using namespace std; 5 6#define N 30 7#define MAX_COST 100 8 9struct node { 10 int id; 11 int cost; 12 13 bool operator<(const node& r) const { return cost < r.cost; } 14}; 15 16node node_data[N]; 17 18void gen_node_data() 19{ 20 mt19937 mt{random_device{}()}; 21 uniform_int_distribution<int> dist(0, MAX_COST); 22 for (int i = 0; i < N; i++) { 23 node_data[i].id = i; 24 node_data[i].cost = dist(mt); 25 } 26} 27 28template<typename T> 29void heap_sort(T& a, int n) 30{ 31 int c, i, j, k = 0; 32 while (++k < n) 33 for (j = k; j > 0 && a[i = (j-1)/2] < a[j]; j = i) // i: parent of j 34 swap(a[i], a[j]); 35 while (--k > 0) { 36 swap(a[0], a[k]); 37 for (i = j = 0; (c = (i+1)*2) <= k; i = j) { // c: right child of i 38 if (a[i] < a[c-1]) j = c-1; // parent(i) < left child(c-1) 39 if (c < k && a[j] < a[c]) j = c; // max(parent, left) < right child(c) 40 if (i == j) break; // parent > both children 41 swap(a[i], a[j]); // swap child and parent 42 } 43 } 44} 45 46bool check_rightmost(int i) { return !(i+2 & i+1); } 47 48void print_heap() 49{ 50 for (int i = 0; i < N; i++) { 51 cout << "[" << node_data[i].id << "," << node_data[i].cost << "]"; 52 if (check_rightmost(i)) cout << endl; 53 else cout << " "; 54 } 55 cout << endl; 56} 57 58int main() 59{ 60 gen_node_data(); 61 print_heap(); 62 heap_sort(node_data, N); 63 print_heap(); 64}

実行結果

text

1[0,63] 2[1,53] [2,3] 3[3,4] [4,79] [5,4] [6,54] 4[7,36] [8,97] [9,62] [10,23] [11,56] [12,28] [13,43] [14,99] 5[15,19] [16,36] [17,3] [18,22] [19,96] [20,99] [21,48] [22,99] [23,40] [24,34] [25,60] [26,18] [27,9] [28,94] [29,49] 6[17,3] 7[2,3] [5,4] 8[3,4] [27,9] [26,18] [15,19] 9[18,22] [10,23] [12,28] [24,34] [7,36] [16,36] [23,40] [13,43] 10[21,48] [29,49] [1,53] [6,54] [11,56] [25,60] [9,62] [0,63] [4,79] [28,94] [19,96] [8,97] [22,99] [20,99] [14,99]

追記
node の配列をソートしなければならないのに、質問のコードは
int の配列をソートしようとしています。修正してみました。

C++

1#include<iostream> 2#include<random> 3using namespace std; 4 5#define N 30 6#define MAX_COST 100 7 8struct node { 9 int id; 10 int cost; 11}; 12 13node node_data[N]; 14 15void gen_node_data() 16{ 17 mt19937 mt{random_device{}()}; 18 uniform_int_distribution<int> dist(0,MAX_COST); 19 20 for (int i=0; i<N; i++) { 21 node_data[i].id = i; 22 node_data[i].cost = dist(mt); 23 } 24} 25 26void swap(node a[],int i,int j) // ★ 27{ 28 node t; // ★ 29 t=a[i]; 30 a[i]=a[j]; 31 a[j]=t; 32} 33 34void make_heap(node a[], int asize) // ★ 35{ 36 for (int k = 0; ++k < asize; ) 37 for (int i, j = k; j > 0; j = i) { 38 i = (j - 1) / 2; 39 if (a[i].cost <= a[j].cost) break; 40 swap(a, i, j); 41 } 42} 43 44bool check_rightmost(int i) 45{ 46 int x = i+2; 47 while (x>1) { 48 if (x%2==1) return 0; 49 else x=x/2; 50 } 51 return 1; 52} 53 54void print_heap() 55{ 56 for (int i=0; i<N; i++) { 57 cout << "id: "; 58 cout << node_data[i].id; 59 cout << ", cost: "; 60 cout << node_data[i].cost; 61 62 if (check_rightmost(i)) cout << endl; 63 else cout << " "; 64 } 65 cout << endl; 66} 67 68int main() 69{ 70 gen_node_data(); 71 print_heap(); 72 73 for (int i=0; i<N; i++) { 74 make_heap(&node_data[i], N-i); // ★ 75 } 76 print_heap(); 77 78 return 0; 79}

実行結果

text

1id: 0, cost: 62 2id: 1, cost: 29 id: 2, cost: 92 3id: 3, cost: 13 id: 4, cost: 34 id: 5, cost: 38 id: 6, cost: 87 4id: 7, cost: 71 id: 8, cost: 40 id: 9, cost: 7 id: 10, cost: 67 id: 11, cost: 68 id: 12, cost: 1 id: 13, cost: 67 id: 14, cost: 79 5id: 15, cost: 68 id: 16, cost: 19 id: 17, cost: 31 id: 18, cost: 6 id: 19, cost: 73 id: 20, cost: 71 id: 21, cost: 93 id: 22, cost: 69 id: 23, cost: 65 id: 24, cost: 63 id: 25, cost: 1 id: 26, cost: 23 id: 27, cost: 84 id: 28, cost: 73 id: 29, cost: 1 6id: 12, cost: 1 7id: 25, cost: 1 id: 29, cost: 1 8id: 18, cost: 6 id: 9, cost: 7 id: 3, cost: 13 id: 16, cost: 19 9id: 26, cost: 23 id: 1, cost: 29 id: 17, cost: 31 id: 4, cost: 34 id: 5, cost: 38 id: 8, cost: 40 id: 0, cost: 62 id: 24, cost: 63 10id: 23, cost: 65 id: 10, cost: 67 id: 13, cost: 67 id: 15, cost: 68 id: 11, cost: 68 id: 22, cost: 69 id: 7, cost: 71 id: 20, cost: 71 id: 19, cost: 73 id: 28, cost: 73 id: 14, cost: 79 id: 27, cost: 84 id: 6, cost: 87 id: 2, cost: 92 id: 21, cost: 93

でも、これは O(n^2・log(n)) の計算量なのでヒープソートとは言えません。

投稿2021/11/04 15:37

編集2021/11/05 01:10
kazuma-s

総合スコア8224

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

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

wagashi_157

2021/11/05 15:27

部分ごとに分かりやすい説明をしてくださったことで上手くいかない原因が分かりました。 ありがとうございました!!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問