C++で連結リストのK個ごとのノードを交互に反転する方法
はじめに
このチュートリアルでは、長さNの連結リストAと整数Kが与えられたとき、サイズKごとのグループに分けて、交互にノードを反転する方法を解説します。なお、NはKで割り切れるものとします。関数の第1引数には連結リストAの先頭ポインタ、第2引数には整数Kが渡されます。
まず、入力と出力の例を見てみましょう。
入力例1
5 -> 6 -> 2 -> 8 -> 5 -> 2 -> 4 -> 8 -> 9 -> 6 -> null(K=2)
出力
6 -> 5 -> 2 -> 8 -> 2 -> 5 -> 4 -> 8 -> 6 -> 9 -> null
入力例2
1 -> 2 -> 5 -> 8 -> 9 -> 6 -> 4 -> 5 -> 8 -> null(K=3)
出力
5 -> 2 -> 1 -> 8 -> 9 -> 6 -> 8 -> 5 -> 4 -> null
このように、最初のK個のノードは反転され、次のK個はそのまま維持され、また次のK個が反転される——という処理がリストの終端まで繰り返されます。
解決策へのアプローチ:反復解法
1回のループごとに2K個のノードを走査し、joinポインタとtailポインタを使って、入力された連結リスト内の各Kノードペアの先頭と末尾を記録します。
次に、連結リストのK個のノードを反転し、反転後のリストの末尾ノードを、joinポインタが指す元のリストの先頭ノードと接続します。
その後、currentポインタを次のK個のノードへ進めます。
通常のリストの末尾(新しいtailが指すノード)が最後のノードとして機能し、joinポインタは新しく反転されたリストの先頭を指すため、両者がマージされます。すべてのノードに対して同じ手順が完了するまで、これらのステップを繰り返します。
反復解法の実装例
#include <bits/stdc++.h>
using namespace std;
class Node {
public:
int data;
Node* next;
};
Node* kAltReverse(struct Node* head, int k){
Node* prev = NULL;
Node* curr = head;
Node* temp = NULL;
Node* tail = NULL;
Node* newHead = NULL;
Node* join = NULL;
int t = 0;
while (curr) {
t = k;
join = curr;
prev = NULL;
// K個のノードを反転する
while (curr && t--) {
temp = curr->next;
curr->next = prev;
prev = curr;
curr = temp;
}
if (!newHead)
newHead = prev;
if (tail)
tail->next = prev;
tail = join;
tail->next = curr;
// 次のK個のノードをスキップする
t = k;
while (curr && t--) {
prev = curr;
curr = curr->next;
}
tail = prev;
}
return newHead;
}
void push(Node** head_ref, int new_data){
Node* new_node = new Node();
new_node->data = new_data;
new_node->next = (*head_ref);
(*head_ref) = new_node;
}
void printList(Node* node){
int count = 0;
while (node != NULL) {
cout << node->data << " ";
node = node->next;
count++;
}
}
int main(void){
Node* head = NULL;
int i;
for (i = 6; i <27; i+=3)
push(&head, i);
int k = 3;
cout << "Given linked list \n";
printList(head);
head = kAltReverse(head, k);
cout << "\n Modified Linked list \n";
printList(head);
return (0);
}
出力結果
Given linked list 24 21 18 15 12 9 6 Modified Linked list 18 21 24 15 12 9 6
再帰解法
次に、再帰を利用したアプローチを紹介します。処理の流れは以下の通りです。
先頭からK個のノードを走査し、temp変数にK+1番目のノードを設定します。
走査したK個のノードをすべて反転します。
そのKノードグループの最後のノードのnextポインタをtempに接続します。
スキップが必要な次のKノードグループのイテレーションを飛ばします。
最後のノードに到達するまで、次のK個のノードを反転するためにこれらの手順を再帰的に繰り返します。
擬似コード
reverseAltK(head, k)
curr = head
prev = null
next = null
count = 0
WHILE count < k AND curr
next = curr.next
curr.next = prev
prev = curr
curr = next
count = count + 1
IF head
head.next = curr
count = 0
WHILE count < k-1 AND curr
curr = curr.next
count = count + 1
IF curr
curr.next = reverseKGroupAltRecursive(curr.next, k)
return prev
再帰解法の実装例
#include <bits/stdc++.h>
using namespace std;
/* 連結リストのノード */
class node{
public:
int data;
node* next;
};
/* kAltReverse() のヘルパー関数 */
node * _kAltReverse(node *node, int k, bool b);
/* 与えられた連結リストを指定されたサイズkの
グループごとに交互に反転する */
node *kAltReverse(node *head, int k){
return _kAltReverse(head, k, true);
}
/* kAltReverse() のヘルパー関数。
第3引数bがtrueの場合のみリストのK個のノードを反転し、
それ以外の場合はポインタをKノード先へ進めてから
自身を再帰的に呼び出す */
node * _kAltReverse(node *Node, int k, bool b){
if(Node == NULL)
return NULL;
int count = 1;
node *prev = NULL;
node *current = Node;
node *next;
/* このループには2つの目的がある
1) bがtrueの場合、K個のノードを反転する
2) bがfalseの場合、currentポインタを進めるだけ */
while(current != NULL && count <= k){
next = current->next;
/* bがtrueの場合のみノードを反転する */
if(b == true)
current->next = prev;
prev = current;
current = next;
count++;
}
/* 3) bがtrueの場合、NodeはK番目のノードである。
そのため、残りのリストをNodeの後に接続する。
4) 接続後、新しい先頭を返す */
if(b == true){
Node->next = _kAltReverse(current, k, !b);
return prev;
}
/* bがtrueでない場合、残りのリストをprevの後に接続する */
else{
prev->next = _kAltReverse(current, k, !b);
return Node;
}
}
/* ユーティリティ関数 */
/* ノードを追加する関数 */
void push(node** head_ref, int new_data){
/* ノードを確保する */
node* new_node = new node();
/* データを格納する */
new_node->data = new_data;
/* 古いリストを新しいノードに接続する */
new_node->next = (*head_ref);
/* 先頭を新しいノードに移動する */
(*head_ref) = new_node;
}
/* 連結リストを出力する関数 */
void printList(node *node){
int count = 0;
while(node != NULL){
cout << node->data << " ";
node = node->next;
count++;
}
}
// ドライバーコード
int main(void){
/* 空のリストから開始 */
node* head = NULL;
int i;
// 1->2->3->4->5......->20 のリストを作成
for(i = 20; i > 0; i--)
push(&head, i);
cout << "Given linked list \n";
printList(head);
head = kAltReverse(head, 3);
cout << "\nModified Linked list \n";
printList(head);
return(0);
}
出力結果
Given linked list 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Modified Linked list 3 2 1 4 5 6 9 8 7 10 11 12 15 14 13 16 17 18 20 19
まとめ
このチュートリアルでは、単一連結リスト内の交互のKノードを反転する方法を学び、C++での擬似コードおよび実装を確認しました。このコードはJavaやPythonなど他の言語でも同様に記述できます。今回のアプローチでは、再帰を活用して交互のKノードを反転し、残りのノードをスキップする処理を実現しました。本チュートリアルが皆様のお役に立てば幸いです。
-
C++で循環リンクリストのノード数をカウントする方法
ノードから構成される循環リンクリスト(Circular Linked List)が与えられ、そのリスト内に存在するノードの総数を求めるのが課題です。 循環リンクリストとは、連結リストの一種であり、最初の要素が最後の要素を指し、最後の要素が最初の要素を指すという特徴を持つデータ構造です。片方向リンクリスト(Singly Linked List)でも双方向リンクリスト(Doubly Linked List)でも、この循環リンクリストとして実装することが可能です。 以下のプログラムでは、片方向リンクリストを循環リンクリストとして実装し、その中に含まれるノード数をカウントする方法を紹介します。 具体
-
【C++】再帰を使ってリンクリストの交互ノードを出力する方法
リンクリスト(連結リスト)とはリンクリストは、各要素(ノード)をメモリ上の連続しない領域に格納できる線形データ構造です。各ノードにはデータ本体と、次のノードを指すポインタが含まれており、ポインタをつなぐことで一連のリストとして扱うことができます。問題の概要今回は、与えられたリンクリストを走査し、交互(ひとつおき)のノードだけを出力するプログラムを作成します。具体的には、1番目・3番目・5番目…というように、奇数番目の要素のみを順に出力していきます。入出力例入力 : 2 -> 4 -> 1 -> 67 -> 48 -> 90 出力 : 2 -> 1 ->