《算法》1.5部分题解

1.5.1 使用quick-find算法处理序列9-0 3-4 5-8 7-2 2-1 5-7 0-3 4-2。对于输入的每一对整数,给出id[]数组的内容和访问数组的次数。

import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;

public class quick_find_UF {
    private int[] id;   //触点
    private int count;   //连通分量
    private int visitnum;   //数组访问次数
    public quick_find_UF(int n) {
        visitnum = 0;
        count = n;
        id = new int[n];
        for (int i = 0; i < n; i++) {
            id[i] = i;
            visitnum++;
        }
    }

    public int getCount() {
        return count;
    }

    public int getVisitnum() {
        return visitnum;
    }

    public int find(int p) {
        visitnum++;
        return id[p];
    }

    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }

    public void union(int p, int q) {
        int a = find(p);
        int b = find(q);
        if(a == b)
            return;
        for(int i = 0; i < id.length; i++, visitnum++) {
            if(id[i] == a)
                id[i] = b;
        }
        count--;
        System.out.println(p + "->" + q);
    }

    public static void main(String[] args) {
        int n = StdIn.readInt();
        int m = StdIn.readInt();

        quick_find_UF uf = new quick_find_UF(n);
        while (m-- > 0) {
            int p = StdIn.readInt();
            int q = StdIn.readInt();
            if(uf.connected(p, q))
                continue;
            uf.union(p, q);
        }
        StdOut.println("共有" + uf.getCount() + "个连通分量");
        StdOut.println("共访问了" + uf.getVisitnum() + "次数组");
    }
}

在这里插入图片描述
1.5.2 使用quick-union算法完成1.5.1。

public int find(int p) {
        while (p != id[p]) {
            p = id[p];
            visitnum += 2;
        }
        visitnum++;
        return p;
}

public void union(int p, int q) {
        int a = find(p);
        int b = find(q);
        if(a == b)
            return;
        id[a] = b;
        visitnum++;
        count--;
        System.out.println(p + "->" + q);
    }

在这里插入图片描述
1.5.3 使用加权quick-union算法完成1.5.1。

import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;

public class WeightedQuickUnionUF {
    private int[] id;
    private int[] weight;   //树的权重
    private int count;
    private int visitnum;

    public WeightedQuickUnionUF(int n) {
        count = n;
        visitnum = 0;
        id = new int[n];
        weight = new int[n];
        for (int i = 0; i < n; i++) {
            id[i] = i;
            weight[i] = 1;
            visitnum++;
        }
    }

    public int getCount() {
        return count;
    }

    public int getVisitnum() {
        return visitnum;
    }

    public int find(int p) {
        while (p != id[p]) {
            p = id[p];
            visitnum += 2;
        }
        visitnum++;
        return p;
    }

    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }

    public void union(int p, int q) {
        int a = find(p);
        int b = find(q);
        if(a == b)
            return;
        if(weight[a] < weight[b]) {
            id[a] = b;
            visitnum++;
            weight[b] += weight[a];
        }
        else {
            id[b] = a;
            visitnum++;
            weight[a] += weight[b];
        }
        count--;
        System.out.println(p + "->" + q);
    }

    public static void main(String[] args) {
        int n = StdIn.readInt();
        int m = StdIn.readInt();
        WeightedQuickUnionUF uf = new WeightedQuickUnionUF(n);
        while (m-- > 0) {
            int p = StdIn.readInt();
            int q = StdIn.readInt();
            if(uf.connected(p, q))
                continue;
            uf.union(p, q);
        }
        StdOut.println("共有" + uf.getCount() + "个连通分量");
        StdOut.println("共访问了" + uf.getVisitnum() + "次数组");
    }
}

在这里插入图片描述
1.5.4 在正文的加权quick-union算法示例中,对于输入的每一对整数(包括对照输入和最坏情况下的输入),给出id[]和weight[]数组的内容以及访问数组的次数。
对照:
在这里插入图片描述
最坏:
在这里插入图片描述
1.5.8 用一个反例证明quick-find算法中的union()方法的以下直观实现是错误的:

public void union(int p, int q) {
        if(connected(p, q))
            return;
        for(int i = 0; i < id.length; i++) {
            if(id[i] == id[p])
                id[i] = id[q];
        }
        count--;
}

