C语言需要掌握的基础知识点之前缀和
C语言需要掌握的基础知识点之前缀和
前缀和(Prefix Sum)是一种重要的数据处理技术,它可以在预处理后快速计算数组任意区间的和。这种技术在算法竞赛和实际开发中都有广泛应用。
前缀和的基本概念
前缀和是一个数组,其中每个元素存储原数组从第一个元素到当前位置所有元素的和。
对于数组 arr[0…n-1],其前缀和数组 prefix 定义为:
prefix[0] = arr[0]prefix[i] = arr[0] + arr[1] + ... + arr[i]
一维前缀和
基本实现
#include <stdio.h>
#include <stdlib.h>// 构建前缀和数组
void buildPrefixSum(int arr[], int n, int prefix[]) {prefix[0] = arr[0];for (int i = 1; i < n; i++) {prefix[i] = prefix[i - 1] + arr[i];}
}// 计算区间 [l, r] 的和
int rangeSum(int prefix[], int l, int r) {if (l == 0) {return prefix[r];}return prefix[r] - prefix[l - 1];
}int main() {int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};int n = sizeof(arr) / sizeof(arr[0]);int *prefix = (int*)malloc(n * sizeof(int));// 构建前缀和数组buildPrefixSum(arr, n, prefix);// 输出原数组printf("原数组: ");for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");// 输出前缀和数组printf("前缀和: ");for (int i = 0; i < n; i++) {printf("%d ", prefix[i]);}printf("\n");// 计算区间和printf("区间 [2, 5] 的和: %d\n", rangeSum(prefix, 2, 5));printf("区间 [0, 3] 的和: %d\n", rangeSum(prefix, 0, 3));printf("区间 [7, 9] 的和: %d\n", rangeSum(prefix, 7, 9));free(prefix);return 0;
}
优化版本(包含边界处理)
#include <stdio.h>
#include <stdlib.h>// 更通用的前缀和实现(从1开始索引,方便处理边界)
int* buildPrefixSumOptimized(int arr[], int n) {// 前缀和数组大小为 n+1,prefix[0] = 0int *prefix = (int*)malloc((n + 1) * sizeof(int));prefix[0] = 0;for (int i = 1; i <= n; i++) {prefix[i] = prefix[i - 1] + arr[i - 1];}return prefix;
}// 计算区间 [l, r] 的和(0-indexed)
int rangeSumOptimized(int prefix[], int l, int r) {// prefix[r+1] - prefix[l] 得到区间 [l, r] 的和return prefix[r + 1] - prefix[l];
}int main() {int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};int n = sizeof(arr) / sizeof(arr[0]);int *prefix = buildPrefixSumOptimized(arr, n);printf("前缀和数组 (包含前导0): ");for (int i = 0; i <= n; i++) {printf("%d ", prefix[i]);}printf("\n");// 测试多个区间和int testCases[][2] = {{0, 2}, {1, 4}, {3, 7}, {0, 9}};int numTests = sizeof(testCases) / sizeof(testCases[0]);for (int i = 0; i < numTests; i++) {int l = testCases[i][0];int r = testCases[i][1];int sum = rangeSumOptimized(prefix, l, r);printf("区间 [%d, %d] 的和: %d\n", l, r, sum);}free(prefix);return 0;
}
二维前缀和
基本概念
对于二维数组,前缀和可以快速计算任意子矩阵的和。
定义:二维前缀和是指从左上角(1,1)累加到(i,j)。
#include <stdio.h>
#include <stdlib.h>// 构建二维前缀和数组
void build2DPrefixSum(int **matrix, int rows, int cols, int **prefix) {// 第一行prefix[0][0] = matrix[0][0];for (int j = 1; j < cols; j++) {prefix[0][j] = prefix[0][j - 1] + matrix[0][j];}// 第一列for (int i = 1; i < rows; i++) {prefix[i][0] = prefix[i - 1][0] + matrix[i][0];}// 其他位置for (int i = 1; i < rows; i++) {for (int j = 1; j < cols; j++) {prefix[i][j] = prefix[i - 1][j] + prefix[i][j - 1] - prefix[i - 1][j - 1] + matrix[i][j];}}
}// 计算子矩阵和 [r1, c1] 到 [r2, c2]
int submatrixSum(int **prefix, int r1, int c1, int r2, int c2) {int total = prefix[r2][c2];int left = (c1 > 0) ? prefix[r2][c1 - 1] : 0;int top = (r1 > 0) ? prefix[r1 - 1][c2] : 0;int corner = (r1 > 0 && c1 > 0) ? prefix[r1 - 1][c1 - 1] : 0;return total - left - top + corner;
}int main() {int rows = 4, cols = 4;// 创建并初始化矩阵int **matrix = (int**)malloc(rows * sizeof(int*));int **prefix = (int**)malloc(rows * sizeof(int*));for (int i = 0; i < rows; i++) {matrix[i] = (int*)malloc(cols * sizeof(int));prefix[i] = (int*)malloc(cols * sizeof(int));}// 填充测试数据int count = 1;for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {matrix[i][j] = count++;}}// 构建前缀和build2DPrefixSum(matrix, rows, cols, prefix);printf("原矩阵:\n");for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {printf("%2d ", matrix[i][j]);}printf("\n");}printf("\n前缀和矩阵:\n");for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {printf("%2d ", prefix[i][j]);}printf("\n");}// 测试子矩阵和printf("\n子矩阵和测试:\n");printf("子矩阵 [1,1] 到 [2,2] 的和: %d\n", submatrixSum(prefix, 1, 1, 2, 2));printf("子矩阵 [0,0] 到 [1,1] 的和: %d\n", submatrixSum(prefix, 0, 0, 1, 1));printf("子矩阵 [0,1] 到 [2,3] 的和: %d\n", submatrixSum(prefix, 0, 1, 2, 3));// 释放内存for (int i = 0; i < rows; i++) {free(matrix[i]);free(prefix[i]);}free(matrix);free(prefix);return 0;
}
前缀和的实际应用
- 统计区间频率
#include <stdio.h>
#include <stdlib.h>// 使用前缀和统计某个值在区间内出现的次数
void frequencyInRange(int arr[], int n, int target) {// 创建频率前缀和数组int *freqPrefix = (int*)malloc((n + 1) * sizeof(int));freqPrefix[0] = 0;for (int i = 1; i <= n; i++) {freqPrefix[i] = freqPrefix[i - 1] + (arr[i - 1] == target ? 1 : 0);}printf("数组: ");for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");printf("数字 %d 在各区间出现的次数:\n", target);int intervals[][2] = {{0, 2}, {1, 4}, {3, 7}, {0, 9}};int numIntervals = sizeof(intervals) / sizeof(intervals[0]);for (int i = 0; i < numIntervals; i++) {int l = intervals[i][0];int r = intervals[i][1];int count = freqPrefix[r + 1] - freqPrefix[l];printf("区间 [%d, %d]: %d 次\n", l, r, count);}free(freqPrefix);
}int main() {int arr[] = {2, 3, 2, 2, 5, 2, 7, 2, 9, 2};int n = sizeof(arr) / sizeof(arr[0]);frequencyInRange(arr, n, 2);return 0;
}
解决子数组和问题
#include <stdio.h>
#include <stdlib.h>// 查找和为k的子数组个数
int subarraySum(int arr[], int n, int k) {// 构建前缀和数组int *prefix = (int*)malloc((n + 1) * sizeof(int));prefix[0] = 0;for (int i = 1; i <= n; i++) {prefix[i] = prefix[i - 1] + arr[i - 1];}int count = 0;// 查找所有满足 prefix[j] - prefix[i] == k 的区间for (int i = 0; i < n; i++) {for (int j = i; j < n; j++) {if (prefix[j + 1] - prefix[i] == k) {count++;printf("找到子数组 [%d, %d]: ", i, j);for (int x = i; x <= j; x++) {printf("%d ", arr[x]);}printf("\n");}}}free(prefix);return count;
}int main() {int arr[] = {1, 1, 1, 2, 2, 3};int n = sizeof(arr) / sizeof(arr[0]);int k = 3;printf("数组: ");for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");int result = subarraySum(arr, n, k);printf("和为 %d 的子数组个数: %d\n", k, result);return 0;
}
性能比较
#include <stdio.h>
#include <time.h>// 传统方法:直接计算区间和
int directRangeSum(int arr[], int l, int r) {int sum = 0;for (int i = l; i <= r; i++) {sum += arr[i];}return sum;
}// 前缀和方法
int prefixRangeSum(int prefix[], int l, int r) {return prefix[r + 1] - prefix[l];
}int main() {const int n = 100000;int *arr = (int*)malloc(n * sizeof(int));int *prefix = (int*)malloc((n + 1) * sizeof(int));// 初始化数组for (int i = 0; i < n; i++) {arr[i] = i + 1;}// 构建前缀和prefix[0] = 0;for (int i = 1; i <= n; i++) {prefix[i] = prefix[i - 1] + arr[i - 1];}// 性能测试clock_t start, end;int queries = 10000;// 测试传统方法start = clock();long long sum1 = 0;for (int i = 0; i < queries; i++) {int l = i % (n - 100);int r = l + 100;sum1 += directRangeSum(arr, l, r);}end = clock();printf("传统方法时间: %f 秒\n", (double)(end - start) / CLOCKS_PER_SEC);// 测试前缀和方法start = clock();long long sum2 = 0;for (int i = 0; i < queries; i++) {int l = i % (n - 100);int r = l + 100;sum2 += prefixRangeSum(prefix, l, r);}end = clock();printf("前缀和方法时间: %f 秒\n", (double)(end - start) / CLOCKS_PER_SEC);printf("结果验证: %s\n", (sum1 == sum2) ? "正确" : "错误");free(arr);free(prefix);return 0;
}