C语言实现数组最大子段和及最接近0的子段和算法

凌晨两点半
凌晨两点半
发布时间:2026-10-13 | 2人浏览过
原创

在数组处理中,求取连续子数组的最大和或最接近零的和是常见的算法问题。本文将基于C语言,从基础的暴力解法出发,逐步优化至分治法和线性扫描法,分析时间复杂度并展示高效实现。

求最大连续子向量和问题

给定一个包含正负数的整数向量,寻找一个连续子向量,使得其元素之和最大。例如对于向量 {31,-41,59,26,-53,58,97,-93,-23,84},最大和为 187(对应子向量 59, 26, -53, 58, 97)。

第一:原始双重循环法

目的: 直观理解问题,验证后续算法的正确性。
入口: 遍历所有可能的起始和结束索引。
动作: 使用双层循环计算每个子向量的和,并记录最大值。
复杂度: O(n²)。虽然简单,但效率较低。

// 原始暴力解法
int original(int v[], int len) {
    int maxsofar = 0;
    for (int i = 0; i < len; i++) {
        int sum = 0;
        for (int j = i; j < len; j++) {
            sum += v[j];
            if (sum > maxsofar) {
                maxsofar = sum;
            }
        }
    }
    return maxsofar;
}

第二:前缀和优化法

目的: 减少重复计算,提升基础解法的速度。
入口: 预先计算数组的前缀和。
动作: 利用公式 sum(i, j) = prefix[j] - prefix[i-1] 快速得到子向量和,仍需遍历所有区间。
复杂度: O(n²),常数项更小。

// 使用前缀和优化的暴力法
int original_ex(int v[], int len) {
    int *cumarr = malloc(len * sizeof(int));
    cumarr[0] = v[0];
    for (int i = 1; i < len; i++) {
        cumarr[i] = cumarr[i-1] + v[i];
    }
    
    int maxsofar = 0;
    for (int i = 0; i < len; i++) {
        for (int j = i; j < len; j++) {
            int sum = (i == 0) ? cumarr[j] : (cumarr[j] - cumarr[i-1]);
            if (sum > maxsofar) {
                maxsofar = sum;
            }
        }
    }
    free(cumarr);
    return maxsofar;
}

第三:分治算法

目的: 利用递归将问题规模减半,平衡左右与跨越中间三种情况。
入口: 定义数组的左右边界 low 和 high。
动作: 1. 计算左半部分的最大和。 2. 计算右半部分的最大和。 3. 计算跨越中点的最大和(需向左延伸找最大,向右延伸找最大)。 4. 三者取最大值作为当前区间的结果。

完成状态: 递归终止于单元素或空区间,返回累加的最大值。

// 分治法求解最大子段和
int divAndCon(int v[], int low, int high) {
    if (low > high) return 0;
    if (low == high) return (v[low] > 0) ? v[low] : 0;
    
    int mid = (low + high) / 2;
    
    // 求左半部分最大值
    int lmax = divAndCon(v, low, mid);
    // 求右半部分最大值
    int rmax = divAndCon(v, mid + 1, high);
    
    // 求跨越中点的最大值
    int l_sum = 0, left_max = 0;
    for (int i = mid; i >= low; i--) {
        l_sum += v[i];
        if (l_sum > left_max) left_max = l_sum;
    }
    
    int r_sum = 0, right_max = 0;
    for (int i = mid + 1; i <= high; i++) {
        r_sum += v[i];
        if (r_sum > right_max) right_max = r_sum;
    }
    
    int cross_max = left_max + right_max;
    
    // 返回三者最大值
    return (cross_max > lmax) ? ((cross_max > rmax) ? cross_max : rmax) : ((lmax > rmax) ? lmax : rmax);
}

第四:线性扫描法(Kadane's Algorithm)

目的: 以最优的时间复杂度解决问题。
入口: 单次遍历数组。
动作: 维护两个变量:max_ending_here 表示以当前元素结尾的最大子段和,若其为负则重置为0;max_so_far 记录全局最大值。

// 线性扫描法,时间复杂度 O(n)
int scan(int v[], int len) {
    int maxsofar = 0;
    int maxendinghere = 0;
    
    for (int i = 0; i < len; i++) {
        maxendinghere += v[i];
        if (maxendinghere < 0) {
            maxendinghere = 0;
        } else if (maxendinghere > maxsofar) {
            maxsofar = maxendinghere;
        }
    }
    return maxsofar;
}

求最接近0的连续子向量和

该问题要求找到和绝对值最小的连续子向量。对于相同向量,若存在和为4与-4的子向量,绝对值均为4,视为最接近0。

操作顺序:暴力遍历比较

入口: 遍历所有子向量组合。
动作: 计算每个子向量的和,并与当前记录的最小绝对值进行比较,更新极值。
注意: 同样可以使用前缀和+排序的方法优化至 O(n log n),但以下展示基础的 O(n²) 实现以确保逻辑清晰。

#include 

// 辅助函数:返回绝对值更小的数
int closeZero(int x, int y) {
    return (abs(x) < abs(y)) ? x : y;
}

// 求解最接近0的子段和
int findClosestToZero(int v[], int len) {
    int min_abs_val = abs(v[0]); // 初始化为第一个元素的绝对值
    
    for (int i = 0; i < len; i++) {
        int current_sum = 0;
        for (int j = i; j < len; j++) {
            current_sum += v[j];
            if (abs(current_sum) < abs(min_abs_val)) {
                min_abs_val = current_sum;
            }
        }
    }
    return min_abs_val;
}
以上就是C语言求向量和的两则问题解答的详细内容,更多关于C语言数组算法的资料请关注本站其它相关文章!
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表