原来的实现是用一个变量存放id[p]的值,让符合条件的id[i]改成变量的值,这里的问题在于id[p]有可能发生改变从而使得后面的过程错误。
当i == p时,符合id[i] == id[p]的条件,id[p]的值被改为id[q]的值,但若是在p之后还有属于这个分量的触点,那么由于id[p]的改变使得后面的点无法被修改。
如下面的id[]情况:
3 3 3 3 7 7 7 7
连接0 5,p == 0, q == 5
id[p] == 3
i == 0时,修改为
7 3 3 3 7 7 7 7
此时id[p]已经变成7了。
i == 1时,id[i] != id[p],不改变。然而它本来应该改变的。
1.5.9 画出下面的id[]数组对应的树。这可能是加权quick-union算法得到的结果吗?解释为什么不可能,或者给出能够得到该数组的一系列操作。
i   0 1 2 3 4 5 6 7 8 9
id[i]  1 1 3 1 5 6 1 3 4 5
在这里插入图片描述
这是不可能的。由书上定理可知树的高度不会超过lgN。这里的高度为4 > lg10。

1.5.10 在加权quick-union算法中,假设我们将id[find§]的值设为q而非id[find(q)],所得的算法是正确的吗?
是的,但是会增加树的高度,无法保证性能。

1.5.11 实现加权quick-find算法,其中我们总是将较小的分量重命名为较大的分量的标识符。这种改变会对性能产生怎样的影响?

public void union(int p, int q) {
        int a = find(p);
        int b = find(q);
        if(a == b)
            return;
        if(weight[a] < weight[b]) {
            for (int i = 0; i < id.length; i++, visitnum++) {
                if (id[i] == a) {
                    id[i] = b;
                    weight[b] += weight[i];
                }
            }
        }
        else {
            for (int i = 0; i < id.length; i++, visitnum++) {
                if (id[i] == b) {
                    id[i] = a;
                    weight[a] += weight[i];
                }
            }
        }
        count--;
        System.out.println(p + "->" + q);
}

把数量少的分量改成数量多的分量可以减少赋值操作,但总体上提升的性能有限,因为无论如何,都要遍历id[],并不会减少循环次数。

1.5.12 使用路径压缩的quick-union算法。

public int find(int p) {
        int a = p;
        while (p != id[p]) 
            p = id[p];
        while (a != id[a]) {
            int b = id[a];
            id[a] = p;
            a = b;
        }
        return p;
}

改进之后,quick-union算法也可以在短时间内处理100万个触点和200万条链接了。
在这里插入图片描述
1.5.13 使用路径压缩的加权quick-union算法。
同上作修改。
对同一测试数据的访问数组次数大约为1.5.12的一半。

1.5.14 根据高度加权的quick-union算法。
把weight[]换成height[],除了union()其它函数不变。
把一棵高度低的树链接到高度高的树上,高度不变,只有两棵相同高度的树链接时,才会使被链接的树高度+1。

public void union(int p, int q) {
        int a = find(p);
        int b = find(q);
        if(a == b)
            return;
        if(height[a] < height[b]) 
            id[a] = b;
        else {
            id[b] = a;
            if(height[a] == height[b])
                height[a]++;
        }
        count--;
        System.out.println(p + "--" + q);
 }

在这里插入图片描述
1.5.15 二项树。请证明,对于加权quick-union算法,在最坏情况下树中每一层的节点数均为二项式系数。在这种情况下,计算含有 N = 2 n N=2^n N=2n个节点的树中节点的平均深度。
在这里插入图片描述

倒数第二行的树,节点数自上而下为1 2 1,倒数第一行的树,节点数为1 3 3 1,这确实符合二项式系数,下面来证明。
在最坏情况下,每次合并的都是节点树相同的树,所以每次合并都会使树的深度增加。可以观察到,合并后的新树,第i层的节点树 k i ′ k'_i ki由合并时的主树第i层的节点数 k i k_i ki加上副树第i-1层的节点数 k i − 1 k_{i-1} ki1得到。
0 1 2 1
1 2 1 0
1 3 3 1
杨辉三角就是这样构造的。
由二项式定理, ( x + y ) n = ∑ i = 0 n C n i x n − i y i (x+y)^n = \sum_{i=0}^{n}C_n^ix^{n-i}y^i (x+y)n=i=0nCnixniyi,当x和y取1时,就得到了如下公式:
2 n = ∑ i = 0 n C n i 2^n=\sum_{i=0}^{n}C_n^i 2n=i=0nCni
所有节点的总的深度和为:
∑ i = 0 n i ⋅ C n i = n ∑ i = 1 n C n − 1 i − 1 = n ⋅ 2 n − 1 \sum_{i=0}^{n}i\cdot C_n^i=n\sum_{i=1}^{n}C_{n-1}^{i-1}=n\cdot 2^{n-1} i=0niCni=ni=1nCn1i1=n2n1
平均深度为 n ⋅ 2 n − 1 2 n = n 2 \frac{n\cdot 2^{n-1}}{2^n}=\frac{n}{2} 2nn2n1=2n

