数据结构与算法

server/2024/12/16 15:19:59/

数据结构与算法复习

最近期末周准备考试,停更一段时间,结束之后继续更新

图连通问题

** 问题描述:**ConnectedComponents 给定无向图,计算无线图的连通分量

java">public class ConnectedComponents {private static Map<Vertex,Integer> cc; // the resulting mapprivate static UnDiGraph G; // the undirected graph/*** Returns the map of the connected components of 'G'* If cc is the returned map, then, for all verticies u and v,* cc.get(u) = cc.get(v) if and only if u and v are in the same* connected component*/public static Map<Vertex,Integer> find(UnDiGraph G) {cc = new HashMap<Vertex,Integer>();ConnectedComponents.G = G;int number = 1;for ( Vertex u : G.vertices() ) {if ( notNumbered(u) )setComponent(u,number++);}return cc;}/*** Set the component number to 'number' for 'u' and* all the vertices reachable from u*/private static void setComponent(Vertex u, int number) {setNumber(u, number);for ( Vertex a : G.adjacents(u) )if ( notNumbered(a) )setComponent(a,number);}private static boolean notNumbered(Vertex u) {return ! cc.containsKey(u);}private static void setNumber(Vertex u, int number) {cc.put(u, number);}public static void main(String[] s) {Map<Vertex,Integer> cc = find(GraphReader.U1);System.out.println(cc);cc = find(GraphReader.U2);System.out.println(cc);}
}

环路检测

**问题描述:**检测有向图和无向图是否带环

java">public class Cycles {private static Graph G; // 当前图private static Set<Vertex> visited; // 已访问顶点集合/// 无向图的环检测/*** 检测无向图是否存在环* @param G 无向图* @return 如果存在环返回 true,否则返回 false*/public static boolean hasCycle(UnDiGraph G) {visited = new HashSet<>();Cycles.G = G;// 遍历所有顶点,启动深度优先搜索for (Vertex u : G.vertices()) {if (!visited.contains(u) && hasCycle(u, null)) {return true; // 如果发现环则直接返回}}return false; // 图中无环}/*** 深度优先搜索辅助方法* @param u 当前顶点* @param from 来自哪个顶点* @return 如果从顶点 u 开始发现环返回 true,否则返回 false*/private static boolean hasCycle(Vertex u, Vertex from) {visited.add(u); // 标记当前顶点为已访问for (Vertex neighbor : G.adjacents(u)) {if (!visited.contains(neighbor)) {if (hasCycle(neighbor, u)) return true; // 递归检测} else if (!neighbor.equals(from)) {return true; // 如果邻居已访问且不是来源顶点,则发现环}}return false;}/// 有向图的环检测private enum Status { UnDiscovered, InProgress, Completed } // 顶点状态枚举private static Map<Vertex, Status> vertexStatus; // 顶点状态映射/*** 检测有向图是否存在环* @param G 有向图* @return 如果存在环返回 true,否则返回 false*/public static boolean hasCycle(DiGraph G) {vertexStatus = new HashMap<>();Cycles.G = G;// 遍历所有顶点,启动深度优先搜索for (Vertex u : G.vertices()) {if (status(u) == Status.UnDiscovered && hasCycleDirected(u)) {return true; // 如果发现环则直接返回}}return false; // 图中无环}/*** 深度优先搜索辅助方法(有向图)* @param u 当前顶点* @return 如果从顶点 u 开始发现环返回 true,否则返回 false*/private static boolean hasCycleDirected(Vertex u) {setStatus(u, Status.InProgress); // 设置当前顶点为处理中for (Vertex neighbor : G.adjacents(u)) {if (status(neighbor) == Status.UnDiscovered) {if (hasCycleDirected(neighbor)) return true; // 递归检测} else if (status(neighbor) == Status.InProgress) {return true; // 如果邻居在处理中,发现环}}setStatus(u, Status.Completed); // 设置当前顶点为已完成return false;}/*** 获取顶点状态* @param u 顶点* @return 顶点的当前状态*/private static Status status(Vertex u) {return vertexStatus.getOrDefault(u, Status.UnDiscovered);}/*** 设置顶点状态* @param u 顶点* @param s 新状态*/private static void setStatus(Vertex u, Status s) {vertexStatus.put(u, s);}
}

寻找路径

**问题描述:**寻找顶点 u 到 v 的路径

java">public class PathFinder {private static Graph G; // 当前图private static Set<Vertex> visited; // 已访问顶点集合/*** 查找从顶点 u 到顶点 v 的路径* @param G 图* @param u 起始顶点* @param v 目标顶点* @return 如果存在路径,返回路径上的顶点列表;否则返回空列表*/public static List<Vertex> findPath(Graph G, Vertex u, Vertex v) {PathFinder.G = G;visited = new HashSet<>();List<Vertex> path = new LinkedList<>();// 尝试找到从 u 到 v 的路径if (findPathHelper(u, v, path)) {return path; // 成功找到路径}return Collections.emptyList(); // 未找到路径}/*** 深度优先搜索辅助方法* @param u 当前顶点* @param v 目标顶点* @param path 路径列表* @return 如果存在路径返回 true,并将路径存储在 path 中;否则返回 false*/private static boolean findPathHelper(Vertex u, Vertex v, List<Vertex> path) {visited.add(u); // 标记当前顶点为已访问if (u.equals(v)) { // 如果到达目标顶点path.add(u); // 将目标顶点加入路径return true;}// 遍历当前顶点的所有相邻顶点for (Vertex neighbor : G.adjacents(u)) {if (!visited.contains(neighbor)) { // 如果相邻顶点未访问if (findPathHelper(neighbor, v, path)) { // 递归尝试找到路径path.add(0, u); // 将当前顶点加入路径前部return true;}}}return false; // 未找到路径}
}

寻找源节点

**问题描述:**寻找有向图中可以到达图中所有顶点的 Root 顶点

java">public class RootFinder {private static DiGraph G; // 当前有向图private static Set<Vertex> visited; // 已访问顶点集合/*** 查找图 G 的根节点* @param G 有向图* @return 如果存在根节点,返回根节点;否则返回 null*/public static Vertex findRoot(DiGraph G) {RootFinder.G = G;visited = new HashSet<>();Vertex candidate = findCandidate(); // 获取根节点候选项visited.clear(); // 清除访问记录// 检查候选节点是否可以访问图中所有顶点if (visit(candidate) == G.nbVertices()) {return candidate;}return null; // 不存在根节点}/*** 寻找根节点的候选节点* @return 最后访问的顶点作为候选节点*/private static Vertex findCandidate() {Vertex lastVisited = null;for (Vertex u : G.vertices()) {if (!visited.contains(u)) {lastVisited = u; // 更新最后访问的顶点visit(u); // 深度优先访问该顶点}}return lastVisited; // 返回候选节点}/*** 访问从顶点 u 可达的所有顶点* @param u 起始顶点* @return 可访问的顶点数量*/private static int visit(Vertex u) {visited.add(u); // 标记当前顶点为已访问int count = 1; // 记录当前顶点// 遍历相邻顶点for (Vertex neighbor : G.adjacents(u)) {if (!visited.contains(neighbor)) {count += visit(neighbor); // 递归访问未访问的相邻顶点}}return count; // 返回总访问顶点数}
}

