【左程云 数据结构与算法笔记】P13 基础提升 有序表,并查集

368 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第22天,点击查看活动详情 

下面是我整理的跟着b站左程云的数据结构与算法学习笔记,欢迎大家一起学习。

岛问题

思路分析:从左往后找到一个非零的点,如下图为第二个1

执行感染(infect)过程,把所有连成1片的1变成2,不断遍历上下左右并将其感染标注成为一片岛

把自身变为2 上下左右递归感染 代码实现

public static int countIslands(int[][] m) {  
    if (m == null || m[0] == null) {  
        return 0;  
    }  
    int N = m.length;  
    int M = m[0].length;  
    int res = 0;  
    for (int i = 0; i < N; i++) {  
        for (int j = 0; j < M; j++) {  
            if (m[i][j] == 1) {  
                res++;  
                infect(m, i, j, N, M);  
            }  
        }  
    }  
    return res;  
}  
  
public static void infect(int[][] m, int i, int j, int N, int M) {  
    if (i < 0 || i >= N || j < 0 || j >= M || m[i][j] != 1) {  
        return;  
    }  
    m[i][j] = 2;  
    infect(m, i + 1, j, N, M);  
    infect(m, i - 1, j, N, M);  
    infect(m, i, j + 1, N, M);  
    infect(m, i, j - 1, N, M);  
}

整个过程的时间复杂度为O(N* M) 整体遍历阶段 一个位置调用1次 infect过程时,最多调用4次(左右上下)

并查集

支持多个集合快速合并查找的结构 集合支持查找和添加在不同情况下的分析

  • 使用hashMap结构保存两个集合的信息,查找时时间复杂度仅为O(1) 但合并时时间复杂度大于O(1)
  • 可以直接合并集合,此时时间复杂度为O(1),但查找的时间复杂度大于O(N)
  • 并查集的结构可以让查找和合并的时间复杂度都接近O(1) 找到a元素对应的点,往上指的指针直到不能再往上找到的代表元素 若a,b所找出的代表元素不同,则不是同一个集合 合并时,先判断是否为同一个集合,当合并之后,b往上的指针指向a,a往上的元素与b往上的元素相同,为同一集合 少元素的顶部挂在多元素的顶部下面 把两个元素的顶部一改,就能做到快速的查找合并 用往上指的图的方式变成一种高效的并查集 在查询两者是否相同时,将其遍历的父扁平化,即都指向最上层的父,若某一条链过长,可以通过这种方式解决这个瓶颈,下次操作便是直接一步到位找到最上层的父

代码实现

public static class UnionFindSet<V> {  
    public HashMap<V, Element<V>> elementMap;  
    public HashMap<Element<V>, Element<V>> fatherMap;  
    public HashMap<Element<V>, Integer> rankMap;  
  
    public UnionFindSet(List<V> list) {  
        elementMap = new HashMap<>();  
        fatherMap = new HashMap<>();  
        rankMap = new HashMap<>();  
        for (V value : list) {  
            Element<V> element = new Element<V>(value);  
            elementMap.put(value, element);  
            fatherMap.put(element, element);  
            rankMap.put(element, 1);  
        }  
    }  
  
    private Element<V> findHead(Element<V> element) {  
        Stack<Element<V>> path = new Stack<>();  
        while (element != fatherMap.get(element)) {  
            path.push(element);  
            element = fatherMap.get(element);  
        }  
        while (!path.isEmpty()) {  
            fatherMap.put(path.pop(), element);  
        }  
        return element;  
    }  
  
    public boolean isSameSet(V a, V b) {  
        if (elementMap.containsKey(a) && elementMap.containsKey(b)) {  
            return findHead(elementMap.get(a)) == findHead(elementMap.get(b));  
        }  
        return false;  
    }  
  
    public void union(V a, V b) {  
        if (elementMap.containsKey(a) && elementMap.containsKey(b)) {  
            Element<V> aF = findHead(elementMap.get(a));  
            Element<V> bF = findHead(elementMap.get(b));  
            if (aF != bF) {  
                Element<V> big = rankMap.get(aF) >= rankMap.get(bF) ? aF : bF;  
                Element<V> small = big == aF ? bF : aF;  
                fatherMap.put(small, big);  
                rankMap.put(big, rankMap.get(aF) + rankMap.get(bF));  
                rankMap.remove(small);  
            }  
        }  
    }

调用次数超过或接近O(N),平均代价为O(1) 当findhead()调用的情况越高,平均时间复杂度越接近O(1)

如何设计一个并行算法解决这个问题

把二维数组分片并行进行计算 当并行处理时,分割图形时可能存在不一样的结果, 并收集边界感染者的信息并做区分

一开始,分为四个集合A,B,C,D,讨论边界是否相碰 如果相碰,即在相邻位置上,需要将两个岛的集合连通合并 整体岛数量-1;不断判断边界上的点是否相邻合并岛 多CPU模式下可以将图分块,再判断边界是否相邻

KMP算法

还是跟普通一样从左到右匹配遍历返回str2,只不过过程上有加速 前缀与后缀 前缀与后缀相等的最大长度为3 找到前缀与后缀相等(不包括本身)的最大长度 k 设置一个nextArr数组记载最大前缀与后缀相等的长度 如下图中第一个s 前缀最大长度都为3 经典过程 当发现后面有不相同时,str1重新回到i+1,str2重新回到0位置上

而KMP在拥有nextArr后,再碰见这种情况时,比对的位置停在x,而Y跳到跳到他最大前缀与后缀相等的长度,即相应位置的nextArr 后面,让x上的位置直接与他对比 实质是 因为前缀与后缀相同,可以直接跳过一些区域实现加速,将str2直接推到最大前缀与后缀长度之后