CCF编程能力等级认证GESP—C++4级—20230611

单选题(每题 2 分,共 30 分)

1、高级语言编写的程序需要经过以下( )操作,可以生成在计算机上运行的可执行代码。

A. 编辑
B. 保存
C. 调试
D. 编译

正确答案:D
A选项:编辑是编程过程中的第一步,它涉及到使用文本编辑器或集成开发环境(IDE)来输入或修改代码。编辑阶段主要是编写或修改程序代码,使其满足特定的功能需求。然而,编辑后的代码仍然是文本文件,并不能直接在计算机上运行。
B选项:保存是将编辑后的代码文件存储到磁盘上的过程。保存是为了确保代码不会丢失,并且可以在以后继续编辑或执行。然而,保存的代码文件仍然是文本文件,同样不能直接在计算机上运行。
C选项:调试是在代码编译后,通过运行程序并检查其输出和内部状态来发现和修复错误的过程。调试是确保程序按照预期工作的关键步骤。在调试过程中,程序员可能会使用各种工具和技术来逐步执行代码、检查变量值、设置断点等。然而,调试本身并不生成可执行代码,而是帮助改进已有的代码。
D选项:编译是将高级语言代码转换为可执行代码的过程。编译器读取高级语言编写的源代码,并将其转换为计算机能够直接执行的机器代码或字节码。编译后的代码(通常是二进制文件或可执行文件)可以直接在计算机上运行,而不需要进一步的转换或解释。

2、排序算法是稳定的(Stable Sorting),就是指排序算法可以保证,在待排序数据中有两个相等记录的关键字 R 和 S(R 出现在 S 之前),在排序后的列表中R也一定在 S 前。下面关于排序稳定性的描述,正确的是( )。

A. 冒泡排序是不稳定的。
B. 插入排序是不稳定的。
C. 选择排序是不稳定的。
D. 以上都不正确。

正确答案:C
稳定性是指当两个元素具有相同的排序关键字时,它们在排序后的序列中的相对位置保持不变。
A选项:在冒泡排序中,如果两个元素具有相同的排序关键字,并且它们在原始序列中的相对位置是 R 在 S 之前,那么在排序后的序列中,R 仍然会出现在 S 之前。
B选项:在插入排序中,新插入的元素会按照顺序插入到已排序的序列中,因此相同关键字的元素之间的相对位置不会改变。
C选项:在选择排序中,每次从剩余未排序的元素中选择最小(或最大)的元素,并将其放置到已排序序列的末尾。这个过程中,相同关键字的元素之间的相对位置可能会改变。
D选项:这个选项是错误的,因为选择排序是不稳定的,而冒泡排序和插入排序是稳定的。

3、下列关于 C++语言中指针的叙述,不正确的是( )。

A. 指针变量中存储的是内存地址。
B. 定义指针变量时必须指定其指向的类型。
C. 指针变量只能指向基本类型变量,不能指向指针变量。
D. 指针变量指向的内存地址不一定能够合法访问。

正确答案:C
A选项:这是正确的,指针变量本质上是一个变量,但它存储的是另一个变量的内存地址,而不是该变量的值。
B选项:这也是正确的。在 C++ 中,定义指针变量时,需要指定指针所指向的数据类型,例如 int* p; 表示 p 是一个指向 int 类型数据的指针。
C选项:这是不正确的。指针变量不仅可以指向基本类型的变量,还可以指向其他指针变量(即指针的指针)、结构体、类对象等。
D选项:这是正确的。指针变量可以包含任何内存地址,包括未分配的内存、已经释放的内存或只读的内存区域等。尝试访问这些地址可能会导致未定义的行为,如程序崩溃或数据损坏。

#include <iostream>

using namespace std;

int main() {
    int *p;
    cout << *p << endl;
    return 0;
}

4、下列关于 C++语言中数组的叙述,不正确的是( )。

A. 一维数组在内存中一定是连续存放的。
B. 二维数组是一维数组的一维数组。
C. 二维数组中的每个一维数组在内存中都是连续存放的。
D. 二维数组在内存中可以不是连续存放的。

