算法学习-----分治法

分治法

分治法的概念

	先分(将问题抽象法)
	后解 (将一个小问题一个个具体求解)
	合(通过更新,比较等方式将子问题的解合并成一个原问题的解)

分治法的经典例子

分法的实验(学校)

最大子序和

代码
    package 分治法;
    public class solution {
        /*
         * 问题描述
         * 1.用分治算法求解最大子段和问题。要求算法的时间复杂度不超过 O(nlogn)。
        最大子段和问题描述:给定由 n 个整数(可能为负整数)组成的序列 a1, a2,„, an, 
        求该序列形如j k i k a的子段和的最大值。当所有整数均为负整数时定义其最大子段和为 0。
         */
        /*
         * 解决办法,思考如何分解,分解成每一个子问题
         * 			然后通过这几个子问题,递归,求出总问题的答案
         */
        //分解,左边一半最大,右边一半最大,或者中间最大
        public int Divid(int[] nums) {
             return CountMinQue(nums,0,nums.length-1);	
        }
        //分解成三部分递归方程,求解每个子问题,这个函数抽象成 “求这一段的最大子序和,先不要想怎么实现”
        public int  CountMinQue(int []nums,int left,int right){
            if(left==right)
                return nums[left]>0?nums[left]:0;
            else {
                int mid=(left+right)/2;
                int leftMax=CountMinQue(nums, left, mid);
                int RightMax=CountMinQue(nums, mid+1, right);/*思考了很久,为啥上面这两个函数跟下面的是不一样的,解析见思路*//*上面两个是抽象的,下面的是具体实现,求解*/
                int MidMax=getMidMax(nums, left, right, mid);
                return Math.max(RightMax, Math.max(leftMax, MidMax));	/*这里是合,谁大就返回谁*/
            }
        }
        /*也是求这一段最大子序和的具体实现*/
        public int getMidMax(int []nums,int left,int right,int mid) {//这个是这个算法的重点,理解好中间的概念!
            int LeftNum=0;
            int RightNum=0;
            int LeftMax=nums[mid];
            int RightMax=nums[mid+1];
            //从中间开始计算左边的最大
            for(int i=mid;i>=left;i--) {
                LeftNum+=nums[i];     /*这里的for循环+LeftNum,保证了该子序号是连续的*/
                LeftMax=Math.max(LeftNum, LeftMax);
            }
            //从中间开始计算右边的最大
            for(int i=mid+1;i<=right;i++) {
                RightNum+=nums[i];	  /*这里的for循环+RightNum,保证了该子序号是连续的*/
                RightMax=Math.max(RightNum, RightMax);
            }
            return LeftMax+RightMax;

        }
    }
/*测试用例

	-1 -1 -2 -3(全是负数)    0

	1 2 -1 -1(左边最大)      3

	-1 -1 1 2(右边最大)      3

	-1 2 1 -1 (中间最大)     3
*/
问题 与 思考
  • 如何求最大子序和(方法总结)

    1. 先分

      ​ (其实是先将问题抽象化,譬如该题,最大子序和可能在左边,可能在右边,或者中间,定义一个抽象的函数(不是那种抽象)“求某一段的最大子序列”。然后这个可以作为递归方程式。)

    2. 具体思考如何求解

      ​ 求左边和求右边实质是求中间最大子序和,我们思考如何解决这个小问题:如何具体求解某一段的最大子序和?这里求解问题的核心和关键所在。

    3. 合,通过子问题的结果,不断更新,最后得到最终问题的结果。

  • 为什么求左边和求右边和求中间的函数不一样?

  • 在这里插入图片描述

    • 实质上求左边/右边,实质上就是用求中间的函数求的

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gLiHLMjY-1647878604690)(C:\Users\xie\AppData\Roaming\Typora\typora-user-images\image-20220321090308547.png)]

      • 原因(从最小的子问题分析)

        • 当分解成只有一个的时候,左边右边中间,所以此时用一个求中间函数就可以了//实际就是它本身

        ​ eg:2---------->2

        • 当有2个的时候,左边的最大值,是上一层的最大值。右边的最大值,是上一层的最大值。所以此时就是从中间开始求,然后比较中间,左,右,就可以了。

          eg:2 | 3 ------------->5

        • 当有3个的时候,左边的最大值是上一层返回的最大值,右边的数也是他本身,也是最大的。所以此时就是从中间开始求,然后比较中间,左,右,就可以了。

          eg:2 3 |-6 -------------------->5

        • 以此类推,实际上,左边和右边的结果不用求,都是上一层返回的结果(上一层的最大子序列和),只需要求该层中间的最大子序和,然后比较中间,左边,右边就可以了。同时要注意,左边和右边严格上来说不是不用求,他们其实是上一层最大子序和返回的(用求中间的函数得到的)。

  • 如何保证了该子序列是连续的?

    用了for从中间向两边拓展,每拓展一次,LeftSum都在加/减,所以保证了LeftSum一直都在更新,所以该结果是连续的

