分治递归法求数组最大值、数组二分查找

本文介绍使用递归分治法在数组中寻找最大值的算法实现,通过比较左右子数组的最大值来确定全局最大值。同时,文章也详细解释了二分查找法的基本原理及代码实现,适用于有序数组中特定元素的查找。

问题如下:

在这里插入图片描述采用递归分治法,类似于二分查找,找左边最大值和右边最大值,然后比较,返回比较大的那一个。递归的出口是当左下标>=右下标时,返回当前元素。
例:if(i>=j) return a[i];
不多说了,思路比较简单,直接堆代码

#include <iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
//分治法求一维数组的最大值问题
int GetMax(int a[],int left,int right)
{
    //只有一个元素的时候,其就是最大值,递归出口
    if(left>=right) return a[left];
    //递归体
    else
    {
        int middle = (right+left)/2;
        //递归找左边部分数组的最大值
        int left_max = GetMax(a,left,middle);
        //递归找右边部分数组的最大值,从middlej+1开始
        int right_max = GetMax(a,middle+1,right);
        if(left_max>right_max)  return left_max;
        else return right_max;
    }
}
int GetMax2(int *a,int lt,int rg)
{
    if(lt>=rg) return a[lt];
    else{
        int mid=(lt+rg)/2;
        if(GetMax2(a,lt,mid)>GetMax2(a,mid+1,rg))  
            return GetMax2(a,lt,mid);
        else 
            return GetMax(a,mid+1,rg);
    }
}
int main()
{
    int n;
    cin>>n;
    int a[n];
    for(int i=0;i<n;i++)
        cin>>a[i];
    cout<<GetMax2(a,0,n-1)<<endl;
    return 0;
}

在这里插入图片描述

二分查找

二分查找法(折半查找法):查找数组中是否包含指定元素。如果包含指定元素,则返回指定元素的index(从0开始);如果不包含指定元素,则返回-1;
前提:数组中的元素必须是有序的。且可以通过下标索引。
将被查找的数组分为三部分,依次是中值前、中值、中值后,将指定元素和数组的中值进行比较,如果指定元素小于中值则在(中值前)中找,如果指定元素大于中值则在(中值后)中找,如果指定元素等于中值则直接返回。依次查找后,如果不包含指定元素,则返回-1;
注:中值即数组中间位置的值。

#include <iostream>
using namespace std;
int a[101];
int BinarySearch(int *a,int key,int left,int right)
{
    //递归出口
    if(left>right) return -1;//未找到
    
    int mid=(left+right)/2;
    if(a[mid]==key) return mid;//找到了,返回当前下标
    else if(left<=right){//left<=right不断调用递归
        if(key>a[mid])  BinarySearch(a,key,mid+1,right);
        else BinarySearch(a,key,left,mid);
    }

}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    int key;
    cin>>key;
    cout << BinarySearch(a,key,1,n);
    return 0;
}

在这里插入图片描述