正确答案:D
A选项:这是正确的,一维数组的元素在内存中占据连续的存储空间。
B选项:这也是正确的。从逻辑上看,二维数组可以被视为由多个一维数组组成,每个一维数组都是二维数组的一行或一列。
C选项:这也是正确的。二维数组中的每一行(或每一列,取决于你如何解释二维数组)在内存中都是作为一维数组连续存放的。
D选项:在C++中,二维数组(无论是静态分配的还是动态分配的)通常都是在内存中连续存放的。对于静态分配的二维数组(如int array[3][4];),它的所有元素在内存中都是连续存放的。对于动态分配的二维数组(如使用new),虽然你可能需要分两步来分配内存(首先为每个一维数组分配空间,然后为每个一维数组分配空间),但每个一维数组的内存仍然是连续的,并且整个二维数组的内存可以看作是连续的(或者至少是可以当作连续的来访问的)。

5、下列关于 C++语言中函数的叙述,正确的是( )。

A. 函数必须有名字。
B. 函数必须有参数。
C. 函数必须有返回值。
D. 函数定义必须写在函数调用前。

正确答案:A
A. 选项:在C++中,函数是通过其名字来被调用的,因此每个函数都必须有一个唯一的名字。
B选项:函数可以有参数,也可以没有参数。例如,一个函数可能不需要任何输入,因此它就没有参数。
C选项:在C++中,函数可以有返回值,也可以没有返回值。如果一个函数没有返回值,它的返回类型应该是void
D选项:在C++中,函数可以在被调用之前定义,也可以在被调用之后定义。如果函数在被调用之前没有被定义,那么你需要提供一个函数原型(也称为函数声明),以告诉编译器函数的存在和它的接口。

6、下列关于 C++语言中变量的叙述,正确的是( )。

A. 变量定义后可以一直使用。
B. 两个变量的变量名不能是相同的。
C. 两个变量的变量名可以相同,但它们的类型必须是不同的。
D. 两个变量的变量名可以相同,但它们的作用域必须是不同的。

正确答案:D
A选项:变量的生命周期取决于它的定义位置和作用域。在函数内部定义的局部变量在函数执行结束后就不能再使用了。全局变量或静态变量在程序的生命周期内都可以使用。
B选项:在C++中,两个变量可以有相同的名字,但它们必须处于不同的作用域中。这被称为变量的重名或遮蔽。
C选项:变量的名字可以相同,但它们的类型可以相同也可以不同,这取决于它们所处的上下文和作用域。
D选项:在C++中,如果两个变量有相同的名字,那么它们必须处于不同的作用域中,这样编译器就能根据上下文区分它们。

7、一个二维数组定义为 double array[3][10];,则这个二维数组占用内存的大小为( )。

A. 30
B. 60
C. 120
D. 240

正确答案:D
在C++中,二维数组double array[3][10];定义了一个包含3行10列的数组。每个元素都是double类型,通常double类型占用8字节(64位)。要计算这个二维数组占用的总内存大小,我们需要将单个元素的大小(8字节)乘以数组中的总元素数量。数组有3行和10列,所以总元素数量是3 * 10 = 30。因此,总内存大小是单个元素大小(8字节)乘以总元素数量(30):8字节/元素 * 30元素 = 240字节。所以,这个二维数组double array[3][10];占用的内存大小为240字节,正确答案是D。

8、一个变量定义为 int *p = nullptr;,则下列说法正确的是( )。

A. 该指针变量的类型为 int。
B. 该指针变量指向的类型为 int。
C. 该指针变量指向的内存地址是随机的。
D. 访问该指针变量指向的内存会出现编译错误。

