単方向連結リストの基礎とJavaによる実装

連結リストとは

連結リストは、配列とは異なり連続したメモリ領域を必要としないデータ構造です。各ノードが次のノードへの参照(ポインタ)を持つことで、離散的なメモリブロックを連結します。

この特性により、要素の検索は先頭から順次走査する必要がありますが、挿入や削除は参照の付け替えだけで完結します。その結果、配列と比較して検索のコストが高く挿入・削除のコストが低いという特徴を持ちます。

連結リストはノードによって構成されます。各ノードは最低限、データを格納するフィールドと次のノードへの参照を持ちます。

ノードクラスの実装

まず、連結リストを構成するノードを表すクラスを定義します。

public class ListNode {

    private int id;
    private Object value;
    private ListNode successor;

    public ListNode(int id, Object value) {
        this.id = id;
        this.value = value;
        this.successor = null;
    }

    public int getId() {
        return this.id;
    }

    public Object getValue() {
        return this.value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

    public ListNode getSuccessor() {
        return this.successor;
    }

    public void setSuccessor(ListNode node) {
        this.successor = node;
    }

    @Override
    public String toString() {
        return "ListNode{id=" + id + ", value=" + value + "}";
    }
}

単方向連結リストの実装

末尾への追加

リストの末尾に新しいノードを追加するには、最後のノードまで走査し、その参照を新ノードに設定します。

public class SinglyLinkedList {

    private ListNode headerNode;

    public SinglyLinkedList() {
        this.headerNode = new ListNode(0, null);
    }

    public void append(ListNode newNode) {
        ListNode current = this.headerNode;
        
        while (current.getSuccessor() != null) {
            current = current.getSuccessor();
        }
        
        current.setSuccessor(newNode);
    }
}

順序を維持した挿入

ID順にノードを挿入する場合、適切な挿入位置を見つける必要があります。

public void insertOrdered(ListNode newNode) {
    ListNode previous = this.headerNode;
    
    while (previous.getSuccessor() != null) {
        ListNode nextNode = previous.getSuccessor();
        
        if (nextNode.getId() > newNode.getId()) {
            newNode.setSuccessor(nextNode);
            previous.setSuccessor(newNode);
            return;
        }
        
        if (nextNode.getId() == newNode.getId()) {
            throw new IllegalArgumentException("IDが重複しています: " + newNode.getId());
        }
        
        previous = nextNode;
    }
    
    previous.setSuccessor(newNode);
}

全ノードの表示

リスト内の全ノードを走査して表示します。

public void displayAll() {
    if (this.headerNode.getSuccessor() == null) {
        System.out.println("リストは空です");
        return;
    }
    
    ListNode current = this.headerNode.getSuccessor();
    
    while (current != null) {
        System.out.println(current);
        current = current.getSuccessor();
    }
}

IDによる検索

指定したIDを持つノードを検索します。

public ListNode findById(int targetId) {
    if (this.headerNode.getSuccessor() == null) {
        throw new NoSuchElementException("リストは空です");
    }
    
    ListNode current = this.headerNode.getSuccessor();
    
    while (current != null) {
        if (current.getId() == targetId) {
            return current;
        }
        current = current.getSuccessor();
    }
    
    throw new NoSuchElementException("ID: " + targetId + " のノードが見つかりません");
}

ノードの更新

指定したIDのノードの値を更新します。

public void modifyNode(ListNode updatedNode) {
    if (this.headerNode.getSuccessor() == null) {
        throw new NoSuchElementException("リストは空です");
    }
    
    ListNode current = this.headerNode.getSuccessor();
    
    while (current != null) {
        if (current.getId() == updatedNode.getId()) {
            current.setValue(updatedNode.getValue());
            return;
        }
        current = current.getSuccessor();
    }
    
    throw new NoSuchElementException("更新対象のノードが見つかりません");
}

ノードの削除

単方向リストでは、削除対象のノードの前のノードを見つける必要があります。前のノードの参照を、削除対象の次のノードに付け替えます。

public void removeById(int targetId) {
    ListNode previous = this.headerNode;
    
    while (previous.getSuccessor() != null) {
        ListNode target = previous.getSuccessor();
        
        if (target.getId() == targetId) {
            previous.setSuccessor(target.getSuccessor());
            return;
        }
        
        previous = target;
    }
    
    throw new NoSuchElementException("削除対象のノードが見つかりません");
}

循環連結リスト

循環連結リストは、単方向連結リストの一種で、末尾ノードの参照が先頭ノードを指すことでリング状に構成されます。この構造は、周回処理が必要な問題(ヨセフスの問題など)の解決に適しています。

タグ: 連結リスト データ構造 Java アルゴリズム 単方向リスト

8月26日 21:39 投稿