JAVA算法思想总结:穷举、递推、递归、概率、分治

一、穷举算法

穷举是最简单的一种算法,依赖计算机的强大计算能力,来穷尽每一种可能的情况,从而达到求解问题的目的。
特点:算法效率低,适合没有明显规律可循的场合。

1.穷举算法的执行步骤是怎样的?

(1)对于一种可能的情况,计算其结果
(2)判断结果是否满足要求,如果不满足,执行第(1)步来搜索下一个可能的情况;如果满足,则表示寻找到一个正确的答案。

2.穷举算法有哪些应用?

2.1鸡兔同笼问题

问题:有鸡兔同笼,上有三十五头,下有九十四足,问鸡兔各几何?
分析:若按以往思维,列方程式:x+y=35;2x+4y=94;得出鸡23只,兔12只。但按照计算机的穷举思想,我们可以遍历各种的组合,得出其中一种可能的值。比如,鸡的取值在0~35之间,通过逐个判断是否符合,从而搜索出答案。

public class Test{
	public static int chicken,rabbit;
    public static void main(String []args) {		
		if(compute(35,94) == 1){
			System.out.println("鸡有"+chicken+"只,兔有"+rabbit+"只");
		}else {
			System.out.println("无法求解");
		}	
    }    
	public static int compute(int head, int foot) {
	    int result,i,j;
		result = 0;
		//鸡的取值范围在0~35之间
		for(i=0;i<=head;i++){
			//通过约束,得出兔子的数量
			j=head-i;
			//当满足判断条件时,就能得出其中一个答案
			if(i*2+j*4 == foot) {
				result=1;
				chicken=i;
				rabbit=j;				
				return result;
			}
		}
		//若循环结束	
		return result;
	}
}

二、递推算法

递推是很常用的算法思想,有广泛的应用。适合有明显规律的场合。

1.递推算法的步骤是怎样的?

(1)根据已知的结果和关系,求解中间结果。
(2)判定是否达到要求,如果没有达到,则继续根据已知结果和关系求解中间结果;如果满足要求,则表示寻找到一个正确的答案。

2.递推算法有哪些应用?

2.1斐波那契数列问题

问题:假设一对两个月大的兔子以后每一个月都可以生一对小兔子,而一对新生的兔子出生两个月后才可以生兔子。例如,1月份出生,3月份才可以产仔。那么假定一年内没有兔子死亡,那么12月份共有多少对兔子呢?
分析:先来分析下每个月兔子的数量。
第1个月,1对兔子出生。兔子共1对。
第2个月,1对兔子一个月大。兔子共1对。
第3个月,1对兔子两个月大,1对兔子出生。兔子共2对。
第4个月,1对兔子三个月大,1对兔子一个月大,1对兔子出生。兔子共3对。
第5个月,1对兔子四个月大,1对兔子两个月大,1对兔子出生一个月大,2对兔子出生。兔子共5对。

在这里插入图片描述

从上述规律,可看出第3个月的兔子数量是前面两个月的数量之和。
第n个月的兔子数量公式:F(n)=F(n-1)+F(n-2),其中F(1),F(2)的值为1

public class Test2 {
    public static void main(String []args) {
        int result = fibonacci(12);
        System.out.println("12月共有"+result+"只兔子");
    }
    public static int fibonacci(int n) {
    	//初始值,已知的结果
        if(n ==1 | n==2){
            return 1;
        }
        //递推公式
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}

三、递归算法

递归算法是很常用的算法思想,可以简化代码编写,提高程序的可读性。但是,不合适的递归往往导致程序的执行效率变低。
因为递归算法不断反复调用自身来达到求解问题,因此要求待求解的问题能够分为为相同问题的子问题。
分为直接递归和间接递归。

1.递归算法有哪些应用?

1.1阶乘问题

问题:所谓阶乘就是从1到指定数之间的所有自然数相乘的结果。
分析:阶乘为n!=n*(n-1)…1,(n-1)!=(n-1)(n-2)…1,递推公式为n!=n*(n-1)!,采用递推算法。

int fact(int n) {
	if(n<=1){
		return 1;
	}else {
		return n*fact(n-1);
	}
}

四、概率算法

概率算法往往不能得到问题的精确解,但是在数值计算领域等到了广泛的应用。

1.概率算法的步骤是怎样的?

(1)将问题转化为相应的集合图形S,S的面积容易计算,问题的结果往往对应几何图形中某一部分S1的面积。
(2)然后,向几何图形中随机撒点。
(3)统计几何图形S和S1中的点数。根据S和S1面积的关系及各图形中的点数来计算得到结果。
(4)判断上述结果是否在需要的精度之内,如果未达到精度则执行步骤(2)。如果达到精度,则输出近似结果。

概率算法可分为:

  • 数值概率算法
  • 蒙特卡洛算法
  • 拉斯维加斯算法
  • 舍伍德算法

2.概率算法有哪些应用?

2.1 根据蒙特卡洛算法计算圆周率π的值

问题:如何计算圆周率π的值?
分析:计算圆周率的值,可以转化为求圆形的面积,S=π*r2;根据微积分的思想,随机向圆内撒点,x和y的取值分别为0~1之间,通过x2+y2<=1判断点是否落在圆形内,变相求出圆的面积。撒的点越多,值越精确。
在这里插入图片描述

public class Test4 {
    public static int count = 0;
    public static void main(String []args) {
        int n = 1000000000;
        System.out.println("取点数为"+n+"次,PI的值为"+MontePI(n));
        //取点数为1000000000次,PI的值为3.141683144
    }   
    public static double MontePI(int n) {
        int sum = 0;
        for(int i=0;i<n;i++) {
            double x = Math.random();
            double y = Math.random();
            if((x*x+y*y)<=1)  sum++;
        }
        double PI = 4.0*sum/n;
        return PI;
    }
}

总结:蒙特卡洛算法采样越多,越近似最优解;例如,在100个苹果找出最大的苹果,随机取出1个苹果放在手中,再随机取出下一个苹果比较大小。当取完最后一个苹果,当然能找到最大的。但是,采样不多时,只能找到接近最大的,当满足我们的要求时,我们就可以停止计算。

五、分治算法

分治算法是一种化繁就简的算法思想。基本思想是将一个计算复杂的问题分为规模较小、计算简单的小问题求解,然后综合各个小问题,得到最终的答案。

1.分治算法的步骤是怎样的?

(1)对于一个规模为N的问题,若该问题比较容易解决,则直接解决;否则执行下个步骤。
(2)将该问题分解为M个规模较小的子问题,这些子问题互相独立,并且与原问题形式相同。子问题与原问题一样只是规模更小。
(3)递归地解决这些子问题。
(4)然后,将各子问题的解合并得到原问题的解。有时,需要求解与原问题不完全一样的子问题,这些子问题看做合并步骤的一部分。
分治算法需要待求解问题能够转化为若干个小规模的相同问题,通过逐步划分,能够达到一个易于求解的阶段而直接进行求解,程序中可以使用递归算法进行求解。

2.分治算法有哪些应用?

2.1硬币问题

问题:一个袋子里有30个硬币,其中一枚是假币,并且假币和真币真假难辨,只知道假币比真币的重量轻一点。如何分辨出假币呢?
分析:利用天平可以分辨出两侧硬币哪个更重,重的一侧会下沉,轻的一侧会抬起,若重量相等,则天平持平。
对于30个硬币,若两两比较,最差情况要称量15次,在最后一次称量中,较轻的假币一侧,天平会升高抬起。
根据分治思想,可将30硬币分为两堆,较轻的一侧必然包含假币。再将较轻的一侧15个硬币分为两堆,若相等,则多余的那个是假币。若不等,则将剩余7枚硬币,再次分为两堆。依次类推,在最差情况下,再将剩余3枚硬币分为两堆。共需要称重4次。

public class Test3 {
    public static int count = 0;
    public static void main(String []args) {
        int[] coins = {
                2,2,2,2,2,2,2,2,2,2,
                2,1,2,2,2,2,2,2,2,2,
                2,2,2,2,2,2,2,2,2,2};
        int position = positionOfFakeCoin(coins, 0, coins.length-1);
        System.out.println("假币位置在数组的下标为"+position+",重量为"+coins[position]);
        System.out.println("称重次数为:"+count);
        //假币位置在数组的下标为11,重量为1
        //称重次数为:4
    }
    //难点1,采用递归算法,为重用数组,函数需要传递高低位指针
    public static int positionOfFakeCoin(int[] coins, int low, int high) {
        //终止条件:当高低位下标相等时,就找出了假币的位置
        if(low == high) return low;
        int lWeight=0,rWeight=0;
        //难点2,对于下标的处理,硬币数有奇数和偶数,分成两堆时,下标需要仔细斟酌
        int half = (high- low -1)/2;
        //称重过程,实际就是将一堆硬币的重量累加
        for(int i=low; i<=(low+half); i++){
            lWeight += coins[i];
        }
        for(int i=low+half+1;i<=(low+2*half+1);i++){
            rWeight += coins[i];
        }
        count++;
        //不断找出较轻的那一堆,若相等,则余下那一枚是假币
        if(lWeight < rWeight){
            return positionOfFakeCoin(coins,low,low+half);
        }
        if(lWeight > rWeight){
            return positionOfFakeCoin(coins,low+half+1,low+2*half+1);
        }
        return high;
    }
}

总结:这是一个规模为30的问题。若规模较小,比如2或3个硬币,则可以直接解决。但是,30个硬币无法直接解决。可分解为15个硬币的子问题。其中重点是,如何将规模为N的问题分解成规模为M的子问题。例如,此问题是通过天平判断,
30个硬币中有一枚假币,分成两堆,转为其中一堆中15个硬币中有一枚假币。对于奇数枚硬币,分成两堆时,余下的那枚硬币,也能通过推理判断。实际上,30个硬币被分成三类,两个需要称重判断的硬币,和余下的一枚硬币。

2.2股票买卖问题

在这里插入图片描述
问题:假设需要在某个周期内买卖某个公司的股票,例如上图中开盘100元/股,在此后的每天中可以买入或卖出此股票一次。在某天中买入此股票,在该天之后才能卖出。若希望达到利益最大化,那么怎样操作?

分析:问题可以转化为股票涨跌的变化,若根据暴力穷举算法来求解,可以有n(n-1)/2种可能的买入和卖出的组合,找出盈利最大的组合,是一种σ(n2)的运算级。运算效率较低
但是对于输入,我们可以从另外的角度将问题转化为股票的涨跌。我们的目的是寻找一段日期,使得从第一天到最后一天的净现值最大。那么问题就变成,寻找数组A的和最大的非空连续子数组。我们称这样的子数组为最大子数组
在这里插入图片描述
乍一看,这样的问题转化,似乎对求解并没有什么帮助,依然要求解n(n-1)/2种组合的值。每一个子数组都要花费线性时间,然而当求解σ(n2)个子数组的和时,我们可以通过分治策略,优化计算方式。

注意:我们说“一个最大子数组”,而不是“最大子数组”,因为可能有多个组合达到最大值。
只有当数组包含负数的时候,最大子数组才有意义。否则,直接是整个数组达到最大值。

public class Test5 {
    public static void main(String[] args) {
        int[] a = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7};
        int[] result = findMaxSubarray(a, 0, a.length - 1);
        System.out.println("低位下标为" + result[0]+",对应的值为"+a[result[0]]);
        System.out.println("高位下标为" + result[1]+",对应的值为"+a[result[1]]);
        System.out.println("值为" + result[2]);
        //低位下标为7,对应的值为18
		//高位下标为10,对应的值为12
		//值为43
    }
    //求解子数组的最大和
    public static int[] findMaxSubarray(int[] a, int low, int high) {
    	//构造数组分别,存入低位,高位下标,和该连续数组的值
        int[] result = {0, 0, 0};
        //基本情况,当数组只有一个值时,返回该数组的数据
        if (low == high) {
            result[0] = low;
            result[1] = high;
            result[2] = a[low];
            return result;
        }
        //中间下标,中间数将数组大致分为两部分,不需要长度完全相等的数组
        int mid = (high + low) / 2;
        //左侧子数组
        int[] lSubarray = findMaxSubarray(a, low, mid);
        //右侧子数组
        int[] rSubarray = findMaxSubarray(a, mid + 1, high);
        //交叉子数组
        int[] mSubarray = findMaxCrossSubarray(a, low, mid, high);
        
        if ((lSubarray[2] >= rSubarray[2]) && (lSubarray[2] >= mSubarray[2])) {
        	//若左子数组最大,则返回左子数组的结果
            return lSubarray;            
        } else if ((rSubarray[2] >= lSubarray[2]) && (rSubarray[2] >= mSubarray[2])) {
        	//若右子数组最大,则返回右子数组的结果
            return rSubarray;
        } else {
        	//若交叉子数组最大,则返回交叉子数组的结果
            return mSubarray;
        }
    }
    //求解交叉子数组的最大和
    public static int[] findMaxCrossSubarray(int[] a, int low, int mid, int high) {
        int[] result = {0, 0, 0};
        int lSum = 0, rSum = 0;
        int lMax = Integer.MIN_VALUE, rMax = Integer.MIN_VALUE;
        for (int i = mid; i >= low; i--) {
            lSum += a[i];
            if (lSum >= lMax) {
                lMax = lSum;
                result[0] = i;
            }
        }
        for (int j = mid + 1; j <= high; j++) {
            rSum += a[j];
            if (rSum >= rMax) {
                rMax = rSum;
                result[1] = j;
            }
        }
        result[2] = lMax + rMax;
        return result;
    }
}