正确答案:B
A选项:这是不正确的。指针变量p的类型是int*,即它是指向int类型数据的指针,而不是int类型本身。
B选项:这是正确的。指针p被声明为指向int类型的指针,所以它所指向的数据类型应该是int。
C选项:这是不正确的。指针p被初始化为nullptr,表示它不指向任何有效的内存地址。在C中,nullptr是一个特殊的指针值,用于表示空指针或无效指针。它不是随机地址,而是一个明确的、表示“无指向”的值。
D选项:这也不完全正确。访问一个空指针(nullptr)通常会导致运行时错误,如程序崩溃,而不是编译错误。编译器通常不会阻止你访问空指针,因为这可能在某些情况下是合法的(例如,在检查指针是否为空之后)。然而,尝试解引用(即访问)一个空指针通常会导致未定义行为,包括程序崩溃。

在C++中,nullptr是一个特殊的字面量,用于表示指针不指向任何有效的内存地址。它是C++11标准中引入的一个新特性,用于替代之前常用的NULL宏或整数字面量0来表示空指针。
使用nullptr的好处之一在于它的类型安全性。nullptr具有专门的类型nullptr_t,可以与其他类型区分开来,这有助于避免一些类型错误。

9、一个二维数组定义为 int array[5][3];,则 array[1][2]和array[2][1]在内存中的位置相差多少字节?( )

A. 2 字节。
B. 4 字节。
C. 8 字节。
D. 无法确定。

正确答案:C
对于一个二维数组int array[5][3];,首先我们需要知道int类型通常占用4个字节。数组array在内存中是按照连续的内存块存储的。array[i][j]的内存位置可以通过以下公式计算:内存位置 = base_address + (i * 列数 + j) * sizeof(int)其中base_address是数组array的起始地址,ij分别是二维数组的行索引和列索引,sizeof(int)int类型的大小(通常是4字节)。
现在,我们来计算array[1][2]array[2][1]在内存中的位置差:
对于array[1][2]内存位置 = base_address + (1 * 3 + 2) * 4
对于array[2][1]内存位置 = base_address + (2 * 3 + 1) * 4
位置差就是两者内存位置的差值:位置差 = ((2 * 3 + 1) - (1 * 3 + 2)) * 4计算这个差值,我们得到:位置差 = (6 + 1 - 3 - 2) * 4 = 2 * 4 = 8 字节因此,array[1][2]array[2][1]在内存中的位置相差8字节。

10、如果 a 为 int 类型的变量,且 a 的值为 6,则执行 a &= 3;之后,a 的值会是( )。

A. 3
B. 9
C. 2
D. 7

正确答案:C

表达式二进制数
a0110
30011
a & 30010

11、一个数组定义为 int a[5] = {1, 2, 3, 4, 5};,一个指针定义为int * p = &a[2];,则执行 a[1] = *p;后,数组 a 中的值会变为( )。

A. {1, 3, 3, 4, 5}
B. {2, 2, 3, 4, 5}
C. {1, 2, 2, 4, 5}
D. {1, 2, 3, 4, 5}

正确答案:A
数组a是一个包含5个整数的数组,初始化为{1, 2, 3, 4, 5}。指针p被初始化为指向数组a的第三个元素,即a[2]的地址。因此,*p的值是3。
接下来,执行语句a[1] = *p;。这个语句将*p(即3)的值赋给a[1]。由于a[1]原本的值是2,执行这条语句后,a[1]的值将被修改为3。
因此,执行这条语句后,数组a中的值将变为{1, 3, 3, 4, 5}。

12、以下哪个函数声明在调用时可以传递二维数组的名字作为参数?( )

A. void BubbleSort(int a[][4]);
B. void BubbleSort(int a[3][]);
C. void BubbleSort(int a[][]);
D. void BubbleSort(int ** a);

