当前位置:首页 > 比赛题解 > 2023年GESP四级图像压缩详解(洛谷B3851题):从原理到C++实现

2023年GESP四级图像压缩详解(洛谷B3851题):从原理到C++实现

2天前比赛题解57

截图未命名.jpg 2023年GESP四级图像压缩详解(洛谷B3851题):从原理到C++实现 图像压缩 C++ GESP四级 洛谷题解 GESP 第1张

一、算法原理概述

本题目要求实现一个简单的图像压缩算法,核心思想是将256级灰度(00-FF)压缩到16级灰度。通过统计原始像中各个灰度值的出现频率,选取出现频率最高的16个灰度值作为压缩后的灰度表,然后将每个原始灰度值替换为压缩表中距离最近的灰度值对应的索引(0-F)。

二、完整代码解析(含注释)

#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <iomanip>
#include <sstream>
using namespace std;

// 自定义比较函数,用于灰度值排序
// 首先按出现频率降序排列,频率相同的按灰度值升序排列
bool cmp(const pair<int, int>& a, const pair<int, int>& b) {
    return a.second != b.second ? a.second > b.second : a.first < b.first;
}

int main() {
    int n;  // 图像行数
    cin >> n;
    vector<string> original_lines(n);  // 存储原始图像数据
    map<int, int> freq;  // 灰度值频率统计
    
    // 读取并存储原始数据,同时统计频率
    for (int i = 0; i < n; ++i) {
        cin >> original_lines[i];
        // 每两个字符表示一个16进制灰度值
        for (size_t j = 0; j < original_lines[i].length(); j += 2) {
            string hex = original_lines[i].substr(j, 2);
            int gray = stoi(hex, nullptr, 16);  // 转换为10进制灰度值
            freq[gray]++;  // 统计频率
        }
    }

    // 将频率统计结果转为vector便于排序
    vector<pair<int, int>> gray_freq(freq.begin(), freq.end());
    sort(gray_freq.begin(), gray_freq.end(), cmp);  // 按自定义规则排序
    
    // 选取前16个最高频的灰度值
    vector<int> compressed_gray;
    for (int i = 0; i < 16; ++i) {
        compressed_gray.push_back(gray_freq[i].first);
    }
    
    // 输出压缩后的16级灰度表(16进制格式)
    for (int gray : compressed_gray) {
        cout << hex << uppercase << setw(2) << setfill('0') << gray;
    }
    cout << endl;
    
    // 处理每行数据,进行灰度值替换
    for (const auto& line : original_lines) {
        string compressed_line;
        for (size_t j = 0; j < line.length(); j += 2) {
            string hex = line.substr(j, 2);
            int gray = stoi(hex, nullptr, 16);  // 当前灰度值
            int min_dist = 256;  // 最小距离初始化为最大值
            int best_index = 0;  // 最佳匹配索引
            
            // 在压缩表中寻找距离最近的灰度值
            for (int k = 0; k < 16; ++k) {
                int dist = abs(gray - compressed_gray[k]);
                // 距离更小,或距离相同但索引更小
                if (dist < min_dist || (dist == min_dist && k < best_index)) {
                    min_dist = dist;
                    best_index = k;
                }
            }
            
            // 将索引转换为字符:0-9或A-F
            compressed_line += (best_index < 10) ? char('0' + best_index) 
                                               : char('A' + best_index - 10);
        }
        cout << compressed_line << endl;
    }
    
    return 0;
}

三、关键步骤详解

  1. 输入处理

    • 读取图像行数n

    • 逐行读取图像数据,每两个字符表示一个16进制灰度值(00-FF)

    • 统计每个灰度值的出现频率

  2. 灰度表压缩

    • 根据频率统计结果排序,选取出现频率最高的16个灰度值

    • 按频率降序排列,频率相同的按灰度值升序排列

  3. 图像数据压缩

    • 对于每个原始灰度值,在压缩后的16级灰度表中找到距离最近的灰度值

    • "距离"定义为两个灰度值的绝对差

    • 距离相同时选择索引较小的灰度值

    • 用对应的索引(0-F)替换原始灰度值

  4. 输出格式

    • 第一行输出压缩后的16级灰度表(32个16进制字符)

    • 随后n行输出压缩后的图像数据(每个灰度值用1个字符表示)

四、常见问题解答

Q: 为什么需要自定义排序函数? A: 题目要求优先按频率排序,频率相同的按灰度值从小到大排序,标准库的sort函数需要自定义比较规则。

Q: 如何处理距离相同的情况? A: 题目要求距离相同时选择索引较小的灰度值,因此在比较时添加了k < best_index的条件。

Q: 为什么最小距离初始化为256? A: 因为灰度值范围是0-255,所以最大可能距离是255,初始化为256确保第一次比较一定会更新。


原创内容 转载请注明出处

分享给朋友:

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。