程序员常用的10个算法

Posted yangzhixue

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序员常用的10个算法相关的知识,希望对你有一定的参考价值。

1.二分查找算法(非递归)

 

 此篇写的是非递归算法,递归的在之前的查找算法中写过了。

1.1 算法的适用条件

二分查找只适用于从有序的数列中进行查找(比如数字和字母等),将数列排序后在查找。

1.2算法的效率:

时间复杂度为O(log2 n)

 

实例:使用二分查找的非递归形式对数组{1 3 8 10  11 67 100}进行查找

public class BinarySearchNoRecur {

    public static void main(String[] args) {
        //测试
        int[] arr = {1,3, 8, 10, 11, 67, 100};
        int index = binarySearch(arr, 100);
        System.out.println("index=" + index);//
    }
    
    //二分查找的非递归实现
    /**
     * 
     * @param arr 待查找的数组, arr是升序排序
     * @param target 需要查找的数
     * @return 返回对应下标,-1表示没有找到
     */
    public static int binarySearch(int[] arr, int target) {
        
        int left = 0;
        int right = arr.length - 1;
        while(left <= right) { //说明继续查找
            int mid = (left + right) / 2;
            if(arr[mid] == target) {
                return mid;
            } else if ( arr[mid] > target) {
                right = mid - 1;//需要向左边查找
            } else {
                left = mid + 1; //需要向右边查找
            }
        }
        return -1;
    }

}

2.分治算法

2.1 分治算法介绍

1)分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题.....直到最后子问题可以简单的直接求解,原问题的姐即子问题的解的合并。这个技巧是很多高效算法的基础,入排序算法(快排,归并排序),傅里叶变换(快速傅里叶变换)...

2)分治算法可以求解的一些经典问题

二分搜索, 大整数乘法,棋盘覆盖,合并排序,快速排序,线性时间选择,最接近点对问题 ,循环赛日称表,汉诺塔等问题。

2.2 分治算法的基本步骤

分治算法在每一层递归上都有三个步骤

1)分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题

2)解决:若干问题规模较小而容易被解决则直接解,否则递归地解决各个子问题

3)合并:将各个子问题的解合并为原问题的解。

2.3 分治(Divide-and Conquer(P))算法设置模式:

技术图片

2.4 分治算法的实践:汉诺塔(有ABC 三个塔)

技术图片

2.5 思路:

1)如果是一个盘,A->C

   如果我们有n>=2个盘,我们总是可以看做是两个盘:1.最下面的盘 2.上面的盘

  (1)先把最上面的盘A->B

    (2) 把下面的一个盘A->C

    (3)把B塔的所有盘从B->C

2.6 代码实现:

public class Hanoitower {

    public static void main(String[] args) {
        hanoiTower(10, ‘A‘, ‘B‘, ‘C‘);
    }
    
    //汉诺塔的移动的方法
    //使用分治算法
    
    public static void hanoiTower(int num, char a, char b, char c) {
        //如果只有一个盘
        if(num == 1) {
            System.out.println("第1个盘从 " + a + "->" + c);
        } else {
            //如果我们有 n >= 2 情况,我们总是可以看做是两个盘 1.最下边的一个盘 2. 上面的所有盘
            //1. 先把 最上面的所有盘 A->B, 移动过程会使用到 c
            hanoiTower(num - 1, a, c, b);
            //2. 把最下边的盘 A->C
            System.out.println("第" + num + "个盘从 " + a + "->" + c);
            //3. 把B塔的所有盘 从 B->C , 移动过程使用到 a塔  
            hanoiTower(num - 1, b, a, c);
            
        }
    }

}

3.动态规划算法

应用场景-背包问题

技术图片

1.动态规划算法介绍:

1)动态规划算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法

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

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

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

2.完全背包与01背包

完全背包指的是:每种物品都有无线件可以使用;01背包只能每件物品取一个。

3.思路与图解:

技术图片

技术图片

代码实现:

public class KnapsackProblem {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int[] w = {1, 4, 3};//物品的重量
        int[] val = {1500, 3000, 2000}; //物品的价值 这里val[i] 就是前面讲的v[i]
        int m = 4; //背包的容量
        int n = val.length; //物品的个数
        
        
        
