KMP算法、Manacher算法、BFPRT算法及应用

KMP算法

KMP算法是一种改进的字符串匹配算法。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。next()数组表示下一次移动到哪个位置。

  • 子序列:不连续
  • 子串:连续

KMP算法针对子串,不针对子序列。

//kmp算法
public class hello {
    //利用kmp算法获得m字符串在s字符串中开始的位置
    public static int getIndexOf(String s, String m) {
        if (s == null || m == null || m.length() < 1 || s.length() < m.length()) {
            return -1;
        }
        //字符串转换为数组
        char[] ss = s.toCharArray();
        char[] ms = m.toCharArray();
        //s下标和m下标
        int si = 0;
        int mi = 0;
        //获得next数组,表示最长前缀(不包括最后一个元素)跟最长后缀(不包括第一个元素)是否存在相等的情况
        int[] next = getNextArray(ms);
        while (si < ss.length && mi < ms.length) {
            //如果两者相等,则直接往下比较
            if (ss[si] == ms[mi]) {
                si++;
                mi++;
            } else if (next[mi] == -1) {
                //如果两者不相等,并且m的第一个字符就不相等,s直接往下
                si++;
            } else {
                //如果两者不相等,并且m与s有字符相等,m滑到next[mi]位置
                mi = next[mi];
            }
        }
        //mi足够m长度,说明s中存在m,返回起始位置,否则返回-1
        return mi == ms.length ? si - mi : -1;
    }
    //利用归纳法获得next数组,next数组的值为[-1,0,...,k],0为不存在,k为最大前缀==最大后缀中,前缀串的后一个字符下标
    public static int[] getNextArray(char[] ms) {
        //m长度只有1,-1表示不动,s串 加 1
        if (ms.length == 1) {
            return new int[] { -1 };
        }
        //m长度超过1
        int[] next = new int[ms.length];
        next[0] = -1;
        next[1] = 0;
        //pos为当前m的位置,起始是第三个数
        int pos = 2;
        //cn为最大前缀==最大后缀中,前缀串的后一个字符下标
        int cn = 0;
        while (pos < next.length) {
            //m当前值的前一个字符等于前缀串的后一个字符
            if (ms[pos - 1] == ms[cn]) {
                next[pos++] = ++cn;
            } else if (cn > 0) {
                //不相等一直找前一个next
                cn = next[cn];
            } else {
                //不存在前缀串==后缀串
                next[pos++] = 0;
            }
        }
        return next;
    }

    public static void main(String[] args) {
        String str = "abcabcababaccc";
        String match = "ababa";
        System.out.println(getIndexOf(str, match));
    }
}

1.获得两个字符串
在一个字符串的末尾加一串字符,使得所加字符最少,最终得到的字符串中有两个原字符串。

public class hello {
    //得到结果字符串
    public static String answer(String str) {
        if (str == null || str.length() == 0) {
            return "";
        }
        char[] chas = str.toCharArray();
        //字符串只有一个字符
        if (chas.length == 1) {
            return str + str;
        }
        //字符串只有两个字符
        if (chas.length == 2) {
            return chas[0] == chas[1] ? (str + String.valueOf(chas[0])) : (str + str);
        }
        //得到缺的字符串下标起始位置
        int endNext = endNextLength(chas);
        return str + str.substring(endNext);
    }
    //得到缺的字符串下标起始位置(利用next数组)
    public static int endNextLength(char[] chas) {
        //定义next数组
        int[] next = new int[chas.length + 1];
        next[0] = -1;
        next[1] = 0;
        int pos = 2;
        int cn = 0;
        while (pos < next.length) {
            if (chas[pos - 1] == chas[cn]) {
                next[pos++] = ++cn;
            } else if (cn > 0) {
                cn = next[cn];
            } else {
                next[pos++] = 0;
            }
        }
        return next[next.length - 1];
    }

    public static void main(String[] args) {
        String test2 = "aa";
        System.out.println(answer(test2));

        String test3 = "ab";
        System.out.println(answer(test3));

        String test4 = "abcdabcd";
        System.out.println(answer(test4));
    }
}

2.子树包含问题
判断某棵树是不是另一个树的一部分

