Java当中常用的算法


今天我们来看看常用的算法,开干。。。。。

算法

二叉树左右变换数据

在这里插入图片描述

实现的代码如下:以下定义的类是内部类

//    2. 讲一个做二叉树左右替换
//    1. 先给一个二叉树的内部类,类里面分出左右的对象的变量
    public static class Tree {
        private Object data;
        public Object getData() {
            return data;
        }
        public void setData(Object data) {
            this.data = data;
        }
        public Tree getLeft() {
            return left;
        }
        public void setLeft(Tree left) {
            this.left = left;
        }
        public Tree getRight() {
            return right;
        }
        public void setRight(Tree right) {
            this.right = right;
        }

        // 给出左右的树
        private Tree left;
        private Tree right;
        // 构造方法
        public Tree(Object data, Tree left, Tree right) {
            this.data = data;
            this.left = left;
            this.right = right;
        }

        /**
         * 获取二叉树的深度
         * @param root root
         * @return int
         */
        public static int getMaxDepth(Tree root) {
            if (root == null) return 0;
            // 需要递归获取深度
            return Math.max(getMaxDepth(root.left), getMaxDepth(root.right)) + 1;
        }

        /**
          * 二叉树的交换方法OK,经过验证
          * @param root Tree
         */
        public static void swap(Tree root) {
            if (root == null) return;
            // 交换
            Tree tmp = root.left;
            root.left = root.right;
            root.right = tmp;
            // 递归交换
            swap(root.left);
            swap(root.right);
        }

        /**
          *  二叉树的左右交换
          * @param root
         */
        public static void swapAgin(Tree root) {
            if (root == null) return;

            Stack<Tree> statck = new Stack<>();
            statck.push(root);
            while (!statck.isEmpty()) {
                Tree node = statck.pop();
                Tree tmp = node.left;
                node.left = node.right;
                node.right = tmp;
                if (node.left != null) {
                    statck.push(node.left);
                }

                if (node.right != null) {
                    statck.push(node.right);
                }
            }
        }

        public static void main(String[] args) {
            // 以下模拟的二叉树图片地址在此: https://img-blog.csdnimg.cn/1ae454d94ab04d8e8fb6f313248beb3a.png
            Tree left9 = new Tree(9, null, null);
            Tree right3 = new Tree(3, null, null);
            Tree right8 = new Tree(8, null, null);
            Tree left7 = new Tree(7, null, right8);
            Tree right11 = new Tree(11, left9, right3);
            Tree root5 = new Tree(5, left7, right11);
            System.out.println("原始的二叉树结果:" + root5);
//       交换
            swap(root5);
//            swapAgin(root5);
            System.out.println("swap()方法交换之后的二叉树结构为:" + root5);
        }

    }

二分法

概念:

  • 二分法(Bisection method) 即一分为二的方法,又叫折半查找方法。
  • 把一组有序数列分为左右两部分,从这组数字的中间位置开始找:
  • 如果中间位置的数等于目标数,则直接返回;
  • 如果中间位置的数大于目标数,则从左边部分查找;
  • 如果小于目标数,则从右边部分查找;
  • 重复以上过程,直到找到满足条件的记录,使查找成功。

时间复杂度:
都是 O(log2 N)

空间复杂度:
非递归方式: 空间复杂度是O(1);
递归方式: 空间复杂度:O(log2N )

实现

1. 递归方式

 public static int binarySearchRecursive(int[] arr, int low, int high, int key) {
        //边界条件:如果目标数没有在数组范围内(即比最左边的数小,比最右边的数大)
        if (arr == null || arr.length == 0 || arr[low] > key || arr[high] < key || low > high) {
            return -1;
        }
        
        // 获取中间位置下标
        int mid = (low + high) / 2;
        // 将中间位置的数和目标数作比较,如果中间位置的数等于目标数,则直接返回下标,
        // 如果中间位置的数大于目标数,则将左边部分用递归方法继续查找;如果小于目标数,则从右边部分用递归方法继续查找
        if (arr[mid] == key) {
            return mid;
        } else if (arr[mid] > key) {
            return binarySearch(arr, low, mid - 1, key);
        } else {
            return binarySearch(arr, mid + 1, high, key);
        }
    }

