数据结构与算法

数据结构与算法复习

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

图连通问题

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

public class ConnectedComponents {
	
	private static Map<Vertex,Integer> cc; // the resulting map
	private 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);
	}
}

环路检测

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

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 的路径

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 顶点

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; // 返回总访问顶点数
    }
}

图广度搜索遍历

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); // 起始顶点的距离初始化为 0
    queue.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 是边数。
/**
 * 使用拓扑排序算法返回有向图 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算法相同。
/**
 * 使用深度优先搜索 (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<>(); // 存储排序后的顶点列表

    // 对图中的每个顶点进行遍历,如果顶点尚未访问,则从该顶点开始执行 DFS
    for (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); // 当前顶点的所有邻接顶点处理完毕后,将该顶点插入结果列表开头
}

哈密尔顿回路

拓扑排序法
/**
 * 测试有向图 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;
    }
    
    // 如果所有相邻顶点都满足条件,返回 true
    return true;
}
回溯法
/**
 * 检查图 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;
        }
    }
    
    // 如果无法找到回路,返回 false
    return false;
}

最小生成树

并查集

/**
 * 使用 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 最小生成树
/**
 * 使用 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>();
    
    // 随机选择一个起始顶点 u
    Vertex 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 最小生成林
/**
 * 使用 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 最小生成树
/**
 * 使用 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:用于维护当前所有未处理顶点的权重,使得可以快速找到权重最小的顶点(优先级队列的核心作用)。

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

protected Graph G; // 图的表示,可以是有向图或无向图
protected Map<Vertex, Vertex> edgeTo; // 用于记录每个顶点的最短路径的前驱顶点
protected Vertex start; // 起点顶点

/**
 * 实现 Dijkstra 算法计算从起点到所有其他顶点的最短路径。
 * 前置条件:图 G 的顶点和边已正确定义,起点 start 已指定。
 */
public void Dijkstra() {
    // 已知集合:存储所有已确定最短路径的顶点
    Set<Vertex> known = new HashSet<>();
    
    // 初始化每个顶点的权重为正无穷大,起点权重为 0
    for (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 记录最短路径的数量

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<>();  // 存储已经处理过的顶点

    // 初始化所有顶点的最短路径为正无穷大,并设置起点的路径长度为 0
    for (Vertex v : G.vertices()) {
        v.setWeight(Double.POSITIVE_INFINITY);  // 将所有顶点的权重设置为无穷大
        nosp.put(v, 0);  // 设置每个顶点的最短路径数为 0
    }
    start.setWeight(0.0);  // 设置起点的权重为 0
    nosp.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

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

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);
    }
    // 设置起点的权重为 0
    start.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

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

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

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

/**
 * 使用辅助数组 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 和目标位置指针 m
    int l = lo;       // 左半部分的起始位置
    int r = mid + 1;  // 右半部分的起始位置
    int m = lo;       // 辅助数组中当前写入的位置

    // 交替从左右两部分中选择较小的元素,写入 tmp
    while (l <= mid && r <= hi) {
        if (array[l].compareTo(array[r]) < 0) {
            tmp[m++] = array[l++];
        } else {
            tmp[m++] = array[r++];
        }
    }

    // 如果左半部分还有剩余元素,则全部写入 tmp
    while (l <= mid) {
        tmp[m++] = array[l++];
    }

    // 如果右半部分还有剩余元素,则全部写入 tmp
    while (r <= hi) {
        tmp[m++] = array[r++];
    }
}

image-20241215192937009

image-20241215192929042

image-20241215192917751

/**
 * 使用辅助数组 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;
        // 递归对左半部分排序并存入 array
        sortInplace(array, tmp, lo, mid);
        // 递归对右半部分排序并存入 array
        sortInplace(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 和目标位置指针 m
    int 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

/**
 * 快速排序主方法
 * @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

/**
 * 返回数组中指定元素 '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),返回 -1
    if (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

/**
 * 使数组 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

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

// 计算二叉树的高度
// 高度定义为从根节点到最远叶子节点的路径长度(不包括根节点)
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) {
    // 如果是叶子节点(没有左右子树),返回 0
    if ( t.left == null && t.right == null )
        return 0;
    // 如果只有左子树,递归计算左子树的 lowness,加 1
    if ( t.left == null )
        return 1 + lowness(t.right);
    // 如果只有右子树,递归计算右子树的 lowness,加 1
    if ( t.right == null )
        return 1 + lowness(t.left);
    // 如果左右子树都存在,递归计算左右子树的 lowness,返回其中较小值加 1
    return 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) {
    // 如果节点为空,返回 0
    if ( t == null )
        return 0;
    // 如果是叶子节点(没有左右子树),返回 1
    if ( 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;
    // 递归检查左右子树是否平衡,并且左右子树的高度差不超过 1
    return 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,表示不平衡,返回空 Optional
    if ( 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) {
    // 如果节点为空,认为是 shapely
    if ( t == null )
        return true;
    // 计算当前节点的高度和 lowness
    int 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) {
    // 如果是叶子节点,返回默认的叶子节点高度和 lowness
    if ( 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; // 如果左子树不符合要求,返回 null
        lowness = 1 + l.lowness;
        height = 1 + l.height;
    }
    // 如果没有左子树,递归检查右子树
    else if ( t.left == null ) {
        Pair r = shapely2(t.right);
        if ( r == null ) return null; // 如果右子树不符合要求,返回 null
        lowness = 1 + r.lowness;
        height = 1 + r.height;
    }
    // 如果左右子树都存在,递归检查左右子树
    else {
        Pair l = shapely2(t.left);
        if ( l == null ) return null; // 如果左子树不符合要求,返回 null
        Pair r = shapely2(t.right);
        if ( r == null ) return null; // 如果右子树不符合要求,返回 null
        height = 1 + Math.max(l.height, r.height); // 当前树的高度是左右子树高度的最大值加 1
        lowness = 1 + Math.min(l.lowness, r.lowness); // 当前树的 lowness 是左右子树 lowness 的最小值加 1
    }
    
    // 如果高度不大于 lowness 的两倍,则返回当前的 Pair,否则返回 null
    if ( height <= 2 * lowness )
        return new Pair(height, lowness);
    return null;
}

Binary Search Trees

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); // 删除最大元素
    }
    else
        t = (t.left != null) ? t.left : t.right; // 当前节点只有一个子节点或没有子节点
    return t; // 返回当前节点
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值