图广度搜索遍历

java">private DiGraph G;
/*** 广度优先搜索(BFS),返回从起始顶点开始按访问顺序的顶点列表。* 通过一个队列逐层遍历图中的顶点,并确保每个顶点只访问一次。* * @param start 起始顶点* @return 从起始顶点出发的按访问顺序排列的顶点列表*/
public List<Vertex> bfs(Vertex start) {Queue<Vertex> queue = new LinkedList<>();List<Vertex> result = new LinkedList<>();Set<Vertex> marked = new HashSet<>();marked.add(start); // 标记起始顶点已访问queue.offer(start); // 将起始顶点加入队列while (!queue.isEmpty()) {Vertex v = queue.poll(); // 从队列中取出一个顶点result.add(v); // 将顶点加入结果列表for (Vertex a : G.adjacents(v)) { // 遍历当前顶点的所有邻接顶点if (!marked.contains(a)) { // 如果邻接顶点未被访问marked.add(a); // 标记邻接顶点已访问queue.offer(a); // 将邻接顶点加入队列}}}return result; // 返回按访问顺序的顶点列表
}/*** 使用广度优先搜索(BFS)计算从起始顶点到所有其他顶点的最短路径距离。* 记录每个顶点的距离并确保每个顶点只计算一次。* * @param start 起始顶点* @return 从起始顶点到其他顶点的最短路径距离映射*/
public Map<Vertex, Integer> distance(Vertex start) {Queue<Vertex> queue = new LinkedList<>();Map<Vertex, Integer> distance = new HashMap<>();distance.put(start, 0); // 起始顶点的距离初始化为 0queue.offer(start); // 将起始顶点加入队列while (!queue.isEmpty()) {Vertex v = queue.poll(); // 从队列中取出一个顶点for (Vertex a : G.adjacents(v)) { // 遍历当前顶点的所有邻接顶点if (!distance.containsKey(a)) { // 如果邻接顶点的距离尚未计算distance.put(a, distance.get(v) + 1); // 计算邻接顶点的距离queue.offer(a); // 将邻接顶点加入队列}}}return distance; // 返回从起始顶点到其他顶点的距离映射
}

拓扑排序

1. Kahn算法(基于入度)

这个算法使用了入度的概念:图中一个顶点的入度是指指向该顶点的边的数量。

  • 步骤
    1. 计算图中每个顶点的入度。
    2. 将所有入度为0的顶点加入一个队列。
    3. 从队列中依次取出顶点,将其加入排序结果中。
    4. 对于每个被访问的顶点,减少它所有邻接顶点的入度。如果某个邻接顶点的入度变为0,加入队列。
    5. 如果所有顶点都被访问完且入度为0的顶点都处理过,输出排序结果。如果有顶点的入度仍不为0,说明图中存在环,无法进行拓扑排序。
  • 时间复杂度O(V + E),其中 V 是顶点数,E 是边数。
java">/*** 使用拓扑排序算法返回有向图 G 的顶点列表,其中顶点按照拓扑顺序排列。* 拓扑排序适用于有向无环图(DAG),表示一种顶点的线性排列,使得对于每条有向边 (u, v),顶点 u 在 v 之前出现。* 算法采用 Kahn 算法的实现,通过记录每个顶点的入度并逐步处理入度为 0 的顶点。* * @param G 输入的有向图* @return 按拓扑顺序排列的顶点列表。如果图中存在环,则返回的列表可能不完整。*/
public static List<Vertex> sort1(DiGraph G) {Map<Vertex, Integer> inDegree = new HashMap<>(); // 记录每个顶点的入度Queue<Vertex> queue = new LinkedList<>(); // 用于存储当前入度为 0 的顶点List<Vertex> sorted = new LinkedList<>(); // 存储排序后的顶点列表// 初始化入度并将入度为 0 的顶点加入队列for (Vertex vertex : G.vertices()) {inDegree.put(vertex, G.inDegree(vertex)); // 获取每个顶点的入度if (G.inDegree(vertex) == 0) { // 如果顶点的入度为 0,则加入队列queue.offer(vertex);}}// 处理队列中的顶点,执行拓扑排序while (!queue.isEmpty()) {Vertex v = queue.poll(); // 从队列中取出一个入度为 0 的顶点sorted.add(v); // 将该顶点加入排序结果for (Vertex a : G.adjacents(v)) { // 遍历该顶点的所有邻接顶点inDegree.put(a, inDegree.get(a) - 1); // 减少邻接顶点的入度if (inDegree.get(a) == 0) { // 如果邻接顶点的入度变为 0,则加入队列queue.offer(a);}}}return sorted; // 返回拓扑排序结果
}
2. 深度优先搜索(DFS)

DFS方法是通过递归或栈的方式实现的,利用递归栈的特点来帮助找到拓扑排序。

  • 步骤
    1. 对图中的每个顶点进行深度优先搜索。
    2. 在递归过程中,标记顶点为已访问。当所有相邻的顶点都被访问后,将当前顶点加入栈中。
    3. 最终,栈中的顶点顺序即为拓扑排序的结果(栈底的元素是最后访问的节点)。
    4. 如果在DFS过程中发现存在回边(即发现一个节点已经在递归栈中),则图中有环,无法进行拓扑排序。
  • 时间复杂度O(V + E),与Kahn算法相同。
java">/*** 使用深度优先搜索 (DFS) 返回有向图 G 的顶点列表,其中顶点按照拓扑顺序排列。* 拓扑排序适用于有向无环图(DAG),表示一种顶点的线性排列,使得对于每条有向边 (u, v),顶点 u 在 v 之前出现。* 此方法通过递归实现拓扑排序,从未访问的顶点开始深度优先遍历,并将顶点按逆后序插入排序结果。** @param G 输入的有向图* @return 按拓扑顺序排列的顶点列表*/
public static List<Vertex> sort2(DiGraph G) {Set<Vertex> visited = new HashSet<>(); // 记录已访问的顶点List<Vertex> sorted = new LinkedList<>(); // 存储排序后的顶点列表// 对图中的每个顶点进行遍历,如果顶点尚未访问,则从该顶点开始执行 DFSfor (Vertex v : G.vertices()) {if (!visited.contains(v)) {visit(G, v, visited, sorted);}}return sorted; // 返回拓扑排序结果
}/*** 从顶点 u 开始对有向图 G 进行深度优先搜索 (DFS),并按照拓扑顺序将顶点添加到列表 sorted 中。* 在递归过程中,所有访问过的顶点会被标记,避免重复访问。** @param G 输入的有向图* @param u 当前访问的顶点* @param visited 记录已访问顶点的集合* @param sorted 存储排序结果的列表,按逆后序插入顶点*/
private static void visit(DiGraph G, Vertex u, Set<Vertex> visited, List<Vertex> sorted) {visited.add(u); // 标记当前顶点为已访问for (Vertex a : G.adjacents(u)) { // 遍历当前顶点的所有邻接顶点if (!visited.contains(a)) { // 如果邻接顶点尚未访问,递归访问visit(G, a, visited, sorted);}}sorted.add(0, u); // 当前顶点的所有邻接顶点处理完毕后,将该顶点插入结果列表开头
}