正确答案:A
在C++中,二维数组在作为函数参数传递时,其类型会退化为指向其第一维的指针。这意味着,如果你有一个二维数组int arr[m][n],当你将它作为参数传递给一个函数时,它会被视为int (*)[n]类型的指针。
A选项:这个声明接受一个指向具有4个整数的数组的指针。这可以匹配任何具有至少4个整数的二维数组的第一维,因为第二维的大小在函数声明中是不重要的。
B选项:这个声明接受一个指向具有不确定数量整数的数组的指针,但该数组必须有3个整数作为它的第一维。这限制了可以传递给该函数的二维数组的第一维大小必须为3。
C选项:这个声明在C++中是不合法的,因为它没有指定任何维度的大小。你不能在函数参数声明中使用空的方括号。
D选项:这个声明接受一个指向指针的指针,而不是一个二维数组。虽然它可以用来处理二维数组,但它与直接接受二维数组的函数参数在类型上是不匹配的。
因此,只有选项A可以接受二维数组的名字作为参数,因为数组的名字在大多数上下文中会退化为指向其第一维的指针。所以正确答案是A。

13、在下列代码的横线处填写( ),可以使得输出是“20 10”。

#include <iostream>

using namespace std;
void xchg(________){ // 在此处填写代码 
	int t = *x;
	*x = *y;
	*y = t;
}
int main() {
	int a = 10, b = 20;
	xchg(&a, &b);
	cout << a << " " << b << endl;
	return 0;
}
A. int x, int y
B. int * x, int * y
C. int a, int b
D. int & a, int & b

正确答案:B
在C++中,如果你想要交换两个变量的值,并且希望函数能够修改原始变量的值,你需要传递变量的地址(即指针)给函数。这是因为C++中的基本数据类型(如int)是按值传递的,这意味着当你将一个变量传递给一个函数时,函数会接收该变量值的一个副本,而不是原始变量本身。因此,如果你在函数内部修改这个副本,原始变量的值不会改变。为了解决这个问题,你可以传递变量的地址(即指针)给函数。这样,函数就可以通过指针来访问和修改原始变量。
A选项:这将声明两个整数变量,而不是指针。因此,函数将接收这两个变量的副本,而不是它们的地址。
B选项:这是正确的选择,因为你可以通过这两个指针来交换ab的值。
C选项:这同样会声明两个整数变量,而不是指针。
D选项:这将声明两个整数引用。虽然引用也可以用来修改原始变量的值,但在这个特定的例子中,函数期望的是指针,而不是引用。

14、执行以下 C++语言程序后,输出结果是( )。

#include <iostream>

using namespace std;

int main() {
	int array[3][3];
	for (int i = 0; i < 3; i++)
		for (int j = 0; j < 3; j++)
			array[i][j] = i * 10 + j;
	int sum;
	for (int i = 0; i < 3; i++)
		sum += array[i][i];
	cout << sum << endl;
	return 0;
}
A. 3
B. 30
C. 33
D. 无法确定。

正确答案:D
这个C++程序存在一个问题,导致它不能正确运行。问题在于变量sum在使用前没有被初始化。在C++中,局部变量如果没有显式初始化,它们的初始值是未定义的。因此,当执行到sum += array[i][i];这一行时,sum可能包含任何值,这取决于它在内存中的初始状态。
如果程序中将sum初始化为0,程序将正确地计算对角线上的元素之和。对角线上的元素是array[0][0]、array[1][1]和array[2][2],它们的值分别是0、11和22。sum的值将是0 + 11 + 22 = 33。

15、在下列代码的横线处填写( ),完成对有 n 个 int 类型元素的数组array 由小到大排序。

void SelectionSoort(int array[], int n){
	int i, j, min, temp;
	for (i = 0; i < n - 1; i++){
		min = i;
		for (j = i + 1; j < n; j++)
			if (________) // 在此处填写代码
				min = j;
		temp = array[min];
		array[min] = array[i];
		array[i] = temp; 
	}
}
A. array[min] > array[j]
B. array[min] > array[i]
C. min > array[j]
D. min > array[i]

