(c言語)ポインタを用いたリスト
ココでは主に挿入と削除について話します。(最後に作成したものをチェックするprintも紹介します。)
- 単方向リスト
- 双方向リスト
単方向リスト
単方向リストとは
単方向リストは下の図のようなもので、1つのリストが次の行先のみを知っているようなものです。
上の図を以下の図のように変換することで挿入(insert)及び削除(delete)を作る際にイメージしやすくなります。
実際に書いてみましょう
挿入(insert)及び削除(delete)以外の部分は以下のようになります。
struct node{ struct node *next; //nextのアドレスを入れる部分 int elem; //値を入れる部分 }; struct node* head(void){ struct node* n;//箱の生成 n=(struct node*)malloc(sizeof(struct node));//箱の拡張 n->next = NULL;//箱の行き先をnullにする return n; } void main(){ struct node* list; list=head; }
ここからがinsert及びdeleteです
void insert(struct node* p, char elem) { struct node* n; //新しく値を代入する箱を作る n = (struct node*)malloc(sizeof(struct node)); //箱の大きさを拡張する n->elem = elem; //値を代入 n->next = p->next; // nの行先をpの行き先にする p->next = n; //作った箱の行先をpにする=次にpが来る } void delete(struct node* p){ if(p->next != NULL){ p->next=p->next->next; //pの行き先を次の次にする=一つ飛ばす } }
注意点
insertでは受け取った引数のpの次に値を挿入し、deleteでは受け取った引数pの次の値を削除する形となります。
作成時のポイント
- 追加する箱を考えて、追加する箱のnext部分を先に指定する。
- その後で追加した箱の一つ後ろ(引数で渡したもの)のnextを変える。
双方向リスト
双方向リストとは
双方向リストは下の図のようなもので、1つのリストが次の行先を知っていて、また、1つのリストが前の行き先も知っているようなものです。
上の図を以下の図のように変換することで挿入(insert)及び削除(delete)を作る際にイメージしやすくなります。
実際に書いてみましょう
挿入(insert)及び削除(delete)以外の部分は以下のようになります。
struct node{ struct node *next; //nextのアドレスを入れる場所 struct node *prev; //prevのアドレスを入れる場所 int elem; //値を入れる場所 }; struct node* head(void){ struct node *n;//箱の生成 n=(struct node*)malloc(sizeof(struct node));//箱の拡張 n->next = NULL;//箱の行き先を指定 n->prev = NULL;//箱の戻り値を指定(使うことはない) return n; } void main(){ struct node* list; list=head; }
ここからがinsert及びdeleteです
void insert(struct node* p, int elem) { struct node* n; //新しく値を代入する箱を作る n = (struct node*)malloc(sizeof(struct node)); //箱の大きさを拡張する n->elem = elem; //値を代入 if (p->next == NULL) { //一個目の代入のみ n->next = p->next; // nの行き先をpの行き先の次の物にする n->prev = p; // nの戻り先をpにする p->next = n; // pの行き先をnにする } else { n->next = p->next; // nの行き先をpの行き先の次の物にする n->prev = p; // nの戻り先をpの戻り先pにする p->next->prev = n; // pの行き先の一つ前をnにする p->next = n; // pの行き先をnにする } } void delete(struct node* p){ if(p != NULL){ p->prev->next=p->next;//pの一つ前(prev)を一つ後(next)を繋げる p->next->prev=p->prev;//pの一つ後(next)と一つ前(prev)を繋げる } }
注意点
insertでは受け取った引数のpの次に値を挿入し、deleteでは受け取った引数pを削除する形となります。
作成時のポイント
- 新しく挿入する値のprevとnextの行き先を先に決める
- headのみの場合とhead以外がある場合で注意をする
- 図に書きながら、prevとnextの行き先を線で結び、コードを書いたら消すようにする。
おまけ(作ったリストの全表示)
リストの中身のみ表示(単方向、双方向対応)
void printlist(struct node* p) { //listを渡す p = p->next; //listの先に行く for (; p != NULL; p = p->next) { printf("%c ", p->elem); //表示 } printf("\n"); }
リストの詳細を表示(単方向リスト用)
void detailprintf_1(struct node* p) { //listを渡す int i = 0; p = p->next; //listの一つ先へ進む for (; p != NULL; p = p->next) { printf("[%d]elem:%c\n", i, p->elem); //値の表示 if (p->next != NULL) { printf("[%d]nextelem:%c\n", i, p->next->elem); //nextの表示 } else { printf("[%d]nextelem:NULL(end)\n", i); //nextがNULLだったNULL(end)と表示 } i++; //先頭からの番号を数える } }
リストの詳細を表示(双方向リスト用)
void detailprintf_2(struct node* p) { //listを渡す int i = 0; p = p->next; //listの一つ先に進む for (; p != NULL; p = p->next) { printf("[%d]elem:%c\n", i, p->elem); //値の表示 if (p->next != NULL) { printf("[%d]nextelem:%c\n", i, p->next->elem); //nextの表示 } else { printf("[%d]nextelem:NULL(end)\n", i); //nextがNULLだったNULL(end)と表示 } if (i != 0) { printf("[%d]prevelem:%c\n", i, p->prev->elem); //prevの表示 } else { printf("[%d]prevelem:HEAD\n", i); //prevがheadだったらHEADと表示 } i++; //先頭からの番号を数える } }
結果(双方向リスト)
int main(void) { struct node* list; list = head(); insert(list, 'a'); insert(list, 'b'); insert(list->next, 'c'); insert(list, 'd'); insert(list, 'e'); delete (list->next); printlist(list); detailprintf(list); return 0; }
d b c a [0]elem:d //先頭(0番目)の値 [0]nextelem:b //次の値(1番目) [0]prevelem:HEAD //前の値(-1番目) [1]elem:b //1番目の値 [1]nextelem:c //次の値(2番目) [1]prevelem:d //前の値(0番目) [2]elem:c //2番目の値 [2]nextelem:a //次の値(3番目) [2]prevelem:b //前の値(1番目) [3]elem:a //3番目の値 [3]nextelem:NULL(end) //次の値(4番目) [3]prevelem:c //前の値(2番目)
まとめ
今回、ポインタを用いたリストの単方向リスト、双方向リストについて書きました。
図を書きながらやるとイメージしやすいです。