<think> 我们参考引用[4]中的代码,它展示了一个分治递归算法来统计数组中特定元素x出现的次数。 基本思路: 1. 将数组分成两部分,分别统计左半部分和右半部分中x出现的次数。 2. 递归终止条件:当子数组只有一个元素时,如果该元素等于x,则返回1,否则返回0。 3. 递归分解:将数组不断二分,直到子数组长度为1,然后合并结果(将左右两部分的计数相加)。 但是,我们也可以考虑另一种分治:不一定要二分到单个元素,也可以二分到较小的区间,但引用[4]中采用的是二分到单个元素。 我们按照引用[4]的代码思路来写一个分治递归函数。 注意:分治递归算法的核心在于递归方程,即:问题分解为两个子问题,然后合并子问题的解。 具体步骤: 函数定义:Countx(arr, left, right, x) - 输入数组arr,查找区间[left, right],目标值x - 输出:x在arr的[left, right]区间中出现的次数 递归终止条件: - 当left == right,即区间只有一个元素时,判断这个元素是否等于x,等于则返回1,否则返回0。 递归过程: - 计算中点 mid = (left + right) / 2 - 分别递归计算左半区间和右半区间: leftCount = Countx(arr, left, mid, x) rightCount = Countx(arr, mid+1, right, x) - 返回 leftCount + rightCount 但是,注意引用[4]中递归终止条件处理了一个元素的情况,然后递归分解时,将数组分成 [left, mid] 和 [mid+1, right] 两部分。 然而,我们还需要考虑另一种情况:当区间为空?实际上,我们的递归终止条件已经避免了空区间(因为当left==right时,区间有一个元素;而递归调用时,当区间被分解,mid+1可能会大于right,但我们的递归终止条件会处理left>right吗?) 在引用[4]的代码中,主函数调用时传递的区间是[0, m-1],所以区间是有效的。在递归过程中,当区间长度为1时,停止分解。当区间长度为2时,分解为两个长度为1的区间,然后合并。所以不会出现left>right的情况。 但是,为了确保安全,我们可以在递归开始时检查区间是否有效(left<=right),如果无效则返回0。不过引用[4]的代码没有做这个检查,因为它的递归调用保证了不会出现left>right的情况(因为mid>=left,且mid+1<=right?实际上,当区间长度为1时,mid=left=right,然后递归调用右半部分为(mid+1, right)即(right+1, right)此时left>right,所以我们需要避免这种情况)。 所以,在递归调用右半部分时,如果mid+1>right,那么就不需要递归右半部分了。在引用[4]的代码中,它直接递归调用了,但因为在递归终止条件中,当p==r时返回,而递归调用右半部分时,如果mid+1>right(即q+1>r),那么就会进入else分支,然后再次递归,但此时递归调用Countx(arr, q+1, r, x)时,由于p>r,所以我们需要在函数开始处处理这种情况。 修改:在函数开始处,判断如果left>right,则返回0。 因此,我们可以这样写: 伪代码: function Countx(arr, left, right, x): if left > right: return 0 if left == right: if arr[left] == x: return 1 else: return 0 mid = (left + right) // 2 leftCount = Countx(arr, left, mid, x) rightCount = Countx(arr, mid+1, right, x) return leftCount + rightCount 但是,这样写效率并不高,因为我们需要递归到每个元素。时间复杂度为O(n),因为每个元素都被访问一次(虽然递归深度为O(log n),但总节点数大约是2n-1,所以时间复杂度O(n))。这种分治算法实际上和遍历一遍数组统计没有本质的效率提升,但这里主要是为了演示分治思想。 我们可以参考引用[4]的代码,它没有检查left>right,因为它的递归分解方式保证了不会出现left>right吗?我们看: if (p==r) { ... } else { q=(p+r)/2; return Countx(arr,p,q,x) + Countx(arr,q+1,r,x); } 当p==r时,不会进入else,所以不会进一步递归。 当p<r时,分解为[p, q]和[q+1, r]。 注意,当p<r时,q=(p+r)/2(向下取整)一定满足 p<=q<r 吗?不一定,因为当p和r相邻时,q=p,然后分解为[p,p]和[p+1,r](此时r=p+1),所以不会出现无效区间。 但是,当p==r时,区间只有一个元素,直接返回。 当p>r时,这个情况在初始调用时不会出现,而且递归调用中,我们分解的区间是[p, q]和[q+1, r],其中q+1>=p(因为q>=p)且q+1<=r+1,但由于q=(p+r)/2,且p<=r,所以q+1<=r+1,而我们的递归终止条件在p==r时就会返回,所以不会出现p>r的情况。 因此,我们不需要检查left>right,因为按照这个分解,区间总是非空且有效的,直到区间长度为1。 但是,在递归调用中,当区间长度为0(即left>right)时,我们应该返回0。而引用[4]的代码中没有处理这种情况,但在实际调用中,由于分解方式,当区间长度为1时,我们不再分解,所以不会出现长度为0的区间。所以我们可以省略这个检查。 所以,按照引用[4]的代码,我们可以这样实现: 我们使用C语言实现(因为引用[4]是C语言): 但是用户没有指定语言,这里为了通用性,我们用伪代码描述,并解释思路。 算法步骤: 1. 如果当前区间只有一个元素(left==right),则检查该元素是否等于x,等于则返回1,否则返回0。 2. 否则,将区间二分,分别统计左右两个子区间中x的个数,然后相加。 3. 递归调用。 注意:这个算法会遍历数组中的每一个元素,所以时间复杂度是O(n),与直接遍历相同。但分治的方在这里主要是为了练习分治思想,并且在某些并行计算中,可以并行统计两个子区间。 下面给出C语言代码(参考引用[4]): ```c #include <stdio.h> int Countx(int arr[], int left, int right, int x) { if (left == right) { // 当前区间只有一个元素 if (arr[left] == x) return 1; else return 0; } else { int mid = (left + right) / 2; int leftCount = Countx(arr, left, mid, x); int rightCount = Countx(arr, mid+1, right, x); return leftCount + rightCount; } } int main() { int arr[] = {1, 2, 3, 4, 5, 5, 1, 3, 5, 8}; int x; int n = sizeof(arr) / sizeof(arr[0]); printf("请输入要查找的数: "); scanf("%d", &x); int count = Countx(arr, 0, n-1, x); if (count == 0) printf("%d 在数组中未出现\n", x); else printf("%d 在数组中出现了 %d 次\n", x, count); return 0; } ``` 但是,上面的代码在递归过程中,当区间长度大于1时,我们将其分为两个子区间,然后分别递归。这个分治并没有减少比较次数,因为每个元素还是要被访问一次。但是,分治的思想为更复杂的问题(如并行计算)提供了基础。 另外,我们也可以考虑另一种分治:将数组分成多个子数组(比如多个线程处理),然后合并结果。但这里只是单线程递归。 总结:使用分治递归算法数组中特定元素x的个数,就是将数组不断二分,直到子数组长度为1,然后判断并返回,最后将左右子数组的结果相加。 注意:这个算法虽然用了分治,但时间复杂度和遍历一样,都是O(n)。空间复杂度为递归栈的深度O(log n)。 如果数组非常大,递归深度可能会很大,可能导致栈溢出。这时可以考虑迭代(循环遍历)或者使用尾递归优化(但这里不是尾递归)。所以实际应用中,如果数组很大,直接遍历可能是更好的选择。 但是,作为分治思想的练习,这个算法是合适的。 相关问题: §§相关问题§§ 1. 分治算法的时间复杂度如何分析? 2. 除了统计特定元素个数,分治算法还能解决哪些数组问题? 3. 如何优化这个分治递归算法以避免栈溢出? 4. 分治算法和动态规划有什么不同? </think> ### 分治递归算法数组中特定元素出现次数 分治算法通过“分而治之”的思想解决数组统计问题,核心步骤是将数组递归分解为更小的子数组,分别统计结果后合并。以下是特定元素 $x$ 出现次数的完整实现: #### 算法步骤 1. **分解**:将数组平均分成左右两个子数组 2. **递归求解**:分别在左右子数组中统计 $x$ 的出现次数 3. **合并结果**:将左右子数组的结果相加 4. **终止条件**:当子数组长度为1时直接判断元素是否等于 $x$ #### C语言实现 ```c #include <stdio.h> // 分治递归统计函数 int countOccurrences(int arr[], int left, int right, int x) { // 终止条件:单个元素 if (left == right) { return arr[left] == x ? 1 : 0; } // 分解数组 int mid = (left + right) / 2; // 递归求解子问题 int leftCount = countOccurrences(arr, left, mid, x); int rightCount = countOccurrences(arr, mid + 1, right, x); // 合并结果 return leftCount + rightCount; } int main() { int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 1, 3}; int n = sizeof(arr) / sizeof(arr[0]); int target = 1; // 要查找的元素 int result = countOccurrences(arr, 0, n - 1, target); printf("元素 %d 出现次数: %d\n", target, result); return 0; } ``` #### 算法分析 - **时间复杂度**:$O(n)$ 每个元素仅被访问一次,递归树深度为 $\log n$,但每层处理 $n$ 个元素 - **空间复杂度**:$O(\log n)$ 递归调用栈的最大深度 - **分治有效性**:虽与遍历效率相同,但为理解分治思想提供了清晰案例[^4] #### 示例说明 对于数组 `[3, 1, 4, 1, 5]` 查找 `1`: 1. 分解为 `[3,1]` 和 `[4,1,5]` 2. 左子数组分解为 `[3]→0` 和 `[1]→1` 3. 右子数组分解为 `[4]→0` 和 `[1,5]→([1]→1 + [5]→0)=1` 4. 合并结果:$0+1+0+1=2$
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值