正确答案:A
在选择排序(Selection Sort)算法中,我们需要找到当前未排序部分的最小元素,并将其与当前未排序部分的第一个元素交换。
在给定的代码中,外部循环遍历数组中的每个元素,而内部循环则查找从当前外部循环索引开始到数组末尾的最小元素。为了找到这个最小元素,我们需要比较array[min](目前认为是最小的元素)和array[j](当前正在检查的元素)。
A选项:这是正确的比较。如果array[j]比array[min]小,我们就更新min为j。
B选项:这是不正确的,因为我们已经在外部循环中固定了i,并且我们正在寻找从i开始的最小元素。
C选项:这是不正确的,因为min是一个索引,而array[j]是一个值。我们不能直接比较它们。
D选项:同样,这也是不正确的,原因与C选项相同。

判断题(每题 2 分,共 20 分)

1、域名是由一串用点分隔的名字来标识互联网上一个计算机或计算机组的名称,CCF 编程能力等级认证官方网站的域名是 gesp.ccf.org.cn,其中顶级域名是gesp。

正确答案:错误
在域名gesp.ccf.org.cn中,.cn是顶级域名(Top-Level Domain, TLD),它代表了中国。而gesp是二级域名(Second-Level Domain, SLD)。

2、数列 1, 1, 2, 3, 5, 8 … 是以意大利数学家列昂纳多·斐波那契命名的数列,从第三个数开始,每个数是前面两项之和。如果计算该数列的第n 项(其中n>3)fib(n),我们采用如下方法:① 令 fib(1)=fib(2)=1 ②用循环for i=3 to n 分别计算 f(i) ③输出 fib(n)。这体现了递推的编程思想。

正确答案:正确
斐波那契数列确实是通过递推关系来定义的,即每个数等于前两个数之和。虽然循环体中的函数名f(i)应该是fib(i)以保持一致,但这不影响递推思想的体现。

3、在 C++语言中,函数的参数默认以引用传递方式进行传递。

正确答案:错误
在C++中,函数的参数默认是按值(by value)传递的,而不是按引用(by reference)传递。如果你想要按引用传递参数,你需要在参数类型后添加&符号,例如void func(int& x)。

4、在 C++语言中,可以定义四维数组,但在解决实际问题时不可能用到,因为世界是三维的。

正确答案:错误
虽然在实际问题中四维数组可能不常见,但C++确实支持定义和使用多维数组,包括四维数组。数组的维度与世界的维度没有直接关系,它们只是用于存储和组织数据的结构。

5、在 C++语言中,一个函数没有被调用时,它的参数不占用内存。

正确答案:正确
在C++中,函数的参数只在函数被调用时才被分配内存(在调用栈上)。如果函数没有被调用,那么它的参数就不会被分配内存。函数的参数只是函数定义的一部分,它们本身并不占用内存空间,直到函数被实际调用。

6、在 C++语言中,如果一个函数可能抛出异常,那么一定要在try 子句里调用这个函数。

正确答案:错误
在C++中,如果一个函数可能抛出异常,那么调用它的代码可以选择是否在try块中调用它。将函数调用放在try块中的目的是为了能够捕获并处理可能出现的异常。但是,并不要求必须在try块中调用可能抛出异常的函数。

7、如果希望记录 10 个最长为 99 字节的字符串,可以将字符串数组定义为chars[100][10];。

正确答案:错误
如果你希望记录10个最长为99字节的字符串,你应该将字符串数组定义为char strings[10][100];。这是因为每个字符串需要额外的空间来存储终止字符\0,所以每个字符串的长度应该是99字节加上1字节的终止字符,总共100字节。数组的第一维是10,表示有10个字符串,第二维是100,表示每个字符串的最大长度为100字节。

8、字符常量’0’和’\0’是等价的。

正确答案:错误
字符常量’0’表示数字字符0,它的ASCII值是48。而字符常量’\0’是一个空字符(null character),通常用作字符串的终止符,它的ASCII值是0。它们不是等价的。

9、>=和>>=都是 C++语言的运算符。

正确答案:正确
=是关系运算符,用于比较两个操作数是否大于或等于。>>=是右移赋值运算符,它将左操作数向右移动右操作数指定的位数,并将结果赋值给左操作数。