1.5.16 均摊成本图像。绘出quick-union和quick-find算法的均摊图像(mediumUF.txt)
quick-union:
在这里插入图片描述

import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;

public class quick_union_UF {
    private int[] id;
    private int count;
    private int visitnum;

    public void visualInit() {
        StdDraw.setXscale(0, count + 1);
        StdDraw.setYscale(0, count + 1);
        StdDraw.setPenRadius(0.005);
    }

    public void drawPoint(int x, int y) {
        StdDraw.setPenColor(StdDraw.RED);
        StdDraw.point(x, y);
        StdDraw.setPenColor(StdDraw.DARK_GRAY);
        StdDraw.point(x, (double)visitnum / x);
    }

    public quick_union_UF(int n) {
        count = n;
        visitnum = 0;
        id = new int[n];
        for (int i = 0; i < n; i++) {
            id[i] = i;
            visitnum++;
        }
    }

    public int getCount() {
        return count;
    }

    public int getVisitnum() {
        return visitnum;
    }

    public int[] find(int p) {
        int arr[] = new int[2];
        int cost = 0;
        while (p != id[p]) {
            p = id[p];
            visitnum += 2;
            cost += 2;
        }
        visitnum++;
        cost++;
        arr[0] = p;
        arr[1] = cost;
        return arr;
    }

    public int union(int p, int q) {
        int prr[] = find(p);
        int qrr[] = find(q);
        int a = prr[0];
        int b = qrr[0];
        if(a == b)
            return prr[1] + qrr[1];
        id[a] = b;
        visitnum++;
        count--;
        System.out.println(p + "--" + q);
        return prr[1] + qrr[1] + 1;
    }

    public static void main(String[] args) {
        int n = StdIn.readInt();
        quick_union_UF uf = new quick_union_UF(n);
        uf.visualInit();
        int i = 1;
        while (!StdIn.isEmpty()) {
            int p = StdIn.readInt();
            int q = StdIn.readInt();
            int num = uf.union(p, q);
            uf.drawPoint(i++, num);
        }
        StdOut.println("共有" + uf.getCount() + "个连通分量");
        StdOut.println("共访问了" + uf.getVisitnum() + "次数组");
    }
}

quick-find:
在这里插入图片描述

import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;

public class quick_find_UF {
    private int[] id;
    private int count;
    private int visitnum;

    public quick_find_UF(int n) {
        visitnum = 0;
        count = n;
        id = new int[n];
        for (int i = 0; i < n; i++) {
            id[i] = i;
            visitnum++;
        }
    }

    public void drawPoint(int x, int y) {
        StdDraw.setPenColor(StdDraw.RED);
        StdDraw.point(x, y);
        StdDraw.setPenColor(StdDraw.DARK_GRAY);
        StdDraw.point(x, (double)visitnum / x);
    }

    public void visualInit() {
        StdDraw.setXscale(0, 1000);
        StdDraw.setYscale(0, 2000);
        StdDraw.setPenRadius(0.005);
    }

    public int getCount() {
        return count;
    }

    public int getVisitnum() {
        return visitnum;
    }

    public int find(int p) {
        visitnum++;
        return id[p];
    }

    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }

    public int union(int p, int q) {
        int a = find(p);
        int b = find(q);
        if(a == b)
            return 2;
        int num = 2;
        for(int i = 0; i < id.length; i++, visitnum++) {
            if(id[i] == a) {
                id[i] = b;
                num++;
            }
            num++;
        }
        count--;
        System.out.println(p + "--" + q);
        return num;
    }

    public static void main(String[] args) {
        int n = StdIn.readInt();
        quick_find_UF uf = new quick_find_UF(n);
        uf.visualInit();
        int i = 1;
        while (!StdIn.isEmpty()) {
            int p = StdIn.readInt();
            int q = StdIn.readInt();
            int num = uf.union(p, q);
            uf.drawPoint(i++, num);
        }
        StdOut.println("共有" + uf.getCount() + "个连通分量");
        StdOut.println("共访问了" + uf.getVisitnum() + "次数组");
    }


}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值