几种常用算法详解

本文详细介绍了二分查找的递归实现及其在有序数组中的应用,同时探讨了插值查找的自适应特性,两种算法在查找效率上的对比。通过实例演示了如何在有序数组中查找特定元素,以及在有重复值的情况下扩展二分查找的方法。
摘要由CSDN通过智能技术生成

二分查找

public class 二分查找 {
    //二分查找是对一个有序数组进行查找
    /*
    思路:
    1.首先确定该数组的中间下标  mid=(left+right)/2;
    2.然后让需要查找的数findval和arr[mid]比较
        2.1 findval>arr[mid],说明你要查找的数在mid右边,因此需要递归向右查找
        2.2 findval<arr[mid],说明你要查找的数在mid左边,因此需要递归向左查找
        2.3 findval=arr[mid],说明找到,返回

    什么时候递归结束
        1.找到就递归结束
        2.递归完整个数组,任然没有找到findval,也需要结束递归当left>right就需要退出。
     */
    public static void main(String[] args) {
        int arr[]={1,2,5,8,15,1000,8700};
        System.out.println(binarySearch(arr,0,arr.length-1,67));
        int arr2[]={1,2,5,8,15,1000,1000,1000,1000,8700};
        System.out.println(binarySearch2(arr2,0,arr.length-1,1000));
    }

    public static int binarySearch(int[] arr,int left,int right,int findVal){
        int mid=(left+right)/2;
        int midVal=arr[mid];
        //避免如果要找的数不存在时,无法终止递归,陷入死循环。给一个递归结束出口
        if(left>right){
            return -1;
        }

        if(findVal>midVal){
            return binarySearch(arr,mid+1,right,findVal);
        }else if(findVal<midVal) {
            return binarySearch(arr,left,mid-1,findVal);
        }else {
            return mid;
        }

    }

    //升级,当一个有序数组中有多个相同数值时,如何将所有数值都查到
    /*
    思考:当有多个相同数值时,如何将所有的数值都查找到,如{1,2,5,8,15,1000,1000,1000,8700};

    思路分析:
    1.在找到mid索引值,不要马上返回
    2.向mid索引值的左边扫描,将所有满足1000的元素下标加入到集合 ArrayList
    3.向mid索引值的右边扫描,将所有满足1000的元素下标加入到集合ArrayList
    4.将ArrayList返回
     */

    public static ArrayList<Integer> binarySearch2(int[] arr, int left, int right, int findVal){
        int mid=(left+right)/2;
        int midVal=arr[mid];
        //避免如果要找的数不存在时,无法终止递归,陷入死循环。给一个递归结束出口
        if(left>right){
            return new ArrayList<Integer>();
        }

        if(findVal>midVal){
            return binarySearch2(arr,mid+1,right,findVal);
        }else if(findVal<midVal) {
            return binarySearch2(arr,left,mid-1,findVal);
        }else {
           /* 思路分析:
            1.在找到mid索引值,不要马上返回
            2.向mid索引值的左边扫描,将所有满足1000的元素下标加入到集合 ArrayList
            3.向mid索引值的右边扫描,将所有满足1000的元素下标加入到集合ArrayList
            4.将ArrayList返回*/

            ArrayList<Integer> resIndexList=new ArrayList<>();
            //向mid索引值的左边扫描,将所有满足1000的元素下标,加入到集合ArrayList
            int temp=mid-1;
            while (true){
                //发生如下情况需要退出
                if (temp<0 || arr[temp]!=findVal){
                    break;
                }
                //没发生退出,则说明查找到了,将temp放入到集合中
                resIndexList.add(temp);
                temp-=1;
            }
            //左边的找完了,中间的值别忘了加进去
                resIndexList.add(mid);
            //向mid索引值的右边扫描,将所有满足1000的元素下标,加入到集合ArrayList
            temp=mid+1;
            while (true){
                if (temp>arr.length-1 || arr[temp]!=findVal){
                    break;
                }
                resIndexList.add(temp);
                temp+=1;
            }
            return resIndexList;
        }

    }
}