哈密尔顿回路

拓扑排序法
java">/*** 测试有向图 G 是否存在哈密顿路径。* 哈密顿路径是图中一种路径,它访问每个顶点恰好一次(不需要回到起点)。* 此方法首先对图进行拓扑排序,然后验证拓扑排序中的每对相邻顶点是否在原图中直接相连。** @param G 输入的有向图* @return 如果存在哈密顿路径则返回 true,否则返回 false*/
public static boolean hasHamiltonianPath(DiGraph G) {// 对有向图 G 进行拓扑排序,获取按拓扑顺序排列的顶点列表List<Vertex> topo = sort1(G);// 获取拓扑排序列表中的第一个顶点Vertex previous = topo.remove(0);// 遍历拓扑排序列表中的剩余顶点for (Vertex v : topo) {// 如果当前顶点和前一个顶点之间不存在有向边,则图中不存在哈密顿路径if (!G.adjacents(previous, v)) {return false;}// 更新前一个顶点为当前顶点previous = v;}// 如果所有相邻顶点都满足条件,返回 truereturn true;
}
回溯法
java">/*** 检查图 G 是否存在哈密顿回路* @param G 输入的有向图* @return 如果存在哈密顿回路则返回 true,否则返回 false*/
public static boolean hasHamiltonianCycle(DiGraph G) {// 图中顶点数量int V = G.nbVertices();// 用来记录访问过的顶点boolean[] visited = new boolean[V];// 假设起始顶点是第一个顶点Vertex start = G.vertices().get(0);// 通过回溯法检测哈密顿回路return hasHamiltonianCycleUtil(G, start, visited, start, 1);
}/*** 使用回溯法从当前顶点 u 开始,检查是否能找到哈密顿回路* @param G 输入的有向图* @param u 当前顶点* @param visited 记录访问过的顶点* @param start 起始顶点,用于回路检测* @param count 已访问顶点的数量* @return 如果存在哈密顿回路,则返回 true,否则返回 false*/
private static boolean hasHamiltonianCycleUtil(DiGraph G, Vertex u, boolean[] visited, Vertex start, int count) {// 如果已访问的顶点数量等于图中的顶点数量,且当前顶点与起始顶点相连,则存在回路if (count == G.nbVertices() && G.adjacents(u, start)) {return true;}// 遍历所有相邻顶点for (Vertex v : G.adjacents(u)) {// 如果 v 尚未访问if (!visited[v.getIndex()]) {// 标记为已访问visited[v.getIndex()] = true;// 递归调用,继续访问相邻的顶点if (hasHamiltonianCycleUtil(G, v, visited, start, count + 1)) {return true;}// 回溯:撤销对 v 的访问visited[v.getIndex()] = false;}}// 如果无法找到回路,返回 falsereturn false;
}

最小生成树

