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

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

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

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

Q&A

解決済

4回答

3439閲覧

隣接リストから指定した点を削除する関数

mothi5656

総合スコア27

C

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

0グッド

0クリップ

投稿2020/06/25 08:49

下のコードは隣接リストを作成するものなのですが、そこに隣接リストから指定した点を削除するdelete関数を作りたいです。自分で考えてはみたのですが分からないので、方法を教えていただきたいです。

#include<stdio.h> #include<stdlib.h> #define VMAX 10 //最大点数 //枝 struct edge { char label; struct edge *next; }; //点 struct vertex { char label; struct edge *link; //リンクリスト }; //グラフ struct graph { int v,e; struct vertex vset[VMAX]; //点集合 }; //指定した点をグラフから削除する  int delete(struct graph *g, char label) {   //labelの点をリンクリストから削除 return 0; } int main() { struct graph grp; //グラフ int i,n; char c1,c2; struct edge *head,*p; for(i=0;i<VMAX;i++)grp.vset[i].link=NULL; //点数読み込み grp.v=scanf("%d%*c",&n); grp.v=n; printf("\n"); //点ラベルを読み込み for(i=0;i<n;i++){ char c; scanf("%c%*c",&c); grp.vset[i].label=c; } printf("\n"); //枝数読み込み grp.e=scanf("%d%*c",&n); grp.e=n; printf("\n"); //点ラベル対(枝)を読み込み struct edge ed[VMAX * (VMAX-1)]; for(i=0;i<n;i++){ scanf("%c%*c %c%*c",&c1,&c2); printf("\n"); int C1,C2; for(C1=0; C1 < n && grp.vset[C1].label != c1;) C1++; for(C2=0; C2 < n && grp.vset[C2].label != c2;) C2++; ed[i*2].next = NULL; ed[i*2].label = c2; ed[i*2+1].next = NULL; ed[i*2+1].label = c1; struct edge **np; for( np = &grp.vset[C1].link; *np != NULL;) np = &(*np)->next; *np = &ed[i*2]; for( np = &grp.vset[C2].link; *np != NULL;) np = &(*np)->next; *np = &ed[i*2+1]; delete(&grp, 'D'); } //グラフの確認 for(i=0;i<grp.v;i++){ printf("[%c]:",grp.vset[i].label); for(p=grp.vset[i].link; p!=NULL; p=p->next){ printf("%c",p->label); if(p->next !=NULL) printf("->"); } printf("\n"); } } コード

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

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

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

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

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

episteme

2020/06/25 09:13

「分からない」で済まさないで。 なにが分からんですか?
guest

回答4

0

ベストアンサー

だいたいこんな感じだと思います。

c

1//指定した点をグラフから削除する 2int delete(struct graph *g, char label) 3{ 4 //labelの点をリンクリストから削除 5 int di = g->v; 6 //該当の点を探し 7 for (int vi = 0; vi < g->v; vi++) { 8 struct vertex *v = g->vset[vi]; 9 //見つけたら位置を記録 10 if (v->label == label) di = vi; 11 //他の点から該当の点との枝を落とし 12 else { 13 //該当の枝が先頭の場合 14 if (v->link->label == label) { 15 v->link = v->link->next; 16 } 17 //そうでない場合はpreを用意して 18 //該当の枝(cur)をスキップする 19 struct edge *pre = v->link; 20 struct edge *cur = v->link; 21 while(cur != NULL) { 22 if (cur->label == label) { 23 pre->next = cur->next; 24 //curがmallocされたものならここでfree 25 } else pre = cur; 26 cur = cur->next; 27 } 28 } 29 } 30 //配列内の点を再配置 31 //di < g->v ならばdiが該当の点の位置 32 for (int vi = di; vi < g->v; vi++) { 33 g->vset[vi] = g->vset[vi+1]; 34 } 35 //di < g->v ならば点の数を更新 36 if (di < g->v) g->v--; 37 return 0; 38}

1つめのforで、該当の点を探しつつ、そうでない点については枝を落としていく処理を行い、2つめのforで配列内の点の再配置を行っている。(anndonutさんより説明)

自信がありませんが、お役に立てたら幸いです。

余計な世話かもしれませんが、勉強するのなら、Rustをお勧めしたいです。Cよりずっと役立ちます。エラーメッセージがすごく丁寧で、参照とポインタを厳密にチェックしてくれます。ただモノ作りをしたい時に、stdにはリンクリストからハッシュマップまであるの上、サードパーティライブラリも簡単に用いられます。

投稿2020/06/25 09:42

編集2020/06/26 14:50
YufanLou