总结:采用分治策略,最大连续子数组可以分解左右两部分子数组交叉部分的子数组。即原问题由分解后规模更小的子问题和不完全一样的子问题组成。规模更小的子问题可以通过递归的方式解决。不完全一样的子问题,需要单独解决,运算是线性的。综合三个子问题的解,合并得出最终问题的答案。
该算法每次计算的运算规模,都缩小一半,也就是是nlgn级数的运算。而交叉部分的子数组的运算,是n级数的运算。因此运用分治算法后,运算级数从n2变为了nlgn的级数,效率更快了。

3.分治策略的术语

(1)当子问题足够大,需要递归求解时,称为递归情况(recursive case)。
(2)当子问题足够小,不再需要递归时,我们说递归已经“触底”,称为基本情况(base case)。

4.分治算法与其他思想的联系

递归式与分治算法紧密相关,因为递归式可以自然刻画分治算法的运行时间
可求解递归式的方法,得出算法的渐进界方法。
可求解形如下面公式的递归的界:

T(n)=aT(n/b)+f(n)

其中a>=1,b>1,f(n)是给定的函数。
它刻画了这样一个算法:将生成a个子问题,每个子问题的规模是原来的1/b,分解和合并步骤总共花费f(n)。

递归式的技术细节
类似于2.1的硬币问题,当n为奇数时,2个子问题的规模分为[n/2]和[n/2],即除以2之后取整的值,而不是n/2,因为当n是奇数时,n/2不是整数。当然对于java来说,当除不尽时恰恰是取整的,对于其他语言,就需要注意了,可能得到小数。
对于该问题的公式为:T(n)=T([n/2])+T([n/2])+σ(n)

