プログラミング原人の進化ログ

プログラミング原人の進化論

オレ プログラミング ベンキョウ スル。マナンダ コト カク。

C++で連結リストを1から実装する

データ構造第3弾、今回は連結リストを実装してみます。
これまでスタックやキューを実装してきましたが、今回はちょっとそれらより難しそうです。

目標

標準ライブラリに入っている連結リストを、自分で実装してみることで理解する。

双方向連結リスト

双方向連結リストとは、リストの各要素が、それぞれ直前と直後の要素の情報を持つようにリンクさせたデータ構造のことです。
要素の削除や挿入を定数時間で高速に行えますが、要素へのアクセスに O(N)かかります。実用の場面ではそれほど使われないみたいです。

実装してみる

f:id:atuyusan:20190215094003p:plain

とはいうものの、勉強のために一度実装してみます。今回実装するのは双方向連結リストで、次のような機能を備えています(標準ライブラリのリストはもっと色々な機能があります)。

insert(x) 先頭にキーxの要素を挿入する
delete_key(x) キーxを持つ最初の要素を削除する
delete_first() 先頭の要素を削除する
delete_last() 末尾の要素を削除する

ポイントとなるのが、番兵と呼ばれる特殊なノードで、これは先頭の要素と末尾の要素にリンクして、特定の値を持たず、両者をつなぐ役割を持っています。これによってスマートに実装できます。要素の挿入や削除は、ノードのリンクをつなぎかえることによって行います。また、要素にアクセスするには、ノードのリンクを目的のノードまで次々と辿っていきます。
ここでは番兵をnilとし、Nodeクラスのメンバであるポインタprev、nextがそれぞれ直前、直後のノードを指しています。また、メンバ変数keyでノードに格納されている値にアクセスします。

#include<iostream>
using namespace std;

class LinkedList {
private:
  class Node {
  public:
    int key;
    Node *prev;
    Node *next;
  };
  
  Node *nil;  // 番兵

  // keyを持つ最初のノードのポインタを返す
  Node* list_search(int key) {
    Node *current = nil->next;
    while (current != nil && current->key != key) {
      current = current->next;
    }
    return current;
  }

  // 引数に与えられたノードを削除
  void delete_node(Node *node) {
    if (node == nil) return;  // nodeが番兵の時は何もしない
    // リンクつなぎ変え
    node->prev->next = node->next;
    node->next->prev = node->prev;
    delete node;  // メモリ解放
  }
  
public:
  LinkedList() {
    nil = new Node;
    nil->prev = nil;
    nil->next = nil;
  }

  // keyを先頭に挿入
  void insert(int key) {
    Node *x;  // 新しいノードをさすポインタ
    x = new Node;
    x->key = key;
    // リンクのつなぎ替え
    x->next = nil->next;
    nil->next->prev = x;
    nil->next = x;
    x->prev = nil;
  }  

  // keyを持つ最初のノードを削除
  void delete_key(int key) {
    delete_node(list_search(key));
  }

  // 先頭を削除
  void delete_first() {
    delete_node(nil->next);
  }

  // 末尾を削除
  void delete_last() {
    delete_node(nil->prev);
  }

  // リストの全ての要素を表示
  void print_list() {
    int cnt = 0;
    Node *current = nil->next;
    
    while (current != nil) {
      if (cnt++ > 0) cout << " ";
      cout << current->key;
      current = current->next;
    }
    cout << endl;      
  }
  
};

int main(void) {
  LinkedList l;
  // 1~10を格納
  for (int i = 10; i > 0; i--) l.insert(i);
  l.print_list();
  // 末尾を削除
  l.delete_last();
  l.print_list();
  // 先頭を削除
  l.delete_first();
  l.print_list();
  // 3を削除
  l.delete_key(3);
  l.print_list();

  return 0;
}


実行結果

1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9
2 3 4 5 6 7 8 9
2 4 5 6 7 8 9

まとめ

連結リストを実装しました。ポインタがぽんぽん出てくるので、C++初心者には少し難しかったです。

参考

渡部有隆. プログラミングコンテスト攻略のためのアルゴリズムとデータ構造. Ozy, 秋葉拓哉協力. 株式会社マイナビ. 2015.