并查集
java">
/*** 使用 up-tree 实现的并查集(Disjoint Set)*/
public class Partition<AnyType> {private int nbTrees; // 当前树的数量private Map<AnyType, InnerTree> map; // 存储元素与其所在树的映射/*** 构造一个空的并查集*/public Partition() {nbTrees = 0;map = new HashMap<>();}/*** 返回当前并查集中的树的数量*/public int nbTrees() {return nbTrees;}/*** 向并查集中添加一个新的单元素树*/public void newTree(AnyType e) {map.put(e, new InnerTree());nbTrees++;}/*** 查找元素 e 所在的树*/public InnerTree find(AnyType e) {return find(map.get(e));}/*** 合并两个树,要求两个树不相同*/public void union(InnerTree x, InnerTree y) {if (x.size <= y.size) {x.parent = y; // 将小树挂到大树下面y.size += x.size; // 更新大小} else {y.parent = x; // 将小树挂到大树下面x.size += y.size; // 更新大小}nbTrees--; // 合并后树的数量减少}/*** 查找某个树的根,使用路径压缩*/private InnerTree find(InnerTree n) {if (n != n.parent) {n.parent = find(n.parent); // 递归查找并压缩路径}return n.parent; // 返回树的根}/*** 代表树的接口,外部只能通过引用来比较树*/public interface Tree {}/*** 内部类 InnerTree,表示树的节点*/private class InnerTree implements Tree {InnerTree parent; // 父节点int size; // 当前树的元素数量InnerTree() {this.parent = this; // 初始化时自己是根this.size = 1; // 初始大小为 1}}
}
Prim 最小生成树
java">/*** 使用 Prim 算法返回加权无向图 G 的最小生成树(MST)边的列表* 前提:图 G 是连通的*/
public static List<Edge> prim(UnDiGraph G) throws FullHeapException, EmptyHeapException {// 存储最小生成树的边List<Edge> mst = new LinkedList<Edge>();// 定义最小堆的比较器,按权重升序排列Comparator<Edge> c = new Comparator<Edge>() {public int compare(Edge e1, Edge e2) {return e1.compareTo(e2);  // 使用边的比较方法进行比较}};// 创建最小堆BinaryHeap<Edge> minHeap = new BinaryHeap<Edge>(G.nbEdges(), c);// 用一个集合记录已知的顶点Set<Vertex> known = new HashSet<Vertex>();// 随机选择一个起始顶点 uVertex u = G.getRandomVertex();known.add(u);// 将与 u 相邻的边加入堆中for (Edge e : G.incidents(u))minHeap.add(e);// 当尚未连接所有顶点时,继续循环while (known.size() < G.nbVertices()) {Edge min = minHeap.deleteExtreme();  // 取出权重最小的边Vertex x = notKnown(min, known);  // 找到该边的未知道顶点if (x != null) {mst.add(min);  // 将该边加入最小生成树known.add(x);  // 将该顶点加入已知集合// 将与 x 相邻的所有边加入堆中for (Edge e : G.incidents(x)) {Vertex y = notKnown(e, known);  // 找到该边的未知道顶点if (y != null)minHeap.add(e);  // 将边加入堆}}}return mst;
}/*** 判断边 e 的两个端点中哪个不在 known 集合中* 前提:至少有一个端点在 known 集合中*/
private static Vertex notKnown(Edge e, Set<Vertex> known) {if (!known.contains(e.origin())) {return e.origin();} else if (!known.contains(e.destination())) {return e.destination();}return null;
}
Prim 最小生成林
java">/*** 使用 Prim 算法返回加权无向图 G 的最小生成森林(MSF)边的列表* 适用于图是非连通的情况*/
public static List<Edge> prim(UnDiGraph G) throws FullHeapException, EmptyHeapException {// 存储最小生成森林的边List<Edge> mst = new LinkedList<Edge>();// 定义最小堆的比较器,按权重升序排列Comparator<Edge> c = new Comparator<Edge>() {public int compare(Edge e1, Edge e2) {return e1.compareTo(e2);  // 使用边的比较方法进行比较}};// 创建最小堆BinaryHeap<Edge> minHeap = new BinaryHeap<Edge>(G.nbEdges(), c);// 用一个集合记录已知的顶点Set<Vertex> known = new HashSet<Vertex>();// 遍历所有顶点,处理每一个连通分量for (Vertex u : G.vertices()) {if (!known.contains(u)) {known.add(u);  // 标记当前顶点为已知// 将与顶点 u 相邻的边加入堆中for (Edge e : G.incidents(u)) {minHeap.add(e);}// 当堆中还有边且存在未连接的顶点时,继续循环while (!minHeap.isEmpty()) {Edge min = minHeap.deleteExtreme();  // 取出权重最小的边Vertex x = notKnown(min, known);  // 找到该边的未知道顶点if (x != null) {mst.add(min);  // 将该边加入最小生成森林known.add(x);  // 将该顶点加入已知集合// 将与顶点 x 相邻的所有边加入堆中for (Edge e : G.incidents(x)) {Vertex y = notKnown(e, known);  // 找到该边的未知道顶点if (y != null) {minHeap.add(e);  // 将边加入堆}}}}}}return mst;
}/*** 判断边 e 的两个端点中哪个不在 known 集合中* 前提:至少有一个端点在 known 集合中*/
private static Vertex notKnown(Edge e, Set<Vertex> known) {if (!known.contains(e.origin())) {return e.origin();} else if (!known.contains(e.destination())) {return e.destination();}return null;
}
Kruskal 最小生成树
java">/*** 使用 Kruskal 算法返回加权无向图 G 的最小生成树(MST)边的列表* 前提:图 G 是连通的*/
public static List<Edge> kruskal(UnDiGraph G) throws FullHeapException, EmptyHeapException {// 存储最小生成树的边List<Edge> mst = new LinkedList<Edge>();// 定义最小堆的比较器,按权重升序排列Comparator<Edge> c = new Comparator<Edge>() {public int compare(Edge e1, Edge e2) {return e1.compareTo(e2);  // 使用边的比较方法进行比较}};// 创建最小堆BinaryHeap<Edge> minHeap = new BinaryHeap<Edge>(G.nbEdges(), c);// 将所有边加入堆中for (Edge e : G.edges())minHeap.add(e);// 初始化并查集(用来判断是否形成环)Partition<Vertex> P = new Partition<Vertex>();// 将每个顶点加入并查集中作为独立的树for (Vertex v : G.vertices())P.newTree(v);// 当所有顶点还未连接成一个连通分量时,继续循环while (P.nbTrees() > 1) {Edge min = minHeap.deleteExtreme();  // 取出权重最小的边Vertex u = min.origin();Vertex v = min.destination();// 判断 u 和 v 是否属于同一树(即是否会形成环)Partition.Tree ru = P.find(u);Partition.Tree rv = P.find(v);if (ru != rv) {  // 如果不在同一树中,则合并它们mst.add(min);  // 将该边加入最小生成树P.union(ru, rv);  // 合并这两棵树}}return mst;
}

最短路径

Dijkstra

ps : 无法处理负权图

**问题描述:**给定有向图或者无向图,计算开始顶点到其他各个顶点的最短路

edgeTo:记录每个顶点到达的前驱顶点,从而形成一棵最短路径树。利用 edgeTo 可以回溯并重建从起点到任意目标点的最短路径。

known:存储已确定最短路径的顶点,避免重复访问。

heap:用于维护当前所有未处理顶点的权重,使得可以快速找到权重最小的顶点(优先级队列的核心作用)。

顶点权重:最短路径的权重值

java">protected Graph G; // 图的表示,可以是有向图或无向图
protected Map<Vertex, Vertex> edgeTo; // 用于记录每个顶点的最短路径的前驱顶点
protected Vertex start; // 起点顶点/*** 实现 Dijkstra 算法计算从起点到所有其他顶点的最短路径。* 前置条件:图 G 的顶点和边已正确定义,起点 start 已指定。*/
public void Dijkstra() {// 已知集合:存储所有已确定最短路径的顶点Set<Vertex> known = new HashSet<>();// 初始化每个顶点的权重为正无穷大,起点权重为 0for (Vertex v : G.vertices()) {v.setWeight(Double.POSITIVE_INFINITY);}start.setWeight(0.0);// 创建一个最小堆,用于动态维护未处理顶点的权重顺序DijkstraHeap heap = new DijkstraHeap(G.nbVertices());// 将所有顶点加入堆中for (Vertex v : G.vertices()) {heap.add(v);}// 主循环:每次处理权重最小的顶点,直到所有顶点都已知while (known.size() != G.nbVertices()) {// 从堆中取出当前权重最小的顶点Vertex min = heap.deleteMin();// 将该顶点加入已知集合known.add(min);// 遍历当前顶点的所有相邻边for (Edge e : G.incidents(min)) {Vertex v = e.otherEnd(min); // 找到相邻的顶点if (!known.contains(v)) { // 如果该顶点尚未确定最短路径double newDist = min.getWeight() + e.weight(); // 计算从起点到该顶点的新路径权重if (newDist < v.getWeight()) { // 如果新路径权重更小v.setWeight(newDist); // 更新顶点的权重edgeTo.put(v, min); // 更新前驱顶点为当前顶点heap.percolateUp(v); // 调整堆中的位置,确保堆的正确性}}}}
}

**问题描述:**起始顶点到目标顶点存在多条最短路径,记录所有最短路径

增加一个 nosp 记录最短路径的数量

