BUAA-OO-第三单元总结

规格实现策略

我实现规格时策略如下:

  • 总结大家在讨论区和微信群里面聊的最多的函数,先看看大概思路,要添加些什么属性,把大致框架思考好,然后这个最难的自然放最后再写:)
  • 从MyPerson、MyGroup等这几个开始写,最后再写MyNetwork。
  • 先写那些函数名简单、JML短的,这些一般代码都很短,实现很快。
  • 对于JML要求多的,先写exceptional_behavior,因为每一种exception一般JML只有一行,简单易懂,实现基本上也很短;然后对于长的JML,其实其中大部分内容是在讲前提和结果,将那些部分先去除,只看真正要写的部分(其实JML就很短了),写完后跟前提和结果对一下,看写的有没有什么问题。
  • 另外长JML有的时候是因为有多个normal_behavior,这个时候分类写就行。

测试策略

  • 主要看自己写的时候错误的地方别人是否注意到,要是没有,就很好构造数据hack。
  • 看JML里面前提和结果,自己和别人的保证了没有,一般里面具体实现不太会错。
  • 每一次作业都有一个很重要的函数,每次很多人问这个函数,所以基本上大家对这个函数的实现完成度很高,扫一眼没问题就不必在这个函数上纠缠;相反大家对MyPerson、MyGroup等里面的函数大多没仔细思考(至少我是的),里面漏洞挺多,多看看就能找到。
  • 还有就是随机生成数据“轰炸”,有的时候能通过这个hack到。

容器选择经验

本单元我使用了如下容器:

  • ArrayList<T>:最常见的存储选择。
  • HashMap<K, V>:Hashmap相较于Arraylist优势在于可以做到key,value对应,方便了许多。如果查找某个存储元素,用containKeyscontainValues可以实现O(1)查找。我大多选择Hashmap
  • LinkedList<T>:用链表实现的list,主要是使用其能做到头插的功能。
  • PriorityQueue<T>:优先队列,因为堆优化的Dijkstra算法需要使用堆,因此用java自带的优先队列予以实现

容器有一大坑点:没有初始化就直接用了。因此建议每次在定义的时候直接初始化了,免得自己忘了。

图模型构建和维护策略

1、并查集

并查集第一次使用是在第九次作业,对指令qci的处理。首先是初始化,并查集初始化应该是在有人的时候,即在addPerson的时候,进行初始化。然后在addRelation的时候更改结点的祖宗结点,这样qci的时候直接比较两个结点祖宗结点是否相同即可,大幅降低复杂度。其主要函数为:

public int find(int x) {
        if (checkset.get(x) == x) {
            return x;
        }
        else {
            int tmp = checkset.get(x);
            checkset.replace(x, find(tmp));
            return checkset.get(x);
        }
    }

并查集第二次使用是在第十次作业的queryLeastConnection函数中,因为要写最小生成树,我采用的Dijkstra,里面用并查集完成点集合并。

2、最小生成树

最小生成树用于第十次作业的queryLeastConnection函数中,这里没什么特别之处,全用普通的Dijkstra,加上并查集和快排优化。具体步骤如下:

每次调用queryLeastConnection时先将构建相关边,初始化并查集:

		for (Integer i : checkset.keySet()) {
            if (find(checkset.get(i)) == ances) {
                newcheck.put(i, i);
                Person p = getPerson(i);
                ArrayList<Person> acquaintance = ((MyPerson) p).getAcquaintance();
                ArrayList<Integer> values = ((MyPerson) p).getValue();
                for (int j = 0; j < acquaintance.size(); j++) {
                    if (i > acquaintance.get(j).getId()) {
                        continue;
                    }
                    Brim brim = new Brim(i, acquaintance.get(j).getId(), values.get(j));
                    //flag[k] = false;
                    valuemap[k++] = brim;
                }
            }
        }

然后用快排将边排序:

public void quickSort(Brim []q, int l, int r) {
        //判断边界
        if (l >= r) {
            return;
        }
        //取分界点
        int i = l - 1;
        int j = r + 1;
        int x = q[l + r >> 1].getLen();
        while (i < j)
        {
            do {
                i++;
            } while (q[i].getLen() < x);
            do {
                j--;
            } while (q[j].getLen() > x);
            if (i < j) {
                Brim tmp = new Brim(q[i].getLeft(), q[i].getRight(), q[i].getLen());
                q[i] = q[j];
                q[j] = tmp;
            }
        }
        quickSort(q, l, j);
        quickSort(q, j + 1, r);
    }

最后就是上kruskal:

		for (int m = 0; m < k; m++) {
            int left = valuemap[m].getLeft();
            int right = valuemap[m].getRight();
            int len = valuemap[m].getLen();
            int leftances = findNew(newcheck.get(left));
            int rightances = findNew(newcheck.get(right));
            if (leftances != rightances) {
                ans += len;
                newcheck.replace(leftances, rightances);
            }
        }

3、最短路dijkstra算法

最短路算法是用在第十一次作业sim指令中,我采用的是堆优化的dijkstra算法。对于堆优化,由于java有内置PriorityQueue实现了优先队列,所以直接用其存储结点。具体算法如下:

	PriorityQueue<Dot> heap = new PriorityQueue<>();
        Dot tmp = new Dot(m.getPerson1().getId(), 0);
        heap.add(tmp);
        HashMap<Integer, Integer> dist = new HashMap<>();
        HashMap<Integer, Boolean> flag = new HashMap<>();
		// 初始化
        for (Person i : people.values()) {
            dist.put(i.getId(), 99999999);
            flag.put(i.getId(), false);
        }
		// start
        while (heap.size() != 0) {
            tmp = heap.peek();
            heap.poll();
            int pid = tmp.getId();
            Person ptmp = getPerson(pid);
            int dis = tmp.getDis();
            if (!flag.get(pid)) {
                flag.replace(pid, true);
                for (int i = 0; i < ((MyPerson) ptmp).getAcquaintance().size(); i++) {
                    Person pac = ((MyPerson) ptmp).getAcquaintance().get(i);
                    if (dist.get(pac.getId()) > dis + ((MyPerson) ptmp).getValue().get(i)) {
                        dist.replace(pac.getId(), dis + ((MyPerson) ptmp).getValue().get(i));
                        Dot tmp1 = new Dot(pac.getId(), dist.get(pac.getId()));
                        heap.add(tmp1);
                    }
                }
            }
        }

4.总结

每次作业的算法思路都是:将时间复杂度压进O(n),如果能到O(1)那更好。因为第九次作业我没有使用并查集,导致时间复杂度为O(n^2),强测直接寄了三个点。正是这次的惨痛经历,督促我每次一定要优化,降低时间复杂度。

性能问题和修复

1、第九次作业

偷懒用bfs实现的isCircled,结果性能直接爆炸,所以在bug修复的时候加上了并查集,将复杂度压进O(n)。

2、第十次作业

这一次是qgvs这个命令会调用两次for循环导致时间复杂度为O(n^2),互测的时候被hack的很惨。因此在MyNetwork中加入一个Hashmap,对每个Group都维护一个valuesum,在addRelationaddToGroupdelFromGroup里面进行操作,对valuesum进行修改维护,实现O(n)复杂度。

3、第十一次作业

这一次是个小失误,在最短路里面要先初始化一个dist数组,我是直接用的int[],这样就会导致我默认id是正的,因为数组默认从0开始。互测时只有一个人发现了,没那么凄惨。将int[]改成hashmap就修复完成了。

Network扩展

假设出现了几种不同的Person

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

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

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

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

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

设计如下:

  • 新建 AdvertiserProducerCustomer类,均继承Person
  • Advertiser类发送的信息定义为Advertise,继承自Message,其socialvalue定义为其对人的吸引力。
  • Producer类有接口直接接收Advertise,然后定义生产的产品为Product类,里面包含id、价格等属性。
  • Customer类新增属性偏好,新增功能购买。
  • 可以新增方法buyProductproduceProductaddAdvertisesendAdvertiseaddProduct等方法

