问题描述
给定一张灰度图,其像素为长度为
n
n
n的灰度值序列:{
p
1
,
p
2
,
⋯
,
p
n
p_1,p_2,\cdots,p_n
p1,p2,⋯,pn},其中
p
i
∈
[
0
:
1
:
255
]
p_i \in [0:1:255]
pi∈[0:1:255]可表示为8位二进制数。现使用一种变位压缩方式对图像进行压缩,具体压缩过程如下:
将{
p
1
,
p
2
,
⋯
,
p
n
p_1,p_2,\cdots,p_n
p1,p2,⋯,pn}分割成为
m
m
m段:
S
1
,
S
2
,
⋯
,
S
m
S_1,S_2,\cdots,S_m
S1,S2,⋯,Sm;
l
[
i
]
l[i]
l[i]为
S
i
S_i
Si段的像素数,要求
l
[
i
]
≤
256
l[i]\leq 256
l[i]≤256;
h
i
h_i
hi为
S
i
S_i
Si段中最大像素灰度值对应的二进制位数,则有
h
i
=
⌈
log
(
max
p
k
∈
s
i
{
p
k
}
+
1
)
⌉
h_i=\left \lceil\log\left(\max\limits_{p_k \in s_i}\{p_k\}+1\right)\right\rceil
hi=⌈log(pk∈simax{pk}+1)⌉
b
[
i
]
b[i]
b[i]为
S
i
S_i
Si段中所有像素的灰度值二进制表示的最小位数,则有
h
i
≤
b
[
i
]
≤
8
h_i \leq b[i] \leq 8
hi≤b[i]≤8
每个分段
S
i
S_i
Si的段头都有11位
b
[
i
]
≤
8
b[i]\leq 8
b[i]≤8的二进制表示:3位
l
[
i
]
≤
256
l[i]\leq 256
l[i]≤256的二进制表示:8位
S
i
S_i
Si段的二进制总位数(占用空间):
11
+
b
[
i
]
×
l
[
i
]
11+b[i]\times l[i]
11+b[i]×l[i];
可以看出,不同的分段方案 T = { S 1 , S 2 , ⋯ , S j } T=\{S_1,S_2,\cdots,S_j\} T={S1,S2,⋯,Sj}导致不同的变位压缩结果,即占用不同大小的总空间。现需要确定空间占用最小的分段方案,即 min T { ∑ i = 1 j ( b [ i ] × l [ i ] + 11 ) } \min\limits_{T}\left\{\sum_{i=1}^{j}(b[i]\times l[i]+11)\right\} Tmin{i=1∑j(b[i]×l[i]+11)}
约束条件
图像压缩问题中约束条件是: l [ i ] ≤ 256 l[i]\leq 256 l[i]≤256,即每个段中的像素数不超过256个。也就是说只要不违背这个约束条件的所有解均是可行解。
目标函数
图像压缩问题是最小化问题,其目标函数是:各个分段占用空间之和,即
∑
i
=
1
j
(
b
[
i
]
×
l
[
i
]
+
11
)
\sum_{i=1}^{j}(b[i]\times l[i]+11)
i=1∑j(b[i]×l[i]+11)
算法设计
子问题边界参数化
在该问题中,我们将问题的左侧边界固定,右侧边界进行参数化,所有子问题可建模为:像素序列 P i = { p 1 , p 2 , ⋯ , p i } , i = 1 , 2 , ⋯ , n P_i=\{p_1,p_2,\cdots,p_i\}, i=1,2,\cdots,n Pi={p1,p2,⋯,pi},i=1,2,⋯,n。
递推方程设计
设
s
[
i
]
s[i]
s[i]是像素序列
P
i
=
{
p
1
,
p
2
,
⋯
,
p
i
}
,
i
=
1
,
2
,
⋯
,
n
P_i=\{p_1,p_2,\cdots,p_i\},i=1,2,\cdots,n
Pi={p1,p2,⋯,pi},i=1,2,⋯,n的最优分段所需存储的位数,则递推关系设计如下:
{
s
[
i
]
=
min
1
≤
j
≤
min
{
i
,
256
}
{
s
[
i
−
j
]
+
j
×
b
m
a
x
(
i
−
j
+
1
,
i
)
+
11
}
s
[
0
]
=
0
\begin{cases} s[i]=\min\limits_{1\leq j \leq \min\{i,256\}}\left\{s[i-j]+j\times b_{max}(i-j+1,i)+11\right\} \\ s[0] = 0 \end{cases}
⎩⎨⎧s[i]=1≤j≤min{i,256}min{s[i−j]+j×bmax(i−j+1,i)+11}s[0]=0
其中,
b
m
a
x
(
i
−
j
+
1
,
i
)
=
⌈
log
(
max
p
k
∈
S
m
p
k
+
1
)
⌉
≤
8
b_{max}(i-j+1,i)=\left\lceil \log \left ( \max\limits_{p_k \in S_m}p_k + 1 \right )\right\rceil \leq 8
bmax(i−j+1,i)=⌈log(pk∈Smmaxpk+1)⌉≤8。
算法的伪代码描述
function Compress(n, p, l, s, b)
lmax ← 256;header ← 11;s[0] ← 0
for i = 1 → n do
b[i] ← length(p[i])
bmax ← b[i]
s[i] ← s[i − 1] + bmax
l[i] ← 1
for j = 2 → min i, lmax do
if bmax < b[i − j + 1] then
bmax ← b[i − j + 1]
if s[i] > s[i − j] + j ∗ bmax then
s[i] ← s[i − j] + j ∗ bmax
l[i] ← j
s[i] ← s[i] + header
return s, b, l
end function
function Traceback(n, i, s, l)
i ← 0
if n == 0 then
return
Traceback(n − l[n], i, s, l)
s[i + +] ← n − l[n]
end function
算法时空效率估计
(1)估计算法Compress的时间复杂度,试给出详细过程。
Compress只需 O ( n ) O(n) O(n),由于在算法中j的次数不超过256次,故对每一个确定的i可在 O ( 1 ) O(1) O(1)时间内完成,因此时间复杂度为 O ( n ) O(n) O(n).
(2)估计算法Traceback的时间复杂度,试给出详细过程。
由于数组 l [ i ] , b [ i ] l[i],b[i] l[i],b[i]记录了最优分段所需的信息,最优分段的最后一段的段长度和像素位数分别存在 l [ n ] , b [ n ] l[n],b[n] l[n],b[n]中,其前一段的段长度和像素位数存储于 l [ n − l [ n ] ] l[n-l[n]] l[n−l[n]]和 b [ n − l [ n ] ] b[n-l[n]] b[n−l[n]]中,依次类推,在 O ( n ) O(n) O(n)时间内构造最优解。
编码实现
#include<iostream>
#include<vector>
using namespace std;
//灰度值二进制位数
int Length(int i){
int k = 1;
i = i/2;
while(i > 0){
k ++;
i = i/2;
}
return k;
}
//迭代备忘录实现动态规划
void Compress(int n,vector<int> &p,vector<int> &s,vector<int> &l,vector<int> &b){
int lmax = 256;
int header = 11;//分段首部
s[0] = 0;
int bmax;
for(int i = 1;i <= n;i ++){
b[i] = Length(p[i]);
bmax = b[i];
s[i] = s[i-1] + bmax;
l[i] = 1;
int k = 0;
if(i > lmax){
k = lmax;
}else{
k = i;
}//k取lmax和 i的较小值
for(int j = 2;j <= k;j ++){
if(bmax < b[i-j+1]){
bmax = b[i-j+1];
}
if(s[i] > s[i-j]+j*bmax){
s[i] = s[i-j]+j*bmax;
l[i] = j;//记录分段
}
}
s[i] = s[i] + header;//加上首部
}
}
//递归追踪解
void Traceback(int n,int& i,vector<int> &s,vector<int> &l){
if(n == 0){
return;
}
Traceback(n-l[n],i,s,l);
s[i++] = n-l[n];//用s表记录分段位置
}
int main(){
int n;
cin >> n;
vector<int> s,b,l,p;
for(int i = 0;i <= n;i ++){
s.push_back(0);
b.push_back(0);
l.push_back(0);
p.push_back(0);
}
for(int i = 1;i <= n;i ++){
int p1;
cin >> p1;
p[i] = p1;
}
Compress(n,p,s,l,b);
cout << "最小存储位数:" << s[n] << endl;
int m = 0;
Traceback(n,m,s,l);
s[m] = n;
cout << "共分段数:" << m << endl;
for(int j = 1;j <= m;j ++){
l[j] = l[s[j]];
b[j] = b[s[j]];
}
for(int j = 1;j <= m;j ++){
cout << "此段个数:" << l[j] << "所需储存位数:" << b[j] << endl;
}
return 0;
}
结果展示
结束语
若结局非你所愿,请在尘埃定前奋力一搏
作者:花城