java">protected Graph G; // 图的表示,可以是有向图或无向图
protected Map<Vertex, Vertex> edgeTo; // 用于记录每个顶点的最短路径的前驱顶点
protected Vertex start; // 起点顶点// 记录每个顶点到达源点的最短路径数
Map<Vertex, Integer> nosp = new HashMap<>();; // Number of Shortest Paths// 计算从起点到每个顶点的最短路径长度及路径数
public void DijkstraWithPath() {Set<Vertex> known = new HashSet<>();  // 存储已经处理过的顶点// 初始化所有顶点的最短路径为正无穷大,并设置起点的路径长度为 0for (Vertex v : G.vertices()) {v.setWeight(Double.POSITIVE_INFINITY);  // 将所有顶点的权重设置为无穷大nosp.put(v, 0);  // 设置每个顶点的最短路径数为 0}start.setWeight(0.0);  // 设置起点的权重为 0nosp.put(start, 1);  // 起点的最短路径数为 1// 创建一个堆来管理所有顶点,并按最短路径权重排序DijkstraHeap heap = new DijkstraHeap(G.nbVertices());for (Vertex v : G.vertices())heap.add(v);  // 将所有顶点添加到堆中// Dijkstra算法的主循环:直到所有顶点都被处理while (known.size() != G.nbVertices()) {// 从堆中取出当前最短路径的顶点Vertex min = heap.deleteMin();known.add(min);  // 将当前顶点加入已处理顶点集合// 遍历当前顶点的所有相邻顶点for (Edge e : G.incidents(min)) {Vertex v = e.otherEnd(min);  // 获取边的另一个顶点double newDist = min.getWeight() + e.weight();  // 计算通过当前边到达 v 的新路径长度if (known.contains(v)) {  // 如果 v 已经处理过// 如果新路径长度等于原路径长度,累加最短路径数if (newDist == v.getWeight()) {nosp.put(v, nosp.get(v) + nosp.get(min));  // 累加路径数}} else if (newDist <= v.getWeight()) {  // 如果新路径更短或相等v.setWeight(newDist);  // 更新 v 的最短路径edgeTo.put(v, min);  // 记录 v 的前驱节点nosp.put(v, nosp.get(min));  // 更新 v 的路径数heap.percolateUp(v);  // 调整堆,保持顺序}}}
}// 返回从源点到每个顶点的最短路径数
public Map<Vertex, Integer> nbPaths() {return nosp;  // 返回路径数的映射
}
BellmanFord

可处理负权值,检测负权回路

java">protected Graph G; // 图的表示,可以是有向图或无向图
protected Map<Vertex, Vertex> edgeTo; // 用于记录每个顶点的最短路径的前驱顶点
protected Vertex start; // 起点顶点
/*** 使用 Bellman-Ford 算法计算从起点到图中所有其他顶点的最短路径。* 该算法能够处理带负权边的图,但如果存在负权回路,则无法保证正确性。*/
public void BellmanFord() {// 初始化所有顶点的权重为正无穷大for (Vertex v : G.vertices()) {v.setWeight(Double.POSITIVE_INFINITY);}// 设置起点的权重为 0start.setWeight(0.0);// 标志变量,用于判断在一次松弛操作中是否更新了权重boolean update = true;// 进行 |V| - 1 次松弛操作(其中 |V| 为顶点数量)// 如果在某一轮没有任何更新,算法提前终止for (int i = 0; update && i < G.nbVertices() - 1; i++) {update = false; // 假设本轮没有权重更新// 遍历图中所有的边for (Edge e : G.edges()) {Vertex u = e.origin();       // 边的起点Vertex v = e.destination();  // 边的终点double newDist = u.getWeight() + e.weight(); // 计算从起点到 v 的新权重// 如果通过边 e 可以找到更短路径,则更新权重和路径if (newDist < v.getWeight()) {update = true;          // 本轮发生了权重更新v.setWeight(newDist);   // 更新顶点 v 的权重edgeTo.put(v, u);       // 更新 v 的前驱顶点为 u}}}
}

排序 Sorting

image-20241215184231952

selection sort

java">public static <AnyType extends Comparable<AnyType>> void selection(AnyType[] array) {for ( int i = 0; i < array.length - 1; i++ ) {int k = i;for ( int j = i + 1; j < array.length; j++ )if ( array[j].compareTo(array[k]) < 0 )k = j;swap(array, i, k);}}

insertion sort

java">public static <AnyType extends Comparable<AnyType>> void insertion(AnyType[] array) {for ( int i = 1; i < array.length; i++ ) {AnyType x = array[i];int j = i; while ( j > 0 && array[j - 1].compareTo(x) > 0 ) {array[j] = array[j - 1];j = j - 1;}array[j] = x;}}

heap sort

java">public static <AnyType extends Comparable<AnyType>> void sort(AnyType[] array) {BinaryHeap<AnyType> heap = new BinaryHeap<AnyType>(array);for ( int i = array.length - 1; i > 0; i-- )try {array[i] = heap.deleteExtreme();}catch ( Exception e ) {}}

merge sort

java">/*** 使用辅助数组 tmp 中 [lo, hi] 的部分对数组 array 中 [lo, hi] 的部分进行排序* 时间复杂度:THETA(n.log(n)),其中 n = hi - lo + 1*/
private static <AnyType extends Comparable<AnyType>> void sort(AnyType[] array, AnyType[] tmp, int lo, int hi) {// 如果区间内至少有两个元素,则继续划分if (lo < hi) {// 计算中点位置int mid = (lo + hi) / 2;// 递归对左半部分排序sort(array, tmp, lo, mid);// 递归对右半部分排序sort(array, tmp, mid + 1, hi);// 合并左右部分merge(array, tmp, lo, mid, hi);// 将辅助数组的结果拷贝回原数组transfer(tmp, array, lo, hi);}
}/*** 合并 array[lo, mid] 和 array[mid+1, hi] 到 tmp[lo, hi],* 然后将结果拷贝回 array[lo, hi]* 前提条件:array[lo, mid] 和 array[mid+1, hi] 已经排序* 时间复杂度:THETA(n),其中 n = hi - lo + 1*/
private static <AnyType extends Comparable<AnyType>> void merge(AnyType[] array, AnyType[] tmp, int lo, int mid, int hi) {// 定义左指针 l、右指针 r 和目标位置指针 mint l = lo;       // 左半部分的起始位置int r = mid + 1;  // 右半部分的起始位置int m = lo;       // 辅助数组中当前写入的位置// 交替从左右两部分中选择较小的元素,写入 tmpwhile (l <= mid && r <= hi) {if (array[l].compareTo(array[r]) < 0) {tmp[m++] = array[l++];} else {tmp[m++] = array[r++];}}// 如果左半部分还有剩余元素,则全部写入 tmpwhile (l <= mid) {tmp[m++] = array[l++];}// 如果右半部分还有剩余元素,则全部写入 tmpwhile (r <= hi) {tmp[m++] = array[r++];}
}

image-20241215192937009

image-20241215192929042

image-20241215192917751

