第三单元总结

第三单元总结

1.自测过程中测试数据的准备

在本单元中,我主要分两个方面来准备测试数据,一是保证程序正确性的随机生成数据,二是测试程序性能的数据,首先与第二单元不同的是,这部分程序的正确性可以通过对拍来验证,我们要做的就是读懂JML了解数据的范围(边界),再写测试程序来随机生成数据,最后再与同学对拍即可。并且由于有JML的加持,静态对比也是一种发现程序bug的好方式。第二部分就是测试程序性能的数据,这部分尽量要人工构造,而不是随机生成,因为要反复去测那几条时间复杂度比较高的指令,并且生成的关系图也不能是随机的,要保证节点数与边数的比例恰当,接着读出程序开始与结束的时间,保证不超时即可。

2.架构设计、图模型构建和维护策略

2.1架构设计

架构设计按照课程组给出的接口来实现就可以了,不需要自己进行设计。独立于给定接口之外的有几个类,一是Edge边类里面存储的是两个PersonId和他们之间的价值,这个是后面用来维护图用的。并且为了减少行数,我将Kruskal和Dijkstra两个算法单独拿出来,成了两个类,将传入参数设置为他们的构造方法,输出的内容也设置为其中的方法,其余内置的步骤全部设置为private,由此实现了封装。

2.2图模型构建

一个图基本由两部分组成,点集和边集,在这里我将JML中的Person[] people作为点集,用自己构造的ArrayList<Edge> edges作为边集,这样我们就将整个社交网络图建立起来。

2.3维护策略

读了JML语言后,用 ArrayList存储所有数据是我的第一想法,但使用ArrayList的存储效率与查找效率都很低,在第一次作业体现不出来,但在后两次作业中就会造成超时。这是选择HashMap进行存储是一个不错的选择,用id作为key,可以很好的进行查找。所以为了维持基本功能,我们首先要维护的就是HashMap

接下来第二个要维护的就是我们并查集的father数组,但由于是personidid间的对应关系,所以我们仍然选择HashMap来维护这种对应关系。

第三个要维护的就是Kruskal算法中单调递增的边集ArrayList<Edge> edges,我们只需要在每次加入关系后调用一次Collections.sort(edges)即可保证边集是按照value单调递增的。

3.代码实现出现的性能问题和修复情况

3.1第一次作业

在第一次作业中,代码的性能问题主要来自于query_block_sumquery_circle这两条指令,用宽度\深度优先搜索的性能不够好,是十分危险的,最后换成了并查集。网上有很多教程,最终选择了路径压缩加上按秩合并的并查集。

首先要定义两个HashMap来作为我们寻找父亲和存储秩的数组,建立ididid与秩的对应关系。

private HashMap<Integer,Integer> fa;
private HashMap<Integer,Integer> rank;

定义查找函数(查找时进行路径压缩)

public int find(int x) {
    if (x == fa.get(x)) {
        return x;
    }
    else {
        fa.put(x,find(fa.get(x)));
        return find(fa.get(x));
    }
}

在加入人时加入相关信息。

public void addPerson(Person person) throws MyEqualPersonIdException {
    fa.put(person.getId(),person.getId());
    rank.put(person.getId(),1);
}

加关系时进行按秩合并

    public void addRelation(int id1, int id2, int value) throws
            MyPersonIdNotFoundException, MyEqualRelationException { 
        int x = find(id1);
        int y = find(id2);
        if (rank.get(x) <= rank.get(y)) {
            fa.put(x,y);
        }
        else {
            fa.put(y,x);
        }
        if ((rank.get(x) == rank.get(y)) && (x != y)) {
            rank.put(y,rank.get(y) + 1);
        }
    }

之后这两条指令便可以简化成为,因为并查集内都是连通的。

    public boolean isCircle(int id1, int id2) throws MyPersonIdNotFoundException {
        if (!contains(id1)) {
            throw new MyPersonIdNotFoundException(id1);
        }
        if (contains(id1) && !contains(id2)) {
            throw new MyPersonIdNotFoundException(id2);
        }
        if (id1 == id2) {
            return true;
        }
        return (find(id1) == find(id2));
    }
    public int queryBlockSum() {
        int sum = 0;
        for (int id : fa.keySet()) {
            if (fa.get(id) == id) {
                sum = sum + 1;
            }
        }
        return sum;
    }

3.2第二次作业

第二次作业出现的性能问题来自于两条指令,query_group_value_sumquery_least_connection

对于前者,通过用HashMap存储数据再进行查询即可解决。对于后者,我们可以采用并查集优化的Kruskal算法来获得最小生成树,由于并查集我们已经写过了,所以并不是很难完成,NetWork里的大并查集用来判断某个人是否在连通分量中,Kruskal算法中的小并查集用来判断是否成环,具体的算法这里就不再赘述了。

3.3第三次作业

第三次作业的性能问题主要来自send_indirect_message指令,这条指令对应着最短距离算法。纯粹的Dijkstra时间复杂度为$o(n^2)$

这是十分危险的,所以我们采用堆优化的Dijkstra算法,将时间复杂度降至$o(nlogn)$。并且堆优化不需要我们自己写,只需借助PriorityQueue即可完成堆优化。