        //创建二维数组,
        //v[i][j] 表示在前i个物品中能够装入容量为j的背包中的最大价值
        int[][] v = new int[n+1][m+1];
        //为了记录放入商品的情况,我们定一个二维数组
        int[][] path = 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[0].length; i++) {
            v[0][i] = 0; //将第一行设置0
        }
        
        
        //根据前面得到公式来动态规划处理
        for(int i = 1; i < v.length; i++) { //不处理第一行 i是从1开始的
            for(int j=1; j < v[0].length; j++) {//不处理第一列, j是从1开始的
                //公式
                if(w[i-1]> j) { // 因为我们程序i 是从1开始的,因此原来公式中的 w[i] 修改成 w[i-1]
                    v[i][j]=v[i-1][j];
                } else {
                    //说明:
                    //因为我们的i 从1开始的, 因此公式需要调整成
                    //v[i][j]=Math.max(v[i-1][j], val[i-1]+v[i-1][j-w[i-1]]);
                    //v[i][j] = Math.max(v[i - 1][j], val[i - 1] + v[i - 1][j - w[i - 1]]);
                    //为了记录商品存放到背包的情况,我们不能直接的使用上面的公式,需要使用if-else来体现公式
                    if(v[i - 1][j] < val[i - 1] + v[i - 1][j - w[i - 1]]) {
                        v[i][j] = val[i - 1] + v[i - 1][j - w[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();
        }
        
        System.out.println("============================");
        //输出最后我们是放入的哪些商品
        //遍历path, 这样输出会把所有的放入情况都得到, 其实我们只需要最后的放入
//        for(int i = 0; i < path.length; i++) {
//            for(int j=0; j < path[i].length; j++) {
//                if(path[i][j] == 1) {
//                    System.out.printf("第%d个商品放入到背包
", i);
//                }
//            }
//        }
        
        //动脑筋
        int i = path.length - 1; //行的最大下标
        int j = path[0].length - 1;  //列的最大下标
        while(i > 0 && j > 0 ) { //从path的最后开始找
            if(path[i][j] == 1) {
                System.out.printf("第%d个商品放入到背包
", i); 
                j -= w[i-1]; //w[i-1]
            }
            i--;
        }
        
    }

}

4.KMP算法

4.1 暴力匹配算法:

技术图片

4.1.1 代码实现

public class ViolenceMatch {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //测试暴力匹配算法
        String str1 = "硅硅谷 尚硅谷你尚硅 尚硅谷你尚硅谷你尚硅你好";
        String str2 = "尚硅谷你尚硅你~";
        int index = violenceMatch(str1, str2);
        System.out.println("index=" + index);

    }

    // 暴力匹配算法实现
    public static int violenceMatch(String str1, String str2) {
        char[] s1 = str1.toCharArray();
        char[] s2 = str2.toCharArray();

        int s1Len = s1.length;
        int s2Len = s2.length;

        int i = 0; // i索引指向s1
        int j = 0; // j索引指向s2
        while (i < s1Len && j < s2Len) {// 保证匹配时,不越界

            if(s1[i] == s2[j]) {//匹配ok
                i++;
                j++;
            } else { //没有匹配成功
                //如果失配(即str1[i]! = str2[j]),令i = i - (j - 1),j = 0。
                i = i - (j - 1);
                j = 0;
            }
        }
        
        //判断是否匹配成功
        if(j == s2Len) {
            return i - j;
        } else {
            return -1;
        }
    }

}

4.2 kmp算法

4.2.1 KMP算法介绍:

1)KMP算法是一个解决模式串在文本串是否出现过,如果出现过,最早出现的位置的经典算法。

2)Knuth-Morrirs-Pratt字符串查找算法,简称为“KMP算法”,常用于在一个文本串S内查找一个模式串P的出现位置,这个算法有Donald Knuth,Vaughan Pratt,James H.Morris三人于1977年联合发表,故取这三个人名命名此算法。

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

4)参考资料:https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html

4.2.2 KMP算法的应用

技术图片

以上是关于程序员常用的10个算法的主要内容,如果未能解决你的问题,请参考以下文章

C#程序员经常用到的10个实用代码片段

程序员常用的10个算法

21个常用代码片段

程序员常用 10 种算法之马踏棋盘算法

代码最常用的10大算法

JS常用代码片段-127个常用罗列-值得收藏