C言語でXOR演算子を使って、古き良きXORリストを実装する方法

XORリストの基本概念

XORリストは、各ノードが前後のノードのアドレスをXOR演算子を使って1つのポインタフィールドに格納する特別な連結リストです。これにより、通常の双方向リストで必要な2つのポインタ(前と次)の代わりに、1つのポインタフィールドで済むため、メモリ使用量を削減できます。

この手法の鍵となるのは、XOR演算子の性質です。AとBがポインタ(アドレス)である場合、A ^ Bの結果は、AとBの両方のビットが異なる部分を表します。そして、この結果に再度BをXORすると、元のAが復元されます。

例: (A ^ B) ^ B = A

この性質を利用して、ノード内に「前のノードのアドレス ^ 次のノードのアドレス」という値を保存します。これにより、現在のノードと「直前に訪れたノード」のアドレスが分かれば、「次のノード」のアドレスを計算で求めることができます。

実装

まず、ノードとリストの構造体を定義します。ポインタのアドレス演算を行うため、uintptr_t型を使用します。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

typedef struct XORNode {
    int data;
    uintptr_t ptr; // 前のノードと次のノードのアドレスをXORした値
} XORNode;

typedef struct XORList {
    XORNode* head;
    XORNode* tail;
} XORList;

ノードの作成

新しいノードを作成するヘルパー関数です。データと、初期状態のXORポインタ(0)を設定します。

XORNode* createNode(int value) {
    XORNode* newNode = (XORNode*)malloc(sizeof(XORNode));
    if (!newNode) return NULL; // メモリ確保失敗

    newNode->data = value;
    newNode->ptr = 0; // 初期状態では前後のノードがないため0
    return newNode;
}

ノードの追加

リストの末尾に新しいノードを追加する関数です。空のリストの場合と、すでにノードがある場合で処理を分けます。

void appendNode(XORList* list, int value) {
    XORNode* newNode = createNode(value);

    if (list->head == NULL) {
        // リストが空の場合
        list->head = newNode;
        list->tail = newNode;
    } else {
        // リストが空でない場合
        // 新しいノードのptrを、現在の末尾ノードのアドレスとXORする
        newNode->ptr = (uintptr_t)list->tail;
        // 現在の末尾ノードのptrを更新する
        // 末尾ノードのptrは「前のノード ^ 次のノード」なので、
        // 新しいノードのアドレスをXORして「前のノード ^ 新しいノード」にする
        list->tail->ptr ^= (uintptr_t)newNode;
        // リストの末尾を新しいノードに更新
        list->tail = newNode;
    }
}

リストの走査

リストを順方向に走査するための関数です。前のノードのアドレスを覚えておく必要があります。

XORNode* getNextNode(XORNode* currentNode, XORNode** prevNode) {
    XORNode* nextNode = (XORNode*)(currentNode->ptr ^ (uintptr_t)*prevNode);
    *prevNode = currentNode; // 前のノードを更新
    return nextNode;
}

void traverseList(XORList* list) {
    XORNode* currentNode = list->head;
    XORNode* prevNode = NULL;

    while (currentNode != NULL) {
        printf("%d ", currentNode->data);
        XORNode* temp = currentNode;
        currentNode = getNextNode(currentNode, &prevNode);
    }
    printf("
");
}

末尾ノードの削除

リストの末尾からノードを削除する関数です。

void detachTail(XORList* list) {
    if (list->tail == NULL) {
        // リストが空
        return;
    }

    if (list->head == list->tail) {
        // ノードが1つだけの場合
        free(list->tail);
        list->head = NULL;
        list->tail = NULL;
    } else {
        // ノードが2つ以上の場合
        XORNode* oldTail = list->tail;
        XORNode* newTail = (XORNode*)(oldTail->ptr); // 新しい末尾ノード

        // 新しい末尾ノードのptrを更新する
        // これまでのptrは「前のノード ^ 旧末尾ノード」なので、
        // 旧末尾ノードのアドレスをXORして「前のノード」だけに戻す
        newTail->ptr ^= (uintptr_t)oldTail;

        // リストの末尾を更新して、旧末尾ノードを解放
        list->tail = newTail;
        free(oldTail);
    }
}

タグ: C言語 XORリスト データ構造 ポインタ

8月4日 11:12 投稿