produceProduct

/*@ public normal_behavior
	@ requires (\exists int i; 0 <= i && i < productList.length; productList[i] == product);
	@ ensures productList[i].num = \old(productList[i].num) + 1
	@ also
	@ public normal_behavior
	@ requires !(\exists int i; 0 <= i && i < \old(productList.length); \old(productList[i]) == product);
	@ ensures (\exists int i; 0 <= i && i < productList.length; productList[i] == id && productList[i].num = 1);
@*/
public void produceProduct(Product product)

sendAdvertise

/*@ public normal_behavior
	@ requires containsM(id)
	@ assignable advertise,productInf;
    @ ensures !containsM(id) && advertise.length == \old(advertise.length) - 1 &&
    @ 	(\forall int i; 0 <= i && i < \old(advertise.length) && \old(advertise[i].getId()) != id;
    @ ensures productInf.length == \old(productInf.length)+1;
    @ (\forall int i; 0 <= i && i < \old(productInf.length);
  	@ 	(\exists int j; 0 <= j && j < productInf.length; productInf[j].getId() == \old(productInf[i].getId());
  	@ also
  	@ public exception_behavior
  	@ signals (MessageIdNotFoundException e) !containsM(id);
 @*/
public void sendAdvertise(int id)  throws MessageIdNotFoundException;

bugProduct

/*@ public normal_behavior
   	@ requires containsM(id) && (getMessage(id) instanceof BuyMessage);
    @ requires (getMessage(id).getPerson1() instanceof Customer) && (getMessage(id).getPerson2() instanceof Advertiser); 	 	
    @ assignable messages, getMessage(id).getPerson1().money;
    @ assignable getMessage(id).getPerson2().messages, getMessage(id).getPerson2().money;
    @ ensures !containsM(id) && messages.length == \old(messages.length) - 1 &&
    @         (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != id;
    @         (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
    @ ensures \old(getMessage(id)).getPerson2().getMessages().size() == \old(getMessage(id).getPerson2().getMessages().size()) + 1;
    @ ensures (\old(getMessage(id)).getPerson1().getMoney() ==
    @         \old(getMessage(id).getPerson1().getMoney()) - ((BuyMessage)\old(getMessage(id))).getMoney() &&
    @         \old(getMessage(id)).getPerson2().getMoney() ==
    @         \old(getMessage(id).getPerson2().getMoney()) + ((BuyMessage)\old(getMessage(id))).getMoney());
    @ also
    @ public exceptional_behavior
    @ signals (MessageIdNotFoundException e) !containsM(id);
 @*/
 public void bugProduct(int id) throws MessageIdNotFoundException;

学习体会

在做完第二单元电梯月后,身心都放松了,因为学长曾说后两个月的oo很简单,因此第九次作业我放松了警惕,虽然很多人说要用并查集,但当时的我觉得不会这么离谱吧,第三单元第一次就搞这么难?结果还真被卡了,从此再也不轻视第三单元。

这个单元学习的JML十分友好,把程序整体框架都告诉你,你只需填空和优化,因此写代码时间骤减。但读代码的时间花费挺久,因为JML的括号极其多,加上每一行必有一个@,整体看上去就很晕,需要将括号对齐,才能看明白。

虽然这个给写代码带来了极大的遍历,但是要我去写它却是极大的麻烦,研讨课写这个的时候是真的头疼...,前提和后果要先列出来,看想全没(很容易漏了条件),然后才能写对。

总的来说是个较为轻松的单元,这次就不说希望下个单元简单了,因为刚做完第四单元第一次,被血虐了(求放过/(ㄒoㄒ)/~~

posted on 2022-06-01 17:18  martinriven  阅读(31)  评论(0编辑  收藏  举报