java">/*** 使用辅助数组 tmp 的 [lo, hi] 部分对 array 的 [lo, hi] 部分进行原地排序* 时间复杂度:THETA(n.log(n)),其中 n = hi - lo + 1*/
private static <AnyType extends Comparable<AnyType>> void sortInplace(AnyType[] array, AnyType[] tmp, int lo, int hi) {// 如果区间内至少有两个元素,继续分割if (lo < hi) {// 计算中间位置int mid = (lo + hi) / 2;// 递归对左半部分进行排序sortTo(array, tmp, lo, mid);// 递归对右半部分进行排序sortTo(array, tmp, mid + 1, hi);// 合并左右部分到原数组中mergeTo(tmp, array, lo, mid, hi);}
}/*** 将 array 中 [lo, hi] 部分排序后存入 tmp 的 [lo, hi] 部分* 时间复杂度:THETA(n.log(n)),其中 n = hi - lo + 1*/
private static <AnyType extends Comparable<AnyType>> void sortTo(AnyType[] array, AnyType[] tmp, int lo, int hi) {// 如果区间内至少有两个元素,继续分割if (lo < hi) {// 计算中间位置int mid = (lo + hi) / 2;// 递归对左半部分排序并存入 arraysortInplace(array, tmp, lo, mid);// 递归对右半部分排序并存入 arraysortInplace(array, tmp, mid + 1, hi);// 合并左右部分到辅助数组中mergeTo(array, tmp, lo, mid, hi);} // 如果区间只有一个元素,直接将其复制到辅助数组else if (lo == hi) {tmp[lo] = array[lo];}
}/*** 将 from 数组的 [lo, mid] 和 [mid+1, hi] 两部分合并到 to 数组的 [lo, hi] 部分* 前置条件:from[lo, mid] 和 from[mid+1, hi] 均已排序* 时间复杂度:THETA(n),其中 n = hi - lo + 1*/
private static <AnyType extends Comparable<AnyType>> void mergeTo(AnyType[] from, AnyType[] to, int lo, int mid, int hi) {// 定义左指针 l、右指针 r 和目标位置指针 mint l = lo;       // 左半部分的起始位置int r = mid + 1;  // 右半部分的起始位置int m = lo;       // 目标数组的写入位置// 合并左右两部分,选择较小的元素放入目标数组while (l <= mid && r <= hi) {if (from[l].compareTo(from[r]) < 0) {to[m++] = from[l++];} else {to[m++] = from[r++];}}// 如果左半部分还有剩余元素,全部写入目标数组while (l <= mid) {to[m++] = from[l++];}// 如果右半部分还有剩余元素,全部写入目标数组while (r <= hi) {to[m++] = from[r++];}
}

quick sort

java">/*** 快速排序主方法* @param array 待排序数组* @param lo 当前排序范围的左边界* @param hi 当前排序范围的右边界*/
private static <AnyType extends Comparable<AnyType>> void sort(AnyType[] array, int lo, int hi) {if (lo < hi) {// 对当前范围进行分区操作,并获取基准值的索引int pivot = partition(array, lo, hi);// 递归排序左半部分sort(array, lo, pivot - 1);// 递归排序右半部分sort(array, pivot + 1, hi);}
}/*** 对数组的部分区间 array[lo, hi] 进行分区操作* 并返回基准值(pivot)的最终索引* @param array 待分区数组* @param lo 分区范围的左边界* @param hi 分区范围的右边界* @return 基准值的最终索引*/
private static <AnyType extends Comparable<AnyType>> int partition(AnyType[] array, int lo, int hi) {// 选择最左边的元素作为基准值AnyType pivot = array[lo];int p = lo; // p 用于记录小于 pivot 的区域的末尾索引// 遍历数组的其余部分for (int i = lo + 1; i <= hi; i++) {// 如果当前元素小于基准值,将其与小于区域的下一个元素交换if (array[i].compareTo(pivot) < 0) {swap(array, i, ++p);}}// 将基准值放到正确位置,即小于区域的末尾swap(array, lo, p);return p; // 返回基准值的最终位置
}/*** 交换数组中的两个元素* @param array 数组* @param i 第一个元素的索引* @param j 第二个元素的索引*/
private static <AnyType> void swap(AnyType[] array, int i, int j) {AnyType temp = array[i];array[i] = array[j];array[j] = temp;
}

rank

java">/*** 返回数组中指定元素 'item' 的秩(排名)。秩是指该元素在已排序数组中的位置(从 1 开始)。* 前提条件:如果 'item' 存在于数组中,它位于切片 array[lo, hi] 范围内。* * @param item 要查找的元素* @param array 待排序的数组* @param lo 当前查找范围的起始索引* @param hi 当前查找范围的结束索引* @return 如果 'item' 存在,返回其秩(1-based index),否则返回 -1*/
private static <AnyType extends Comparable<AnyType>> int rank(AnyType item, AnyType[] array, int lo, int hi) {// 如果当前查找范围无效(hi < lo),返回 -1if (hi < lo)return -1;// 对当前范围进行分区操作,返回基准元素的索引int pivot = partition(array, lo, hi);// 比较 'item' 和基准元素的大小int cmp = item.compareTo(array[pivot]);// 如果 'item' 小于基准元素,递归查找左半部分if (cmp < 0)return rank(item, array, lo, pivot - 1);// 如果 'item' 大于基准元素,递归查找右半部分if (cmp > 0)return rank(item, array, pivot + 1, hi);// 如果相等,返回该基准元素的秩(索引 + 1)return pivot + 1;
}/*** 对数组的部分区间 array[lo, hi] 进行分区操作* 并返回基准元素(pivot)的最终索引* * @param array 待分区数组* @param lo 分区范围的起始索引* @param hi 分区范围的结束索引* @return 基准元素的最终索引*/
private static <AnyType extends Comparable<AnyType>> int partition(AnyType[] array, int lo, int hi) {// 选择最左边的元素作为基准元素AnyType pivot = array[lo];int p = lo; // p 用于记录小于基准元素的区域的末尾索引// 遍历数组的其余部分for (int i = lo + 1; i <= hi; i++) {// 如果当前元素小于基准元素,则将其与小于区域的下一个元素交换if (array[i].compareTo(pivot) < 0) {swap(array, i, ++p);}}// 将基准元素放到正确的位置,即小于区域的末尾swap(array, lo, p);return p; // 返回基准元素的最终位置
}/*** 交换数组中的两个元素* * @param array 数组* @param i 第一个元素的索引* @param j 第二个元素的索引*/
private static <AnyType> void swap(AnyType[] array, int i, int j) {AnyType temp = array[i];array[i] = array[j];array[j] = temp;
}

二叉堆 Heap

Binary heap

java">/*** 使数组 A 中的元素满足堆的性质(大顶堆)。* 时间复杂度: O(size),其中 size 是堆的大小。*/
private void buildHeap() {// 从最后一个非叶子节点开始向上调整堆,使得整个数组满足堆的性质for (int i = parent(size - 1); i >= 0; i--)percolateDown(i);
}
/*** 对堆中指定节点 n 执行下沉操作,调整堆使其仍然保持堆的性质。* 时间复杂度: O(log(size)),其中 size 是堆的大小。*/
private void percolateDown(int n) {// 获取当前节点的左右子节点索引int g = left(n); // 左子节点int d = right(n); // 右子节点int k = n; // 假设当前节点是最大的节点// 如果左子节点存在且左子节点大于当前节点,则更新 k 为左子节点的索引if (g < size && c.compare(A[g], A[n]) > 0)k = g;// 如果右子节点存在且右子节点大于当前的最大节点,则更新 k 为右子节点的索引if (d < size && c.compare(A[d], A[k]) > 0)k = d;// 如果 k 不等于 n,说明当前节点不是最大节点,需要交换并继续下沉if (k != n) {swap(k, n); // 交换当前节点和最大子节点percolateDown(k); // 递归调用下沉操作,继续调整堆}
}/*** 对堆中指定节点 n 执行上浮操作,调整堆使其仍然保持堆的性质。* 时间复杂度: O(log(size)),其中 size 是堆的大小。*/
private void percolateUp(int n) {// 记录当前节点的值AnyType e = A[n];// 上浮元素,使其不违反堆的性质while (n > 0 && c.compare(e, A[parent(n)]) > 0) {// 如果当前节点比父节点大,则将父节点下沉到当前节点的位置A[n] = A[parent(n)];n = parent(n); // 更新 n 为父节点的索引}// 将上浮的元素放到正确的位置A[n] = e;
}
/*** 返回并删除堆中的极端元素(根节点元素)。* 时间复杂度: O(log(size)),其中 size 是堆的大小。*/
public AnyType deleteExtreme() throws EmptyHeapException {// 如果堆为空,则抛出异常if (size == 0)throw new EmptyHeapException();// 记录极端元素(堆顶元素)AnyType extreme = A[0];// 将堆的最后一个元素放到堆顶A[0] = A[--size];// 如果堆不为空,执行下沉操作调整堆if (size > 0)percolateDown(0);return extreme; // 返回删除的极端元素
}/*** 向堆中添加一个新元素。* 时间复杂度: O(log(size)),其中 size 是堆的大小。*/
public void add(AnyType e) throws FullHeapException {// 如果堆满,则抛出异常if (size == A.length)throw new FullHeapException();// 将新元素添加到堆的末尾A[size++] = e;// 上浮新元素,使得堆保持堆的性质percolateUp(size - 1);
}

D-heaps

java">private void percolateDown(int n) {int k = n;// 遍历当前节点的所有子节点,子节点的范围是 [d*k+1, d*(k+1)] for (int i = d * k + 1; i < size && i <= d * (k + 1); i++) {// 如果当前子节点比当前节点大,则更新 k 为子节点的索引if (c.compare(A[i], A[k]) > 0)k = i;}// 如果 k 被更新了,说明子节点中有比当前节点更大的元素,交换并继续下沉操作if (k != n) {swap(k, n);percolateDown(k); // 递归继续下沉}
}
private void percolateUp(int n) {AnyType e = A[n];// 上浮元素,直到堆的性质满足,或者到达根节点while (n > 0 && c.compare(e, A[parent(n)]) > 0) {// 如果当前节点比父节点大,则交换当前节点与父节点A[n] = A[parent(n)];n = parent(n); // 更新 n 为父节点的索引}A[n] = e; // 将上浮后的元素放置到正确的位置
}

