最小生成树算法(Prim算法和Kruskal算法)
Prim算法
Prim算法是一种用于在加权无向图中找到最小生成树的贪心算法。它从任意一个顶点开始,每次迭代中选择与当前生成树中已选择的顶点集合中权重最小的边对应的顶点,并将该顶点及其边添加到生成树中。
import java.util.*;
class Graph {
int vertices;
int[][] adjMatrix;
Graph(int v) {
vertices = v;
adjMatrix = new int[v][v];
for (int i = 0; i < v; i++) {
for (int j = 0; j < v; j++) {
adjMatrix[i][j] = Integer.MAX_VALUE; // 默认设置边权值为无穷大
}
}
}
void addEdge(int src, int dest, int weight) {
adjMatrix[src][dest] = weight;
adjMatrix[dest][src] = weight; // 因为是无向图
}
// Prim算法
List<Edge> primMST() {
boolean[] inMST = new boolean[vertices];
int[] key = new int[vertices];
int[] parent = new int[vertices];
// 初始化
Arrays.fill(inMST, false);
Arrays.fill(key, Integer.MAX_VALUE);
Arrays.fill(parent, -1);
key[0] = 0; // 假设从第一个顶点开始
List<Edge> mst = new ArrayList<>();
for (int count = 0; count < vertices - 1; count++) {
int u = minKey(key, inMST);
inMST[u] = true;
for (int v = 0; v < vertices; v++) {
if (adjMatrix[u][v] != 0 && !inMST[v] && adjMatrix[u][v] < key[v]) {
parent[v] = u;
key[v] = adjMatrix[u][v];
}
}
// 将边添加到最小生成树中
if (parent[v] != -1) {
mst.add(new Edge(parent[v], v, adjMatrix[parent[v]][v]));
}
}
return mst;
}
int minKey(int[] key, boolean[] inMST) {
int min = Integer.MAX_VALUE, min_index = -1;
for (int v = 0; v < vertices; v++) {
if (key[v] < min && !inMST[v]) {
min = key[v];
min_index = v;
}
}
return min_index;
}
// 辅助类表示边
static class Edge {
int src, dest, weight;
Edge(int src, int dest, int weight) {
this.src = src;
this.dest = dest;
this.weight = weight;
}
@Override
public String toString() {
return "Edge: " + src + " - " + dest + " == " + weight;
}
}
// 示例用法
public static void main(String[] args) {
Graph g = new Graph(4);
g.addEdge(0, 1, 10);
g.addEdge(0, 2, 6);
g.addEdge(0, 3, 5);
g.addEdge(1, 3, 15);
g.addEdge(2, 3, 4);
List<Edge> mst = g.primMST();
System.out.println("Edges in the constructed MST");
for (Edge edge : mst) {
System.out.println(edge);
}
}
}
Kruskal算法
Kruskal算法也是用于在加权无向图中找到最小生成树的算法。它的基本思想是从小到大选择边,条件是选择的边不能形成一个环。
import java.util.*;
class Graph {
int vertices;
List<Edge> edges;
Graph(int v) {
vertices = v;
edges = new ArrayList<>();
}
// 定义Edge类,用于存储图的边
static class Edge implements Comparable<Edge> {
int src, dest, weight;
Edge(int src, int dest, int weight) {
this.src = src;
this.dest = dest;
this.weight = weight;
}
// 用于Kruskal算法中边的比较
@Override
public int compareTo(Edge other) {
return this.weight - other.weight;
}
}
// 添加边到图中
void addEdge(int src, int dest, int weight) {
Edge edge = new Edge(src, dest, weight);
edges.add(edge);
}
// 查找集合中的元素
int find(int parent[], int i) {
if (parent[i] == i)
return i;
return find(parent, parent[i]);
}
// 合并两个集合
void union(int parent[], int[] rank, int x, int y) {
int xroot = find(parent, x);
int yroot = find(parent, y);
if (rank[xroot] < rank[yroot])
parent[xroot] = yroot;
else if (rank[xroot] > rank[yroot])
parent[yroot] = xroot;
else {
parent[yroot] = xroot;
rank[xroot]++;
}
}
// Kruskal算法实现
List<Edge> kruskalMST() {
// 初始化
Collections.sort(edges); // 根据边的权重进行排序
int[] parent = new int[vertices];
int[] rank = new int[vertices];
for (int node = 0; node < vertices; ++node)
parent[node] = node;
List<Edge> result = new ArrayList<>();
for (Edge edge : edges) {
int x = edge.src, y = edge.dest;
int xroot = find(parent, x);
int yroot = find(parent, y);
if (xroot != yroot) {
result.add(edge);
union(parent, rank, xroot, yroot);
}
// 当边数等于顶点数减一时,最小生成树已经构成
if (result.size() == vertices - 1)
break;
}
return result;
}
// 示例用法
public static void main(String[] args) {
Graph g = new Graph(4);
g.addEdge(0, 1, 10);
g.addEdge(0, 2, 6);
g.addEdge(0, 3, 5);
g.addEdge(1, 3, 15);
g.addEdge(2, 3, 4);
List<Edge> mst = g.kruskalMST();
System.out.println("Edges in the constructed MST");
for (Edge edge : mst) {
System.out.println(edge.src + " -- " + edge.dest + " == " + edge.weight);
}
}
}
单源最短路径(Dijkstra算法)
import java.util.*;
class Graph {
private int numVertices; // 顶点数量
private LinkedList<Integer>[] adjLists; // 邻接表
private int[] dist; // 存储最短距离的数组
private boolean[] visited; // 标记顶点是否已访问
// 构造函数
Graph(int numVertices) {
this.numVertices = numVertices;
adjLists = new LinkedList[numVertices];
for (int i = 0; i < numVertices; i++) {
adjLists[i] = new LinkedList<>();
}
dist = new int[numVertices];
visited = new boolean[numVertices];
}
// 添加边和权重
void addEdge(int src, int dest, int weight) {
adjLists[src].add(dest); // 添加边到邻接表
// Dijkstra算法通常用于非负权重的图,这里假设权重是非负的
// 如果图中存在负权重边,应使用其他算法(如Bellman-Ford算法)
}
// 使用Dijkstra算法计算从src到所有其他顶点的最短路径
void dijkstra(int src) {
// 初始化距离数组和已访问数组
Arrays.fill(dist, Integer.MAX_VALUE);
dist[src] = 0;
// 优先队列(小顶堆)用于存储待处理的顶点
PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> dist[a] - dist[b]);
pq.offer(src); // 将源顶点加入优先队列
while (!pq.isEmpty()) {
int u = pq.poll(); // 取出距离最小的顶点
// 标记该顶点为已访问
visited[u] = true;
// 遍历u的所有邻居
Iterator<Integer> i = adjLists[u].iterator();
while (i.hasNext()) {
int v = i.next();
// 如果邻居v尚未被访问,并且从源顶点到u再到v的路径更短
if (!visited[v] && dist[u] != Integer.MAX_VALUE && dist[u] + getWeight(u, v) < dist[v]) {
dist[v] = dist[u] + getWeight(u, v); // 更新最短距离
pq.offer(v); // 将v加入优先队列
}
}
}
// 打印最短路径结果
printSolution(src);
}
// 假设边的权重为1(简化版,实际中可能需要一个二维数组来存储权重)
private int getWeight(int u, int v) {
return 1; // 假设所有边的权重都是1
}
// 打印从src到所有其他顶点的最短路径
private void printSolution(int src) {
System.out.println("从顶点 " + src + " 到其他顶点的最短路径:");
for (int i = 0; i < numVertices; ++i) {
if (dist[i] == Integer.MAX_VALUE)
System.out.print(src + " 到 " + i + " : 无路径\n");
else
System.out.print(src + " 到 " + i + " : 距离 = " + dist[i] + "\n");
}
}
// 示例用法
public static void main(String args[]) {
Graph g = new Graph(9);
g.addEdge(0, 1, 1);
g.addEdge(0, 7, 1);
g.addEdge(1, 2, 1);
g.addEdge(1, 7, 1);
g.addEdge(2, 3, 1);
g.addEdge(2, 8, 1);
g.addEdge(3, 4, 1);
g.addEdge(3, 5, 1);
g.addEdge(4, 5, 1);
g.addEdge(5, 6, 1);
g.addEdge(6, 7, 1);
g.addEdge(7, 8, 1);
g.addEdge(8, 6, 1); // 继续添加边
// 调用Dijkstra算法计算从顶点0到所有其他顶点的最短路径
g.dijkstra(0);
}
}
// 运行上述代码,将会得到从顶点0到所有其他顶点的最短路径距离
在上面的代码中,我添加了边和权重的添加方法,但是为了简化示例,getWeight
方法假设了所有边的权重都是1。在实际应用中,你可能需要一个二维数组或者其他数据结构来存储图中每对顶点之间的权重。
注意,Dijkstra算法不能处理包含负权重的图,因为它基于贪心策略,可能会选择局部最优解而非全局最优解。如果图中存在负权重边,应使用Bellman-Ford算法或者Floyd-Warshall算法等能够处理负权重的算法。
在main
方法中,我添加了一些边和它们的权重(在这个例子中,我假设所有边的权重都是1),然后调用了dijkstra
方法来计算从顶点0到所有其他顶点的最短路径。最后,printSolution
方法将打印出计算结果。
3.哈夫曼编码(Huffman Coding)
哈夫曼编码是一种可变长度编码方式,用于无损数据压缩。它的基本原理是频繁出现的字符使用较短的编码,而不常出现的字符使用较长的编码。下面是一个简单的Java实现哈夫曼编码的示例,包括构建哈夫曼树和生成哈夫曼编码的步骤。
import java.util.*;
// 自定义的哈夫曼树节点类
class HuffmanNode implements Comparable<HuffmanNode> {
char data; // 节点数据(字符)
int frequency; // 节点频率(权值)
HuffmanNode left, right; // 左右子节点
// 构造函数
public HuffmanNode(char data, int frequency) {
this.data = data;
this.frequency = frequency;
}
// 比较节点频率,用于优先队列排序
@Override
public int compareTo(HuffmanNode other) {
return this.frequency - other.frequency;
}
}
public class HuffmanCoding {
// 哈夫曼编码
Map<Character, String> huffmanCodes = new HashMap<>();
// 从哈夫曼树生成编码
private void generateCodes(HuffmanNode root, String str, Map<Character, String> codes) {
if (root.left == null && root.right == null) {
codes.put(root.data, str);
} else {
generateCodes(root.left, str + "0", codes);
generateCodes(root.right, str + "1", codes);
}
}
// 构建哈夫曼树
private HuffmanNode buildHuffmanTree(Map<Character, Integer> freqMap) {
PriorityQueue<HuffmanNode> minHeap = new PriorityQueue<>();
// 初始化优先队列
for (Map.Entry<Character, Integer> entry : freqMap.entrySet()) {
minHeap.offer(new HuffmanNode(entry.getKey(), entry.getValue()));
}
// 构建哈夫曼树
while (minHeap.size() != 1) {
HuffmanNode left = minHeap.poll();
HuffmanNode right = minHeap.poll();
HuffmanNode top = new HuffmanNode('$', left.frequency + right.frequency);
top.left = left;
top.right = right;
minHeap.offer(top);
}
return minHeap.poll(); // 返回根节点
}
// 主函数:构建哈夫曼编码
public void createHuffmanCodes(String data) {
// 字符频率映射
Map<Character, Integer> freqMap = new HashMap<>();
// 统计字符频率
for (char c : data.toCharArray()) {
freqMap.put(c, freqMap.getOrDefault(c, 0) + 1);
}
// 构建哈夫曼树
HuffmanNode root = buildHuffmanTree(freqMap);
// 生成哈夫曼编码
generateCodes(root, "", huffmanCodes);
// 打印哈夫曼编码
for (Map.Entry<Character, String> entry : huffmanCodes.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
// 示例使用
public static void main(String[] args) {
HuffmanCoding huffmanCoding = new HuffmanCoding();
String data = "this is an example for huffman coding";
huffmanCoding.createHuffmanCodes(data);
}
}
在这个实现中,我们定义了一个HuffmanNode
类来表示哈夫曼树的节点,每个节点包含一个字符、一个频率以及左右子节点。HuffmanCoding
类包含了构建哈夫曼树和生成哈夫曼编码的逻辑。我们使用了一个优先队列(最小堆)来根据频率选择节点来构建哈夫曼树。然后,我们遍历哈夫曼树来生成每个字符的哈夫曼编码,并将它们存储在huffmanCodes
映射中。最后,我们在main
函数中调用createHuffmanCodes
方法来构建并打印哈夫曼编码。