[Java]常见算法问题(持续学习,更新)

1.最大子序列和问题的四种算法:O(N^3),O(N^2),O(NlogN),O(N)

算法1:

public static int Method1(int[] arr)//O(N^3)
	{
		int max=0;
		for(int i=0;i<arr.length;i++){//子列起始坐标
			for(int j=i;j<arr.length;j++){//子列终坐标
				
				int sum=0;
				for(int k=i;k<=j;k++)
					sum+=arr[k];
				if (sum>max)
				{
					max=sum;
				}
			}
		}

		return max;
	}

算法2:

public static int Method2(int[] arr)//O(N^2)
	{
		int max=0;
		for(int i=0;i<arr.length;i++){
			
			int sum=0;//它在这,和上一个位置不同!
			for(int j=i;j<arr.length;j++){
				
				
				sum+=arr[j];//只要保证a[j]遍历了每个元素,子列加长只不断加上后面元素即可,避免重复计算
				if (sum>max)
				{
					max=sum;
				}
			}
		}
		return max;
	}

算法3:

public static int Method3(int[] arr,int left,int right)
	{
		if(left==right)//Base Case
			if(arr[left]>0)
				return arr[left];
			else
				return 0;
		
		int center=(left+right)/2;//最自然简化实用正确地去思考,不要去想细节!
		int maxLeftSum=Method3(arr,left,center);//递归
		int maxRightSum=Method3(arr,center+1,right);


		//包含最后元素的左边最大与包含第一元素的右边最大

		int maxLeftBorderSum=0,leftBorderSum=0;
		for(int i=center;i>=left;i--)
		{
			leftBorderSum+=arr[i];
			if(leftBorderSum>maxLeftBorderSum)
				maxLeftBorderSum=leftBorderSum;
		}

		int maxRightBorderSum=0,rightBorderSum=0;

		for(int i=center+1;i<=right;i++)
		{
			rightBorderSum+=arr[i];
			if(rightBorderSum>maxRightBorderSum)
				maxRightBorderSum=rightBorderSum;
		}

		return max3(maxLeftSum,maxRightSum,maxLeftBorderSum+maxRightBorderSum);
	}

	public static int max3(int x,int y,int z)
	{
		return x>y?(x>z?x:z):(y>z?y:z);
	}

测试:

public static void main(String[] args) 
	{
		int[] arr=new int[]{121234,435,3942,786,354542,34244324,765,799078,3455,242343243,35355345,2342432};

		System.out.println(Method1(arr));
		System.out.println(Method2(arr));
		System.out.println(Method3(arr,0,arr.length-1));
	}

结果:

D:\java\practice4>javac MaxSub.java

D:\java\practice4>java MaxSub
315569581
315569581
315569581

D:\java\practice4>


讨论:

第三种算法的执行时间分析:

T(1)=1

T(N)=2T(N-1)+O(N)

为了简化计算,以N代替O(N),观察:

T(2)=2*2,T(4)=4*3,T(8)=8*4,T(16)=16*5,

若N=2^k,则T(N)=N*(k+1)=N*logN+N=O(NlogN)

这个分析假设N为偶数,否则N/2就不确定了。当N不是2的幂时,需要复杂一些的分析,但大O的结果是不变的。


算法4:

public static int Method4(int[] arr)
	{
		int max=0,sum=0;

		for(int j=0;j<arr.length;j++)
		{
			sum+=arr[j];
			if(sum>max){
				max=sum;
			}else if(sum<0){
				sum=0;
			}
		}
		return max;
	}
结果一致,时间为O(N).

分析:一个负值绝不是一个最大子列的头;一个值为负的子列绝不是一个最大子列的前缀;如果arr[j]是第一个使一个子列为负的值(而不是什么小于前面子列的值,因为包含arr[j]及后面值的子列仍有可能大于前面子列)(值为负的前缀子列),那么我们可以把头直接推进到j+1(也就是代码中直接将sum赋值为0的情况),所以我们一个最优解也不会错过!------->简单,大气地分析,确定最优想法的每一个简单事实,累积和排除后,就是最优正确答案,不要钻牛角尖!

附带优点:只对数据进行一次扫描,一旦a[i]被读入并处理,它就不需要再被记忆。如果数组在磁盘上或互联网上被传输,可以按顺序读入,主存中不必存储数组的任何部分。在任意时刻,算法都能对它已经读入的数据给出子序列问题的正确答案(其他算法不具备!),具有这种特性的算法叫做联机算法。仅需要常量空间,并以线性时间运行的联机算法,几乎是完美的算法!


2.O(logN)讨论与折半查找

分析算法最混乱的方面在于对数,某些分治算法(如例子1算法3)将以O(NlogN)的时间运行。对数最常出现的规律概括为以下法则:

如果一个算法用常数时间(O(1))将问题削减为其一部分(通常是1/2),该算法就是O(logN)

如果使用常数时间只把问题减少一个常数数量(如减少1),那么它就是O(N)的


折半查找:在一个已预先排序的集合中查找Ai=X,如果X不在集合中则返回-1

实现:

public static <AnyType extends Comparable<? super AnyType>> 
		int binarySearch(AnyType[] a,AnyType x)
	{
		int low=0,high=a.length-1;

		while(low<=high)
		{
			int mid=(low+high)/2;

			if(a[mid].compareTo(x)<0)
				low=mid+1;
			else if(a[mid].compareTo(x)>0)
				high=mid-1;
			else
				return mid;
		}

		return -1;
	}

测试:字符串类型

public static void main(String[] args) 
	{
		String[] arr=new String[]{"a","abc","abcde","abde","adx","bdxc","def","fx","fxck","jaxp","jdbc","jsp","jvm"};
		System.out.println(binarySearch(arr,"jdbc"));
	}

结果:10

分析:循环从high-low=N-1开始到high-low<=-1结束,每次循环后值至少折半,循环次数最多为「log(N-1) +2(向上取整),因此运行时间为O(logN).

折半查找提供了O(logN)时间内的数据结构的contains操作,但所有其他操作特别是insert都需要O(N)的时间。在数据稳定(不允许插入和删除)的情况下它是非常有用的,只需一次排序,此后访问会很快。顺序查找则需要多的多的时间。


欧几里得算法计算最大公约数:

public static long gcd(long m,long n)
	{
		while(n!=0)
		{
			long rem=m%n;
			m=n;
			n=rem;
		}
		return m;
	}

结果:gcd(1989,1590)的余数序列:399,393,6,3,0,故gcd(1989,1590)=3

一次迭代中余数并不按一个常数因子递减、但可以证明,在两次迭代后余数最多是原始值的一半,故迭代次数至多是2logN,即O(logN)

定理:如果M>N,则M mod N<M/2

你可能感兴趣的:([Java]常见算法问题(持续学习,更新))