C++ でリンクリストのノードを削除する

胡金庫 2023年10月12日
C++ でリンクリストのノードを削除する

この記事では、リンクリスト C++ の特定のノードを削除する関数を実装する方法について説明します。

リンクリスト内の特定のノードを削除する関数を実装する

この記事では、STL のコンテナーを使用せずに、単一リンクリストを最初から実装します。したがって、リンクリスト内のノードを管理するために必要ないくつかの関数を定義する必要があります。

insertNode 要素は、新しいリンクリストを作成するために呼び出す必要のあるコア関数です。リストの先頭を格納する必要があるため、insertNode はタイプ ListNode のノードへのポインタを返します。後者は、リンクリストノードの最小限の表現です。次のノードへのポインタと文字列データオブジェクトのみが含まれます。

プログラムのテストおよびデモンストレーション中に、ユーザーコンソールにいくつかのメッセージを表示するためのいくつかのユーティリティ機能を用意することが重要です。この場合、printNodes 要素を使用して、指定されたリスト構造内のすべてのノードを出力します。さらに、動的メモリに新しいノードを割り当てるときに、freeNodes 関数を使用して、プログラム終了時にリスト内の既存のすべてのノードの割り当てを解除する必要があります。

次のコードスニペットの main 関数に示すように、任意のデータでリストを作成したら、ノード削除操作を実装する deleteNode を呼び出す準備ができています。ノードを削除するには、ノードでいくつかのコーナーケースを処理する必要があります。つまり、指定されたノードがリストの先頭と同じである場合のシナリオと、指定されたノードがリスト内の唯一のノードである場合のシナリオです。

後者のシナリオでは、ノードポインタで delete 演算子を呼び出して、関数から戻ることができます。nullptr をアドレスに割り当てることは重要ですが、これは、main 関数の同じ head 変数に新しい要素を追加するのに役立つためです。

一方、リストに他のノードがある場合にヘッドノードを削除するのは比較的注意が必要です。2 番目のノードの内容をヘッドノードにコピーし、前のノードを削除する必要があります。いずれの場合も、関数呼び出しが成功したことを示す EXIT_SUCCESS 値を返すことに注意してください。

#include <iostream>
#include <string>

using std::cin;
using std::cout;
using std::endl;
using std::string;

struct ListNode {
  struct ListNode *next{};
  string data;
};

struct ListNode *insertNode(struct ListNode *root, string data) {
  auto new_node = new ListNode;
  if (root) {
    while (root->next) root = root->next;

    new_node->next = nullptr;
    new_node->data = std::move(data);
    root->next = new_node;

    return root->next;
  }
  new_node->next = nullptr;
  new_node->data = std::move(data);
  return new_node;
}

int deleteNode(struct ListNode *root, struct ListNode *node) {
  if (node == nullptr || root == nullptr) return EXIT_FAILURE;

  if (root == node) {
    if (root->next == nullptr) {
      delete node;
      root = nullptr;
      return EXIT_SUCCESS;
    }

    node = root->next;
    root->data = root->next->data;
    root->next = root->next->next;
    delete node;
    return EXIT_SUCCESS;
  }

  auto prev = root;
  while (prev->next != node && prev->next != nullptr) {
    prev = prev->next;
  }

  prev->next = node->next;
  delete node;
  return EXIT_SUCCESS;
}

void freeNodes(struct ListNode *root) {
  struct ListNode *tmp = nullptr;
  while (root) {
    tmp = root;
    root = root->next;
    delete tmp;
  }
}

void printNodes(struct ListNode *node) {
  auto count = 0;
  while (node) {
    cout << "node " << count << " - data: " << node->data << endl;
    node = node->next;
    count++;
  }
}

int main() {
  struct ListNode *head = nullptr;

  head = insertNode(head, "Xenial");

  insertNode(head, "Artful");
  printNodes(head);
  cout << " ----------------------------------- " << endl;

  deleteNode(head, head);
  printNodes(head);

  freeNodes(head);
  return EXIT_SUCCESS;
}

出力:

node 0 - data: Xenial
node 1 - data: Artful
 -----------------------------------
node 0 - data: Artful

残りの関数コードは、指定されたノードがリストチェーンからフック解除され、delete 演算子を使用して割り当てが解除される通常のケースを実装します。ただし、この操作では前のノードを見つける必要があります。これは、リストの先頭を除くすべてのノードの線形時間操作です。通常、リストの終わりを一定時間削除することを保証するために、リストの終わりをリンクリスト構造に格納する必要があります。

次のコードスニペットは、ノードがリストの中央から削除される、別の main 関数のシナリオを示しています。

#include <iostream>
#include <string>

using std::cin;
using std::cout;
using std::endl;
using std::string;

struct ListNode {
  struct ListNode *next{};
  string data;
};

struct ListNode *insertNode(struct ListNode *root, string data) {
  auto new_node = new ListNode;
  if (root) {
    while (root->next) root = root->next;

    new_node->next = nullptr;
    new_node->data = std::move(data);
    root->next = new_node;

    return root->next;
  }
  new_node->next = nullptr;
  new_node->data = std::move(data);
  return new_node;
}

int deleteNode(struct ListNode *root, struct ListNode *node) {
  if (node == nullptr || root == nullptr) return EXIT_FAILURE;

  if (root == node) {
    if (root->next == nullptr) {
      delete node;
      root = nullptr;
      return EXIT_SUCCESS;
    }

    node = root->next;
    root->data = root->next->data;
    root->next = root->next->next;
    delete node;
    return EXIT_SUCCESS;
  }

  auto prev = root;
  while (prev->next != node && prev->next != nullptr) {
    prev = prev->next;
  }

  prev->next = node->next;
  delete node;
  return EXIT_SUCCESS;
}

void freeNodes(struct ListNode *root) {
  struct ListNode *tmp = nullptr;
  while (root) {
    tmp = root;
    root = root->next;
    delete tmp;
  }
}

void printNodes(struct ListNode *node) {
  auto count = 0;
  while (node) {
    cout << "node " << count << " - data: " << node->data << endl;
    node = node->next;
    count++;
  }
}

int main() {
  struct ListNode *head = nullptr;

  head = insertNode(head, "Xenial");

  auto iter = insertNode(head, "Bionic");
  insertNode(head, "Artful");
  printNodes(head);
  cout << " ----------------------------------- " << endl;

  deleteNode(head, iter);
  printNodes(head);

  freeNodes(head);
  return EXIT_SUCCESS;
}

出力:

node 0 - data: Xenial
node 1 - data: Bionic
node 2 - data: Artful
 -----------------------------------
node 0 - data: Xenial
node 1 - data: Artful
著者: 胡金庫
胡金庫 avatar 胡金庫 avatar

DelftStack.comの創設者です。Jinku はロボティクスと自動車産業で8年以上働いています。自動テスト、リモートサーバーからのデータ収集、耐久テストからのレポート作成が必要となったとき、彼はコーディングスキルを磨きました。彼は電気/電子工学のバックグラウンドを持っていますが、組み込みエレクトロニクス、組み込みプログラミング、フロントエンド/バックエンドプログラミングへの関心を広げています。

LinkedIn Facebook

関連記事 - C++ Data Structure