分治法的例题

最大子序列和

leetcode53最大子数组和

面试题16.17连续数列

​ 这两题与上面的最大子序列和一模一样,就不写总结了。

/*最大子数组和,唯一不同就是负数也要算*/
class Solution{
    public int maxSubArray(int[] nums) {
       return max(nums,0,nums.length-1);
   }
   public int max(int[] nums,int left,int right){
       if(left==right)
           return nums[left];
       else{
           int mid=(left+right)/2;
           int LeftSum=max(nums,left,mid);
           int RightSum=max(nums,mid+1,right);
           int MidSum=Mid(nums,left,right,mid);
           return Math.max(LeftSum,Math.max(RightSum,MidSum));
       }
   }
   public int Mid(int []nums,int left,int right,int mid){
       int LSum=0;
       int RSum=0;
       int LeftSum=nums[mid];
       int RightSum=nums[mid+1];
       for(int i=mid;i>=left;i--){
           LSum+=nums[i];
           LeftSum=Math.max(LeftSum,LSum);
       }
       for(int i=mid+1;i<=right;i++){
           RSum+=nums[i];
           RightSum=Math.max(RightSum,RSum);
       }
       return LeftSum+RightSum;
   }
}

多数元素

leetcode169多数元素

class Solution {
    public int majorityElement(int[] nums) {
        return divid(nums,0,nums.length-1);
    }
    //作用:分别求出在某个区间的众数(左边,右边)
    public int divid(int []nums,int left,int right){
        if(left==right){
            return nums[left];
        }
        else{
            int mid=(left+right)/2;
            int Left=divid(nums,left,mid);//左边的众数
            int Right=divid(nums,mid+1,right);//右边的众数
            if(Left==Right)//如果两边的众数都一样,说明这个数就是众数
                return Left;
            int LeftCount=helpAll(nums,left,right,Left);
            int RightCount=helpAll(nums,left,right,Right);
            return LeftCount>RightCount?Left:Right;
        }
    }
    public int helpAll(int nums[],int left,int right,int temp){
        int count=0;
        for(int i=left;i<=right;i++){
            if(temp==nums[i])
                count++;
                System.out.println(nums[i]);
        }
        return count;
    }
}

数组中的第k个最大元素

​ [leetcode215数组中的第k个最大元素](215. 数组中的第K个最大元素)

class Solution {
    public int findKthLargest(int[] nums, int k) {
        //快排,归并排序
        QuickSort(nums,0,nums.length-1,k);
        for(int i=0;i<nums.length;i++){//0 1
            System.out.println(nums[i]);
        }
        return nums[nums.length-k];//直接返回一个第K大的元素
    }
    //用归并排序,先找一个基准,然后比基准大的数放基准的左边
    //比基准小的数放基准的右边
    public void QuickSort(int[] nums,int left,int right,int k){
        if(left<right){
            int mid=Quick(nums,left,right);
        if(mid==nums.length-k)
            return ;
            QuickSort(nums,left,mid-1,k);
            QuickSort(nums,mid+1,right,k);
        }
        else
            return ;
    }
    public int Quick(int[] nums,int left,int right){//找一次基准你
    System.out.printf("左边"+left);
        System.out.println("一开始的"+left);
        // System.out.println("基准元素"+p);
         int p=nums[left];
        while(left<right){
           //
            while(nums[right]>=p&&left<right){//比基准大的话,放在基准的左边
                // System.out.println(left);
                //这里错了
                right--;
            }
             nums[left]=nums[right];
             while(nums[left]<=p&&left<right){//比基准大的话,放在基准的左边
               left++;
                // System.out.println("lll"+right);
            }
            nums[right]=nums[left];
             
        }
        //出来之后,left等于right
       nums[left]=p;
        return left;
    }
}

Pow(x,n)

Pow(x,n)

数值的整数次方

class Solution{
    public double myPow(double x, int n){
        return n>=0?PowCount(x,n):1.0/PowCount(x,-n);
    }
    //这里是算幂的
    public double PowCount(double x,int n){
        if(n==0)
            return 1;
        if(n==1)
            return x;
        else if(n%2==0){
            double y=PowCount(x,n/2);
            return y*y;
        }
        else {
            double y=PowCount(x,n/2);
            return y*y*x;
        }
    }
}

x的平方根

