Lru算法
1.新数据插入到链表头部
2.当缓存命中(即缓存数据被访问),数据要移到表头
3.当链表满的时候,将链表尾部的数据丢弃
树
**结点的度:**结点的子树个数
**树的度:**树中所有结点中最大的度
**结点的层次:**规定根结点在1层,子结点的层数是它父结点的层数加1
**树的高度:**树中所有结点中最大的层次是这棵树的高度
树与非树
1.子树是不相交的
2.除了根结点之外,每个结点之有且只有一个父结点
3.一个N个结点的树只有N-1条边
哈夫曼树
给定一段字符串,如何对字符串进行编码,可以使得该字符串的编码存储空间最少?
解决方案:等长编码和不等长编码
不等长编码:出现频次高的字符用的编码短些,出现频次低的编码长些
字符只在叶节点上(就不会有二义性)
构造方式:
每次把权值最小的两棵二叉树合并;左节点权值比右节点小
public class HuffmanTree {
//节点
public static class Node<E> {
E data; //数据
int weight; //权重
Node leftChild; //左子节点
Node rightChild;//右子节点
public Node(E data, int weight) {
super();
this.data = data;
this.weight = weight;
}
public String toString() {
return "Node[" + weight + ",data=" + data + "]";
}
}
public static void main(String[] args) {
List<Node> nodes = new ArrayList<Node>();
//把节点加入至list中
nodes.add(new Node("a", 10));
nodes.add(new Node("b", 15));
nodes.add(new Node("c", 12));
nodes.add(new Node("d", 3));
nodes.add(new Node("e", 4));
nodes.add(new Node("f", 13));
nodes.add(new Node("g", 1));
//进行哈夫曼树的构造
Node root = HuffmanTree.createTree(nodes);
//打印哈夫曼树
printTree(root);
}
/**
* 构造哈夫曼树
*
* @param nodes
* 节点集合
* @return 构造出来的哈夫曼树的根节点
*/
private static Node createTree(List<Node> nodes) {
//如果节点node列表中海油2个和2个以上的节点
while(nodes.size()>1){
//什么是最小的,list表进行排序,增序的方式, 0,1,
sort(nodes);//排序方式是增序的,因为其实只关注最小值,可以用堆实现
Node left = nodes.get(0);//权重最小的
Node right = nodes.get(1);//权重第二小的
//生成一个新的节点(父节点),父节点的权重为两个子节点的之和
Node parent = new Node(null,left.weight+right.weight);
//树的连接,让子节点与父节点进行连接
parent.leftChild = left;
parent.rightChild = right;
nodes.remove(0);//删除最小的
nodes.remove(0);//删除第二小的。
nodes.add(parent);
}
return nodes.get(0); //返回根节点
}
/**
* 冒泡排序,用于对节点进行排序(增序排序)
*/
public static void sort(List<Node> nodes) {
if (nodes.size() <= 1)
return ;
/*循环数组长度的次数*/
for (int i = 0; i < nodes.size(); i++){
/*从第0个元素开始,依次和后面的元素进行比较
* j < array.length - 1 - i表示第[array.length - 1 - i]
* 个元素已经冒泡到了合适的位置,无需进行比较,可以减少比较次数*/
for (int j = 0; j < nodes.size() - 1 - i; j++){
/*如果第j个节点比后面的第j+1节点权重大,交换两者的位置*/
if (nodes.get(j + 1).weight < nodes.get(j).weight) {
Node temp = nodes.get(j + 1);
nodes.set(j+1,nodes.get(j));
nodes.set(j,temp);
}
}
}
return ;
}
/*
* 递归打印哈夫曼树(先左子树,后右子树打印)
*/
public static void printTree(Node root) {
System.out.println(root.toString());
if(root.leftChild !=null){
System.out.print("left:");
printTree(root.leftChild);
}
if(root.rightChild !=null){
System.out.print("right:");java
printTree(root.rightChild);
}
}
}
二叉搜索树
也称二叉查找树,或二叉排序树 (BST,Binary Sort Tree)
定义
左子树的所有的值小于根节点的值
右子树的所有的值大于根节点的值
左、右子树满足以上两点
平衡二叉树
(AVL树,Balance Binary Search Tree )
它是一 棵二叉排序树,它的左右两个子树的高度差(平衡因子)的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
**目的:**使得树的高度最低,因为树查找的效率决定于树的高度
平衡二叉树的调整
调整原则:根据插入节点与失衡结点的位置关系来划分
LL旋转
插入节点在失衡结点的左子树的左边
只需要经过一次左旋即可达到平衡
RR旋转
插入节点在失衡结点的右子树的右边
只需要经过一次右旋即可达到平衡
LR旋转
插入节点在失衡结点的左子树的右边
失衡结点的左子树先做RR旋转
失衡结点再做LL旋转
RL旋转
插入节点在失衡结点的右子树的左边
失衡结点的右子树先做LL旋转
失衡结点再做RR旋转也可达到平衡
public class AVLTree {
//节点
public static class Node {
int data; //数据
Node leftChild; //左子节点
Node rightChild;//右子节点
int height; // 记录该节点所在的高度
public Node(int data) {
this.data = data;
}
}
//获取节点的高度
public static int getHeight(Node p){
return p == null ? -1 : p.height; // 空树的高度为-1
}
public static void main(String[] args) {
Node root = null;
root = insert(root,30);
root = insert(root,20);
root = insert(root,40);
root = insert(root,10);
root = insert(root,25);
//插入节点在失衡结点的左子树的左边
root = insert(root,5);
//打印树,按照先打印左子树,再打印右子树的方式
printTree(root);
}
public static void printTree(Node root) {
System.out.println(root.data);
if(root.leftChild !=null){
System.out.print("left:");
printTree(root.leftChild);
}
if(root.rightChild !=null){
System.out.print("right:");
printTree(root.rightChild);
}
}
// AVL树的插入方法
public static Node insert(Node root, int data) {
if (root == null) {
root = new Node(data);
return root;
}
if (data <= root.data) { // 插入到其左子树上
root.leftChild = insert(root.leftChild, data);
//平衡调整
if (getHeight(root.leftChild) - getHeight(root.rightChild) > 1) {
if (data <= root.leftChild.data) { // 插入节点在失衡结点的左子树的左边
System.out.println("LL旋转");
root = LLRotate(root); // LL旋转调整
}else{ // 插入节点在失衡结点的左子树的右边
System.out.println("LR旋转");
root = LRRotate(root);
}
}
}else{ // 插入到其右子树上
root.rightChild = insert(root.rightChild, data);
//平衡调整
if(getHeight(root.rightChild) - getHeight(root.leftChild) > 1){
if(data <= root.rightChild.data){//插入节点在失衡结点的右子树的左边
System.out.println("RL旋转");
root = RLRotate(root);
}else{
System.out.println("RR旋转");//插入节点在失衡结点的右子树的右边
root = RRRotate(root);
}
}
}
//重新调整root节点的高度值
root.height = Math.max(getHeight(root.leftChild), getHeight(root.rightChild)) + 1;
return root;
}
// LR旋转
public static Node LRRotate(Node p){
p.leftChild = RRRotate(p.leftChild); // 先将失衡点p的左子树进行RR旋转
return LLRotate(p); // 再将失衡点p进行LL平衡旋转并返回新节点代替原失衡点p
}
// RL平衡旋转
public static Node RLRotate(Node p){
p.rightChild = LLRotate(p.rightChild); // 先将失衡点p的右子树进行LL平衡旋转
return RRRotate(p); // 再将失衡点p进行RR平衡旋转并返回新节点代替原失衡点p
}
/*
* LL旋转
* 左旋示意图(对结点20进行左旋)
* 30 20
* / \ / \
* 20 40 10 30
* / \ --LL旋转- / / \
* 10 25 5 25 40
* /
* 5
*
*/
public static Node LLRotate(Node p){ // 30为失衡点
Node lsubtree = p.leftChild; //失衡点的左子树的根结点20作为新的结点
p.leftChild = lsubtree.rightChild; //将新节点的右子树25成为失衡点30的左子树
lsubtree.rightChild = p; // 将失衡点30作为新结点的右子树
// 重新设置失衡点30和新节点20的高度
p.height = Math.max(getHeight(p.leftChild), getHeight(p.rightChild)) + 1;
lsubtree.height = Math.max(getHeight(lsubtree.leftChild), p.height) + 1;
return lsubtree; // 新的根节点取代原失衡点的位置
}
/*
* RR旋转
* 右旋示意图(对结点30进行左旋)
* 20 30
* / \ / \
* 10 30 20 40
* / \ --RR旋转- / \ \
* 25 40 10 25 50
* \
* 50
*
*/
// RR旋转
public static Node RRRotate(Node p){ // 20为失衡点
Node rsubtree = p.rightChild; //失衡点的右子树的根结点30作为新的结点
p.rightChild = rsubtree.leftChild; //将新节点的左子树25成为失衡点20的右子树
rsubtree.leftChild = p; // 将失衡点20作为新结点的左子树
// 重新设置失衡点20和新节点30的高度
p.height = Math.max(getHeight(p.leftChild), getHeight(p.rightChild)) + 1;
rsubtree.height = Math.max(getHeight(rsubtree.leftChild), getHeight(rsubtree.rightChild)) + 1;
return rsubtree; // 新的根节点取代原失衡点的位置
}
}
红黑树
2-3树:一个绝对平衡的树,节点可以有1节点和2节点,插入时,先融合为3节点,再进行分裂或者向上提升为2节点,当平衡被打破时,进行分裂或者向上融合。
红黑树与2-3树的关系:红色节点就是2节点右边的那个节点。
图
图由顶点(vertex)和边(edge)组成的一种结构。顶点的集合V,边的集合是E,记为G = (V,E)
图的存储结构:邻接矩阵,邻接表
无向图顶点的边数叫度,有向图顶点的边数叫出度和入度
图的分类
是否有方向:有向图和无向图
全图:所有的顶点之间都有边;有向全图,无向全图
是否带权:带权图和无权图
图的遍历
深度优先遍历DFS
public void DeepFirst(int start) {//从第n个节点开始遍历
visit[start] = 1; //标记为1表示该顶点已经被处理过
System.out.println("齐天大圣到—>" + this.nodes[start]+"一游"); //输出节点数据
for (int i=0;i<this.size;i++){
if (this.edges[start][i] == 1 && visit[i]==0){
//邻接点
DeepFirst(i);
}
}
}
广度优先遍历BFS
private int[] queue = new int[size];
public void BreadthFirst(int front,int tail) {
int last = tail;
for (int index=front;index<=tail;index++){
int node = queue[index];
System.out.println("齐天大圣到—>" + this.nodes[node]+"一游"); //输出节点数据
//找出所有的邻接点
for (int i=0;i<this.size;i++){
if (this.edges[node][i] == 1 && visit[i]==0){
//邻接点
visit[i] = 1;
queue[++last] = i;
}
}
}
//遍历下一批节点
if (last > tail){
BreadthFirst(tail+1,last);
}
}
public void BreadthFirst(int start){
queue[0] = start;
visit[start] = 1;
BreadthFirst(0,0);
}
图的最短路径 Dijkstra算法
1、扫描AA邻接点,记录邻接点权重值;2、找出邻接点里最小的那个值;3.如果有的邻接点有多条路,则更该点的权为最小的那个
得到图的最短路径树;只适应权为正数的图
public class Dijkstra {
//节点数目
protected int size;
//定义数组,保存顶点信息
protected String[] nodes;
//定义矩阵保存顶点信息
protected int[][] edges;
private int[] isMarked;//节点确认--中心标识
private String[] path;//源到节点的路径信息
private int[] distances;//源到节点的距离
public Dijkstra(){
init();
isMarked = new int[size];
path = new String[size];
distances = new int[size];
for (int i=0;i<size;i++){
path[i] = "";
distances[i] = Integer.MAX_VALUE;
}
}
public static void main(String[] args) {
Dijkstra dijkstra = new Dijkstra();
dijkstra.search(3);
}
public void search(int node){
path[node] = nodes[node];
distances[node] = 0;
do {
flushlast(node);
node = getShort();
}while (node != -1);
}
//1、扫描AA邻接点,记录邻接点权重值
private void flushlast(int node){
isMarked[node] = 1;
System.out.println(path[node]);
//扫描邻接点
for (int i=0;i<size;i++){
if (this.edges[node][i] > 0){
//计算AA节点到 i节点的权重值
int distant = distances[node] + this.edges[node][i];
if (distant < distances[i]){
distances[i] = distant;
path[i] = path[node] +"-->"+ nodes[i];
}
}
}
}
// 2、找出邻接点里最小的那个值
private int getShort(){
int last = -1;
int min = Integer.MAX_VALUE;
for (int i=0;i<size;i++){
if (isMarked[i] == 1){
continue;
}
if (distances[i] < min){
min = distances[i];
last = i;
}
}
return last;
}
public void init(){
//初始化顶点
nodes = new String[]{"AA","A","B","C","D","E","F","G","H","M","K","N"};
//节点编号-常量
final int AA=0,A=1,B=2,C=3,D=4,E=5,F=6,G=7,H=8,M=9,K=10,N=11;
size=nodes.length;
edges = new int[size][size];
edges[AA][A] = 3;
edges[AA][B] = 2;
edges[AA][C] = 5;
edges[A][AA] = 3;
edges[A][D] = 4;
edges[B][AA] = 2;
edges[B][C] = 2;
edges[B][G] = 2;
edges[B][E] = 3;
edges[C][AA] = 5;
edges[C][E] = 2;
edges[C][B] = 2;
edges[C][F] = 3;
edges[D][A] = 4;
edges[D][G] = 1;
edges[E][B] = 3;
edges[E][C] = 2;
edges[E][F] = 2;
edges[E][K] = 1;
edges[E][H] = 3;
edges[E][M] = 1;
edges[F][C] = 3;
edges[F][E] = 2;
edges[F][K] = 4;
edges[G][B] = 2;
edges[G][D] = 1;
edges[G][H] = 2;
edges[H][G] = 2;
edges[H][E] = 3;
edges[K][E] = 1;
edges[K][F] = 4;
edges[K][N] = 2;
edges[M][E] = 1;
edges[M][N] = 3;
edges[N][K] = 2;
edges[N][M] = 3;
}
}
图的拓扑排序
一个工程的项目依赖上一个项目,找出一条可以完成工程的路径
1、计算出各个节点的入度
2、入度为0节点入队
3、入队节点的邻接点入度-1
4、重复2-3步骤
针对有向无环图
图的关键路径算法
一个工程由多个步骤互相依赖得以完成,找出其中影响项目最终完成时间的那条关键路径。
节点的最早施工时间和最晚施工时间相等的节点就是关键节点;所有的关键节点,组成关键路径;
public class Aov {
//节点数目
protected int size;
//定义数组,保存顶点信息
protected String[] nodes;
//定义矩阵保存顶点信息
protected int[][] edges;
public Aov(){
init();
}
//入度数组
private int[] eSize;
private int[] fast;//最早时间
private int[] last;//最晚时间
public static void main(String[] args) {
Aov aov = new Aov();
aov.flush();
//int[] path = aov.getPath();
aov.exeKey();
}
public void exeKey(){
int[] path = getPath();
int start = path[0],end = path[size-1];
exeFast(start);
for (int i=0;i<size;i++){//初始化成工程最大值
last[i] = fast[end];
}
exeLast(end);
for (int i=0;i<size;i++){
int node = path[i];
if (fast[node] == last[node]){
System.out.print("--->"+nodes[node]);
}
}
System.out.println();
}
private void exeFast(int node){
for (int i=0;i<size;i++){
if (this.edges[node][i] > 0){
int cost = fast[node] + this.edges[node][i];
if (cost > fast[i]){
fast[i] = cost;
exeFast(i);
}
}
}
}
private void exeLast(int node){
for (int i=0;i<size;i++){
if (this.edges[i][node] > 0){
int cost = last[node] - this.edges[i][node];
if (cost < last[i]){
last[i] = cost;
exeLast(i);
}
}
}
}
//1、计算出各个节点的入度
private void flush(){
eSize = new int[size];
for (int node=0;node<size;node++){
for (int i=0;i<size;i++){
if (edges[i][node] > 0){
eSize[node]++;
}
}
}
}
private int[] getPath(){
int count = 0;
int[] path = new int[size];
// 2、入度为0节点入队
// Queue<Integer> queue = new LinkedList<>();
Stack<Integer> stack = new Stack<>();
for (int i=0;i<size;i++){
if (eSize[i] == 0){
//queue.offer(i);
stack.push(i);
}
}
// 3、入队节点的邻接点入度-1
while (!stack.empty()){
Integer node = stack.pop();
// System.out.print("---->"+nodes[node]);
path[count++] = node;
for (int i=0;i<size;i++){
if (this.edges[node][i] > 0){
eSize[i]-- ;
if (eSize[i] == 0){
// queue.offer(i);
stack.push(i);
}
}
}
}
return path;
}
public void init(){
//初始化顶点
nodes = new String[]{"AA","A","B","C","D","E","F","G","H","M","K","N"};
//节点编号-常量
final int AA=0,A=1,B=2,C=3,D=4,E=5,F=6,G=7,H=8,M=9,K=10,N=11;
size=nodes.length;
fast = new int[size];
last = new int[size];
edges = new int[size][size];
edges[AA][A] = 3;
edges[AA][B] = 2;
edges[AA][C] = 5;
edges[A][D] = 4;
edges[B][G] = 2;
edges[B][E] = 3;
edges[C][E] = 2;
edges[C][F] = 3;
edges[D][G] = 1;
edges[E][K] = 1;
edges[E][M] = 8;
edges[F][K] = 4;
edges[G][H] = 2;
edges[H][M] = 3;
edges[K][N] = 2;
edges[M][N] = 3;
}
}
布隆过滤器
布隆过滤器可以用于检索一个元素是否在一个集合中,因此它是一个空间效率极高的概率型算法;它实际上是一个很长的二进制向量和一系列随机映射函数;
优点
仅仅保留数据的指纹信息,空间效率极高;
查询效率极高,时间复杂度为:O(n);
信息安全性较高;
缺点
存在一定的误判;数据删除困难;
应用场景
字处理软件中,需要检查一个英语单词是否拼写正确(100w)
在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上(100w)
在网络爬虫里,一个网址是否被访问过(5亿个网站)
问题本质在于:快速判断一个数据是否存在于海量数据之中!
Google Guava实现了布隆过滤器
一系列随机映射函数 二进制向量
插入时:数据经过一系列随机映射函数的计算,计算出对应在二进制向量中的位置,如果是0,修改为1;
查询时:数据经过一系列随机映射函数的计算,计算出对应在二进制向量中的位置,如果都是1,则存在;
B B+树
磁盘读取原理
盘片被划分成一系列同心环,圆心是盘片中心,每个同心环叫做一个磁道。磁道被沿半径线划分成一个个小的段,每个段叫做一个扇区,每个扇区是磁盘的最小存储单元。
当需要从磁盘读取数据时,为了读取这个扇区的数据,需要将磁头放到这个扇区上方,为了实现这一点,磁头需要移动对准相应磁道,这个过程叫做寻道,所耗费时间叫做寻道时间,然后磁盘旋转将目标扇区旋转到磁头下,这个过程耗费的时间叫做旋转时间。
由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理:
当一个数据被用到时,其附近的数据也通常会马上被使用。
由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。预读的长度一般为4k的整数倍(这个4K大小的数据块,称为页)。
B树
综上所述,有没有一种既可以避免二叉树这种搜寻深度过深,又可以充分利用磁盘预读原理的数据结构呢。这个就是B树了。
B树中一个节点可以允许有多个子节点,在实际使用时,一个B树节点的实际大小一般设为一个4K大小的页,这样每个节点只需要一次I/O就可以完全载入。
同时,B树的每个节点有多个key,并且以升序排列。这样在查找时就很方便了。
大家可以从图上看到,存储了27个数据,允许7个子节点的B树的层次比存储了20个数据的平衡二叉树要少,数据越多,层次相对于平衡二叉树就越少,而且B树的允许的子节点个数越多,这个B树也就层次越少。
在硬盘上实际存储B树时,一个B树节点的实际大小一般设为一个4K大小的页,所以B树的允许子节点都非常大(通常在100到1000之间),所以即使存储大量的数据,B树的高度仍然比较小。
每个结点中存储了关键字(key)和关键字对应的数据(data),以及孩子结点的指针。我们将一个key和其对应的data称为一个记录**。**在数据库中我们将B树(和B+树)作为索引结构,可以加快查询速速,此时B树中的key就表示键,而data表示了这个键对应的条目在硬盘上的逻辑地址。
B+树
Mysql等数据库系统实际使用的则是B+树,B+树是B-树的变体,其定义基本与B-树相同,主要的不同点在于:
1、所有的非叶子节点上不包含数据信息,因此在内存页中能够存放更多的key,所有数据(或者说记录)都保存在叶子结点中。
2、叶子结点都是相链的,因此对整棵树的遍历只需要一次线性遍历叶子结点即可
Mysql索引的实现
MyISAM
使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址,这里设表一共有三列,假设我们以Col1为主键,可以看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。如果我们在Col2上建立一个辅助索引,同样也是一颗B+Tree,data域保存数据记录的地址。因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。MyISAM的索引方式也叫做“非聚集”的。
InnoDB
也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。
第一个重大区别是InnoDB的数据文件本身就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。
叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。
第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域
聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。
字符串
朴素的模式匹配算法BF(Brute-Force)
public static void bruteForce(String s, String p) {
int index = -1;// 成功匹配的位置
int sLength = s.length();// 主串长度
int pLength = p.length();// 子串长度
if (sLength < pLength) {
System.out.println("Error.The main string is greater than the sub string length.");
return;
}
int i = 0;
int j = 0;
while (i < sLength && j < pLength) {
if (s.charAt(i) == p.charAt(j)) {// 判断对应位置的字符是否相等
i++;// 若相等,主串、子串继续依次比较
j++;
} else {// 若不相等
i = i - j + 1;// 主串回溯到上次开始匹配的下一个字符
j = 0;// 子串从头开始重新匹配
}
}
if (j >= pLength) {// 匹配成功
index = i - j;java
System.out.println("Successful match,index is:" + index);
} else {// 匹配失败
System.out.println("Match failed.");
}
}
坏字符串算法BM
从后向前比较。
KMP
其核心思想就是主串不回溯,模式串尽量多地往右移动
构建next表
- 因为空串是任何非空串的真字串,真前缀,真后缀,故只要 j > 0,则必有 0 ∈ N(P, j)。
此时N(P, j) 必非空,从而保证“在其中取最大值”这一操作可行。反之。若j=0,则前缀prefix(P, j)
本身就是空串,它没有真子串,于是必有集合N(P, j) = φ。此种情况下,next[0] 该如何定义呢? - 按照串匹配算法的构思,倘若某轮迭代中第一对字符即失配,则应该将模式串P直接右移一位,然后从其首字符继续下一轮对比
就实际效果而言,这一处理方法完全等价于“令next[0] = -1”。
//第一步构造next表
public static int[] buildNext(String p){
//构建next表就是查找真前缀 == 真后缀的最大长度,以获取模式串尽量多地往右移动
int[] N = new int[p.length()];
int m = p.length(),j = 0;//主串位置
int t = N[0] = -1;//字串位置
while(j < m -1){
if(t < 0 || p.charAt(j) == p.charAt(t)){
j++;t++;
N[j] = t;
}else{//失配
t = N[t];
}
}
return N;
}
//第二步利用next表尽量多地往右移动
public static void kmp(String s, String p) {
int[] next = buildNext(p);// 调用next(String p)方法
int index = -1;// 成功匹配的位置
int sLength = s.length();// 主串长度
int pLength = p.length();// 子串长度
if (sLength < pLength) {
System.out.println("Error.The main string is greater than the sub string length.");
return;
}
int i = 0;
int j = 0;
while (i < sLength && j < pLength) {
/*
* 如果j = -1, 或者当前字符匹配成功(即s.charAt(i) == p.charAt(j)), 都令i++,j++
* 这两个条件能否交换次序?
*/
if (j == -1 || s.charAt(i) == p.charAt(j)) {
i++;
j++;
} else {
//如果j != -1,且当前字符匹配失败, 则令 i 不变,j = next[j], next[j]即为j所对应的next值
j = next[j];
}
}
if (j >= pLength) {// 匹配成功
index = i - j;
System.out.println("Successful match,index is:" + index);java
} else {// 匹配失败
System.out.println("Match failed.");
}
}