10、由于文件重定向操作,程序员在使用 C++语言编写程序时无法确定通过cout
输出的内容是否会被输出到屏幕上。

正确答案:正确
在C++中,cout是一个输出流对象,它通常与标准输出(通常是屏幕)相关联。但是,通过文件重定向操作(如在命令行中使用>符号),可以将cout的输出重定向到文件而不是屏幕。因此,程序员在使用cout时不能确保内容一定会被输出到屏幕上,除非他们知道没有发生重定向。

编程题 (每题 25 分,共 50 分)

幸运数

【问题描述】
小明发明了一种“幸运数”。一个正整数,其偶数位不变(个位为第1 位,十位为第 2 位,以此类推),奇数位做如下变换:将数字乘以7,如果不大于9则作为变换结果,否则把结果的各位数相加,如果结果不大于9 则作为变换结果,否则(结果仍大于 9)继续把各位数相加,直到结果不大于9,作为变换结果。变换结束后,把变换结果的各位数相加,如果得到的和是8 的倍数,则称一开始的正整数为幸运数。
例如,16347:第 1 位为 7,乘以 7 结果为 49,大于9,各位数相加为13,仍大于 9,继续各位数相加,最后结果为 4;第 3 位为3,变换结果为3;第5位为 1,变换结果为 7。最后变化结果为 76344,对于结果76344 其各位数之和为 24,是 8 的倍数。因此 16347 是幸运数。
【输入描述】
输入第一行为正整数N,表示有N个待判断的正整数。约定1 ≤N≤20。从第 2 行开始的N行,每行一个正整数,为待判断的正整数。约定这些正整数小于 1 0 12 10^{12} 1012
【输出描述】
输出N行,对应N个正整数是否为幸运数,如是则输出’T’,否则输出’F’。提示:不需要等到所有输入结束在依次输出,可以输入一个数就判断一个数并输出,再输入下一个数。
【样例输入 1】
2
16347
76344
【样例输出 1】
T
F
【样例输入 2】
11 59 59 P
【样例输出 2】
86399

#include <iostream>

using namespace std;

int change(int num){
	int tmp = num * 7;
	// 如果*7后不大于9, 直接作为结果返回 
	if (tmp <= 9){
		return tmp;
	}
	// 不断的数位分离并累加, 直到结果<=9 
	while (tmp > 9){
		int ge, shi;
		ge = tmp % 10;
		shi = tmp / 10 % 10;
		tmp = ge + shi;
	}
	return tmp;
}

bool is_t(string ans){
	int sumn = 0; // 统计变换后所有位数的和
	// 遍历所有的位数, 如果位数在偶数位上, 则直接累加起来
	// 如果位数在奇数位上, 通过change函数进行变换, 再累加起来 
	for (int i = 1; i <= ans.length(); i++){ // 为了方便统计位数的奇偶性 
		int tmp = ans[ans.length() - i] - '0'; // 将当前位上的字符转换为整数类型 
		if (i % 2 == 0){
			sumn += tmp;
		}else{
			sumn += change(tmp);
		}
	}
	// 如果变换后所有位数能被8整除,则返回true, 否则返回false 
	if (sumn % 8 == 0){  
		return true;
	}
	return false;
} 

int main() {
    int n;
    string num;
	cin >> n;
	// 输入 n 个整数 
	for (int i = 1; i <= n; i++){
		cin >> num;
		// 如果是幸运数, 返回T, 否则返回F 
		if (is_t(num)){
			cout << "T\n";
		}else{
			cout  << "F\n";
		}
	}    
    return 0;
}
#include <iostream>

using namespace std;
// 奇数位要做的数字变换
int trans(int t) {
	if (t == 0)
		return 0;
	return (t * 7 - 1) % 9 + 1;
}
// 判断是否为幸运数
bool judge(long long x) {
	int sum = 0;
	for (int d = 1; x > 0; d++, x /= 10) {
		int t = (int)(x % 10);
		if (d % 2 == 0)
			sum += t;
		else
			sum += trans(t);
	}
	return (sum % 8 == 0);
}
int main() {
	int N = 0;
	cin >> N;
	for (int n = 0; n < N; n++) {
		long long x = 0;
		cin >> x;
		if (judge(x))
			cout << "T" << endl;
		else
			cout << "F" << endl;
	}
	return 0;
}