総合スコア464

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

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

anndonut

2020/06/26 11:26

それで考え方的には合っていると思います。質問者のコードをきちんと読み解けてるので偉いと思います(私はすぐには読み解けなかった…)。点を削除するときにどうしてこの手順を踏まなければならないのかについては質問者さんにきちんと説明する必要があるかと思います。1つめのforで、該当の点を探しつつ、そうでない点については枝を落としていく処理を行い、2つめのforで配列内の点の再配置を行っている。この処理では点の配列については最適化されるが枝の配列については最適化を行っていないので削除した後に追加しようとするのは難しい、といったことですね。というより、どうして質問者さんが点の削除だけを行おうとしたのかが謎で仕方がないのですが…。
YufanLou

2020/06/26 14:31

コードの説明ありがとうございます!良ければ回答に添えたいです。最適化のことはよくわかりませんが。初心者にはリンクリスト最も難しいのが削除だと思います。pre を用意するのを思いつかないとよく聞きます。質問者さんもそうでしょう。
anndonut

2020/06/26 14:34

どうぞどうぞ!
guest

0

隣接リストとリンクリストは違います。C言語にはガベージコレクタがないので何かを追加したり削除したりするにはmalloc, freeを用います。隣接リストはリンクリストを用いて表現することができます。貴殿のコードには動的生成の考えがないのでそれを平易に導入して、回答としたいと思います。

隣接リストというのはリンクリストよりも複雑な構造であり、隣接リストよりもまずリンクリストのコードをしっかりと構築する必要があると感じました。大きな問題を小さな問題に分けて考える、という考え方は重要です。そのため、私が書いたコードは関数の数が多いです。このことによりテスタビリティ(テストのしやすさ)が向上し、コーディングで詰まることは少なくなると思います。

ただし、大きな問題を小さな問題に分けて考えるとき、プログラミングの多くの概念を知っていなければなりません。わたしが書いたコードはLisp風のオブジェクトシステムが主体になっていますが、オブジェクトシステムという大仰なシステムを用意しなくとも、よりシンプルに書くことができたんだろうと思います。それはわーっと書き出してからリファクタリングをするとよいと思うのですが。

書いててつくづく思ったのは、複雑なデータ構造を取り扱うのにC言語では貧弱で、C++言語がどうしても欲しくなるということでしょうか。例えば、今回のコードではオブジェクトシステムという大仰なシステムを組んでいますがC++のテンプレートを使えば同じようなコードを2回かかなくて済むわけですね。また、単方向リストについてのライブラリはC++では充実しているので単方向リストのコードをわざわざ書きに行く必要はなかったのかなと。

最後に、コードを書く際はテスタビリティを意識していただきたいと思います。テスタビリティを上げるには、関数やクラスを小分けにする、プログラミング業界で一般的に使われている語彙を意識してできるだけ使うようにする、といった工夫が必要になると思います。

私が書いたコードは恐らく自己満足なのでしょうが、正しいコードを書くには概念を注意深く小分けにして少しずつデバッグを行っていくのが肝心なのだと思います。私からは以上です。

C

