Java
 Computer >> コンピューター >  >> プログラミング >> Java

JavaでK個のソート済み連結リストを効率的にマージする方法

本記事では、それぞれ異なる長さを持ち、昇順にソートされたK個の連結リスト(Linked List)を、1つのソート済み結果リストへマージする方法を解説します。マージ後のリストは昇順に並べ替えられ、その結果がユーザーに出力されます。この問題は、JavaのPriorityQueue(優先度付きキュー)を使うことで、効率よく美しく実装できます。

具体例で理解しよう

入力 −

int k = 3;
list[0] = new Node(11);
list[0].next = new Node(15);
list[0].next.next = new Node(17);
list[1] = new Node(2);
list[1].next = new Node(3);
list[1].next.next = new Node(26);
list[1].next.next.next = new Node(39);
list[2] = new Node(4);
list[2].next = new Node(8);
list[2].next.next = new Node(10);

出力 − マージ後のリストは以下の通りです。

2>> 3>> 4>> 8>> 10>> 11>> 15>> 17>> 26>> 39>> null

解説 − 昇順にソートされた3つの連結リストが与えられています。マージ処理では、JavaのComparatorを使って各リストの先頭ノード同士を比較し、最も小さい値を持つノードから順に結果リストへ連結していきます。

入力 −

int k = 2;
list[0] = new Node(1);
list[0].next = new Node(4);
list[0].next.next = new Node(5);
list[1] = new Node(2);
list[1].next = new Node(3);
list[1].next.next = new Node(6);
list[1].next.next.next = new Node(8);

出力 − マージ後のリストは以下の通りです。

1>> 2>> 3>> 4>> 5>> 6>> 8>> null

解説 − この場合も同様に、2つのソート済みリストの先頭要素を比較しながら、小さい方のノードを結果リストの末尾へ順次追加していくことで、全体がソートされた1つのリストが完成します。

プログラムで使用するアプローチ

  • マージ対象となるリストの個数(K)を入力として受け取ります。
  • 連結リストのノードを生成するためのNodeクラスを定義します。各ノードはデータ(data)と次ノードへの参照(next)を持ちます。
  • 各連結リストをソート済みの状態で初期化し、先頭ノードの配列とリスト数kを引数としてmergeLists関数に渡します。
  • 関数内では、ノードの値を基準に並べるPriorityQueueを作成し、すべてのリストの先頭ノードをキューに登録します。
  • キューが空になるまでループを回し、最小値のノードを取り出しては結果リストに連結します。最初に取り出したノードが結果リストの先頭(head)となります。
  • 取り出したノードに次のノード(min.next)が存在する場合は、それをキューに再登録します。これにより、常に各リストの未処理の中で最小の候補だけがキューに残ります。
  • すべてのノードを処理し終えたら、結果リストの先頭を呼び出し元に返します。

この手法の計算量は、全ノード数をN、リスト数をKとすると、時間計算量はO(N log K)、空間計算量はO(K)となり、単純な全比較方式(O(N×K))よりも大幅に高速です。

サンプルコード

import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;

class Node {
    int data;
    Node next;
    public Node(int data) {
        this.data = data;
        this.next = null;
    }
}

public class testClass {
    public static Node mergeLists(Node[] list, int k) {
        PriorityQueue<Node> priorityQueue;
        priorityQueue = new PriorityQueue<Node>(Comparator.comparingInt(a -> ((Node) a).data));
        priorityQueue.addAll(Arrays.asList(list).subList(0, k));
        Node head = null, last = null;
        while (!priorityQueue.isEmpty()) {
            Node min = priorityQueue.poll();
            if (head == null) {
                head = last = min;
            } else {
                last.next = min;
                last = min;
            }
            if (min.next != null) {
                priorityQueue.add(min.next);
            }
        }
        return head;
    }

    public static void main(String[] s) {
        int k = 3;
        Node[] list = new Node[k];
        list[0] = new Node(11);
        list[0].next = new Node(15);
        list[0].next.next = new Node(17);
        list[1] = new Node(2);
        list[1].next = new Node(3);
        list[1].next.next = new Node(26);
        list[1].next.next.next = new Node(39);
        list[2] = new Node(4);
        list[2].next = new Node(8);
        list[2].next.next = new Node(10);
        System.out.println("The merged list is-->");
        Node head = mergeLists(list, k);
        while (head != null) {
            System.out.print(head.data + ">> ");
            head = head.next;
        }
        System.out.print("null");
    }
}

実行結果

上記のコードを実行すると、以下の出力が得られます。

The merged list is-->
2>> 3>> 4>> 8>> 10>> 11>> 15>> 17>> 26>> 39>> null

まとめ

K個のソート済み連結リストのマージには、PriorityQueueを活用するのが最も効率的です。各リストの先頭ノードだけをキューに保持することでメモリ使用量をO(K)に抑えつつ、O(N log K)の時間計算量で全体をソートされた1つのリストに統合できます。アルゴリズムの学習だけでなく、実務でのデータ統合処理にも応用できる重要なテクニックなので、ぜひマスターしておきましょう。

  1. Javaで連結リストを交互の位置にある別の連結リストにマージする方法

    この記事では、2つの連結リスト(以下 List_1 および List_2)が与えられたとき、List_2 の要素を List_1 の交互の位置に挿入してマージする方法を解説します。List_1 に挿入しきれなかった要素は、「List_2 の残りの要素」として出力されます。具体例入力 −List_1 =List_2 =出力 − マージ後のリスト:説明 − 2つのリスト List_1 と List_2 が与えられています。List_2 の要素を可能な限り List_1 の交互の位置に挿入すると、マージ後の List_1 は 10→3→2→1→1→4→2→5→5 となり、List_2 には 7→2

  2. Javaで2つの連結リストの交点を見つける方法

    連結リスト(Linked List)は、各ノードが2つのブロックで構成される線形データ構造です。一方のブロックにはノードの値(データ)が格納され、もう一方のブロックには次のノードへのアドレス(ポインタ)が格納されます。ここでは、2つの連結リストが交差するノードを見つける問題を扱います。2つのリストが共通のノードを持つ場合、その交点となるノードを特定します。交点が存在しない場合は、NULL(または空)を出力として返します。具体例入力1:出力:2説明: 与えられた連結リストは値「2」のノードで交差しているため、出力として「2」を返します。入力2:出力:NULL説明: 共通のノードが存在しないため、