配列と連結リスト

連続したメモリ領域と参照によるデータ構造の違いを理解する。

配列
連続したメモリ領域にデータを格納するデータ構造。
連結リスト
各要素が次の要素への参照を持つデータ構造。
ノード
連結リストの各要素。データと次のノードへの参照を持つ。
ヘッド
連結リストの最初のノード。
テール
連結リストの最後のノード。
計算量
アルゴリズムの実行時間を表す指標。

配列

本棚

配列は「本棚」のようなもの。本棚の各棚には、連続して本が並んでいます。ある棚の本を取り出すとき、棚番号を指定すれば、すぐに取り出せます。ただし、新しい本を追加するときは、棚の空きを探す必要があります。

なぜ配列が必要か?

配列は、連続したメモリ領域にデータを格納するためのデータ構造です。インデックスによる高速なアクセス、メモリ効率の良さ、実装の容易さなど、多くの利点があります。特に、データの順序が重要で、頻繁なアクセスが必要な場合に適しています。

いつ使うか?

  • データの順序が重要な場合
  • 頻繁なランダムアクセスが必要な場合
  • データのサイズが事前に分かっている場合
  • メモリ効率が重要な場合

実践テクニック

リスト内包表記を活用する

リスト内包表記を使うことで、簡潔で効率的なコードを書くことができます。例:`[x ** 2 for x in arr]` は、各要素を2乗した新しいリストを作成します。

enumerate()を使う

インデックス付きの走査には `enumerate()` 関数を使います。これにより、インデックスと要素の両方にアクセスできます。例:`for index, element in enumerate(arr):`

スライスを活用する

スライスを使うことで、配列の一部を簡単に取り出せます。例:`arr[1:4]` は、インデックス1から3までの要素を取り出します。

計算量を理解する

配列の操作の計算量を理解することは、パフォーマンス最適化に重要です。例:要素へのアクセスはO(1)、要素の削除はO(n)です。これらを理解して、適切なデータ構造を選択します。

Array Operations
# 配列の基本操作
arr = [1, 2, 3, 4, 5]
print(arr[0]) # 1
print(arr[2]) # 3
# 要素の追加
arr.append(6) # [1, 2, 3, 4, 5, 6]
# 要素の削除
arr.remove(3) # [1, 2, 4, 5, 6]
# 要素の挿入
arr.insert(2, 3) # [1, 2, 3, 4, 5, 6]
# 要素の取得
print(arr.pop()) # 6
print(arr) # [1, 2, 3, 4, 5]

連結リスト

宝探し

連結リストは「宝探し」のようなもの。各宝箱には、次の宝箱への地図が入っています。ある宝箱を開けると、地図を見て次の宝箱へ移動します。宝箱を追加するときは、最後の宝箱に地図を追加します。

なぜ連結リストが必要か?

連結リストは、各要素が次の要素への参照を持つデータ構造です。動的なサイズ変更、効率的な挿入・削除、メモリの柔軟性など、多くの利点があります。特に、要素の挿入・削除が頻繁な場合に適しています。

いつ使うか?

  • 要素の挿入・削除が頻繁な場合
  • データのサイズが動的に変わる場合
  • メモリの断片化を避けたい場合
  • 先頭または末尾への操作が頻繁な場合

実践テクニック

ダミーヘッドを使う

ダミーヘッドを使うことで、挿入・削除のロジックを簡素化できます。ダミーヘッドは、実際のデータを持たず、最初のノードへの参照を持つノードです。

双方向連結リストを検討する

双方向連結リストを使うことで、前後のノードへのアクセスが可能になります。これは、特定の操作(逆走査など)を効率化できます。

計算量を理解する

連結リストの操作の計算量を理解することは、パフォーマンス最適化に重要です。例:要素の追加はO(1)、要素の検索はO(n)です。これらを理解して、適切なデータ構造を選択します。

Linked List Operations
# 連結リストの基本操作
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
def display(self):
current = self.head
while current:
print(current.data, end=" -> ")
current = current.next
print("None")
# 使用例
ll = LinkedList()
ll.append(1)
ll.append(2)
ll.append(3)
ll.display() # 1 -> 2 -> 3 -> None

走査

配列と連結リストの走査方法を理解する。

Array Traversal
# 配列の走査
arr = [1, 2, 3, 4, 5]
# for ループ
for element in arr:
print(element)
# インデックス付きの走査
for index, element in enumerate(arr):
print(f"Index {index}: {element}")
# リスト内包表記
squared = [x ** 2 for x in arr]
print(squared) # [1, 4, 9, 16, 25]
Linked List Traversal
# 連結リストの走査
class LinkedList:
def __init__(self):
self.head = None
def append(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
def traverse(self):
current = self.head
while current:
print(current.data)
current = current.next
# 使用例
ll = LinkedList()
ll.append(1)
ll.append(2)
ll.append(3)
ll.traverse() # 1 2 3

計算量

配列と連結リストの操作の計算量を比較する。

Array Complexity
# 配列の計算量
arr = [1, 2, 3, 4, 5]
# 要素へのアクセス: O(1)
print(arr[2]) # 3
# 要素の追加: O(1)
arr.append(6)
# 要素の削除: O(n)
arr.remove(3)
# 要素の検索: O(n)
if 3 in arr:
print("Found")
Linked List Complexity
# 連結リストの計算量
ll = LinkedList()
# 要素の追加: O(1)
ll.append(1)
# 要素の削除: O(n)
# (削除する要素を探す必要がある)
# 要素の検索: O(n)
# (先頭から順に探す必要がある)
# 要素へのアクセス: O(n)
# (先頭から順にたどる必要がある)

使用例

配列と連結リストの実際の使用例を確認する。

Array Use Cases
# 配列の使用例
# データの収集
scores = [85, 90, 78, 92, 88]
# データのフィルタリング
high_scores = [score for score in scores if score >= 90]
print(high_scores) # [90, 92]
# データの変換
normalized = [score / 100 for score in scores]
print(normalized) # [0.85, 0.9, 0.78, 0.92, 0.88]
# データの集計
average = sum(scores) / len(scores)
print(average) # 86.6
Linked List Use Cases
# 連結リストの使用例
# 動的なデータの管理
ll = LinkedList()
# 順序を保ったデータの追加
ll.append("First")
ll.append("Second")
ll.append("Third")
# メモリ効率の良いデータ管理
# (要素の削除が頻繁な場合に有利)

比較

配列と連結リストの特徴を比較する。

特徴 配列 連結リスト
メモリレイアウト 連続 非連続
アクセス O(1) (インデックス) O(n) (走査)
挿入 O(n) O(1) (先頭)
削除 O(n) O(n) (ノード探索)
サイズ変更 固定 動的
メモリ効率 高い 低い

合格ライン

配列の基本操作ができる
連結リストの基本操作ができる
配列と連結リストの走査ができる
配列と連結リストの計算量を理解している
配列と連結リストの使い分けができる

演習課題

課題1: 配列の基本操作
配列を作成し、要素を追加・削除・検索するコードを書いてください。
課題2: 連結リストの実装
連結リストクラスを実装し、要素の追加・削除・走査を行うメソッドを作成してください。
課題3: 計算量の比較
配列と連結リストの操作の計算量を比較し、どのような場面でどちらが有利かを説明してください。

参考文献

この記事は以下の公的ガイドライン/標準に基づいています。