分治算法

public class 分治算法 {
    /*
分治算法的基本步骤

    分治法在每一层递归上都有三个步骤:
    分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
    解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
    合并:将各个子问题的解合并为原问题的解。

分治算法最佳实践-汉诺塔
    汉诺塔游戏的演示和思路分析:
    如果是有一个盘, A->C
    如果我们有 n >= 2 情况,我们总是可以看做是两个盘 1.最下边的盘 2. 上面的盘
    先把 最上面的盘 A->B
    把最下边的盘 A->C
    把B塔的所有盘 从 B->C

     */

    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);
        }
    }
}

动态规划
动态规划算法介绍
动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法 动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。 ( 即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解 )动态规划可以通过填表的方式来逐步推进,得到最优解.

动态规划算法最佳实践-背包问题
        背包问题:有一个背包,容量为4磅 , 现有如下物品

        物品	  重量	  价格
        吉他(G)	    1	  1500
        音响(S)	    4	  3000
        电脑(L)	    3	  2000
        要求达到的目标为装入的背包的总价值最大,并且重量不超出
        要求装入的物品不能重复

        思路分析
        背包问题主要是指一个给定容量的背包、若干具有一定价值和重量的物品,如何选择物品放入背包使物品的价值最大。其中又分01背包和完全背包(完全背包指的是:每种物品都有无限件可用)
        这里的问题属于01背包,即每个物品最多放一个。而无限背包可以转化为01背包。

       思路分析
        算法的主要思想,利用动态规划来解决。每次遍历到的第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]]}  
        // 当 准备加入的新增的商品的容量小于等于当前背包的容量,
		// 装入的方式:
        v[i-1][j]: 就是上一个单元格的装入的最大值
        v[i] : 表示当前商品的价值
        v[i-1][j-w[i]] : 装入i-1商品,到剩余空间j-w[i]的最大值
        当j>=w[i]时: v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]} 
public class 动态规划 {
    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个商品放入到背包\n", 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个商品放入到背包\n", i);
                j -= w[i-1]; //w[i-1]
            }
            i--;
        }

    }
}

插值查找

//插值查找能自适应找到mid,效率上大于二分查找。
public class 插值查找 {
    public static void main(String[] args) {

//		int [] arr = new int[100];
//		for(int i = 0; i < 100; i++) {
//			arr[i] = i + 1;
//		}

        int arr[] = { 1, 8, 10, 89,1000,1000, 1234 };

        int index = insertValueSearch(arr, 0, arr.length - 1, 1234);
        //int index = binarySearch(arr, 0, arr.length, 1);
        System.out.println("index = " + index);

        //System.out.println(Arrays.toString(arr));
    }

    public static int binarySearch(int[] arr, int left, int right, int findVal) {
        System.out.println("二分查找被调用~");
        // 当 left > right 时,说明递归整个数组,但是没有找到
        if (left > right) {
            return -1;
        }
        int mid = (left + right) / 2;
        int midVal = arr[mid];

        if (findVal > midVal) { // 向 右递归
            return binarySearch(arr, mid + 1, right, findVal);
        } else if (findVal < midVal) { // 向左递归
            return binarySearch(arr, left, mid - 1, findVal);
        } else {

            return mid;
        }

    }

    //编写插值查找算法
    //说明:插值查找算法,也要求数组是有序的
    /**
     *
     * @param arr 数组
     * @param left 左边索引
     * @param right 右边索引
     * @param findVal 查找值
     * @return 如果找到,就返回对应的下标,如果没有找到,返回-1
     */
    public static int insertValueSearch(int[] arr, int left, int right, int findVal) {

        System.out.println("插值查找次数~~");

        //注意:findVal < arr[0]  和  findVal > arr[arr.length - 1] 必须需要
        //否则我们得到的 mid 可能越界
        if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
            return -1;
        }

