データ構造:
-
配列: 同じタイプの要素のセットを格納できる線形データ構造。Java の配列は固定長であり、配列内の要素には添字を使用してアクセスできます。
-
リンク リスト: 一連のノードで構成される線形データ構造。各ノードにはデータ要素と次のノードへのポインターが含まれます。Java では、LinkedList を使用して単一リンク リストを実装したり、カスタム クラスを使用して二重リンク リストを実装したりできます。
-
スタック: 配列またはリンク リストを使用して実装できる後入れ先出し (LIFO) データ構造。スタックは、Stack クラスを使用して Java で実装できます。
-
キュー: 配列またはリンク リストを使用して実装できる先入れ先出し (FIFO) データ構造。Java では、Queue インターフェイスを使用してキューを実装したり、LinkedList を使用して両端キューを実装したりできます。
-
ツリー: ノードとノード間の接続で構成される非線形データ構造。Java では、TreeNode クラスを使用してバイナリ ツリーを実装したり、カスタム クラスを使用して他のタイプのツリーを実装したりできます。
-
ヒープ: ヒープ特性 (大きなルート ヒープまたは小さなルート ヒープ) を満たす特別なツリー データ構造。Java では、PriorityQueue を使用してヒープを実装できます。
-
グラフ: 一連のノードとそれらの間の接続で構成される非線形データ構造。グラフは、カスタム クラスを使用して Java で実装できます。
目次
配列
配列に関する共通の知識事項は次のとおりです。
-
配列を定義する: 配列を定義するには、配列タイプ、配列名、および配列長を指定する必要があります。たとえば、長さ 10 の整数配列を定義するには、次のコードを使用できます。
int[] arr = new int[10];
-
配列の初期化: 配列を定義するときに初期化することも、プログラムの後半で配列要素に値を割り当てることもできます。たとえば、文字列の配列を定義し、次のように初期化します。
String[] names = {"Alice", "Bob", "Charlie"};
-
配列要素へのアクセス: 配列要素には添え字を介してアクセスできます。添字は 0 で始まり、配列の長さから 1 を引いた長さで終わります。たとえば、上で定義した名前配列の最初の要素にアクセスするには、次のようにします。
String name = names[0];
-
多次元配列: Java では、2 次元配列、3 次元配列などの多次元配列を定義できます。2 次元配列を定義するには、配列タイプ、配列名、行数、列数を指定する必要があります。たとえば、3 行 4 列の 2 次元整数配列を定義します。
int[][] arr = new int[3][4];
-
配列の長さ: 配列の長さは、配列の長さプロパティを通じて取得できます。たとえば、上で定義した名前配列の長さを取得するには、次のようにします。
int len = names.length;
-
配列の走査: for ループまたは foreach ループを使用して、配列内の要素を走査できます。たとえば、foreach ループを使用して、上で定義した名前配列内のすべての要素を反復処理します。
for (String name : names) { System.out.println(name); }
-
配列の並べ替え: Arrays クラスの sort メソッドを使用して配列を並べ替えることができます。たとえば、整数の配列を並べ替えるには次のようにします。
int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3}; Arrays.sort(arr);
-
配列のコピー: Arrays クラスの copyOf メソッドを使用して配列をコピーできます。たとえば、整数の配列を新しい配列にコピーするには、次のようにします。
int[] arr = {1, 2, 3, 4, 5}; int[] newArr = Arrays.copyOf(arr, arr.length);
リンクされたリスト
リンク リストは、キュー、スタック、その他の高度なデータ構造を実装するために使用できる基本的なデータ構造です。Java では、リンク リストは通常、Node オブジェクトによって表されます。各ノードにはデータ要素と次のノードへの参照が含まれています。データ構造とアルゴリズム (Java Edition) のリンク リストに関する知識ポイントを以下に示します。
1. リンク リストの基本構造: リンク リストはノードで構成され、各ノードにはデータ要素と次のノードへの参照が含まれます。次のように:
class Node {
int data; // 数据元素
Node next; // 指向下一个节点的引用
}
リンク リストの定義: リンク リストはノードで構成され、各ノードにはデータ要素と次のノードへのポインターが含まれます。
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
2. リンク リストのトラバース: リンク リストのトラバースとは、リンク リスト内の各ノードを順番に訪問することを指します。リンクされたリストは、ループまたは再帰を使用して走査できます。次のように:
// 使用循环方式遍历链表
Node current = head;
while (current != null) {
// 访问当前节点的数据元素
System.out.print(current.data + " ");
// 将当前节点移动到下一个节点
current = current.next;
}
// 使用递归方式遍历链表
public void traverse(Node node) {
if (node != null) {
// 访问当前节点的数据元素
System.out.print(node.data + " ");
// 递归访问下一个节点
traverse(node.next);
}
}
リンク リストのトラバーサル: リンク リストの先頭ノードから開始して、末尾ノードまで各ノードを順番に訪問します。
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
// 输出:1 2 3
3. リンクリストの挿入操作: リンクリストの挿入操作とは、リンクリストに新しいノードを挿入することを指します。挿入動作は頭部挿入方式と尾部挿入方式に分けられます。次のように:
// 头插法:将新节点插入到链表的头部
Node newNode = new Node(5);
newNode.next = head;
head = newNode;
// 尾插法:将新节点插入到链表的尾部
Node newNode = new Node(5);
if (head == null) {
head = newNode;
} else {
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
4. リンクリストの削除操作: リンクリストの削除操作とは、リンクリスト内のノードを削除することを指します。削除操作には通常、削除されるノードの先行ノードに関する知識が必要です。次のように:
// 删除指定节点
Node current = head;
Node prev = null;
while (current != null) {
if (current.data == key) {
if (prev == null) {
// 要删除的节点是头节点
head = current.next;
} else {
// 删除中间节点或尾节点
prev.next = current.next;
}
return true;
}
prev = current;
current = current.next;
}
return false;
ヘッド ノードを削除します。
head = head.next;
末尾ノードを削除します。
ListNode cur = head;
while (cur.next.next != null) {
cur = cur.next;
}
cur.next = null;
指定したノードを削除します。
ListNode prev = null;
ListNode cur = head;
while (cur != null) {
if (cur.val == target) {
if (prev == null) {
head = head.next;
} else {
prev.next = cur.next;
}
break;
}
prev = cur;
cur = cur.next;
}
5. リンクリストの反転操作: リンクリストの反転操作とは、リンクリスト内のノードを反転することを指します。逆の操作では、通常、prev、current、next の 3 つのポインターを使用する必要があります。次のように:
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode current = head;
ListNode next = null;
while (current != null) {
// 保存当前节点的下一个节点
next = current.next;
// 反转当前节点的指针,指向前一个节点
current.next = prev;
// 将prev指针指向当前节点
prev = current;
// 将current指针指向next节点,继续遍历链表
current = next;
}
// prev指向的节点即为反转后的头节点
return prev;
}
このアルゴリズムでは、最初に 3 つのポインター (prev、current、next) を定義します。prev は前のノードを指し、current は現在のノードを指し、next は現在のノードの次のノードを指します。ループでは、現在のノードの次のポインタが前のノードを指し、次に前のポインタが現在のノードを指し、現在のポインタが次のノードを指し、リンク リストの最後に到達するまでリンク リストのトラバースを続けます。最後に、prev が指すノードが反転ヘッド ノードです。
反転操作では、反転操作後にリンク リストの次のノードに移動できなくなるのを防ぐために、現在のノードの次のノードを事前に保存する必要があることに注意してください。
6. リンク リストの中間ノード: リンク リストの中間ノードを見つけるには、高速ポインタと低速ポインタの方法を使用できます。
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
7. リンク リストのマージ: 2 つのソートされたリンク リストを 1 つのソートされたリンク リストにマージします。
このうち、ListNode はリンク リストのノード クラス、val はノードの値、next は次のノードへのポインタです。
public class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
}
public class LinkedList {
public ListNode merge(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
ListNode head = null;
if (l1.val < l2.val) {
head = l1;
head.next = merge(l1.next, l2);
} else {
head = l2;
head.next = merge(l1, l2.next);
}
return head;
}
}
このメソッドは、まず l1 と l2 が null かどうかを判断し、どちらかが null であれば、別のリンク リストを直接返します。次に、マージされたリンク リストのヘッド ノードとしてヘッド ノードを定義し、l1 と l2 の値を比較し、小さい方の値をヘッド ノードとして使用し、その次を再帰的にマージされたリンク リストにポイントします。最後に、ヘッド ノードに戻ります。
アルゴリズムの時間計算量は O(n) です。ここで、n は 2 つのリンクされたリスト内のノードの合計数です。
スタック
スタック(Stack)は、「先入れ後出し」の特徴を持つ線形データ構造です。スタックの挿入操作はプッシュと呼ばれ、削除操作はポップと呼ばれます。スタックには非常に重要な操作もあります。これは、スタックの最上位要素を表示しますが、スタックの外に出ることはなく、「スタックの最上位要素」(ピーク) と呼ばれます。
以下は、スタックに関する一般的な知識のポイントです。
-
スタックの実装: スタックは配列またはリンク リストによって実装できます。
-
スタック操作: スタックの主な操作には、スタック、ポップ、スタックが空かどうかの判断、スタックの最上位要素の表示などが含まれます。
-
スタックのアプリケーション: スタックの一般的なアプリケーションには、式の評価、ブラケット マッチング、迷路解決、再帰関数の実装などがあります。
以下は、いくつかの一般的なスタック操作のサンプル コードです。
1. プッシュ操作
public void push(int val) {
stack.add(val);
}
2. ポップ操作
public int pop() {
if (stack.isEmpty()) {
throw new EmptyStackException();
}
return stack.remove(stack.size() - 1);
}
3. スタックが空かどうかを確認します。
public boolean isEmpty() {
return stack.isEmpty();
}
4. スタックの最上位要素を表示する
public int peek() {
if (stack.isEmpty()) {
throw new EmptyStackException();
}
return stack.get(stack.size() - 1);
}
列
キュー (Queue) は、先入れ先出し (FIFO) の特性を持つ線形データ構造です。キューには、エンキューとデキューという 2 つの基本操作があります。エンキュー操作はキューの最後に要素を挿入し、デキュー操作はキューの最初の要素を削除して返します。
Java で一般的に使用されるキューの実装クラスは次のとおりです。
1. LinkedList: LinkedList は Java 標準ライブラリの二重リンク リスト実装であり、キューとしても使用できます。LinkedList は Deque インターフェイスを実装しているため、両端キューとしても使用できます。
以下は、LinkedList を使用してキューを実装するサンプル コードです。
import java.util.LinkedList;
import java.util.Queue;
public class MyQueue {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.offer(1); // 入队操作
queue.offer(2);
queue.offer(3);
System.out.println(queue.peek()); // 获取队列头部元素
System.out.println(queue.poll()); // 出队操作
System.out.println(queue.poll());
System.out.println(queue.poll());
}
}
2. ArrayDeque: ArrayDeque は、キューおよびスタックとして使用できる配列ベースの両端キューです。
以下は、ArrayDeque を使用してキューを実装するサンプル コードです。
import java.util.ArrayDeque;
import java.util.Queue;
public class MyQueue {
public static void main(String[] args) {
Queue<Integer> queue = new ArrayDeque<>();
queue.offer(1); // 入队操作
queue.offer(2);
queue.offer(3);
System.out.println(queue.peek()); // 获取队列头部元素
System.out.println(queue.poll()); // 出队操作
System.out.println(queue.poll());
System.out.println(queue.poll());
}
}
3. PriorityQueue: PriorityQueue は、要素の優先度に従ってソートできる優先度ヒープ (最小ヒープ) に基づくキュー実装です。
以下は、PriorityQueue を使用してキューを実装するためのサンプル コードです。
import java.util.PriorityQueue;
import java.util.Queue;
public class MyQueue {
public static void main(String[] args) {
Queue<Integer> queue = new PriorityQueue<>();
queue.offer(3); // 入队操作
queue.offer(1);
queue.offer(2);
System.out.println(queue.peek()); // 获取队列头部元素
System.out.println(queue.poll()); // 出队操作
System.out.println(queue.poll());
System.out.println(queue.poll());
}
}
上記 3 つの実装メソッドに加えて、Java には BlockingQueue や ConcurrentLinkedQueue などのキュー実装クラスも用意されており、必要に応じて選択できます。
木
データ構造とアルゴリズム (Java バージョン) ツリーのすべてのナレッジ ポイント:
ツリーは非常に一般的なデータ構造です。n 個のノードの集合であり、各ノードには 0 個以上の子ノードがあります。親ノードのないノードはルート ノードと呼ばれます。ルート ノードを除く各子ノードには親ノードがあり、それらはエッジで接続されています。ツリー構造には適切な階層があり、通常はファイル システムや HTML ドキュメントなどの階層的に関連するデータを格納するために使用されます。
ツリーの基本概念:
- ノード: ツリー内の基本単位。
- ルート ノード: ツリーの最上位ノード。
- 親ノード: ノードの直属の上位ノード。
- 子ノード: ノードの直接の下位ノード。
- リーフノード: 子ノードを持たないノード。
- 兄弟ノード: 同じ親ノードを持つノード。
- サブツリー: ノードとそのすべての子のコレクション。
- 深さ: ルート ノードから現在のノードまでのパスの長さ。
- 高さ: 現在のノードからリーフ ノードまでの最長パスの長さ。
ツリートラバーサル:
ツリー トラバーサルとは、ツリー内のすべてのノードを特定の順序で訪問するプロセスを指します。一般的なツリー走査方法は 3 つあります。
- 事前順序トラバーサル: 「ルート ノード - 左のサブツリー - 右のサブツリー」の順序で各ノードを訪問します。
- インオーダートラバーサル: 「左のサブツリー - ルートノード - 右のサブツリー」の順序で各ノードを訪問します。
- 事後走査: 「左のサブツリー - 右のサブツリー - ルート ノード」の順序で各ノードを訪問します。
ツリー トラバーサルは、再帰と反復の 2 つの方法で実装できます。
ツリーに対する一般的な操作:
- ノードの挿入: 新しいノードをツリーに挿入します。
- ノードの削除: ツリーからノードを削除します。
- ノードの検索: ツリー内の指定したノードを検索します。
- 木の高さを取得する: 木の高さを計算します。
- ツリー内のノードの数を取得する: ツリー内のノードの数を計算します。
ツリーの実装:
ツリーは、チェーン ストレージとアレイ ストレージを使用して実装できます。
リンク ストレージとは、ツリー内の各要素を表すノードの使用を指します。各ノードには、値、左の子ノード、右の子ノードの 3 つの部分が含まれます。
配列ストレージとは、配列を使用してツリー内の各要素を表し、配列の添え字を使用してノード番号を表し、各ノードに値が含まれることを指します。
一般的なツリー構造には、バイナリ ツリー、バイナリ サーチ ツリー、バランス ツリー、ヒープなどが含まれます。
上記は、データ構造とアルゴリズム (Java Edition) のツリーに関連する知識ポイントであり、ツリーの基本概念、走査、操作、実装が含まれます。
ヒープ
ヒープは完全なバイナリ ツリーであり、最大ヒープと最小ヒープの 2 つの形式があります。最大ヒープでは、親ノードの値は常にその子の値以上になります。ただし、min-heap では、親ノードの値は常にその子ノードの値以下になります。
ヒープは、PriorityQueue クラスを使用して Java で実装できます。PriorityQueue は優先キューであり、ヒープを使用して内部的に実装されます。追加および削除操作の時間計算量は O(logn) です。
一般的なヒープ操作には次のものがあります。
-
要素の追加: 新しい要素をヒープの最後に追加し、ヒープのプロパティを満たすようにヒープを上方に調整します。
-
要素の削除: ヒープの最上位要素を削除し、次にヒープの最後の要素をヒープの最上位に移動し、ヒープのプロパティに合うようにヒープを下方に調整します。
-
ヒープの最上位要素を取得する: ヒープの最上位要素、つまりヒープ内の最大または最小の要素を返します。
以下は、PriorityQueue を使用してヒープを実装するサンプル コードです。
import java.util.PriorityQueue;
public class HeapExample {
public static void main(String[] args) {
// 创建一个最小堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
// 添加元素
minHeap.add(3);
minHeap.add(1);
minHeap.add(2);
// 获取堆顶元素
System.out.println(minHeap.peek()); // 输出1
// 删除堆顶元素
minHeap.poll();
// 获取堆顶元素
System.out.println(minHeap.peek()); // 输出2
}
}
上記のコードは最小ヒープを作成し、3 つの要素を追加します。次に、 Peak() メソッドを使用してヒープの最上位要素を取得し、ヒープの最上位要素を削除して、ヒープの最上位要素を再度取得します。最終的な出力は 1 と 2 で、これはヒープの性質に準拠しています。
写真
グラフは、現実世界の多くの問題を表すために使用できるノード (または頂点) とエッジで構成されるデータ構造です。Java のグラフは、隣接行列または隣接リストを使用して表現できます。
Java のグラフに関するすべての知識ポイントは次のとおりです。
1. 隣接行列の表記法
隣接行列表記では、2 次元配列を使用して、グラフ内の各ノード間の関係を表します。2 次元配列の行と列はノードに対応し、各要素の値は対応するノード間にエッジがあるかどうかを示します。
サンプルコード:
public class Graph {
private int[][] adjMatrix;
private int vertexCount;
public Graph(int vertexCount) {
this.vertexCount = vertexCount;
adjMatrix = new int[vertexCount][vertexCount];
}
public void addEdge(int i, int j, int weight) {
adjMatrix[i][j] = weight;
adjMatrix[j][i] = weight;
}
public void removeEdge(int i, int j) {
adjMatrix[i][j] = 0;
adjMatrix[j][i] = 0;
}
public int getEdge(int i, int j) {
return adjMatrix[i][j];
}
public int getVertexCount() {
return vertexCount;
}
}
2. 隣接リストの表記
隣接リスト表記では、配列を使用して各ノードの隣接ノードを格納します。配列内の各要素はノードに対応し、各要素はノードの隣接ノードを格納するリンク リストです。
サンプルコード:
public class Graph {
private LinkedList<Integer>[] adjList;
private int vertexCount;
public Graph(int vertexCount) {
this.vertexCount = vertexCount;
adjList = new LinkedList[vertexCount];
for (int i = 0; i < vertexCount; i++) {
adjList[i] = new LinkedList<>();
}
}
public void addEdge(int i, int j) {
adjList[i].add(j);
adjList[j].add(i);
}
public void removeEdge(int i, int j) {
adjList[i].remove(Integer.valueOf(j));
adjList[j].remove(Integer.valueOf(i));
}
public boolean hasEdge(int i, int j) {
return adjList[i].contains(j);
}
public LinkedList<Integer> getAdjacentVertices(int i) {
return adjList[i];
}
public int getVertexCount() {
return vertexCount;
}
}
3. 深さ優先トラバース
深さ優先トラバーサルは、グラフ内のノードから開始し、パスをたどってリーフ ノードに到達し、その後、前のノードに戻って次の子ノードへの訪問を続けます。深さ優先トラバーサルは再帰を使用して実装されます。
サンプルコード:
public void dfs(int start, boolean[] visited) {
visited[start] = true;
System.out.print(start + " ");
for (int i : adjList[start]) {
if (!visited[i]) {
dfs(i, visited);
}
}
}
4. 幅優先トラバーサル
幅優先走査 (BFS) は、グラフを走査するためのアルゴリズムで、「幅優先検索」または「水平優先検索」とも呼ばれます。
幅優先走査は、グラフ内のノードから開始し、最初にこのノードに直接隣接するすべてのノードを訪問し、次にこのノードに直接または間接的に隣接するすべてのノードが走査されるまで層ごとに走査します。幅優先トラバーサルは通常、キューを使用して実装する必要があります。
以下は、Java で幅優先トラバーサルを実装するサンプル コードです。
import java.util.*;
public class Graph {
private int V; // 图的顶点数
private LinkedList<Integer>[] adj; // 邻接表表示图
// 构造函数
public Graph(int v) {
V = v;
adj = new LinkedList[v];
for (int i = 0; i < v; i++) {
adj[i] = new LinkedList<>();
}
}
// 添加边
public void addEdge(int v, int w) {
adj[v].add(w);
}
// 广度优先遍历
public void BFS(int s) {
boolean[] visited = new boolean[V];
LinkedList<Integer> queue = new LinkedList<>();
visited[s] = true;
queue.add(s);
while (queue.size() != 0) {
s = queue.poll();
System.out.print(s + " ");
Iterator<Integer> i = adj[s].listIterator();
while (i.hasNext()) {
int n = i.next();
if (!visited[n]) {
visited[n] = true;
queue.add(n);
}
}
}
}
// 测试代码
public static void main(String[] args) {
Graph g = new Graph(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
System.out.println("广度优先遍历 (从顶点 2 开始):");
g.BFS(2);
}
}
上記のサンプルコードでは、まずグラフを表現するための Graph クラスを定義します。このクラスでは、隣接リストを使用してグラフを表現します。隣接リストは、各頂点の隣接頂点のリストをリンク リストとして保存する、グラフを表現するための一般的なデータ構造です。
Graph クラスでは、addEdge メソッドを使用してエッジを追加し、BFS メソッドを使用して幅優先トラバーサルを実装します。BFS 法では、各頂点が訪問されたかどうかを記録するためにブール型の配列 Visited を使用し、訪問する頂点を格納するために LinkedList 型のキュー キューを使用します。まず開始頂点をキューに追加し、次にキュー内の各頂点を順番に走査し、隣接する未訪問の頂点をキューに追加して、それらを訪問済みとしてマークします。トラバーサルはキューが空になるまで終了します。
最後に、テスト コードでは、6 つの頂点を持つグラフを作成し、BFS メソッドを呼び出して幅優先トラバーサルを実装します。プログラムを実行すると、出力は次のようになります。
广度优先遍历 (从顶点 2 开始):
2 0 3 1
もっと詳しく:
以下は、頂点 2 から開始する幅優先トラバーサルの例です。
次のグラフがあるとします。
2 —— 0
/ \
1 —— 3
このグラフを隣接リストを使用して表現します。
import java.util.ArrayList;
public class Graph {
private int V; // 顶点数
private ArrayList<ArrayList<Integer>> adj; // 邻接表
public Graph(int v) {
V = v;
adj = new ArrayList<>();
for (int i = 0; i < v; i++) {
adj.add(new ArrayList<Integer>());
}
}
public void addEdge(int v, int w) {
adj.get(v).add(w);
adj.get(w).add(v);
}
public ArrayList<Integer> getAdj(int v) {
return adj.get(v);
}
}
次に、頂点 2 から幅優先トラバースを開始します。
import java.util.LinkedList;
import java.util.Queue;
public class BFS {
public static void main(String[] args) {
Graph g = new Graph(4);
g.addEdge(2, 0);
g.addEdge(2, 1);
g.addEdge(2, 3);
bfs(g, 2);
}
public static void bfs(Graph g, int s) {
boolean[] visited = new boolean[g.V];
Queue<Integer> queue = new LinkedList<>();
visited[s] = true;
queue.add(s);
while (!queue.isEmpty()) {
int v = queue.poll();
System.out.print(v + " ");
for (int w : g.getAdj(v)) {
if (!visited[w]) {
visited[w] = true;
queue.add(w);
}
}
}
}
}
出力結果は次のとおりです: 2 0 1 3
これは、頂点 2 から開始する幅優先トラバースの結果です。