zoukankan      html  css  js  c++  java
  • 算法<初级>

    算法<初级> - 第七章 KMP/Manacher/BFPRT算法(完结)

    <一>KMP算法及其复杂度估计

    • KMP算法解决的问题:在str1字符串(长度n)中是否包含str2(长度m),返回-1或者首位置

      • 暴力解O(nm),KMP算法时间复杂度O(n)
      • next数组与最长前缀/后缀匹配长度
      • KMP算法就是遍历匹配串,然后根据next[]数组跳转匹配
    • next[]数组:[i]存储的是模式串在i位置前的子串中,其前缀和后缀最长的匹配长度

      • eg. 模式串p=aaaab,其next[]数组在[4]值为3 - 因为前的子串aaaa,前缀a=后缀a,前缀aa=后缀aa,前缀aaa=后缀aaa,规定前后缀为子串真子集,所以最长前后缀匹配长度为3。
      • next[]前两项人为规定:next[0]=-1,next[1]=0。
      • 时间复杂度O(m)
      • next[i]位置的求解:先看[i-1]位置处的值,p[i-1] ?= p[next[i-1]+1],if相等,则next[i] = next[i-1]+1;不相等则继续看next[ p[next[i-1]+1]]...直至为0。
    	public static int getIndexOf(String s, String m) {   //kmp算法
    		if (s == null || m == null || m.length() < 1 || s.length() < m.length()) {
    			return -1;
    		}
    		char[] ss = s.toCharArray(); // 匹配串(是否包含模式串)
    		char[] ms = m.toCharArray(); // 模式串
    		int si = 0;
    		int mi = 0;
    		int[] next = getNextArray(ms);
    		while (si < ss.length && mi < ms.length) {
    			if (ss[si] == ms[mi]) {
    				si++;
    				mi++;
    			} else if (next[mi] == -1) {
    				si++;
    			} else {
    				mi = next[mi];
    			}
    		}
    		return mi == ms.length ? si - mi : -1;
    	}
    
    	public static int[] getNextArray(char[] ms) {  //求解next数组
    		if (ms.length == 1) {
    			return new int[] { -1 };
    		}
    		int[] next = new int[ms.length];
    		next[0] = -1;
    		next[1] = 0;
    		int pos = 2; //求解位置
    		int cn = 0; //前索引的对称位置
    		while (pos < next.length) {
    			if (ms[pos - 1] == ms[cn]) {
    				next[pos++] = ++cn;
    			} else if (cn > 0) {
    				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));
    
    	}
    

    KMP算法扩展题目一 - ShortestHaveTwice

    • 题目表述:给定一个字符串str1,只能往str1的后面添加字符变成str2。要求一:str2必须包含两个str1,两个str1可以有重合,但是不能以同一位置开头(不能完全重合)。要求二:str2尽量短。最终返回str2。

    • 思路:

      • 实际上也就是运用next数组问题,找最长前后缀匹配长度,拼接最长匹配长度后的子串。
    • 算法实现(java)

    	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);
    	}
    
    	public static int endNextLength(char[] chas) { // 就是求解next数组,只不过直接返回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 test1 = "a";
    		System.out.println(answer(test1));
    
    		String test2 = "aa";
    		System.out.println(answer(test2));
    
    		String test3 = "ab";
    		System.out.println(answer(test3));
    
    		String test4 = "abcdabcd";
    		System.out.println(answer(test4));
    
    		String test5 = "abracadabra";
    		System.out.println(answer(test5));
    
    	}
    

    KMP算法扩展题目二 - T1SubtreeEqualsT2

    • 题目表述:给定两个二叉树T1和T2,返回T1的某个子树是否与T2的结构相等(就是T2为T1的某一子树)

      • 二叉树序列化:用一个字符串存储树结构和数据,只需要用比如先序遍历,孩子为空的情况用#特殊字符表示,每走一个结点输出再加上_(一个表示结束一个表示空)。eg. 1_2_#_#_3_#_#_,表示1->2左3右的一棵二叉树。(使用前+中序这种还原树方法是没有办法还原结点数据全都相同的情况)
    • 思路:

      • 首先将两棵二叉树序列化用字符串存储。
      • T1的某个子树是否与T2的结构相等,也就是T1字符串中是否包含T2字符串 - KMP(当每个结点的数据值长度不一样时存在反例,eg. 12_#_#_2_#_#_
      • 可以使用定长/等长或者hash/MD5(返回一个等长的hash串)解决反例(eg. 12和02)
    • 算法实现(java)

    	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算法:
      • 求解最长的回文子串

        • 添加特殊字符 - 实轴 / 虚轴,字符串长度/2=实际长度,奇偶回文同时解决
        • p_arr[]回文半径数组 - 回文半径/直径,添加字符后的字符串对应设置个回文半径/直径数组用来存储每个位置的值
        • R - 回文半径的最右边界:目前遍历的所有位置的回文半径最右边界
        • C - 最右边界R对应的回文中心(当R更新时C也更新)
      • RC与当前遍历i的关系:

        • i在R外:即刚好过边界的下一个元素,暴力扩。
        • i在R内(且i对称点i'的回文半径在L内):此时i的回文半径就是i‘的回文半径,因为在整个LR里面都是回文对称的。
        • i在R内(且i对称点i'的回文半径不在L内):此时i的回文半径是R-i长度的回文半径,因为L-1至少在i‘回文半径左边内,那么对应右边也有一个(L-1),同理对称C在C右边也有一个(L-1),若是C右边的(L-1)=R+1,则C的回文半径就比R长,所以(L-1) != R+1,所以i同理回文半径长度最多也就R-i。
        • i在R内(且i对称点i'的回文半径在L上):这种情况跟第一种不一样,因为L-1 != C,需要判断R+1 ?= C。如果等于则i从R开始往外暴力扩。
        • 从总体来看,R只会往前不会回退,所以Manacher算法的时间复杂度是O(n)
      • 算法实现(java)

    	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];
    		int index = -1;
    		int pR = -1;
    		int max = Integer.MIN_VALUE;
    		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;
    			}
    			max = Math.max(max, pArr[i]);
    		}
    		return max - 1;
    	}
    
    	public static void main(String[] args) {
    		String str1 = "abc1234321ab";
    		System.out.println(maxLcpsLength(str1));
    	}
    

    Manacher算法扩展题目:ShortestEnd

    • 题目表述:给定一个字符串str1,只能往str1的后面添加字符变成str2,要求str2整体都是回文串且最短。eg. str1=ABC12321,返回ABC12321CBA。

    • 思路:

      • 找到最长回文子串后缀,对前面的非回文部分直接逆序拼接到原串后面即可。
      • 找最长回文子串后缀,只需要改写马拉车算法,使得当R=最后位置时返回就可以了,此时就找到了最左中心最长回文子串。
      • 算法实现(java)
    	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));
    
    	}
    

    <三> BFPRT算法及其复杂度估计

    • BFPRT线性查找算法:
      • 在一个无序数组中找到一个最小第k个数(第k小的数)
      • 算法笔记:1)用树状数组求解序列第k大问题:O(logN) * O(logN) 2)分块思想求解实时序列第k大元素:O(sqrt(N)+sqrt(N))=O(sqrt(N)) 3)大根堆求解第k大元素:O(nlogk),构造+调整
      • 简单版本改写快排:随机选数partition划分区间,只走一边区间,直至划分到k个元素。
      • BFPRT算法:每相邻5个数成为一组,一共产生n/5组;每一个小组找出中位数生成一个数组,n/5长度无序数组(到此时间复杂度O(n))。递归调用新产生的无序数组,直到拿出所有数的中位数作为value。选出value作为主元,进行partition划分区间,判断主元的位置与k的大小,有选择的对左边或右边递归(未命中,最大规模不超过7/10N)。
      • 时间复杂度分析:T(n)=O(1)+O(n)+T(n/5)+T(7/10n)+O(n) - O(n)
      • 算法实现(java)
    	// O(N*logK)
    	public static int[] getMinKNumsByHeap(int[] arr, int k) {
    		if (k < 1 || k > arr.length) {
    			return arr;
    		}
    		int[] kHeap = new int[k];
    		for (int i = 0; i != k; i++) {
    			heapInsert(kHeap, arr[i], i);
    		}
    		for (int i = k; i != arr.length; i++) {
    			if (arr[i] < kHeap[0]) {
    				kHeap[0] = arr[i];
    				heapify(kHeap, 0, k);
    			}
    		}
    		return kHeap;
    	}
    
    	public static void heapInsert(int[] arr, int value, int index) {
    		arr[index] = value;
    		while (index != 0) {
    			int parent = (index - 1) / 2;
    			if (arr[parent] < arr[index]) {
    				swap(arr, parent, index);
    				index = parent;
    			} else {
    				break;
    			}
    		}
    	}
    
    	public static void heapify(int[] arr, int index, int heapSize) {
    		int left = index * 2 + 1;
    		int right = index * 2 + 2;
    		int largest = index;
    		while (left < heapSize) {
    			if (arr[left] > arr[index]) {
    				largest = left;
    			}
    			if (right < heapSize && arr[right] > arr[largest]) {
    				largest = right;
    			}
    			if (largest != index) {
    				swap(arr, largest, index);
    			} else {
    				break;
    			}
    			index = largest;
    			left = index * 2 + 1;
    			right = index * 2 + 2;
    		}
    	}
    
    	// O(N)
    	public static int[] getMinKNumsByBFPRT(int[] arr, int k) {
    		if (k < 1 || k > arr.length) {
    			return arr;
    		}
    		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;
    	}
    
    	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;
    	}
    
    	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);
    	}
    
    	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 };
    		// sorted : { 1, 1, 1, 1, 2, 2, 2, 3, 3, 5, 5, 5, 6, 6, 6, 7, 9, 9, 9 }
    		printArray(getMinKNumsByHeap(arr, 10));
    		printArray(getMinKNumsByBFPRT(arr, 10));
    
    	}
    
  • 相关阅读:
    weka中算法说明[转]
    浅入浅出JS中的eval及json
    JavaScript变量声明提前
    三种常用的js数组去重方法
    深入理解JavaScript的变量作用域
    调试工具--console用法收藏
    《js高级程序设计》--第三章数据类型
    Oracle数据备份和恢复
    Oracle归档日志管理
    Oracle字符集的设置
  • 原文地址:https://www.cnblogs.com/ymjun/p/12746173.html
Copyright © 2011-2022 走看看