图像压缩

【问题描述】
图像是由很多的像素点组成的。如果用 0 表示黑,255 表示白,0 和255之间的值代表不同程度的灰色,则可以用一个字节表达一个像素(取值范围为十进制 0-255、十六进制 00-FF)。这样的像素组成的图像,称为256 级灰阶的灰度图像。
现在希望将 256 级灰阶的灰度图像压缩为 16 级灰阶,即每个像素的取值范围为十进制 0-15、十六进制 0-F。压缩规则为:统计出每种灰阶的数量,取数量最多的前 16 种灰阶(如某种灰阶的数量与另外一种灰阶的数量相同,则以灰阶值从小到大为序),分别编号 0-F(最多的编号为 0,以此类推)。其他灰阶转换到最近的 16 种灰阶之一,将某个点灰阶数与 16 种灰阶种的一种相减,绝对值最小即为最近,如果绝对值相等,则编号较小的灰阶更近。
【输入描述】
输入第 1 行为一个正整数N,表示接下来有N行数据组成一副256 级灰阶的灰度图像。约定 10 ≤ N ≤ 20。
第 2 行开始的N行,每行为长度相等且为偶数的字符串,每两个字符用十六进制表示一个像素。约定输入的灰度图像至少有 16 种灰阶。约定每行最多20个像素。
【输出描述】
第一行输出压缩选定的 16 种灰阶的十六进制编码,共计32 个字符。第二行开始的N行,输出压缩后的图像,每个像素一位十六进制数表示压缩后的灰阶值。
【样例输入 1】
10
00FFCFAB00FFAC09071B5CCFAB76
00AFCBAB11FFAB09981D34CFAF56
01BFCEAB00FFAC0907F25FCFBA65
10FBCBAB11FFAB09981DF4CFCA67
00FFCBFB00FFAC0907A25CCFFC76
00FFCBAB1CFFCB09FC1AC4CFCF67
01FCCBAB00FFAC0F071A54CFBA65
10EFCBAB11FFAB09981B34CFCF67
01FFCBAB00FFAC0F071054CFAC76
1000CBAB11FFAB0A981B84CFCF66
【样例输出 1】
ABCFFF00CB09AC07101198011B6776FC
321032657CD10E
36409205ACC16D
B41032657FD16D
8F409205ACF14D
324F326570D1FE
3240C245FC411D
BF4032687CD16D
8F409205ACC11D
B240326878D16E
83409205ACE11D
【样例解释 1】
灰阶‘AB’、‘CF’和‘FF’出现 14 次,‘00’出现 10 次,‘CB’出现9 次,‘09’出现7次,‘AC’出现 6 次,‘07’出现 5 次,‘10’、‘11’和‘98’出现4 次,‘01’、‘1B’、‘67’、‘76’ 和‘FC’出现 3 次。

#include <iostream>
#include <cstring>
#include <cmath> // 绝对值函数
using namespace std;
const int maxn = 300;
string num16 = "0123456789ABCDEF";
int n, num[maxn][maxn], Index;

struct Point{
    string shiliu;
    int shi, ci;
}arr[maxn];


void copy_count(int hang, string str){
    // 1. 保存十进制的二维数组
    // 2. 统计每种灰阶值出现的次数
    Index = 1;
    for(int i = 0; str[i] != '\0'; i += 2){
        string tmp = "";
        tmp += str[i];
        tmp += str[i + 1];
        for (int j = 0; j < 256; j++){
            if (tmp == arr[j].shiliu){
                num[hang][Index++] = arr[j].shi;
                arr[j].ci++;
                break;
            }
        }
    }
}

