連結リストを反転するCプログラムの解説【反復法・再帰法の2通り】
この記事では、連結リスト(Linked List)が与えられたときに、それを反転するプログラムの作成方法を解説します。
作成するプログラムは、与えられた連結リストのリンクの向きを逆にし、反転後の連結リストを返します。
連結リストとは
連結リストとは、データ項目を格納するノードがリンクで順につながったデータ構造です。各ノードには、次のノードへの接続(ポインタ)が含まれています。
連結リストの反転とは
「連結リストの反転」とは、リスト内のリンクの向きをすべて逆向きにして、新しい連結リストを作る操作のことです。反転後は、元のリストの先頭ノードが末尾ノードになり、元の末尾ノードが新しい先頭(head)ノードになります。
例
9 -> 32 -> 65 -> 10 -> 85 -> NULL
上記の連結リストを反転すると、次のようになります。
85 -> 10 -> 65 -> 32 -> 9 -> NULL
反転のアルゴリズム
連結リストを反転するには、previous(前)・current(現在)・after(次)という3つの補助ポインタを使用します。
- previous と after を NULL で初期化し、current をリストの先頭ノードに設定します。
- current が NULL になるまで、以下の処理を繰り返します。
after = current->next; current->next = previous; previous = current; current = after;
ループが終了した時点で、previous が指しているノードが反転後のリストの先頭になります。
実装方法は大きく分けて2つあります。1つは反復法(イテレーティブ)、もう1つは再帰法です。それぞれのプログラムを見ていきましょう。
プログラム1:末尾再帰による連結リストの反転
#include <stdio.h>
struct Node {
int data;
struct Node* next;
};
Node* insertNode(int key) {
Node* temp = new Node;
temp->data = key;
temp->next = NULL;
return temp;
}
void tailRecRevese(Node* current, Node* previous, Node** head){
if (!current->next) {
*head = current;
current->next = previous;
return;
}
Node* next = current->next;
current->next = previous;
tailRecRevese(next, current, head);
}
void tailRecReveseLL(Node** head){
if (!head)
return;
tailRecRevese(*head, NULL, head);
}
void printLinkedList(Node* head){
while (head != NULL) {
printf("%d ", head->data);
head = head->next;
}
printf("\n");
}
int main(){
Node* head1 = insertNode(9);
head1->next = insertNode(32);
head1->next->next = insertNode(65);
head1->next->next->next = insertNode(10);
head1->next->next->next->next = insertNode(85);
printf("Linked list : \t");
printLinkedList(head1);
tailRecReveseLL(&head1);
printf("Reversed linked list : \t");
printLinkedList(head1);
return 0;
}
出力
Linked list : 9 32 65 10 85 Reversed linked list : 85 10 65 32 9
このプログラムでは、tailRecRevese 関数が自分自身を呼び出す末尾再帰の形でリンクを逆方向につなぎ替えています。最後のノードに到達した時点で、そのノードを新しい head として設定します。
プログラム2:反復法による連結リストの反転
#include <stdio.h>
struct Node {
int data;
struct Node* next;
Node(int data){
this->data = data;
next = NULL;
}
};
struct LinkedList {
Node* head;
LinkedList(){
head = NULL;
}
void interReverseLL(){
Node* current = head;
Node *prev = NULL, *after = NULL;
while (current != NULL) {
after = current->next;
current->next = prev;
prev = current;
current = after;
}
head = prev;
}
void print() {
struct Node* temp = head;
while (temp != NULL) {
printf("%d ", temp-> data);
temp = temp->next;
}
printf("\n");
}
void push(int data){
Node* temp = new Node(data);
temp->next = head;
head = temp;
}
};
int main() {
LinkedList linkedlist;
linkedlist.push(85);
linkedlist.push(10);
linkedlist.push(65);
linkedlist.push(32);
linkedlist.push(9);
printf("Linked List : \t");
linkedlist.print();
linkedlist.interReverseLL();
printf("Reverse Linked List : \t");
linkedlist.print();
return 0;
}
出力
Linked List : 9 32 65 10 85 Reverse Linked List : 85 10 65 32 9
こちらのプログラムでは、push メソッドがリストの先頭に要素を追加していくため、挿入順とは逆の「9 32 65 10 85」の並びのリストが構築されます。その後、interReverseLL メソッドが前述の3ポインタを使った反復処理でリストを反転しています。
計算量について
- 時間計算量: どちらの手法でも、各ノードを一度ずつ処理するため O(n) です。
- 空間計算量: 反復法は補助ポインタ3個だけで済むため O(1)。一方、再帰法は呼び出しスタックをノード数分使用するため O(n) となります。
大きなリストを扱う場合やメモリ効率を重視する場合は、反復法を採用するのが一般的です。
-
C言語で連結リストの末尾からn番目のノードを取得するプログラム
n個のノードからなる連結リストが与えられたとき、その末尾からn番目のノードを出力するのが本記事の目的です。プログラムはリスト内のノードの並び順を変更してはならず、あくまで末尾から数えてn番目に位置するノードの値を表示するだけでなければなりません。具体例入力 -: 10 20 30 40 50 60 N = 3 出力 -: 40上記の例では、先頭ノードから順に「count − n」個目までのノード(10, 20, 30, 40, 50, 60)を走査し、末尾から3番目のノードとして 40 が得られます。効率的なアプローチリスト全体を最後まで走査しなくても、以下の手順で目的のノードを見つけられ
-
Cプログラムで追加領域やリストの変更なしに連結リストを逆順に表示する方法
この課題は、連結リスト(リンクリスト)のノードを末尾から先頭に向かって表示するというものです。ただし、追加のメモリ領域を使用しないことが条件です。つまり、再帰呼び出しやスタックのような補助変数・データ構造を使わず、先頭ノードを指すヘッドポインタだけを利用して実現する必要があります。例入力:10 21 33 42 89 出力:89 42 33 21 10連結リストを逆順に表示する方法はいくつか考えられます。例えば、以下のようなアプローチが挙げられます。再帰的な手法:関数呼び出しのスタックを使用するため、O(n) の追加領域が必要になります。リスト自体を反転させる手法:元の連結リストに変更を加えて