public class hello {
    //定义树节点
    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }
    //判断是否为子树
    public static boolean isSubtree(Node t1, Node t2) {
        //树序列化变字符串
        String t1Str = serialByPre(t1);
        String t2Str = serialByPre(t2);
        return getIndexOf(t1Str, t2Str) != -1;
    }
    //先序序列化
    public static String serialByPre(Node head) {
        if (head == null) {
            return "#!";
        }
        String res = head.value + "!";
        res += serialByPre(head.left);
        res += serialByPre(head.right);
        return res;
    }

    // KMP判断是否为子串,是子串即是子树
    public static int getIndexOf(String s, String m) {
        if (s == null || m == null || m.length() < 1 || s.length() < m.length()) {
            return -1;
        }
        //字符串变数组
        char[] ss = s.toCharArray();
        char[] ms = m.toCharArray();
        int[] nextArr = getNextArray(ms);
        int index = 0;
        int mi = 0;
        while (index < ss.length && mi < ms.length) {
            if (ss[index] == ms[mi]) {
                index++;
                mi++;
            } else if (nextArr[mi] == -1) {
                index++;
            } else {
                mi = nextArr[mi];
            }
        }
        return mi == ms.length ? index - mi : -1;
    }

    public static int[] getNextArray(char[] ms) {
        if (ms.length == 1) {
            return new int[] { -1 };
        }
        int[] nextArr = new int[ms.length];
        nextArr[0] = -1;
        nextArr[1] = 0;
        int pos = 2;
        int cn = 0;
        while (pos < nextArr.length) {
            if (ms[pos - 1] == ms[cn]) {
                nextArr[pos++] = ++cn;
            } else if (cn > 0) {
                cn = nextArr[cn];
            } else {
                nextArr[pos++] = 0;
            }
        }
        return nextArr;
    }

    public static void main(String[] args) {
        Node t1 = new Node(1);
        t1.left = new Node(2);
        t1.right = new Node(3);
        t1.left.left = new Node(4);
        t1.left.right = new Node(5);
        t1.right.left = new Node(6);
        t1.right.right = new Node(7);
        t1.left.left.right = new Node(8);
        t1.left.right.left = new Node(9);

        Node t2 = new Node(2);
        t2.left = new Node(4);
        t2.left.right = new Node(8);
        t2.right = new Node(5);
        t2.right.left = new Node(9);

        System.out.println(isSubtree(t1, t2));

    }
}

Manacher算法

Manacher算法用于求解回文子串

//Manacher算法
public class hello {
    //将字符串的前后以及中间都加#字符,变为字符数组
    public static char[] manacherString(String str) {
        char[] charArr = str.toCharArray();
        char[] res = new char[str.length() * 2 + 1];
        int index = 0;
        for (int i = 0; i != res.length; i++) {
            res[i] = (i & 1) == 0 ? '#' : charArr[index++];
        }
        return res;
    }
    //最大回文字符串长度
    public static int maxLcpsLength(String str) {
        //字符串为空
        if (str == null || str.length() == 0) {
            return 0;
        }
        //将字符串的前后以及中间都加#字符,变为字符数组
        char[] charArr = manacherString(str);
        //建立一个回文半径数组
        int[] pArr = new int[charArr.length];
        //字符数组初始为第一个#,位置为-1
        int index = -1;
        //初始回文半径数组(右侧)起始在-1位置
        int pR = -1;
        //定义最大值
        int max = Integer.MIN_VALUE;

        //获得回文字符串长度数组最大值,整个过程分为回文半径在当前位置之前和之后两种情况,
        //其中回文半径在当前位置之后分为三种情况:当前位置对称位置的边界在左边界之前,之后,压线
        for (int i = 0; i != charArr.length; i++) {
            //i的对称位置的回文和i到pR的距离,哪个小哪个作为起始距离,pR>i表示i在回文里面(之前)
            pArr[i] = pR > i ? Math.min(pArr[2 * index - i], pR - i) : 1;
            //条件为当前位置的半径不超出数组长度并且左边区域也没越界
            while (i + pArr[i] < charArr.length && i - pArr[i] > -1) {
                //扩位置(当前位置对称位置的边界在左边界之前,之后不需要要扩,直接break)
                if (charArr[i + pArr[i]] == charArr[i - pArr[i]])
                    pArr[i]++;
                else {
                    break;
                }
            }
            //确定回文半径右边界
            if (i + pArr[i] > pR) {
                pR = i + pArr[i];
                index = i;
            }
            max = Math.max(max, pArr[i]);
        }
        return max - 1;
    }

    public static void main(String[] args) {
        String str1 = "abc1234321ab";
        System.out.println(maxLcpsLength(str1));
    }
}

1.最短结尾
在一个字符串的末尾加一串字符,使得所加字符最少,最终得到回文字符串

public class hello {
    //转换字符串为数组
    public static char[] manacherString(String str) {
        char[] charArr = str.toCharArray();
        char[] res = new char[str.length() * 2 + 1];
        int index = 0;
        for (int i = 0; i != res.length; i++) {
            res[i] = (i & 1) == 0 ? '#' : charArr[index++];
        }
        return res;
    }
    //获得最短字符串
    public static String shortestEnd(String str) {
        if (str == null || str.length() == 0) {
            return null;
        }
        //转换为字符串数组
        char[] charArr = manacherString(str);
        int[] pArr = new int[charArr.length];
        int index = -1;
        int pR = -1;
        int maxContainsEnd = -1;
        //获得回文半径达到最右边界的半径值(本身包含#,所有其实是原数组回文直径)
        for (int i = 0; i != charArr.length; i++) {
            pArr[i] = pR > i ? Math.min(pArr[2 * index - i], pR - i) : 1;
            while (i + pArr[i] < charArr.length && i - pArr[i] > -1) {
                if (charArr[i + pArr[i]] == charArr[i - pArr[i]])
                    pArr[i]++;
                else {
                    break;
                }
            }
            if (i + pArr[i] > pR) {
                pR = i + pArr[i];
                index = i;
            }
            if (pR == charArr.length) {
                maxContainsEnd = pArr[i];
                break;
            }
        }
        char[] res = new char[str.length() - maxContainsEnd + 1];
        for (int i = 0; i < res.length; i++) {
            res[res.length - 1 - i] = charArr[i * 2 + 1];
        }
        return String.valueOf(res);
    }

