面试题答案
一键面试优化方案
- 使用指针遍历:通过指针直接访问内存地址,减少数组索引计算带来的开销。
- 按内存连续性访问:确保访问顺序与内存布局一致,提高缓存命中率。对于二维图像,最好按行优先顺序访问。
代码示例
#include <iostream>
#include <chrono>
// 常规数组索引方式实现图像卷积
void convolutionWithArrayIndex(const int* image, int width, int height, const int* kernel, int kernelSize, int* result) {
int halfKernel = kernelSize / 2;
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int sum = 0;
for (int ky = 0; ky < kernelSize; ++ky) {
for (int kx = 0; kx < kernelSize; ++kx) {
int imgY = y + ky - halfKernel;
int imgX = x + kx - halfKernel;
if (imgY >= 0 && imgY < height && imgX >= 0 && imgX < width) {
sum += image[imgY * width + imgX] * kernel[ky * kernelSize + kx];
}
}
}
result[y * width + x] = sum;
}
}
}
// 使用指针优化的图像卷积
void convolutionWithPointers(const int* image, int width, int height, const int* kernel, int kernelSize, int* result) {
int halfKernel = kernelSize / 2;
const int* imgPtr;
const int* kerPtr;
int* resPtr;
for (int y = 0; y < height; ++y) {
imgPtr = image + y * width;
resPtr = result + y * width;
for (int x = 0; x < width; ++x) {
int sum = 0;
for (int ky = 0; ky < kernelSize; ++ky) {
kerPtr = kernel + ky * kernelSize;
for (int kx = 0; kx < kernelSize; ++kx) {
int imgY = y + ky - halfKernel;
int imgX = x + kx - halfKernel;
if (imgY >= 0 && imgY < height && imgX >= 0 && imgX < width) {
sum += *(imgPtr + imgY * width + imgX) * *(kerPtr + kx);
}
}
}
*resPtr++ = sum;
}
}
}
复杂度分析
- 时间复杂度:
- 优化前:
- 对于每个像素,需要遍历内核的所有元素。假设图像大小为 $M \times N$,内核大小为 $K \times K$。时间复杂度为 $O(M \times N \times K \times K)$。因为对于图像中的 $M \times N$ 个像素,每个像素都要进行 $K \times K$ 次乘法和加法操作。
- 优化后:
- 同样是对每个像素遍历内核所有元素,时间复杂度仍然是 $O(M \times N \times K \times K)$。但是由于减少了数组索引计算的开销,实际运行速度会更快。
- 优化前:
- 空间复杂度:
- 优化前:
- 除了输入的图像和内核,需要额外的空间存储结果图像。假设图像和结果图像每个像素占用相同空间,空间复杂度为 $O(M \times N)$(不考虑常数系数),因为结果图像大小为 $M \times N$。
- 优化后:
- 空间复杂度与优化前相同,为 $O(M \times N)$。指针操作本身不增加额外的空间复杂度,只是改变了访问内存的方式。
- 优化前:
测试代码
int main() {
int width = 100;
int height = 100;
int kernelSize = 3;
int* image = new int[width * height];
int* kernel = new int[kernelSize * kernelSize];
int* resultArrayIndex = new int[width * height];
int* resultPointers = new int[width * height];
// 初始化图像和内核数据
for (int i = 0; i < width * height; ++i) {
image[i] = i % 256;
}
for (int i = 0; i < kernelSize * kernelSize; ++i) {
kernel[i] = 1;
}
auto startArrayIndex = std::chrono::high_resolution_clock::now();
convolutionWithArrayIndex(image, width, height, kernel, kernelSize, resultArrayIndex);
auto endArrayIndex = std::chrono::high_resolution_clock::now();
auto durationArrayIndex = std::chrono::duration_cast<std::chrono::microseconds>(endArrayIndex - startArrayIndex).count();
auto startPointers = std::chrono::high_resolution_clock::now();
convolutionWithPointers(image, width, height, kernel, kernelSize, resultPointers);
auto endPointers = std::chrono::high_resolution_clock::now();
auto durationPointers = std::chrono::duration_cast<std::chrono::microseconds>(endPointers - startPointers).count();
std::cout << "Convolution with array index took " << durationArrayIndex << " microseconds." << std::endl;
std::cout << "Convolution with pointers took " << durationPointers << " microseconds." << std::endl;
delete[] image;
delete[] kernel;
delete[] resultArrayIndex;
delete[] resultPointers;
return 0;
}
在实际测试中,指针优化版本通常会比数组索引版本运行得更快,主要原因是减少了数组索引计算开销以及更好地利用了缓存。