树 Trees

java">// 计算二叉树的高度
// 高度定义为从根节点到最远叶子节点的路径长度(不包括根节点)
private int height(BinaryNode<AnyType> t) {// 如果节点为空,则返回 -1(空树的高度)if ( t == null )return -1;// 否则,递归计算左子树和右子树的高度,返回它们的最大值加一return 1 + Math.max(height(t.left), height(t.right));
}// 计算二叉树的 lowness,定义为从根节点到最接近的叶子节点的路径长度
public int lowness(BinaryNode<AnyType> t) {// 如果是叶子节点(没有左右子树),返回 0if ( t.left == null && t.right == null )return 0;// 如果只有左子树,递归计算左子树的 lowness,加 1if ( t.left == null )return 1 + lowness(t.right);// 如果只有右子树,递归计算右子树的 lowness,加 1if ( t.right == null )return 1 + lowness(t.left);// 如果左右子树都存在,递归计算左右子树的 lowness,返回其中较小值加 1return 1 + Math.min(lowness(t.left), lowness(t.right));
}// 计算二叉树的大小,即节点的总数
private int size(BinaryNode<AnyType> t) {// 如果节点为空,返回 0(树的大小)if ( t == null )return 0;// 否则,递归计算左右子树的大小,并加上当前节点return 1 + size(t.left) + size(t.right);
}// 计算二叉树的叶子节点数量
private int leaves(BinaryNode<AnyType> t) {// 如果节点为空,返回 0if ( t == null )return 0;// 如果是叶子节点(没有左右子树),返回 1if ( t.left == null && t.right == null )return 1;// 否则,递归计算左右子树中的叶子节点数量return leaves(t.left) + leaves(t.right); 
}// 检查两个二叉树是否同构
private boolean isomorphic(BinaryNode<AnyType> t1, BinaryNode<AnyType> t2) {// 如果两个树都为空,则是同构的if ( t1 == null )return t2 == null;// 如果一个树为空而另一个非空,则不是同构的if ( t2 == null )return false;// 递归检查左子树和右子树是否相同return isomorphic(t1.left, t2.left) && isomorphic(t1.right, t2.right);
}// 检查二叉树是否平衡(左右子树的高度差不超过 1)
private boolean balanced1(BinaryNode<AnyType> t) {// 如果节点为空,认为是平衡的if ( t == null )return true;// 递归检查左右子树是否平衡,并且左右子树的高度差不超过 1return balanced1(t.left) && balanced1(t.right) &&Math.abs(height(t.left) - height(t.right)) <= 1;
}private static final int NOT_BALANCED = -2;
// 检查二叉树是否平衡,并计算树的高度
private int balanced2(BinaryNode<AnyType> t) {// 如果节点为空,返回 -1 表示空树平衡if ( t == null )return -1;// 递归计算左子树的平衡状态和高度int l = balanced2(t.left);if ( l == NOT_BALANCED ) return NOT_BALANCED; // 如果左子树不平衡,返回标志// 递归计算右子树的平衡状态和高度int r = balanced2(t.right);if ( r == NOT_BALANCED ) return NOT_BALANCED; // 如果右子树不平衡,返回标志// 如果左右子树的高度差大于 1,表示不平衡if ( Math.abs(l - r) > 1 ) return NOT_BALANCED;// 返回当前树的高度return 1 + Math.max(l, r);
}// 使用 Optional 返回树是否平衡以及树的高度
private Optional<Integer> balanced22(BinaryNode<AnyType> t) {// 如果节点为空,返回 Optional.of(-1) 表示平衡的空树if ( t == null )return Optional.of(-1);// 递归检查左子树的平衡状态Optional<Integer> l = balanced22(t.left);if ( l.isEmpty() ) return Optional.empty(); // 如果左子树不平衡,返回空 Optional// 递归检查右子树的平衡状态Optional<Integer> r = balanced22(t.right);if ( r.isEmpty() ) return Optional.empty(); // 如果右子树不平衡,返回空 Optional// 如果左右子树的高度差大于 1,表示不平衡,返回空 Optionalif ( Math.abs(l.get() - r.get()) > 1 ) return Optional.empty();// 返回树的高度return Optional.of(1 + Math.max(l.get(), r.get()));
}// 检查二叉树是否 shapely (符合一定的形状约束)
// 高度不应大于 lowness 的两倍
private boolean shapely1(BinaryNode<AnyType> t) {// 如果节点为空,认为是 shapelyif ( t == null )return true;// 计算当前节点的高度和 lownessint height = height(t);int lowness = lowness(t);// 递归检查左右子树是否符合 shapely 约束return shapely1(t.left) && shapely1(t.right) && height <= 2 * lowness;
}// 记录树的高度和 lowness 的类
private record Pair(int height, int lowness) {}// 默认叶子节点的高度和 lowness
private final Pair leafPair = new Pair(-1, -1);// 检查树是否 shapely2 (符合形状约束)
public boolean shapely2() {// 调用 shapely2 方法检查根节点Pair p = shapely2(this);return p != null; // 如果返回非空值,表示树是 shapely 的
}// 递归检查树的形状是否符合约束
private Pair shapely2(BinaryNode<AnyType> t) {// 如果是叶子节点,返回默认的叶子节点高度和 lownessif ( t.left == null && t.right == null )return leafPair;int lowness, height;// 如果没有右子树,递归检查左子树if ( t.right == null ) {Pair l = shapely2(t.left);if ( l == null ) return null; // 如果左子树不符合要求,返回 nulllowness = 1 + l.lowness;height = 1 + l.height;}// 如果没有左子树,递归检查右子树else if ( t.left == null ) {Pair r = shapely2(t.right);if ( r == null ) return null; // 如果右子树不符合要求,返回 nulllowness = 1 + r.lowness;height = 1 + r.height;}// 如果左右子树都存在,递归检查左右子树else {Pair l = shapely2(t.left);if ( l == null ) return null; // 如果左子树不符合要求,返回 nullPair r = shapely2(t.right);if ( r == null ) return null; // 如果右子树不符合要求,返回 nullheight = 1 + Math.max(l.height, r.height); // 当前树的高度是左右子树高度的最大值加 1lowness = 1 + Math.min(l.lowness, r.lowness); // 当前树的 lowness 是左右子树 lowness 的最小值加 1}// 如果高度不大于 lowness 的两倍,则返回当前的 Pair,否则返回 nullif ( height <= 2 * lowness )return new Pair(height, lowness);return null;
}