    public static void main(String[] args) {
        String str2 = "abcd123321";
        System.out.println(shortestEnd(str2));
    }
}

结果:dcba

BFPRT算法

BFPRT算法步骤:

  1. 分组
  2. 组内排序
  3. 每个组的中位数拿出组成N/5大小的新数组
  4. bfprt获得新数组中位数
  5. 以该中位数为划分再次进行上述操作
//BFPRT,O(N)
public class hello {
    //获得数组前k小的值
    public static int[] getMinKNumsByBFPRT(int[] arr, int k) {
        if (k < 1 || k > arr.length) {
            return arr;
        }
        //获得第k小的数
        int minKth = getMinKthByBFPRT(arr, k);
        int[] res = new int[k];
        int index = 0;
        for (int i = 0; i != arr.length; i++) {
            if (arr[i] < minKth) {
                res[index++] = arr[i];
            }
        }
        for (; index != res.length; index++) {
            res[index] = minKth;
        }
        return res;
    }
    //获得第k小的数
    public static int getMinKthByBFPRT(int[] arr, int K) {
        int[] copyArr = copyArray(arr);
        return select(copyArr, 0, copyArr.length - 1, K - 1);
    }
    //复制数组
    public static int[] copyArray(int[] arr) {
        int[] res = new int[arr.length];
        for (int i = 0; i != res.length; i++) {
            res[i] = arr[i];
        }
        return res;
    }
    //获得第k小的数
    public static int select(int[] arr, int begin, int end, int i) {
        if (begin == end) {
            return arr[begin];
        }
        //获得中位数数组的中位数
        int pivot = medianOfMedians(arr, begin, end);
        //以得到的中位数做划分
        int[] pivotRange = partition(arr, begin, end, pivot);
        if (i >= pivotRange[0] && i <= pivotRange[1]) {
            return arr[i];
        } else if (i < pivotRange[0]) {
            return select(arr, begin, pivotRange[0] - 1, i);
        } else {
            return select(arr, pivotRange[1] + 1, end, i);
        }
    }
    //中位数数组的中位数
    public static int medianOfMedians(int[] arr, int begin, int end) {
        //分组
        int num = end - begin + 1;
        int offset = num % 5 == 0 ? 0 : 1;
        int[] mArr = new int[num / 5 + offset];
        //获得中位数数组
        for (int i = 0; i < mArr.length; i++) {
            int beginI = begin + i * 5;
            int endI = beginI + 4;
            mArr[i] = getMedian(arr, beginI, Math.min(end, endI));
        }
        //获得中位数数组的中位数
        return select(mArr, 0, mArr.length - 1, mArr.length / 2);
    }

    //返回等于pivotValue的两端下标
    public static int[] partition(int[] arr, int begin, int end, int pivotValue) {
        int small = begin - 1;
        int cur = begin;
        int big = end + 1;
        while (cur != big) {
            if (arr[cur] < pivotValue) {
                swap(arr, ++small, cur++);
            } else if (arr[cur] > pivotValue) {
                swap(arr, cur, --big);
            } else {
                cur++;
            }
        }
        int[] range = new int[2];
        range[0] = small + 1;
        range[1] = big - 1;
        return range;
    }

    //获得中位数
    public static int getMedian(int[] arr, int begin, int end) {
        insertionSort(arr, begin, end);
        int sum = end + begin;
        int mid = (sum / 2) + (sum % 2);
        return arr[mid];
    }
    //排序
    public static void insertionSort(int[] arr, int begin, int end) {
        for (int i = begin + 1; i != end + 1; i++) {
            for (int j = i; j != begin; j--) {
                if (arr[j - 1] > arr[j]) {
                    swap(arr, j - 1, j);
                } else {
                    break;
                }
            }
        }
    }
    //交换
    public static void swap(int[] arr, int index1, int index2) {
        int tmp = arr[index1];
        arr[index1] = arr[index2];
        arr[index2] = tmp;
    }
    //打印
    public static void printArray(int[] arr) {
        for (int i = 0; i != arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int[] arr = { 6, 9, 1, 3, 1, 2, 2, 5, 6, 1, 3, 5, 9, 7, 2, 5, 6, 1, 9 };
        printArray(getMinKNumsByBFPRT(arr, 10));
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值