x的平方根

class Solution {
    //分治法练习
    public int mySqrt(int x) {
        if(x==0)
            return 0;
        if(x==1)//为啥还要考虑这种情况?他边界,0,x我还是好乱,救命,二分查找还是好乱啊
            return 1;
       int left=-1;//这里是那个视频的讲解
       int right=x+1;//这里是那个视频的讲解
       while(left+1!=right){
           int mid=(left+right)/2;
            if(mid>x/mid)
                right=mid;//这里是那个视频的讲解
            else if(mid<x/mid)
                left=mid;//这里是那个视频的讲解
            else if(mid==x/mid)
                return mid;
            // System.out.println(mid);
       }
       return left;
    }
}

二叉搜索树的后序遍历

二叉搜索数的后序遍历

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        return verify(postorder,0,postorder.length-1);
    }
    public boolean verify(int []postorder,int left,int right){

        int record=left;
        for(;record<=right;record++){
            if(postorder[record]>postorder[right])
                break;
        }//找到一组左子树的,思考,要是没有呢?
        for(int i)//如果右子树比左子树小,则返回FALSE;
        verify(postorder,left,record-1);
        verify(postorder,record,right-1);
    }
}

求众数

思路

​ 同最大子序和,可以在左边,可能在右边,可能在中间。

但是这样找的方法需要众数比较集中,所以先将众数进行排序。

代码

先排序

/*归并排序*/
package 分治法;
//归并排序
public class MergeSort {
	//
	//先分
	//后合并
	
	//1.先传入一个数组
	public void MergeNums(int[] nums) {
		DividNums(nums, 0, nums.length-1);
	}
	//分,然后递归
	public void DividNums(int[] nums,int left,int right) {
		if(left==right) {
			//分到只有一个元素,这里还没有写,等会认真思考
			return ;
		}
		else {
			//大于等于一个元素
			int mid=(left+right)/2;
			DividNums(nums, left, mid);
			DividNums(nums, mid+1, right);
			Merge(nums,left,mid,right);//这里的关键是归并,前面的重点是中间的求和
		}
	}
	public void Merge(int[] nums,int left,int mid,int right) {
		//将左边的数组和右边的数组合起来
		//创造一个数组装他们拍完序的数组
		int[] MyNew=new int[right-left+1];
		//还要建立两个指针,用来移动位置
		int PointerLeft=left;
		int PointerRight=mid+1;
		int Point=0;
		while(PointerLeft<=mid&&PointerRight<=right) {
			if(nums[PointerLeft]<=nums[PointerRight]) {
				MyNew[Point]=nums[PointerLeft];
				Point++;
				PointerLeft++;
			}
			else {
				MyNew[Point]=nums[PointerRight];
				Point++;
				PointerRight++;
			}	
		}
		while(PointerLeft<mid+1) {
			MyNew[Point]=nums[PointerLeft];
			Point++;
			PointerLeft++;
		}
		while(PointerRight<=right) {
			MyNew[Point]=nums[PointerRight];
			Point++;
			PointerRight++;
		}
		for(int i = 0;i<MyNew.length;i++) {
			nums[left+i]=MyNew[i];//找了很久的bug,原来在这里!!!!!
		}
	}

}

后分治

package 分治法;

public class MyMode {
	int ModeValue;//众数的值
	int ModeCount;//众数的次数
	int Myleft;//因为这两个一直变,所以定义成全局变量
	int Myright;
	//
	public void FindMode(int[] nums) {
		ResultMyNode(nums, 0,nums.length-1);
		System.out.println("众数是"+ModeValue+"重数是"+ModeCount);
	}
    /*先分 ,将问题抽象化,要不众数在左边,要不在右边,要不在中间*/
    /*这里先算众数在中间,这样的话,先分出左边,中间,右边,并且要是左边或者右边的子问题比中间的众数小,那更加不用求了,可以节省时间*/
	public void ResultMyNode(int nums[],int left,int right) {
		split(nums, left, right);//从中间分开两半先
		//算出他的长度
//		int len=Myright-Myleft-1;//这里的重数
		if(ModeCount<=Myleft-left+1) {//说明中间的重数小,开始求左边的重数
			ResultMyNode(nums,left,Myleft);
		}
		if(ModeCount<=nums.length-Myright) {//说明中间的重数小,开始求右边的重数
			ResultMyNode(nums,Myright,nums.length-1);
		}	
	}
	//这里是具体求众数,从中间两边出发,统计众数的个数,并且边界在移动(原来的边界在中间)。当遇到不等的时候,跳出循环,更新左边界和右边界,同时这个左边界作为左边数组的右边界,右边界作为右边数组的左边界。继续求解子问题*/
	public void split(int nums[],int left,int right) {//扩大左边的和扩大右边的
		//前提:有序的数组
		int mid=(left+right)/2;
		int recover;
		for(recover=mid;recover>=left;recover--) {
			if(nums[recover]!=nums[mid])
				break;//找到不等的值,然后跳出来,找到子问题的边界(left,right)
		}
		this.Myleft=recover;
		for(recover=mid+1;recover<=right;recover++) {
			if(nums[recover]!=nums[mid])
				break;
		}
		this.Myright=recover;
		if(Myright-Myleft-1>this.ModeCount) {
			this.ModeCount=Myright-Myleft-1;
			this.ModeValue=nums[mid];
		}
		if(Myright-Myleft-1==this.ModeCount&&nums[mid]>this.ModeValue) {
			
			this.ModeCount=Myright-Myleft-1;
			this.ModeValue=nums[mid];
		}
	}
}

