动态规划——图像压缩

动态规划——“深谋远虑,以空间换时间”。动态规划问题是算法学习中最头痛的问题之一,但它却也是求解最优化问题的最佳方法。其关键也是最难理解的莫过于寻找问题的重叠子问题。下面以图像压缩问题为例浅析动态规划算法在图像压缩中的应用,其关键在于如何通过对灰度序列的分段寻找最优存储结构。

问题:在计算机中常用像素点灰度值序列{P1,P2, … ,Pn}表示图像。其中整数Pi,1<=i<=n,表示像素点i的灰度值。通常灰度值的范围是0~255.图像压缩问题要求确定像素序列{P1,P2,…Pn}的最优分段,使得以此分段所需要的存储空间最小,其中0<=Pi<=256,1<=i<=n,每个分段的长度不超过256位。
设计:
l[i] :表示第i个像素段有l[i]个像素点
b[i]:表示第i个像素段的每个像素点占b[i]个位数
Si:表示第i段的像素点集合
t[i]:表示第i段前的所有段的像素点个数之和

l[i]*b[i]+11:表示第i个像素段所需要的存储空间,其中11是因为l[i]需要记录,l[i]的最大值不超过256,所以需要8位来表示l[i],b[i]也需要记录,b[i]最大值为8,所以需要3位来存储b[i]的大小。
(l[1]*b[1]+l[2]*b[2]+…+l[m]*b[m])+11m:表示所有像素点所需要最小位的存储空间
图像的变位压缩存储格式将所给的像素点序列{P1,P2, … ,Pn}分割成m个连续段S1,S2,…Sm,第i个像素段Si中(1<=i<=m),有l[i]个像素,且该段中每个像素都只有b[i]位表示。设t[i]= l[1]+l[2]+…+l[i-1],1<=i<=m,则第i个像素点Si为
Si={Pt[i]+1,…,Pt[i]+l[i]},1<=i<=m
设hi=log(max(Pt[i]+1+1,…Pt[i]+l[i]+1),则hi<=b[i]<=8.因此需要用3位表示b[i],1<=i<=m。如果被限制1<=l[i]<=255,则需要用b位表示l[i],1<=i<=m。因此第i个像素段所需要的存储空间为了l[i]*b[i]+11位,按此格式存储像素序列{P1,P2,…Pn},需要(l[1]*b[1]+l[2]*b[2]+…+l[m]*b[m])+11m位的存储空间。
最优子结构性质
设l[i],b[i],1<=i<=m是{P1,P2,…Pn}的一个最优分段。假设l[1],b[1]不是{P1,…Pl[1]}的一个最优分段,且l[i],b[i],2<=i<=m是{Pl[1]+1,…,Pn}的一个最优分段,则{P1,…Pl[1]}一定存在一个最优分段l[1],b[1]与l[i],b[i], 2<=i<=m形成一个更优l[i],b[i], 1<=i<=m的分段,与已知矛盾,故原假设不成立,l[1],b[1] 是{P1,…Pl[1]}的一个最优分段.
递归计算最优值
设S[i],1<=i<=n是像素序列{P1,P2,…Pi}的最优分段所需要存储的位数,S[i]=S[i-1]+1*log(Pi)+11 或 S[i]=S[i-2]+2*log(max(Pi-1,Pi))+11 或S[i]=S[i-3]=log(max(Pi,Pi-1,Pi-2))+11… 或S[i]=S[0]+i*log(max(Pi,Pi-1…,P1))+11中的最小值,此可归纳为
S[i]=min{S[i-k]+k*bmax(i-k+1,i)}+11 ,其中1<=k<=min{i,256}
bmax(i,j)=log(max{Pk}+1) (i<=k<=j)
这里,S[i]的计算求解可采用k从1开始,一直到i或256其中之一,因为这是在固定的长度段中,从Pi开始,依次进行比较,找到最小的存储位。在计算时,我们可以先求S[1],在求S[2],S[2]中就可以用到S[1],之后求S[3],可以用到S[1],S[2],S[3],依次类推,就满足了动态规划的要用重叠子问题。
构造最优解
由于根据设计的求最小存储位的算法,l[i]和b[i]记录了最优分段所需的信息,假设求P1…Pn的最优分段,因为算法求的最优分段是从Pn,以此向前比较直到P1,找到最小的存储位和最优的断点,然后记录在l[n]中,因此最后一段的段长和像素位数分别存储于l[n]和b[n]中,他的前一段的段长度记录在l[n-l[n]]和b[n-l[n]]中,依次类推,有此可用递归函数来求出最优解的断点位置,之后在求出最优解。
时间复杂度分析:
动态规划是通过n步迭代计算子结构的最优解,在该算法中j的循环次数小于256次,故对每一个确定的i,可在O(1)的时间内完成计算。综上,整个算法所需的计算时间为O(n)。
实现代码:



import java.util.Scanner;

public class Image_compression {
    /**
     * 
     * @param n 图像灰度数组的大小
     * @param p 图像灰度数组
     * @param s s[i]表示从0到i压缩为一共占多少存储空间
     * @param l l代表length
     * @param b b代表bits
     */
    public void Compress(int n, int[] p, int[] s, int[] l, int[] b) {
        int Lmax = 256;
        int header = 11;
        s[0] = 0;
        for (int i = 1; i <= n; i++) {
            b[i] = length(p[i]);// 计算像素点p需要的存储位数
            int bmax = b[i];
            s[i] = s[i - 1] + bmax;
            l[i] = 1;
            for (int j = 2; j <= i && j <= Lmax; 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] += header;
        }
    }

    /*
     * 计算整数的二进制的长度 return 长度;
     */
    public int length(int i) {
        int k = 1;
        i = i / 2;
        while (i > 0) {
            k++;
            i = i / 2;
        }
        return k;
    }

    /*
     * 构造最优解函数1
     */
    public int Traceback(int n, int i, int[] s, int[] l) {
        if (n == 0)
            return i;
        i = Traceback(n - l[n], i, s, l);
        s[i++] = n - l[n];// 重新为s[]数组赋值,用来存储分段位置
        return i;
    }

    /*
     * 构造最优解函数2
     */
    public void Output(int[] s, int[] l, int[] b, int n) {
        // 在输出s[n]存储位数后,s[]数组则被重新赋值,用来存储分段的位置
        System.out.println("图像压缩后的最小空间为: " + s[n]);
        int m = 0;
        m = Traceback(n, m, s, l);
        s[m] = n;
        System.out.println("将原灰度序列分成  " + m + " 段序列段");
        for (int j = 1; j <= m; j++) {
            l[j] = l[s[j]];
            b[j] = Maxb(s, l, b, j);
        }
        for (int j = 1; j <= m; j++) {
            System.out.println("段长度:" + l[j] + ",所需存储位数:" + b[j]);
        }
    }

    /*
     * 计算第j段中bmax
     */
    public int Maxb(int[] s, int[] l, int[] b, int j) {
        int bmax = 0;
        if (j == 1) {
            bmax = b[1];
            for (int i = 2; i <= s[j]; i++) {
                if (bmax < b[i])
                    bmax = b[i];
            }
        } else {
            bmax = b[s[j - 1] + 1];
            for (int i = s[j - 1] + 2; i <= s[j]; i++) {
                if (bmax < b[i]) {
                    bmax = b[i];
                }
            }
        }
        return bmax;
    }
    /**
     * 图片压缩
     */
    public static void image_compressions(Scanner sc)
            throws NumberFormatException {

        String c = sc.next();
        String[] o = c.split(",");
        int[] p = new int[o.length + 1]; // 图像灰度数组 下标从1开始计数
        for (int i = 1; i < p.length; i++) {
            p[i] = Integer.parseInt(o[i - 1]);
        }

        int N = p.length;
        System.out.println();
        // int N=7;
        // int[] p={0,10,12,15,255,1,2};//图像灰度数组 下标从1开始计数
        int[] s = new int[N];
        int[] l = new int[N];
        int[] b = new int[N];
        System.out.println("图像的灰度值序列为:");
        for (int i = 1; i < N; i++) {
            System.out.print(p[i] + " ");
        }
        System.out.println();

        Image_compression ic = new Image_compression();
        ic.Compress(N - 1, p, s, l, b);
        ic.Output(s, l, b, N - 1);
    }

    public static void main(String[] args) {
        boolean flag = true;
        boolean bflag = true;
        Scanner sc = new Scanner(System.in);
        a: while (flag) {
            System.out.println("请输入图像的灰度序列(整数,以逗号为分隔符):");
            try {
                image_compressions(sc);
            } catch (Exception e) {
                System.out.println("输入不符合规范,请重新输入!");
                System.out
                        .println("---------------------------------------------------------------------------------------");
                System.out.println();
                continue;
            }
            System.out.println();
            System.out
                    .println("---------------------------------------------------------------------------------------");
            System.out.println();
            b: while (bflag) {
                System.out.println("选择接下来的操作: 1--继续   2--退出");
                int t = sc.nextInt();
                switch (t) {
                case 1:
                    System.out
                            .println("---------------------------------------------------------------------------------------");
                    System.out.println();
                    break b;
                case 2:
                    System.out.println("已退出!");
                    break a;
                default:
                    System.out.println("没有可执行的操作,请重新输入!");
                    System.out
                            .println("---------------------------------------------------------------------------------------");
                    System.out.println();
                    break;
                }
            }
        }
    }
}

你可能感兴趣的:(算法学习笔记)