int ans(int num){
    // 根据十进制数 返回 十六进制数的索引(排序后)
    // tmp存储灰阶值的差值, min_index存储当前的索引
    int tmp = 300, min_index = 0;
    for (int i = 0; i < 16; i++){
        if (abs(num - arr[i].shi) < tmp){
            min_index = i;
            tmp = abs(num - arr[i].shi);
        }
    }
    return min_index;
}

int main() {
    // 生成0 - 255 每个像素点的16进制和十进制数
    int struct_index = 0;
    for(int i = 0; i < 16; i++){
        for (int j = 0; j < 16; j++){
            arr[struct_index].shi = struct_index;
            arr[struct_index].shiliu += num16[i];
            arr[struct_index++].shiliu += num16[j];
        }
    }
    cin >> n;
    for (int i = 1; i <= n; i++){
        string tmp;
        cin >> tmp;
        copy_count(i, tmp);
    }
    // 根据要求对灰阶值进行排序, 出现的次数越多, 排序越靠前, 如果出现的次数相同, 则灰阶值大的往后排
    for (int i = 1; i < 256; i++){
        for (int j = 0; j < 256 - i; j++){
            if (arr[j].ci < arr[j + 1].ci){
                swap(arr[j], arr[j + 1]);
            }else if (arr[j].ci == arr[j + 1].ci && arr[j].shi > arr[j + 1].shi){
                swap(arr[j], arr[j + 1]);
            }
        }
    }
    // 输出压缩选定的16种灰阶的十六进制编码,共计32个字符。
    for (int i = 0; i < 16; i++){
        cout << arr[i].shiliu;
    }
    cout << endl;
    
    // 遍历十进制的二维数组, n = 10, Index = 15
    for (int i = 1; i <= n; i++){
        for (int j = 1; j < Index; j++){
            cout << num16[ans(num[i][j])];
        }
        cout << endl;
    }
    return 0;
}
#include <iostream>
#include <cstring>
using namespace std;
int image[20][20];
int cpimg[20][20];
int his[256];
int color[16];
// 一位十六进制字符转换为数字
int trans(char a) {
	if (a <= '9')
		return (a - '0');
	return (a - 'A' + 10);
}
// 十进制数字转换为十六进制数字
char itrans(int n) {
	if (n >= 10)
		return (char)(n - 10 + 'A');
	return (char)(n + '0');
}
// 寻找离 c 最近的灰阶
int compress(int c) {
	int dis = 256, res = -1;
	for (int i = 0; i < 16; i++) {
		int d = c - color[i];
		if (d < 0)
			d = -d;
		if (d < dis) {
			dis = d;
			res = i;
		}
	}
	return res;
}
int main() {
	int N = 0, M = 0;
	cin >> N;
	// 灰阶计数,初始化为-1
	for (int i = 0; i < 256; i++)
		his[i] = -1;
	// 输入图像,并对灰阶计数
	for (int i = 0; i < N; i++) {
		char line[50];
		cin >> line;
		M = strlen(line) / 2;
		for (int j = 0; j < M; j++) {
			int c = trans(line[j * 2]) * 16 + trans(line[j * 2 + 1]);
			image[i][j] = c;
			his[c]++;
		}
	}
	// 选取出现次数最多的 16 个灰阶
	for (int c = 0; c < 16; c++) {
		int max = 0, max_id = -1;
		for (int i = 0; i < 256; i++)
			if (his[i] > max) {
				max = his[i];
				max_id = i;
			}
		color[c] = max_id;
		his[max_id] = -1;
	}
	// 将 image 的灰阶压缩为 cpimg
	for (int i = 0; i < N; i++)
		for (int j = 0; j < M; j++)
			cpimg[i][j] = compress(image[i][j]);
	// 输出选取的 16 个灰阶
	for (int c = 0; c < 16; c++)
		cout << itrans(color[c] / 16) << itrans(color[c] % 16);
	cout << endl;
	// 输出压缩后的图像
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++)
			cout << itrans(cpimg[i][j]);
		cout << endl;
	}
	return 0;
}
  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青岛少儿编程-王老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值