/*测试用例
	1 1 1 2 3(众数在左边)   众数1 重数3
	2 3 1 1 1(众数在右边)	众数1 重数3
	2 1 1 1 3(众数在中间)	众数1 重数3
	2 2 1 1 3(众数有多个,选值大的那一个)	众数2 重数2
*/
问题 与 思考
  1. 如何求众数?

    通过不断维护以下四个变量。

    int ModeValue;//众数的值
    int ModeCount;//众数的次数
    int Myleft;//因为这两个一直变,所以定义成全局变量
    int Myright;
    

    (此过程,不断地分解递归,不断更新这四个值,或者不更新)

    ​ 通过MyLeft和Myright(边界)不断地移动,可以求出某个数的重数。

    ​ 若该数的重数大,更新众数的值。//另外这里有个规则,要是重数一样,谁众数的值大,谁就当众数

    ​ 否则,原众数及重数不变。

    ​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-blnI0r3I-1647878101820)(C:\Users\xie\AppData\Roaming\Typora\typora-user-images\image-20220321130732032.png)]

测试用例
1 1 1 2 3(众数在左边) 众数1 重数3
2 3 1 1 1(众数在右边) 众数1 重数3
2 1 1 1 3(众数在中间) 众数1 重数3
2 2 1 1 3(众数有多个,选值大的那一个) 众数2 重数2
*/


#### 问题 与 思考

1. 如何求众数?

   通过不断维护以下四个变量。

   ```java
   int ModeValue;//众数的值
   int ModeCount;//众数的次数
   int Myleft;//因为这两个一直变,所以定义成全局变量
   int Myright;

(此过程,不断地分解递归,不断更新这四个值,或者不更新)

​ 通过MyLeft和Myright(边界)不断地移动,可以求出某个数的重数。

​ 若该数的重数大,更新众数的值。//另外这里有个规则,要是重数一样,谁众数的值大,谁就当众数

​ 否则,原众数及重数不变。
在这里插入图片描述
以上内容是3-21所总结的学习动态规划内容,不过学校实验的求子序和要下标,迟点再解出这个问题,切记。
不是很完整,继续完善。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
算法导论》Hardcover版的引言(Introduction to Algorithms - Hardcover Edition)是一本经典的计算机科学教材。该版本不仅在内容上与平装版相同,还具有精美的硬皮封面,能够更好地保护书籍,并增添一份高质感和专业感。 首先,这本书是由Thomas H. Cormen等四位作者共同编写。他们是计算机科学领域的权威人物,在算法研究和教育方面具有丰富的经验。这本书的目的是为计算机科学专业的学生和从业人员提供系统而全面的算法知识,帮助他们深入理解和应用算法。 《算法导论》Hardcover版首先介绍了算法设计和分析的基础知识,包括分治法、贪婪算法、动态规划和回溯法等。接着,书中详细阐述了各种经典算法,如排序、图算法、字符串匹配、高级数据结构等。此外,书中还介绍了算法的优化技巧和应用领域,例如算法的并行化和近似算法。 与平装版相比,Hardcover版的封面更加美观,书页由高品质纸张制成,更加耐用。这使得读者在长时间研究和使用这本书时,能够更好地保存它的完整性和精美外观。此外,Hardcover版也更加适合作为礼品或收藏品,体现了读者对该书的重视和对算法学习的热爱。 总之,《算法导论》Hardcover版是一本内容丰富、思想深刻的算法教材,通过系统化的介绍和实例,帮助读者深入理解和应用各种算法。同时,Hardcover版的精美外观和耐用性也增强了读者在日常使用和收藏方面的满意度。无论是学习算法的新手还是资深专家,都能从这本书中获得极大的收益。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冲鸭的猪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值