数据结构和算法

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表

  1. 因为空串是任何非空串的真字串,真前缀,真后缀,故只要 j > 0,则必有 0 ∈ N(P, j)
    此时N(P, j) 必非空,从而保证“在其中取最大值”这一操作可行。反之。若j=0,则前缀prefix(P, j)
    本身就是空串,它没有真子串,于是必有集合N(P, j) = φ。此种情况下,next[0] 该如何定义呢?
  2. 按照串匹配算法的构思,倘若某轮迭代中第一对字符即失配,则应该将模式串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.");
    }
}

基本排序:冒泡,选择,插入

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值