        // 插值查找核心,自适应求出mid,
        int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);
        int midVal = arr[mid];
        if (findVal > midVal) { // 说明应该向右边递归
            return insertValueSearch(arr, mid + 1, right, findVal);
        } else if (findVal < midVal) { // 说明向左递归查找
            return insertValueSearch(arr, left, mid - 1, findVal);
        } else {
            return mid;
        }

    }
}

暴力算法

//查找str1中是否含有str2的字符串,有的话返回其所在位置,没有的话返回-1;
public class 暴力算法 {
    public static void main(String[] args) {
        String str1="硅硅谷 尚硅谷你尚硅 尚硅谷你尚硅你尚硅你好";
        String str2="尚硅谷你尚硅你";
        System.out.println(violenceMatch(str1,str2));
    }

    public static int violenceMatch(String str1,String str2){
        char[] s1=str1.toCharArray();
        char[] s2=str2.toCharArray();

        int s1Len=str1.length();
        int s2Len=str2.length();

        int i=0;
        int j=0;
        while(i<s1Len && j<s2Len){
            if(s1[i]==s2[j]){
                i++;
                j++;
            }else{
                //相同位置字符不相同,则将返回到这一次匹配搜索开始位置的下一个位置,重新匹配搜索。
                i=i-(j-1);
                //s2从开始位置重新搜索。
                j=0;
            }
        }
        //判断是否匹配成功
        if(j==s2Len){
            return i-j;//返回匹配开始位置
        }else{
            return  -1;
        }
    }
}

贪心算法
贪心算法介绍
贪婪算法(贪心算法)是指在对问题进行求解时,在每一步选择中都采取最好或者最优(即最有利)的选择,从而希望能够导致结果是最好或者最优的算法贪婪算法所得到的结果不一定是最优的结果(有时候会是最优解),但是都是相对近似(接近)最优解的结果

public class 贪心算法 {
    public static void main(String[] args) {
        //创建广播电台,放入到Map
        HashMap<String, HashSet<String>> broadcasts = new HashMap<String, HashSet<String>>();
        //将各个电台放入到broadcasts
        HashSet<String> hashSet1 = new HashSet<String>();
        hashSet1.add("北京");
        hashSet1.add("上海");
        hashSet1.add("天津");

        HashSet<String> hashSet2 = new HashSet<String>();
        hashSet2.add("广州");
        hashSet2.add("北京");
        hashSet2.add("深圳");

        HashSet<String> hashSet3 = new HashSet<String>();
        hashSet3.add("成都");
        hashSet3.add("上海");
        hashSet3.add("杭州");


        HashSet<String> hashSet4 = new HashSet<String>();
        hashSet4.add("上海");
        hashSet4.add("天津");

        HashSet<String> hashSet5 = new HashSet<String>();
        hashSet5.add("杭州");
        hashSet5.add("大连");

        //加入到map
        broadcasts.put("K1", hashSet1);
        broadcasts.put("K2", hashSet2);
        broadcasts.put("K3", hashSet3);
        broadcasts.put("K4", hashSet4);
        broadcasts.put("K5", hashSet5);

        //allAreas 存放所有的地区
        HashSet<String> allAreas = new HashSet<String>();
        allAreas.add("北京");
        allAreas.add("上海");
        allAreas.add("天津");
        allAreas.add("广州");
        allAreas.add("深圳");
        allAreas.add("成都");
        allAreas.add("杭州");
        allAreas.add("大连");

        //创建ArrayList, 存放选择的电台集合
        ArrayList<String> selects = new ArrayList<String>();

        //定义一个临时的集合, 在遍历的过程中,存放遍历过程中的电台覆盖的地区和当前还没有覆盖的地区的交集
        HashSet<String> tempSet = new HashSet<String>();

        //定义给maxKey , 保存在一次遍历过程中,能够覆盖最大未覆盖的地区对应的电台的key
        //如果maxKey 不为null , 则会加入到 selects
        String maxKey = null;
        while(allAreas.size() != 0) { // 如果allAreas 不为0, 则表示还没有覆盖到所有的地区
            //每进行一次while,需要
            maxKey = null;

            //遍历 broadcasts, 取出对应key
            for(String key : broadcasts.keySet()) {
                //每进行一次for
                tempSet.clear();
                //当前这个key能够覆盖的地区
                HashSet<String> areas = broadcasts.get(key);
                tempSet.addAll(areas);
                //求出tempSet 和   allAreas 集合的交集, 交集会赋给 tempSet
                tempSet.retainAll(allAreas);
                //如果当前这个集合包含的未覆盖地区的数量,比maxKey指向的集合地区还多
                //就需要重置maxKey
                // tempSet.size() >broadcasts.get(maxKey).size()) 体现出贪心算法的特点,每次都选择最优的
                if(tempSet.size() > 0 &&
                        (maxKey == null || tempSet.size() >broadcasts.get(maxKey).size())){
                    maxKey = key;
                }
            }
            //maxKey != null, 就应该将maxKey 加入selects
            if(maxKey != null) {
                selects.add(maxKey);
                //将maxKey指向的广播电台覆盖的地区,从 allAreas 去掉
                allAreas.removeAll(broadcasts.get(maxKey));
            }

        }

        System.out.println("得到的选择结果是" + selects);//[K1,K2,K3,K5]
    }
}