// 测试下:从一组数中找3,输出数组下标
 public static void main(String[] args) {
        int[] arr = {2, 3, 5, 7, 9, 78, 90, 167};
        System.out.println(binarySearchRecursive(arr, 0, (arr.length) - 1, 3));
 }

2. 非递归方式

 public static int binarySearch(int[] arr, int key) {
        int low = 0;
        int high = arr.length - 1;
        while (low <= high) {
            int middle = (low + high) / 2;
            if (key < arr[middle]) {
                high = middle - 1;
            } else if (key > arr[middle]) {
                low = middle + 1;
            } else {
                return middle;
            }
        }
        return -1;

    }
    
    // 测试下:从一组数中找3,输出数组下标
     public static void main(String[] args) {
        int[] arr = {2, 3, 5, 7, 9, 78, 90, 167};
        System.out.println("数组下标:"+binarySearch(arr, 3));
    }

3.非递归

/**
 * 二分查找
 * @param srcArray 源数组
 * @param des 目标元素
 * @return 如果找到则返回索引位置,找不到则返回-1
 */
public static int binarySearch(int[] srcArray, int des) {
    //定义初始最小、最大索引
    int start = 0;
    int end = srcArray.length - 1;
    //确保不会出现重复查找,越界
    while (start <= end) {
        //计算出中间索引值  >>> 逻辑右移 也就是 int middle = (end + start)/2
        int middle = (end + start)>>>1 ;//防止溢出
        if (des == srcArray[middle]) {
            return middle;
            //判断下限
        } else if (des < srcArray[middle]) {
            end = middle - 1;
            //判断上限
        } else {
            start = middle + 1;
        }
    }
    //若没有,则返回-1
    return -1;
}

冒泡排序算法

算法原理
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

示例代码:

