配列と連結リスト
連続したメモリ領域と参照によるデータ構造の違いを理解する。
配列
なぜ配列が必要か?
配列は、連続したメモリ領域にデータを格納するためのデータ構造です。インデックスによる高速なアクセス、メモリ効率の良さ、実装の容易さなど、多くの利点があります。特に、データの順序が重要で、頻繁なアクセスが必要な場合に適しています。
いつ使うか?
- データの順序が重要な場合
- 頻繁なランダムアクセスが必要な場合
- データのサイズが事前に分かっている場合
- メモリ効率が重要な場合
実践テクニック
リスト内包表記を活用する
リスト内包表記を使うことで、簡潔で効率的なコードを書くことができます。例:`[x ** 2 for x in arr]` は、各要素を2乗した新しいリストを作成します。
enumerate()を使う
インデックス付きの走査には `enumerate()` 関数を使います。これにより、インデックスと要素の両方にアクセスできます。例:`for index, element in enumerate(arr):`
スライスを活用する
スライスを使うことで、配列の一部を簡単に取り出せます。例:`arr[1:4]` は、インデックス1から3までの要素を取り出します。
計算量を理解する
配列の操作の計算量を理解することは、パフォーマンス最適化に重要です。例:要素へのアクセスはO(1)、要素の削除はO(n)です。これらを理解して、適切なデータ構造を選択します。
# 配列の基本操作arr = [1, 2, 3, 4, 5]print(arr[0]) # 1print(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()) # 6print(arr) # [1, 2, 3, 4, 5]連結リスト
なぜ連結リストが必要か?
連結リストは、各要素が次の要素への参照を持つデータ構造です。動的なサイズ変更、効率的な挿入・削除、メモリの柔軟性など、多くの利点があります。特に、要素の挿入・削除が頻繁な場合に適しています。
いつ使うか?
- 要素の挿入・削除が頻繁な場合
- データのサイズが動的に変わる場合
- メモリの断片化を避けたい場合
- 先頭または末尾への操作が頻繁な場合
実践テクニック
ダミーヘッドを使う
ダミーヘッドを使うことで、挿入・削除のロジックを簡素化できます。ダミーヘッドは、実際のデータを持たず、最初のノードへの参照を持つノードです。
双方向連結リストを検討する
双方向連結リストを使うことで、前後のノードへのアクセスが可能になります。これは、特定の操作(逆走査など)を効率化できます。
計算量を理解する
連結リストの操作の計算量を理解することは、パフォーマンス最適化に重要です。例:要素の追加はO(1)、要素の検索はO(n)です。これらを理解して、適切なデータ構造を選択します。
# 連結リストの基本操作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走査
配列と連結リストの走査方法を理解する。
# 配列の走査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]# 連結リストの走査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計算量
配列と連結リストの操作の計算量を比較する。
# 配列の計算量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")# 連結リストの計算量ll = LinkedList()
# 要素の追加: O(1)ll.append(1)
# 要素の削除: O(n)# (削除する要素を探す必要がある)
# 要素の検索: O(n)# (先頭から順に探す必要がある)
# 要素へのアクセス: O(n)# (先頭から順にたどる必要がある)使用例
配列と連結リストの実際の使用例を確認する。
# 配列の使用例# データの収集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# 連結リストの使用例# 動的なデータの管理ll = LinkedList()
# 順序を保ったデータの追加ll.append("First")ll.append("Second")ll.append("Third")
# メモリ効率の良いデータ管理# (要素の削除が頻繁な場合に有利)比較
配列と連結リストの特徴を比較する。
| 特徴 | 配列 | 連結リスト |
|---|---|---|
| メモリレイアウト | 連続 | 非連続 |
| アクセス | O(1) (インデックス) | O(n) (走査) |
| 挿入 | O(n) | O(1) (先頭) |
| 削除 | O(n) | O(n) (ノード探索) |
| サイズ変更 | 固定 | 動的 |
| メモリ効率 | 高い | 低い |
合格ライン
演習課題
参考文献
この記事は以下の公的ガイドライン/標準に基づいています。
- Python Data Structures - 公式ドキュメント
- list Documentation - list() 関数ドキュメント
- Linked Lists in Python - Real Python チュートリアル