Binary Search Trees

java">private BinaryNode add(AnyType x, BinaryNode t) {if (t == null)return new BinaryNode(x); // 如果树为空,创建一个新的节点返回int compareResult = x.compareTo(t.element); // 比较 x 和当前节点 t.element 的大小if (compareResult < 0) // 如果 x 比当前节点小,插入到左子树t.left = add(x, t.left);else if (compareResult > 0) // 如果 x 比当前节点大,插入到右子树t.right = add(x, t.right);return t; // 返回当前节点 t(树的结构可能发生变化,但节点本身不变)
}
private BinaryNode remove(AnyType x, BinaryNode t) {if (t == null)return t; // 如果树为空,返回 null,表示没有找到该元素int compareResult = x.compareTo(t.element); // 比较 x 和当前节点 t.element 的大小if (compareResult < 0) // 如果 x 比当前节点小,从左子树删除t.left = remove(x, t.left);else if (compareResult > 0) // 如果 x 比当前节点大,从右子树删除t.right = remove(x, t.right);else if (t.left != null && t.right != null) { // 当前节点有两个子节点t.element = findMax(t.left).element; // 找到左子树中的最大元素t.left = remove(t.element, t.left); // 删除最大元素}elset = (t.left != null) ? t.left : t.right; // 当前节点只有一个子节点或没有子节点return t; // 返回当前节点
}

http://www.ppmy.cn/server/150663.html

相关文章

uni-app多环境配置动态修改

前言 这篇文章主要介绍uniapp在Hbuilderx 中&#xff0c;通过工程化&#xff0c;区分不同环境、动态修改小程序appid以及自定义条件编译&#xff0c;解决代码发布和运行时手动切换问题。 背景 当我们使用uniapp开发同一个项目发布不同的环境二级路径不同时&#xff0c;这时候…

OpenCV图片添加水印

函数效果图&#xff1a; 本来只有蓝色背景&#xff0c;这两个人物是水印添加上去的 原理&#xff1a; 本实验中添加水印的概念其实可以理解为将一张图片中的某个物体或者图案提取出来&#xff0c;然后叠加到另一张图片上。具体的操作思想是通过将原始图片转换成灰度图&#x…

python数据分析一例:使用SQL和pandas对数据进行聚合和diff

对一系列数据聚合后进行diff&#xff0c;是一种常见的数据分析需求。例如&#xff0c;我们可能会需要将每个月的财务支出流水数据进行分类汇总&#xff0c;再对不同月的汇总数据进行比较&#xff0c;看看哪些分类支出变多了&#xff0c;哪些变少了。此次我将使用SQL和pandas来实…

CTF 攻防世界 Web: FlatScience write-up

题目名称-FlatScience 网址 index 目录中没有发现提示信息&#xff0c;链接会跳转到论文。 目前没有发现有用信息&#xff0c;尝试目录扫描。 目录扫描 注意到存在 robots.txt 和 login.php。 访问 robots.txt 这里表明还存在 admin.php admin.php 分析 在这里尝试一些 sql…

Python什么是动态调用方法?What is Dynamic Method Invocation? (中英双语)

什么是动态调用方法&#xff1f; 动态调用方法指通过方法或属性的名称&#xff0c;在运行时而非编译时调用对象的方法或访问其属性。换句话说&#xff0c;在编写代码时&#xff0c;方法名或属性名可以是变量&#xff0c;只有在程序运行时才能确定调用的内容。这种特性允许程序…

机器学习干货笔记分享:朴素贝叶斯算法

朴素贝叶斯分类是一种十分简单的分类算法&#xff0c;即对于给出的待分类项&#xff0c;求解在此项出现的条件下各个类别出现的概率&#xff0c;哪个最大&#xff0c;就认为此待分类项属于哪个类别。 以判定外国友人为例做一个形象的比喻。 若我们走在街上看到一个黑皮肤的外…

Elasticsearch的一些介绍

你想问的可能是 **Elasticsearch**&#xff0c;以下是关于它的一些介绍&#xff1a; ### 概述 Elasticsearch是一个基于Apache Lucene库构建的开源分布式搜索和分析引擎&#xff0c;采用Java语言编写&#xff0c;具有高性能、可扩展性和易用性等特点&#xff0c;可用于各种数据…

Unity学习笔记(一)如何实现物体之间碰撞

前言 本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记 如何实现物体之间碰撞 实现物体之间的碰撞关键组件&#xff1a;Rigidbody 2D(刚体)、Collider 2D(碰撞体)、Sprite Renderer&#xff08;Sprite渲染器&#xff09; 实现物体之间的碰撞 …