public static void bubbleSort(int arr[]) {
    for (int i = 0; i < arr.length - 1; i++) {
        for (int j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

示例代码2:

public static void bubbleSort2(int[] a, int n) {
    int i, j;
    for (i = 0; i < n; i++) {//表示 n 次排序过程。
        for (j = 1; j < n - i; j++) {
            if (a[j - 1] > a[j]) {//前面的数字大于后面的数字就交换
                //交换 a[j-1]和 a[j]
                int temp;
                temp = a[j - 1];
                a[j - 1] = a[j];
                a[j] = temp;
            }
        }
    }
}

插入排序算法

(1)概念:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应的位置并插入。

(2)一个通俗的比喻

插入排序就类似于斗地主时,整理扑克牌的情况。第一次摸牌时,左收是空的,之后每次摸牌插入到左手的牌时,都会将这张牌和左手中已经排好序的牌,从右到左比较,确认这张牌该放的位置。

示例代码:

public static void insertionSort(int arr[]) {
    for (int i = 1; i < arr.length; i++) {
        //插入的数
        int insertVal = arr[i];
        //被插入的位置(准备和前一个数比较)
        int index = i - 1;
        //如果插入的数比被插入的数小
        while (index >= 0 && insertVal < arr[index]) {
            //将把 arr[index] 向后移动
            arr[index + 1] = arr[index];
            //让 index 向前移动
            index--;
        }
        //把插入的数放入合适位置
        arr[index + 1] = insertVal;
    }
}

快速排序算法

(1)概念:快速排序是指通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序。整个排序过程可以递归进行,以此达到整个数据变成有序序列。

(2)快速排序的的过程简图:

选择一个关键值作为基准值。比基准值小的都在左边序列(一般是无序的),比基准值大的都在右边(一般是无序的)。

在这里插入图片描述

示例代码:

/**
 * 快速排序
 *
 * @param arr   需要排序的数组
 * @param start 数组的最小索引: 0
 * @param end   数组的最大索引: arr.length - 1
 * @return 排序好的数组
 */
public static int[] quickSort(int arr[], int start, int end) {
    int pivot = arr[start];
    int i = start;
    int j = end;
    while (i < j) {
        while ((i < j) && (arr[j] > pivot)) {
            j--;
        }
        while ((i < j) && (arr[i] < pivot)) {
            i++;
        }
        if ((arr[i] == arr[j]) && (i < j)) {
            i++;
        } else {
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    if (i - 1 > start) arr = quickSort(arr, start, i - 1);
    if (j + 1 < end) arr = quickSort(arr, j + 1, end);
    return (arr);
}

示例代码2:

/**
 * 快速排序(无返回值)
 * @param a 需要排序的数组
 * @param low 数组的最小索引: 0
 * @param high 数组的最大索引: arr.length - 1
 */
public static void quickSort2(int[] a, int low, int high) {
    int start = low;
    int end = high;
    int key = a[low];
    while (end > start) {
        //从后往前比较
        while (end > start && a[end] >= key)
            //如果没有比关键值小的,比较下一个,直到有比关键值小的交换位置,然后又从前往后比较
            end--;
        if (a[end] <= key) {
            int temp = a[end];
            a[end] = a[start];
            a[start] = temp;
        }
        //从前往后比较
        while (end > start && a[start] <= key)
            //如果没有比关键值大的,比较下一个,直到有比关键值大的交换位置
            start++;
        if (a[start] >= key) {
            int temp = a[start];
            a[start] = a[end];
            a[end] = temp;
        }
        //此时第一次循环比较结束,关键值的位置已经确定了。左边的值都比关键值小,右边的值都比关键值大,但是两边的顺序还有可能是不一样的,进行下面的递归调用
    }
    //递归
    if (start > low) quickSort2(a, low, start - 1);//左边序列。第一个索引位置到关键值索引-1
    if (end < high) quickSort2(a, end + 1, high);//右边序列。从关键值索引+1 到最后一个
}

希尔排序算法

(1)基本思想:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

/**
 * 希尔排序
 * @param a 
 */
private static void shellSort(int[] a) {
    int dk = a.length / 2;
    while (dk >= 1) {
        //类似插入排序,只是插入排序增量是 1,这里增量是 dk,把 1 换成 dk 就可以了
        for (int i = dk; i < a.length; i++) {
            if (a[i] < a[i - dk]) {
                int j;
                int x = a[i];//x 为待插入元素
                a[i] = a[i - dk];
                for (j = i - dk; j >= 0 && x < a[j]; j = j - dk) {
                    //通过循环,逐个后移一位找到要插入的位置。
                    a[j + dk] = a[j];
                }
                a[j + dk] = x;//插入
            }
        }
        dk = dk / 2;
    }
}

归并排序算法

(1)基本思想:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

(2)归并排序:是建立在归并操作上的一种有效,稳定的排序算法。

什么是归并操作?

归并操作,也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。
如 设有数列{6,202,100,301,38,8,1}
初始状态:6,202,100,301,38,8,1
第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;
第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;
第三次归并后:{1,6,8,38,100,202,301},比较次数:4;
总的比较次数为:3+4+4=11;
逆序数为14;

示例代码:

/**
 * 归并排序
 * @param nums 待排序数组
 * @param l 开始索引:0
 * @param h 最大索引:nums.length - 1
 * @return 排序好的数组
 */
public static int[] mergeSort(int[] nums, int l, int h) {
    if (l == h)
        return new int[]{nums[l]};
 
    int mid = l + (h - l) / 2;
    int[] leftArr = mergeSort(nums, l, mid); //左有序数组
    int[] rightArr = mergeSort(nums, mid + 1, h); //右有序数组
    int[] newNum = new int[leftArr.length + rightArr.length]; //新有序数组
 
    int m = 0, i = 0, j = 0;
    while (i < leftArr.length && j < rightArr.length) {
        newNum[m++] = leftArr[i] <= rightArr[j] ? leftArr[i++] : rightArr[j++];
    }
    while (i < leftArr.length)
        newNum[m++] = leftArr[i++];
    while (j < rightArr.length)
        newNum[m++] = rightArr[j++];
    return newNum;
}

桶排序算法

(1)基本思想:把数组 arr 划分为 n 个大小相同子区间(桶),每个子区间各自排序,最后合并 。计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。

(2)排序过程

  • 找出待排序数组中的最大值 max、最小值 min
  • 我们使用 动态数组 ArrayList 作为桶,桶里放的元素也用 ArrayList 存储。桶的数量为(maxmin)/arr.length+1
  • 遍历数组 arr,计算每个元素 arr[i] 放的桶
  • 每个桶各自排序

示例代码:

/**
 * 桶排序
 *
 * @param data 待排序数组
 */
public static void bucketSort(int data[]){
    int n = data.length;
    int bask[][] = new int[10][n];
    int index[] = new int[10];
    int max = Integer.MIN_VALUE;
    for (int i = 0; i < n; i++) {
        max = max > (Integer.toString(data[i]).length()) ? max : (Integer.toString(data[i]).length());
    }
    String str;
    for (int i = max - 1; i >= 0; i--) {
        for (int j = 0; j < n; j++) {
            str = "";
            if (Integer.toString(data[j]).length() < max) {
                for (int k = 0; k < max - Integer.toString(data[j]).length(); k++)
                    str += "0";
            }
            str += Integer.toString(data[j]);
            bask[str.charAt(i) - '0'][index[str.charAt(i) - '0']++] = data[j];
        }
        int pos = 0;
        for (int j = 0; j < 10; j++) {
            for (int k = 0; k < index[j]; k++) {
                data[pos++] = bask[j][k];
            }
        }
        for (int x = 0; x < 10; x++) index[x] = 0;
    }
}

基数排序算法

(1)基本思想:将整数按位数切割成不同的数字,然后按每个位数分别比较。

(2)排序过程:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

(3)代码示例:

/**
 * 基数排序
 * @param number 待排序的数组
 * @param d 表示最大的数有多少位
 */
public static void sort(int[] number, int d) 
{
    int k = 0;
    int n = 1;
    int m = 1; //控制键值排序依据在哪一位
    int[][] temp = new int[10][number.length]; //数组的第一维表示可能的余数0-9
    int[] order = new int[10]; //数组order[i]用来表示该位是i的数的个数
    while (m <= d) {
        for (int i = 0; i < number.length; i++) {
            int lsd = ((number[i] / n) % 10);
            temp[lsd][order[lsd]] = number[i];
            order[lsd]++;
        }
        for (int i = 0; i < 10; i++) {
            if (order[i] != 0)
                for (int j = 0; j < order[i]; j++) {
                    number[k] = temp[i][j];
                    k++;
                }
            order[i] = 0;
        }
        n *= 10;
        k = 0;
        m++;
    }
}

分治算法

分治算法基本介绍
分治法(Divide-and-Conquer)是一种很重要的算法。字面上的解释是"分而治之",就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)…

在这里插入图片描述

分治算法的基本实现步骤(分治法在每一层递归上都有三个步骤):

  1. **分解:**将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
  2. **解决:**若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
  3. **合并:**将各个子问题的解合并为原问题的解

汉诺塔问题

分治算法经典问题:汉诺塔问题

汉诺塔的传说

汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着 64 片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

假如每秒钟一次,共需多长时间呢?移完这些金片需要 5845.54 亿年以上,太阳系的预期寿命据说也就是数百亿年。真的过了 5845.54 亿年,地球上的一切生命,连同梵塔、庙宇等,都早已经灰飞烟灭。

体验完后,我们来整理下移动盘子的思路(假设有A、B、C柱子):

1、如果只有一个盘,直接可以A->C

2、如果盘子的数量 n >= 2,我就可以看做是两个盘子。。

  • 最下边(最大)的盘
  • 上面的盘

因此就可以走三部曲

先把最上面的盘A->B
把最下边的盘A->C
把B塔的所有盘从B->C

在这里插入图片描述

代码示例:

/**
 * 汉诺塔问题解决
 * @param discNum 盘子数量
 * @param a A柱子
 * @param b B柱子
 * @param c C柱子
 */
public static void towerOfHanoi(int discNum, char a, char b, char c) {
    // 如果只有一个盘
    if (discNum == 1) {
        System.out.println("第1个盘" + a + "->" + c);
    } else {
        // 盘的数量 >= 2
        // 1.上盘A->B
        towerOfHanoi(discNum - 1, a, c, b);
        // 2.下盘A->C
        System.out.println("第" + discNum + "个盘" + a + "->" + c);
        // 3.把B柱子的所有盘子移至C柱子
        towerOfHanoi(discNum - 1, b, a, c);
    }
}

动态规划算法

引子

背包问题:现有一个背包,容量为4磅。现有如下物品:

在这里插入图片描述

1、要求达到的目标为装入的背包的总价值最大,并且重量不超出

2、要求装入的物品不能重复

3.2、动态规划算法基本介绍
1、动态规划(Dynamic Programming)算法(简称DP算法)的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法

2、动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解

3、与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。 ( 即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解 )

4、动态规划可以通过填表的方式来逐步推进,得到最优解

代码实现背包问题

1、背包问题主要是指一个给定容量的背包、若干具有一定价值和重量的物品,如何选择物品放入背包使物品的价值最大。其中又分 01 背包和完全背包(完全背包指的是:每种物品都有无限件可用)

2、这里的问题属于 01 背包,即每个物品最多放一个。而无限背包可以转化为 01 背包。

3、算法的主要思想:利用动态规划来解决。每次遍历到的第 i 个物品,根据w[i] 和 v[i] 来确定是否需要将该物品放入背包中。即对于给定的 n 个物品,设 v[i]、w[i]分别为第 i 个物品的价值和重量,C 为背包的容量。再令 v[i][j]表示在前 i 个物品中能够装入容量为 j 的背包中的最大价值。

基于以上设定我们得出:

/*
(1) v[i][0]=v[0][j]=0; //表示 填入表 第一行和第一列是 0
(2) 当 w[i]> j时:v[i][j]=v[i-1][j] // 当准备加入新增的商品的容量大于 当前背包的容量时,就直接使用上一个单元格的装入策略
(3) 当 j>=w[i]时: v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]} // 当准备加入的新增的商品的容量小于等于当前背包的容量,装入的方式:
	1. v[i-1][j]: 就是上一个单元格的装入的最大值
	2. v[i]: 表示当前商品的价值
	3. v[i-1][j-w[i]]: 装入 i-1 商品,到剩余空间 j-w[i]的最大值
	4. 当 j>=w[i]时: v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]}
*/

在这里插入图片描述

代码示例:

public static void main(String[] args) {
    int[] wight = new int[]{1, 4, 3}; // 物品的重量
    int[] price = new int[]{1500, 3000, 2000}; // 物品的价格
    int m = 4; // 背包的容量
    int n = price.length; // 物品的个数

    // 创建一个二维数组
    // v[i][j] 表示在前i个物品中能够装入容量为j的背包中的最大价值
    int[][] v = new int[n + 1][m + 1];
    // 初始化第一行和第一列,这里在本程序中,可以不去处理,因为默认就是0
    for (int i = 0; i < v.length; i++) {
        v[i][0] = 0; // 将第一列设置为0
    }
    for (int i = 0; i < v.length; i++) {
        v[0][i] = 0; // 将第一行设置为0
    }

    // 为了记录放入商品的情况,我们定一个二维数组
    int[][] path = new int[n + 1][m + 1];

    // 动态规划处理背包问题
    // i和j初始都等于1,目的是不处理第一行第一列
    for (int i = 1; i < v.length; i++) {
        for (int j = 1; j < v[i].length; j++) {
            // 公式
            if (wight[i - 1] > j) { // 因为我们程序i是从1开始的,因此原理公式中的w[i]修改成[i-1]
                v[i][j] = v[i - 1][j];
            } else {
                // 因为 i 是从1开始的,因此公式需要做出调整,如下所示
                // v[i][j] = Math.max(v[i - 1][j], price[i - 1] + v[i - 1][j - wight[i - 1]]);
                if (v[i - 1][j] < price[i - 1] + v[i - 1][j - wight[i - 1]]) {
                    v[i][j] = price[i - 1] + v[i - 1][j - wight[i - 1]];
                    // 把当前的情况记录到path
                    path[i][j] = 1;
                } else {
                    v[i][j] = v[i - 1][j];
                }
            }
        }
    }

    // 输出v
    for (int i = 0; i < v.length; i++) {
        for (int j = 0; j < v[i].length; j++) {
            System.out.print(v[i][j] + " ");
        }
        System.out.println();
    }

    // 输出放入的商品情况
    int i = path.length - 1; // 行的最大下标
    int j = path[0].length - 1; // 列的最大下标
    while (i > 0 && j > 0) {
        if (path[i][j] == 1) {
            System.out.printf("第%d个商品放入到背包\n", i);
            j -= wight[i - 1];
        }
        i--;
    }
}

KMP算法

什么是KMP算法

KMPKnuth、Morris和Pratt首字母的缩写,KMP也是由这三位学者发明(1977年联合发表论文)。

KMP主要应用在字符串的匹配,是一个解决模式串在文本串是否出现过,如果出现过,得出最早出现的位置的经典算法。其主要思想是:当出现字符串不匹配时,可以知道之前已经匹配的文本内容,可以利用这些信息避免从头再去匹配,从而提高匹配效率。

因此如何记录已经匹配的文本内容,才是KMP的重点~这也使得next数组派上了用场。

KMP算法就利用之前判断过信息,通过一个 next 数组,保存模式串中前后最长公共子序列的长度,每次回溯时,通过 next 数组找到前面匹配过的位置,省去了大量的计算时间。

在这里插入图片描述

暴力匹配

现给出一段字符串str1:“硅硅谷 尚硅谷你尚硅 尚硅谷你尚硅谷你尚硅你好”,和一段子字符串str2:“尚硅谷你”。

要求写出判断str1是否含有str2的代码,如果存在就返回第一次出现的位置,如果没有则返回-1。

说到字符串匹配,我们第一时间想到的是直接遍历字符串,看看是否存在。这种方法称为暴力匹配,抛开效率不说,这种方式是最直接,最简单的方式。

然而暴力匹配也是一种算法,一种解决方案,针对上述问题,我们可以得出暴力匹配算法的思路(假设现在 str1 匹配到 i 位置,子串 str2 匹配到 j 位置):

  • 如果当前字符匹配成功(即 str1[i] == str2[j]),则 i++,j++,继续匹配下一个字符
  • 如果失配(即 str1[i] != str2[j]),令 i = i - (j - 1),j = 0。相当于每次匹配失败时,i 回溯,j 被置为 0
  • 用暴力方法解决的话就会有大量的回溯,每次只移动一位,若是不匹配,移动到下一位接着判断,浪费了大量的时间(不可行!)

代码示例:

/**
 * 暴力匹配算法
 * @param str1
 * @param str2
 * @return 返回str2首次出现在str1的位置,匹配不到则返回-1
 */
public static int violenceMatch(String str1, String str2) {
    char[] s1 = str1.toCharArray();
    char[] s2 = str2.toCharArray();

    int i = 0; // 指向s1
    int j = 0; // 指向s2

    while (i < s1.length && j < s2.length) {
        if (s1[i] == s2[j]) {
            i++;
            j++;
        } else {
            // 只要有一个没有匹配上
            i = i - (j - 1);
            j = 0;
        }
    }
    // 判断是否匹配成功
    if (j == s2.length) {
        return i - j;
    }
    return -1;
}

main方法中测试暴力匹配:

public class AlgorithmUtils {
    public static void main(String[] args) {
        String str1 = "硅硅谷 尚硅谷你尚硅 尚硅谷你尚硅谷你尚硅你好";
        String str2 = "尚硅谷你";

        int index = AlgorithmUtils.violenceMatch(str1, str2);
        if (index != -1) {
            System.out.printf("第一次出现的位置是%d", index);
        }
    }
}

KMP算法实现

前面呢我们已经使用暴力匹配算法,完成了上述问题的求解!也知道了暴力匹配存在效率问题,那么KMP算法又是怎样实现呢?

为方便阐述,这里我们换个案例:现有两组字符串

str1 = “BBC ABCDAB ABCDABCDABDE”;
str2 = “ABCDABD”;

要求使用 KMP算法 完成判断,str1 是否含有 str2,如果存在,就返回第一次出现的位置,如果没有,则返回-1。

备注:不能使用简单的暴力匹配算法!!!

  • 34
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

皮皮攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值