kmp算法

public class KMP算法 {
    public static void main(String[] args) {
            // TODO Auto-generated method stub
            String str1 = "BBC ABCDAB ABCDABCDABDE";
            String str2 = "ABCDABD";
            //String str2 = "BBC";

            int[] next = kmpNext("ABCDABD"); //[0, 1, 2, 0]
            System.out.println("next=" + Arrays.toString(next));

            int index = kmpSearch(str1, str2, next);
            System.out.println("index=" + index); // 15了


        }

        //写出我们的kmp搜索算法
        /**
         *
         * @param str1 源字符串
         * @param str2 子串
         * @param next 部分匹配表, 是子串对应的部分匹配表
         * @return 如果是-1就是没有匹配到,否则返回第一个匹配的位置
         */
        public static int kmpSearch(String str1, String str2, int[] next) {

            //遍历
            for(int i = 0, j = 0; i < str1.length(); i++) {

                //需要处理 str1.charAt(i) != str2.charAt(j), 去调整j的大小
                //KMP算法核心点, 可以验证...
                while( j > 0 && str1.charAt(i) != str2.charAt(j)) {
                    j = next[j-1];
                }

                if(str1.charAt(i) == str2.charAt(j)) {
                    j++;
                }
                if(j == str2.length()) {//找到了 // j = 3 i
                    return i - j + 1;
                }
            }
            return  -1;
        }

        //获取到一个字符串(子串) 的部分匹配值表
        public static  int[] kmpNext(String dest) {
            //创建一个next 数组保存部分匹配值
            int[] next = new int[dest.length()];
            next[0] = 0; //如果字符串是长度为1 部分匹配值就是0
            for(int i = 1, j = 0; i < dest.length(); i++) {
                //当dest.charAt(i) != dest.charAt(j) ,我们需要从next[j-1]获取新的j
                //直到我们发现 有  dest.charAt(i) == dest.charAt(j)成立才退出
                //这时kmp算法的核心点
                while(j > 0 && dest.charAt(i) != dest.charAt(j)) {
                    j = next[j-1];
                }

                //当dest.charAt(i) == dest.charAt(j) 满足时,部分匹配值就是+1
                if(dest.charAt(i) == dest.charAt(j)) {
                    j++;
                }
                next[i] = j;
            }
            return next;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值