数据结构:
-
数组:一种线性数据结构,可以存储一组相同类型的元素。Java中数组是固定长度的,可以使用下标访问数组中的元素。
-
链表:一种线性数据结构,由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。Java中可以使用LinkedList实现单向链表,或者使用自定义类实现双向链表。
-
栈:一种后进先出(LIFO)的数据结构,可以使用数组或链表实现。Java中可以使用Stack类实现栈。
-
队列:一种先进先出(FIFO)的数据结构,可以使用数组或链表实现。Java中可以使用Queue接口实现队列,或者使用LinkedList实现双端队列。
-
树:一种非线性数据结构,由一些节点和它们之间的连接组成。Java中可以使用TreeNode类实现二叉树,或者使用自定义类实现其他类型的树。
-
堆:一种特殊的树形数据结构,满足堆属性(大根堆或小根堆)。Java中可以使用PriorityQueue实现堆。
-
图:一种非线性数据结构,由一组节点和它们之间的连接组成。Java中可以使用自定义类实现图。
目录
数组
下面是数组的常见知识点:
-
定义数组:定义一个数组需要指定数组类型、数组名称和数组长度。例如,定义一个整型数组,长度为10,可以使用以下代码:
int[] arr = new int[10];
-
初始化数组:可以在定义数组时对其进行初始化,或者在之后的程序中给数组元素赋值。例如,定义一个字符串数组并初始化如下:
String[] names = {"Alice", "Bob", "Charlie"};
-
数组元素的访问:可以通过下标访问数组元素。下标从0开始,到数组长度减1结束。例如,访问上面定义的names数组中的第一个元素:
String name = names[0];
-
多维数组:在Java中,可以定义多维数组,例如二维数组、三维数组等。定义一个二维数组需要指定数组类型、数组名称、行数和列数。例如,定义一个3行4列的二维整型数组:
int[][] arr = new int[3][4];
-
数组的长度:可以通过数组的length属性获取数组的长度。例如,获取上面定义的names数组的长度:
int len = names.length;
-
数组的遍历:可以使用for循环或者foreach循环遍历数组中的元素。例如,使用foreach循环遍历上面定义的names数组中的所有元素:
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版)中链表的一些知识点:
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。如下所示:
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;
}
在这个算法中,我们首先定义了三个指针:prev、current和next。prev指向前一个节点,current指向当前节点,next指向当前节点的下一个节点。在循环中,我们不断地将当前节点的next指针指向prev节点,然后将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. 链表的合并:将两个有序链表合并为一个有序链表。
其中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,则直接返回另一个链表。接着,定义一个head节点作为合并后链表的头节点,并比较l1和l2的值,将较小的值作为head节点,并将其next指向递归合并后的链表。最后,返回head节点即可。
该算法的时间复杂度为O(n),其中n为两个链表的节点总数。
栈
栈(Stack)是一种线性数据结构,具有“先进后出”的特点。栈的插入操作称为入栈(push),删除操作称为出栈(pop)。栈还有一个很重要的操作,即查看栈顶元素但不出栈,称为“栈顶元素”(peek)。
下面是一些栈的常用知识点:
-
栈的实现方式:栈可以通过数组或链表实现。
-
栈的操作:栈的主要操作包括入栈、出栈、判断栈是否为空、查看栈顶元素等。
-
栈的应用:栈的常用应用包括表达式求值、括号匹配、迷宫求解、递归函数的实现等。
下面是一些常见的栈操作的示例代码:
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)是一种线性数据结构,具有先进先出(First In First Out,FIFO)的特点。队列有两个基本操作:入队(enqueue)和出队(dequeue)。入队操作是将一个元素插入到队列的末尾,出队操作是将队列的第一个元素删除并返回。
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());
}
}
除了以上三种实现方式,Java中还提供了BlockingQueue、ConcurrentLinkedQueue等其他队列实现类,具体可根据需要选择使用。
树
数据结构与算法(Java版)树所有知识点:
树是一种非常常见的数据结构,它是由n个节点组成的集合,其中每个节点都有零个或多个子节点,没有父节点的节点称为根节点,除了根节点之外,每个子节点都有一个父节点,它们之间通过边连接。树结构具有良好的层次性,通常用于存储有层次关系的数据,例如文件系统、HTML文档等。
树的基本概念:
- 节点:树中的基本单元。
- 根节点:树的顶端节点。
- 父节点:一个节点的直接上级节点。
- 子节点:一个节点的直接下级节点。
- 叶节点:没有子节点的节点。
- 兄弟节点:有相同父节点的节点。
- 子树:一个节点及其所有子节点的集合。
- 深度:从根节点到当前节点的路径长度。
- 高度:从当前节点到叶节点的最长路径长度。
树的遍历:
树的遍历是指按照一定顺序访问树中所有节点的过程。常见的树的遍历方式有三种:
- 先序遍历:按照“根节点-左子树-右子树”的顺序访问每个节点。
- 中序遍历:按照“左子树-根节点-右子树”的顺序访问每个节点。
- 后序遍历:按照“左子树-右子树-根节点”的顺序访问每个节点。
树的遍历可以使用递归和迭代两种方式实现。
树的常见操作:
- 插入节点:向树中插入一个新节点。
- 删除节点:从树中删除一个节点。
- 查找节点:在树中查找一个指定的节点。
- 获取树的高度:计算树的高度。
- 获取树的节点数:计算树中的节点数量。
树的实现:
树可以使用链式存储和数组存储两种方式实现。
链式存储是指使用节点来表示树中的每个元素,每个节点包含三个部分:值、左子节点和右子节点。
数组存储是指使用数组来表示树中的每个元素,使用数组下标表示节点的编号,每个节点包含一个值。
常见的树结构有二叉树、二叉搜索树、平衡树、堆等。
以上是数据结构与算法(Java版)中树的相关知识点,涉及到树的基本概念、遍历、操作和实现等方面。
堆
堆是一种完全二叉树,它有两种形式:最大堆和最小堆。在最大堆中,父节点的值总是大于或等于它的子节点的值。而在最小堆中,父节点的值总是小于或等于它的子节点的值。
Java中可以使用PriorityQueue类实现堆。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
}
}
上面的代码创建了一个最小堆,并添加了三个元素。然后通过peek()方法获取堆顶元素,删除堆顶元素后再次获取堆顶元素。最终输出结果为1和2,符合堆的性质。
图
图是由节点(或顶点)和边组成的数据结构,可以用于表示许多现实世界的问题。Java中的图可以使用邻接矩阵或邻接表表示。
以下是Java中图的所有知识点:
1. 邻接矩阵表示法
邻接矩阵表示法使用二维数组表示图中的每个节点之间的关系。二维数组的行和列分别对应于节点,每个元素的值表示对应节点之间是否存在边。
示例代码:
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方法中,我们使用一个boolean类型的数组visited来记录每个顶点是否已经被访问过,使用一个LinkedList类型的队列queue来存储待访问的顶点。我们首先将起始顶点加入队列中,然后依次遍历队列中的每个顶点,将与其相邻的未访问过的顶点加入队列中,并将其标记为已访问。直到队列为空为止,遍历结束。
最后,在测试代码中,我们创建了一个包含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开始的广度优先遍历的结果。