OO第三单元总结

OO第三单元总结

架构设计

本单元主要任务为阅读JML的属性和方法规格,来模拟一个社交网络模型,通过三次迭代,实现简单社交关系的模拟和查询、群组和消息功能以及社交关系系统中不同消息类型和操作。

由于是在JML规格的基础上编写代码,架构设计基本仿照代码模板进行设计,相比前两单元较为简单,除了独立出少数工具类以简化代码之外,其他部分按照JML实现接口即可。

图模型构建和维护策略

此题本质上将Network视为一张图,person为点,relation为边,各个方法即为建立、查询、维护图的方法。代码的关键也在于Network类的方法中

hw9:并查集维护连通图

本次作业的isCirclequeryBlockSum方法利用并查集维护较为简单,同时也具备性能上的优势。

//最朴素的并查集算法
int get(int x) {
   if(fa[x] == x) {
       return x;
  }
   return get(fa[x]);
}
void merge(int x, int y) {
   x = get(x);
   y = get(y);
   if(x != y) {
       fa[y] = x;
  }
}

hw10:kruskal维护最小生成树

本题中,queryLeastConnection这个函数用于Network图中的最小生成树,由于上次作业中实现了并查集,故使用依赖于并查集的kruskal算法更为合适。

Kruskal 算法

Kruskal 算法是最小生成树算法的一种。算法过程如下:

首先我们定义带权无向图G的边集合为E,接着我们再定义最小生成树的边集为T,初始集合T为空。接着执行以下操作:

  1. 首先,我们把图G看成一个有N-1棵树的森林,图上每个顶点对应一棵树;

  1. 接着,我们将边集E的每条边,按权值从小到大进行排序;

  1. 按边权从小到大的顺序遍历每条边e=(u,v) ,我们记顶点u所在的树为Tu,顶点v所在的树为Tv,如果Tu和Tv不是同一棵树,则我们将边e加入集合T并将两棵树Tu和Tv进行合并;否则不进行任何操作。

算法执行完毕后,如果集合T包含N-1条边,则T就代表最小生成树中的所有边。

第三步操作需要对集合进行操作,用并查集来维护。

int kruskal(int n, int m) {
   int sum = 0;
   for(int i = 1; i <= n; i++) {
       fa[i] = i;
  }
   sort(E, E + m, cmp);
   for(int i = 0; i < m; i++) {
       int fu = get(E[i].u);
       int fv = get(E[i].v);
       if(fu != fv) {
           fa[fv] = fu;
           sum += E[i].len;
      }
  }
   return sum;
}

hw11:dijkstra维护最短路

sendIndirectMessage方法所求的是两点之间的最短路。利用dijkstra算法处理较为合适。

void dijkstra(int u) {
   memset(vis, false, sizeof(vis));
   memset(dis, 0x3f, sizeof(dis));
   dis[u] = 0;
   for(int i = 0; i < n; i++) {
       int mind = 1000000000, minj = -1;
       for(int j = 1; j <= n; j++) {
           if(!vis[j] && dis[j] < mind) {
               minj = j;
               mind = dis[j];
          }
      }
       if(minj == -1) {
           return;
      }
       vis[minj] = true;
       for(int j = head[minj]; ~j; j = e[j].next) {
           int v = e[j].v;
           int w = e[j].w;
           if(!vis[v] && dis[v] > dis[minj] + w) {
               dis[v] = dis[minj] + w;
          }
      }
  }
}

性能问题

容器选择

JML并未规定类中数据存储容器的具体种类。因此可以视情况选择Arraylist或者Hashmap,以适应不同的查询、维护要求。

    //MyNetwork类中所使用的容器
   private HashMap<Integer, Person> people;
   private HashMap<Integer, Group> groups;
   private ArrayList<Message> messages;
   private Dsu dsu;
   private ArrayList<Integer> emojiIdList;
   private ArrayList<Integer> emojiHeatList;
   private HashMap<Integer, ArrayList<Message>> emoCod;

算法优化

并查集路径压缩

路径压缩的思想是,我们只关心每个结点所在集合的根结点,而并不太关心树的真正的结构是怎么样的。这样我们在一次查询的时候,可以直接把查询路径上的所有结点的fa[i]都赋值成为根结点。实现这一步只需要在我们之前的查询函数上面进行很 小的改动。

堆优化dijkstra

优先队列实现的堆优化dijkstra算法,能将时间复杂度降为O(nlogn),java中的PriorityQueue结构使优化较为简便。

代码维护

在查询指令中,有一个trick是对于某些需要查询且随对数据结构的维护而发生变化的信息,可以用变量来储存,并且在建立维护关系时对其进行更新。比如联通块的数量就可以用qbs来存储,并且在addPerson、addReletion的时候加以更新。

bug修复

hw9

第一次强测得分不高,主要问题在于测试不充分,一些特殊情况未做特殊处理,比如删除最后一个Person时程序会报错,之类的错误。

互测未出问题。

hw10

强测没有问题,但是互测被hack了两处,主要是性能方面的缺陷,比如Group类中的查询函数的for循环中出现了不必要的函数嵌套,导致时间过慢。

hw11

强测无问题,互测被hack的原因是出现了将emojiHeatList错写为emojiIdList的笔误,但可能此处代码对程序影响较小,自测和强测都未能测出来。

自测

自测采取了程序随机/定向生成数据+与同学对拍的方式,一方面使用随机数据对所有指令进行全面覆盖式测试,以减少BUG出现;另一方面针对时间复杂度较高的函数构造极端数据进行测试,寻求优化的可能。

Network扩展

题目要求

假设出现了几种不同的Person

  • Advertiser:持续向外发送产品广告

  • Producer:产品生产商,通过Advertiser来销售产品

  • Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息

  • Person:吃瓜群众,不发广告,不买东西,不卖东西

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

解答

建立Advertiser、Producer、Customer三种Person扩展类,以及Advertisement和Product两种Message扩展类。

发送广告:

/*
  @ public normal_behavior
  @ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == id && people[i] instanceof Advertiser);
  @ ensures getPerson(id).advertisements.length == \old(getPerson(id).advertisements.length) - 1;
  @ ensures (\forall int j; 0 <= j && j < people[i].messages.length; people[i].messages[j] == \old(people[i].messages[j])))) && (\forall int i; 0 <= i && i < people.length; !(getPerson(id).isLinked(people[i])) ==> (people[i].messages.length == \old(people[i].messages.length;
  @ ensures (\forall int j; 1 <= j && j < people[i].messages.length; people[i].messages[j] == \old(people[i].messages[j - 1])))) && (\forall int i; 0 <= i && i < people.length; (getPerson(id).isLinked(people[i])) ==> (people[i].messages.length == \old(people[i].messages.length) + 1 && people[i].messages[0] == \old(getPerson(id).advertisements[0]);
*/
public void sendAdvertisement(int id);

查询销售量:

/*
  @ public normal_behavior
  @ ensures \result == numbers;
@*/
*/
public void getSalingNumber();

统计销售量:

/*
  @ public normal_behavior
  @ assignable numbers
  @ ensures numbers == \old(numbers) + 1;
*/
public void addSalingNumber();

学习体会

本单元的学习,不太push的同时也收获满满;不仅学习掌握了面对JML规格书写代码的能力,同时复习拓展了数据结构相关知识,熟悉了利用JAVA写图论算法,其包含的容器不可谓不强大。

同时,与同学共同对拍,共享测试数据,也是一次良好的体验。

posted @ 2022-06-04 08:10  陶索梓  阅读(211)  评论(1编辑  收藏  举报