### 回答1: Active Directory域服务是一种由微软公司开发的网络服务,它提供了一种集中管理和控制网络资源的方式。它可以在一个域中集中管理用户、计算机、应用程序和其他网络资源,从而提高了网络的安全性和可管理性。Active Directory域服务还提供了一些高级功能,如单点登录、组策略管理和域名系统(DNS)集成等,使得网络管理员可以更加轻松地管理和维护网络。 ### 回答2: Active Directory域服务(Active Directory Domain Services,简称AD DS)是微软公司的一项用于管理和组织网络资源的目录服务。它是一种基于LDAP(轻量级目录访问协议)的目录服务,可以让用户和管理员方便地管理和访问网络中的资源。 AD DS的主要功能包括用户身份认证、访问控制、组管理和资源管理等。通过AD DS,管理员可以集中管理和配置用户和计算机的访问权限,确保系统安全。同时,AD DS还提供了域的集中管理功能,管理员可以通过域控制器管理域中的所有对象,并在域中实施策略。 AD DS还支持单点登录功能,用户只需在登录到域之后,即可自动访问到所属域中的资源,而无需再次输入用户名和密码。这大大提高了用户的工作效率。 此外,AD DS还支持多域架构,可以通过建立信任关系实现跨域资源的访问和管理。管理员可以维护多个域之间的信任关系,实现用户和资源的统一管理。 总而言之,AD DS是一种强大的目录服务,可以实现用户和资源的集中管理和访问控制,提高网络系统的稳定性和安全性。它是企业网络管理的重要组成部分,为企业提供了高效的身份认证和资源管理功能,增强了企业的生产力和安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值