1#include <stdio.h> 2#include <stdlib.h> 3#include <stdbool.h> 4 5/* 6 * リンクリストによるオブジェクトシステム 7 */ 8 9typedef enum tag_ObjectType { 10 OBJECT_TYPE_INTEGER, 11 OBJECT_TYPE_LIST 12} ObjectType; 13 14struct tag_Object; // オブジェクト型の前方宣言 15 16typedef struct tag_ListObjectComponent { 17 struct tag_Object *car; // carはリストの要素(伝統的な概念) 18 struct tag_Object *cdr; // cdrはリストの後続部(伝統的な概念) 19} ListObjectComponent; 20 21typedef struct tag_Object { 22 ObjectType type; 23 union { 24 int i; 25 ListObjectComponent list; 26 } d; 27} Object; 28 29void object_print(Object *obj) { 30 // 引数objはconst型でないので関数内部で書き換え可能であることに注意 31 if (obj == NULL) { 32 printf("[]"); 33 return; 34 } 35 if (obj->type == OBJECT_TYPE_INTEGER) { 36 printf("%d", obj->d.i); 37 return; 38 } 39 printf("["); 40 object_print(obj->d.list.car); 41 obj = obj->d.list.cdr; 42 while (obj != NULL) { 43 if (obj->type != OBJECT_TYPE_LIST) { 44 printf(" . "); // .はドット対であることを表す記号 45 object_print(obj); 46 printf("]"); 47 return; 48 } 49 printf(", "); 50 object_print(obj->d.list.car); 51 obj = obj->d.list.cdr; 52 } 53 printf("]"); 54} 55 56void object_println(Object *obj) { 57 object_print(obj); 58 printf("\n"); 59} 60 61Object *object_allocateInteger(int n) { 62 Object *obj = malloc(sizeof(Object)); 63 obj->type = OBJECT_TYPE_INTEGER; 64 obj->d.i = n; 65 return obj; 66} 67#define Int object_allocateInteger 68 69Object *object_allocateList(Object *car, Object *cdr) { 70 Object *obj = malloc(sizeof(Object)); 71 obj->type = OBJECT_TYPE_LIST; 72 obj->d.list.car = car; 73 obj->d.list.cdr = cdr; 74 return obj; 75} 76#define List object_allocateList 77 78void object_free(Object *obj) { 79 if (obj == NULL) return; 80 if (obj->type == OBJECT_TYPE_LIST) { 81 object_free(obj->d.list.car); 82 object_free(obj->d.list.cdr); 83 } 84 free(obj); 85} 86 87/* 88 * 標準リストについての関数 89 * 90 * ここでは、次のものを標準リストとする。 91 * 1. NULL 92 * 2. nを任意のint型として、List(Int(n), 標準リスト) 93 * 94 * すなわち、標準リストとは要素が全て整数のリンクリストのこと 95 */ 96 97Object *list_add(Object *list, int n) { 98 return List(Int(n), list); 99} 100 101Object *list_remove(Object *list, int n) { 102 if (list == NULL) return NULL; 103 if (list->d.list.car->d.i == n) { 104 Object *cdr = list->d.list.cdr; 105 free(list->d.list.car); 106 free(list); 107 return list_remove(cdr, n); 108 } 109 list->d.list.cdr = list_remove(list->d.list.cdr, n); 110 return list; 111} 112 113// Predicate型は、オブジェクト型とvoidポインタ型(環境を与えるために使用する)を 114// 与えてbool型を返す関数のポインタ型 115typedef bool (*Predicate)(Object *, void *); 116 117// 述語(boolを返す関数)を与えて、trueならば要素を削除する関数 118// (要素が整数でないリストにも適用することができる) 119Object *list_remove_if(Object *list, Predicate pred, void *pEnv) { 120 if (list == NULL) return NULL; 121 if (pred(list->d.list.car, pEnv)) { 122 Object *cdr = list->d.list.cdr; 123 object_free(list->d.list.car); 124 free(list); 125 return list_remove_if(cdr, pred, pEnv); 126 } 127 list->d.list.cdr = list_remove_if(list->d.list.cdr, pred, pEnv); 128 return list; 129} 130 131bool car_equals_n(Object *obj, void *pNum) { 132 return (obj->d.list.car->d.i == *((int*)pNum)) ? true : false; 133} 134 135// MapFunc型はオブジェクト型とvoidポインタ型(環境を与えるために使用する)を 136// 与えてオブジェクト型を返す関数のポインタ型 137typedef Object *(*MapFunc)(Object *, void *); 138 139// リストとMapFunc型の関数を与えてリストの各要素を破壊的に変換する関数 140// (要素が整数でないリストにも適用することができる) 141Object *list_map_into(Object *list, MapFunc fn, void *pEnv) { 142 Object *p = list; 143 while (p != NULL) { 144 p->d.list.car = fn(p->d.list.car, pEnv); 145 p = p->d.list.cdr; 146 } 147 return list; 148} 149 150Object *remove_n_for_cdr(Object *obj, void *pNum) { 151 obj->d.list.cdr = list_remove(obj->d.list.cdr, *(int*)pNum); 152 return obj; 153} 154 155// list_find関数は指定した整数nを見つけるとそれ以降の部分リストを返します。 156// 整数nがリストにない場合はNULLを返します。 157// (list_find関数は整数nがリスト内に存在するかどうかを判定するために使います) 158Object *list_find(Object *list, int n) { 159 while (list != NULL) { 160 if (list->d.list.car->d.i == n) { 161 return list; 162 } 163 list = list->d.list.cdr; 164 } 165 return NULL; 166} 167 168/* 169 * 隣接リスト(無向グラフ) 170 */ 171 172typedef struct tag_AdjacencyList { 173 // 隣接リストの構造 174 // [ [ 頂点1 . [隣接する頂点11, ..., 隣接する頂点1n] ], 175 // ... 176 // [ 頂点m . [隣接する頂点m1, ..., 隣接する頂点ml] ] ] 177 Object *d; 178} AdjacencyList; 179#define Adj AdjacencyList 180 181// ヘルパー関数: 頂点uに隣接する頂点のリストを取得する 182static Object *adjacencyList_getEdges(Adj *adj, int u) { 183 Object *d = adj->d; 184 while (d != NULL) { 185 if (d->d.list.car->d.list.car->d.i == u) { 186 return d->d.list.car->d.list.cdr; 187 } 188 d = d->d.list.cdr; 189 } 190 return NULL; 191} 192 193// ヘルパー関数: 頂点uに隣接する頂点のリストをobjに設定する 194static void adjacencyList_setEdges(Adj *adj, int u, Object *obj) { 195 Object *d = adj->d; 196 while (d != NULL) { 197 if (d->d.list.car->d.list.car->d.i == u) { 198 d->d.list.car->d.list.cdr = obj; 199 } 200 d = d->d.list.cdr; 201 } 202} 203 204// 隣接リストadjが頂点nを持っているならばtrue, そうでなければfalse 205bool hasNode(Adj *adj, int n) { 206 Object *d = adj->d; 207 while (d != NULL) { 208 if (d->d.list.car->d.list.car->d.i == n) return true; 209 d = d->d.list.cdr; 210 } 211 return false; 212} 213 214// 隣接リストadjに頂点nを追加 215void addNode(Adj *adj, int n) { 216 if (hasNode(adj, n)) { 217 fprintf(stderr, "addNode: 隣接リストはすでに頂点%dを持っています.\n", n); 218 return; 219 } 220 adj->d = List(List(Int(n), NULL), adj->d); 221} 222 223// 隣接リストadjに枝(u, v)を追加 224void addEdge(Adj *adj, int u, int v) { 225 if (!hasNode(adj, u)) { 226 fprintf(stderr, "addEdge: 隣接リストは始点%dを持っていません.\n", u); 227 return; 228 } 229 if (!hasNode(adj, v)) { 230 fprintf(stderr, "addEdge: 隣接リストは終点%dを持っていません.\n", v); 231 return; 232 } 233 Object *ue = adjacencyList_getEdges(adj, u); 234 Object *ve = adjacencyList_getEdges(adj, v); 235 if (list_find(ue, v)) { 236 fprintf(stderr, "addEdge: 始点%dの隣接ノードとして終点%dがすでに登録されています.\n", u, v); 237 return; 238 } 239 if (list_find(ve, u)) { 240 fprintf(stderr, "addEdge: 終点%dの隣接ノードとして始点%dがすでに登録されています.\n", v, u); 241 } 242 adjacencyList_setEdges(adj, u, list_add(ue, v)); 243 adjacencyList_setEdges(adj, v, list_add(ve, u)); 244} 245 246// 頂点uを削除(それに付随する枝も削除) 247void removeNode(Adj *adj, int u) { 248 adj->d = list_remove_if(adj->d, &car_equals_n, (void*)(&u)); 249 adj->d = list_map_into(adj->d, &remove_n_for_cdr, (void*)(&u)); 250} 251 252int main() { 253 Adj adj = { .d = NULL }; 254 addNode(&adj, 1); 255 addNode(&adj, 2); 256 addNode(&adj, 3); 257 addEdge(&adj, 1, 2); 258 addEdge(&adj, 1, 3); 259 addEdge(&adj, 2, 3); 260 object_println(adj.d); 261 removeNode(&adj, 2); 262 object_println(adj.d); 263 return 0; 264}

投稿2020/06/25 16:59

編集2020/06/26 00:01
anndonut

総合スコア667

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

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

0

※ 回答ではありません

  1. 単方向リストから'なにか'を削除する場合、

「p->next が'なにか'である edge* p を見つけなければならない」
ことは理解できますか?

  1. p->next が'なにか'である edge* p が得られたなら、

その'なにか'を削除する手順は分かりますか?

投稿2020/06/25 10:22

episteme

総合スコア16612

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

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

0

コードは隣接リストを作成するものなのですが、そこに隣接リストから指定した点を削除するdelete関数を作りたい

……つまり,

  1. リストを作ることはできる
  2. リストから要素を削除することができない

という状況だということでしょうか.
そのような状況にあるのであれば,1.を利用して2.を達成してはどうでしょうか.

「あるリストの完全なるコピー:そのリストの全ての要素持つ別のリスト」を作る処理を考えましょう.
その手続きにおいて「特定の要素だけをコピーしない」ならば,作られたリストは,元のリストから特定の要素を「削除した」リストになるでしょう.
そうしたら,元のリストは捨てて,何食わぬ顔で新しいリストを採用すれば良いのです.

投稿2020/06/25 10:04

fana

総合スコア11996

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問