左神算法笔记08:并查集、KMP

岛问题

一个矩阵中只有0和1两种值,每个位置都可以和自己的上下左右四个位置相连,如果有一片1连在一起,这个部分叫做一个岛,求一个矩阵上有多少个岛
举例:
001010
111010
100100
000000
这个矩阵有3个岛
进阶:
如何设计一个并行算法解决这个问题

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;
		}
		//i,j没有越界,且当前位置值为1
		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(m * n)

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

先看并查集,再来思考
先把整个二维数组分为两部分,左部分统计有几个岛,右部分统计有几个岛,同时两部分都要统计起始点和结束点,最后通过并查集将相连的岛重新合在一起(即岛的数量减一)
在这里插入图片描述

并查集

分析

在这里插入图片描述

具体实现

import java.util.HashMap;
import java.util.List;
import java.util.Stack;

/**
 * @Author laimouren
 * @Date 2021/12/7 19:35
 */
public class UnionFind {
    //样本进来会包一层,叫做元素
    public static class Element<V>{
        public V value;
        public Element(V value){
            this.value = value;
        }
    }
    public static class UnionFindSet<V>{
        public HashMap<V,Element<V>> elementMap;
        //key 某个元素 value 该元素的父亲
        public HashMap<Element<V>,Element<V>> fatherMap;
        //key 某个集合的代表元素,value 该集合的大小
        public HashMap<Element<V>,Integer> sizeMap;
        public UnionFindSet(List<V> list){
            elementMap = new HashMap<>();
            fatherMap = new HashMap<>();
            sizeMap = new HashMap<>();
            for (V value : list) {
                Element<V> element = new Element<V>(value);
                elementMap.put(value,element);
                fatherMap.put(element,element);
                sizeMap.put(element,1);
            }
        }

        //给定一个ele,往上一直找,将代表元素返回
        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 = sizeMap.get(aF) >= sizeMap.get(bF) ? aF : bF;
                    Element<V> small = big == aF ? bF : aF;
                    fatherMap.put(small,big);
                    sizeMap.put(big,sizeMap.get(aF) + sizeMap.get(bF));
                    sizeMap.remove(small);
                }
            }
        }
    }
}

KMP算法

解决的问题:字符串str1和str2,str1是否包含str2,如果包含则返回str2在str1中开始的位置。
如何做到时间复杂度为O(N)

前缀和后缀匹配的最长长度K

str2求前缀和后缀匹配的最长长度K
在这里插入图片描述

举例说明

在这里插入图片描述

在这里插入图片描述
i位置的nextarr
在这里插入图片描述

代码实现

/**
 * @Author laimouren
 * @Date 2021/12/7 21:41
 */
public class KMP {
    //N >= M
    public static int getIndexOf(String s, String m){
        if(s == null || m == null || m.length() < 1 || s.length() < m.length()){
            return -1;
        }
        char [] str1 = s.toCharArray();
        char [] str2 = m.toCharArray();
        int i1 = 0;
        int i2 = 0;
        //O(M)
        int [] next = getNextArray(str2);
        //O(N)
        while(i1 < str1.length && i2 < next.length){
            //匹配则str1和str2都加一
            if(str1[i1] == str2[i2]){
                i1++;
                i2++;
            }
            //str2当前位置为0,等同与i2 == 0
            else if(next[i2] == -1){
                i1++;
            }
            //此时str1和str2不匹配,通过nextArray往前跳,得到i2应该在的下一个位置
            else {
                i2 = next[i2];
            }
        }
        //i2越界则匹配成功,结果为i1-i2; 否则i1越界了,此时匹配失败,返回-1
        return ((i2 == str2.length) ? (i1 - i2) : -1);
    }
    /**
     * 获取next数组
     */
    public static int [] getNextArray(char [] ms){
        //人为规定第一个为-1
        if(ms.length == 1) {
            return new int[]{-1};
        }
        int [] next = new int[ms.length];
        next[0] = -1;
        //人为规定第一个为0
        next[1] = 0;
        //next数组的位置
        int i = 2;
        //拿cn字符和i-1的字符比
        int cn = 0;
        while(i < ms.length){

            if(ms[i - 1] == ms[cn]) {
                //next[i] = cn + 1;
                //i++;
                //cn++;此时cn值是next[i-1]的值,这个i是更新后的i
                next[i++] = ++cn;
            }
            //当前跳到cn位置的字符,和i-1位置的字符配不上
            //cn得往前跳,即它的k值
            else if(cn > 0) {
                cn = next[cn];
            }
            //cn <= 0,没办法往前跳了
            //此时next[i]为0
            else {
                next[i++] = 0;
            }
        }
        return next;
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值