4.Network扩展

假设出现了几种不同的Person

  • Advertiser:持续向外发送产品广告
  • Producer:产品生产商,通过Advertiser来销售产品
  • Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
  • Person:吃瓜群众,不发广告,不买东西,不卖东西

如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格。

首先要定义产品,产品需要有产品种类,产品价格,并且需要在NetWork 中维护一个产品的热度,像emoji那样,在每次发送广告后会使产品热度增加。

   产品属性
   /*@ public instance model int id;
      @ public instance model int price;
      @ public instance model int type;
      @*/
    社交网络中维护的量
    /*@ public instance model non_null Product[] products;
    	@ public instance model non_null Advertisement[] advertisements;
      @ public instance model non_null int[] productTypeList;
      @ public instance model non_null int[] productHeatList;
      @*/

首先定义增加一个产品种类。

	  /*@ public normal_behavior
      @ requires !(\exists int i; 0 <= i && i < productTypeList.length; productTypeList[i] == type);
      @ assignable productTypeList, productHeatList;
      @ ensures (\exists int i; 0 <= i && i < productTypeList.length; productTypeList[i] == type && 
      					productHeatList[i] == 0);
      @ ensures productTypeList.length == \old(productTypeList.length) + 1 &&
      @          productHeatList.length == \old(productHeatList.length) + 1;
      @ ensures (\forall int i; 0 <= i && i < \old(productTypeList.length);
      @          (\exists int j; 0 <= j && j < productTypeList.length; productTypeList[j] == \old(productTypeList[i]) &&
      @          productHeatList[j] == \old(productHeatList[i])));
      @ also
      @ public exceptional_behavior
      @ signals (EqualProductTypeException e) (\exists int i; 0 <= i && i < productTypeList.length;
      @                                     productTypeList[i] == type);
      @*/
      public void storeProductType(int type) throws ProductTypeException;

接着定义增加一个产品

    /*@ public normal_behavior
      @ requires !(\exists int i; 0 <= i && i < products.length; products[i].equals(product)) && 
      					 (containsProductType(product.getProductType()));
      @ assignable products;
      @ ensures products.length == \old(products.length) + 1;
      @ ensures (\forall int i; 0 <= i && i < \old(products.length);
      @          (\exists int j; 0 <= j && j < products.length; products[j] == (\old(products[i]))));
      @ ensures (\exists int i; 0 <= i && i < products.length; products[i] == product);
      @ also
      @ public exceptional_behavior
      @ signals (EqualProductIdException e) (\exists int i; 0 <= i && i < products.length;
      @                                     products[i].equals(product));
      @ also
      @ public exceptional_behavior
      @ signals (ProductTypeNotFoundException e) !(\exists int i; 0 <= i && i < products.length; 		
      	         products[i].equals(product)) && !(containsProductType(product.getProductType()));
      @*/
      public void addProduct(Product product) throws EqualProductIdException, ProductTypeNotFoundException;

最后定义发送广告(让商品热度增加)

    /*@ public normal_behavior
      @ requires (\exists int i; 0 <= i && i < advertisements.length; advertisements[i].getId == id) &&
      					(containsProductType(advertisements[i].getType()));
      @ assignable advertisements, productHeatList;
      @ ensures (\exists int i; 0 <= i && i < productTypeList.length && productTypeList[i] ==
      @			    (\old(getAdvertisement(id))).getType();
      @         productHeatList[i] == \old(productHeatList[i]) + 1);
      @ ensures !containsAdvertisement(id) && advertisements.length == \old(advertisements.length) - 1 &&
      @         (\forall int i; 0 <= i && i < \old(advertisements.length) && \old(advertisements[i].getId()) != id;
      @         (\exists int j; 0 <= j && j < advertisements.length; 
      @					advertisements[j].equals(\old(advertisements[i]))));
      @ also
      @ public exceptional_behavior
      @ signals (AdvertisementIdNotFoundException e) !(\exists int i; 0 <= i && i < advertisements.length;
      @                                     advertisements[i].getId == id);
      @ also
      @ public exceptional_behavior
      @ signals (ProductTypeNotFoundException e) (\exists int i; 0 <= i && i < advertisements.length; 		
      	         advertisements[i].getId == id) && !(containsProductType(advertisements[i].getType()));
      @*/
      public void sendAdvertisement(int id) throws
            AdvertisementIdNotFoundException, ProductTypeNotFoundException;

5.心得体会

  • 本单元让我了解了契约式编程的思想,对日后的学习工作有很大的帮助,并且在自己实现的过程中体会到了实现JML的过程中,可以按照JML的说发来,但是性能不一定好,简单来说就是做出来非常容易但做好难。并且也体会到了JML语言的严谨性,可以将所有逻辑都概括全面。
  • 本单元的难点主要在优化性能上,这也是在图论学习以来第一次尝试使用堆或者并查集来优化算法。让我复习了这部分有关知识,提高了算法能力。
  • 同时实现JML的过程中也让我感到灵活变通是十分重要的,如果一味的按照JML语言来维护数据结构会造成严重的性能下降。
posted @ 2022-06-05 15:00  i7水一  阅读(36)  评论(1编辑  收藏  举报