【问题标题】:Why does creating a copy of an object still alter instance variables of original object?为什么创建对象的副本仍然会改变原始对象的实例变量?
【发布时间】:2020-10-23 08:01:56
【问题描述】:

我有两节课。类 Algorithm 实现了一个方法 findIntersections(),它是一个 sweep line algorithm,用于在 O(nLogN) 时间内检查交叉点。

它还实现了一个函数addSegments,它将Segment类型的对象(两个点)添加到基于x坐标的优先队列中。

public class Algorithm {
 
    public PriorityQueue pQueue = new PriortiyQueue();

    //This function adds objects of type Segments to a priority queue
    public void addSegments(List<Segment> segments) {
        pQueue.add(segments);
        //do something
    }

    //This function implements a sweep line algorithm to check for Intersections.
    public void findIntersection() {
        while (!pQueue.isEmpty()) {
            p.poll(); //removes element from Queue
            // do something
        }
    }
}

另一个类Model 将数据从CSV 文件加载到优先队列中。这是一个密集的过程,我只想做一次。

另一方面,checkForCollissions 被调用了数百万次。

  • 我想检查提供的段与从 csv 文件添加到优先级队列中的其余段之间的冲突

  • 我不想每次都从头开始向优先级队列中添加元素。这是不可行的。

     public class Model  {
         public Algorithm algoObj = new Algorithm();
         public ArrayList<Segment> algoObj = new ArrayList<>();
         public ArrayList<Segment> segments = new ArrayList<>();
         public ArrayList<Segment> single_segment = new ArrayList<>();
    
         public boolean loadCSV() {
             //read csv file
             while ((strLine = br.readLine()) != null) {
               segments.add(new Segment()); //Add all segments in CSV file to ArrayLisyt
               algo.addSegments(segments);  //Adds 4000 objects of type segment to priority Queue
             }
         }
    
         //This function is called millions of times
         public boolean checkForCollisions(segment_to_check) {
             single_segment.add(segment_to_check);    //Add 1 segment. 
             algoObj.addSegments(single_segment);     //Adds 1 object of type segment to priority Queue
             algoObj.findIntersection();
             single_segment.remove(new Segment()); //Remove above segment to get back to original data
         }
     }
    

TL;DR

我遇到的问题是,在第一次调用 checkForCollisions 之后,优先级队列发生了变化,因为 findIntersection() 通过轮询队列中的元素来工作,从而改变队列。

如何防止 algoObj.addSegments() 创建的优先级队列在函数调用之间发生变化?

这是否必须按照here 的解释进行浅层和深层复制?


我尝试在函数开头创建队列的副本,然后更改副本:

        public boolean checkForCollisions(segment_to_check) {
            Algorithm copy = algoObj;
            single_segment.add(segment_to_check);    //Add 1 segment. 
            copy.addSegments(single_segment);     //Adds 1 object of type segment to priority Queue
            copy.findIntersection();
            single_segment.remove(new Segment()); //Remove above segment to get back to original data
        }
    }

但这不起作用,因为它仍然会改变原始algoObj 的优先级队列。

我相信这是一个初学者的问题,源于我在使用 OO 语言时缺乏正确的理解。任何帮助将不胜感激。

【问题讨论】:

  • 您说 findIntersection() 通过轮询队列中的元素来工作,从而改变队列我不想将元素添加到每次都从头开始优先队列。如果findIntersection 修改了队列(这是你的算法),你必须创建一个新的队列对象来传递你拥有的段(注意:你仍然只从文件中读取一次)
  • 它们是不相关的事件,如果我不清楚,对不起。通过在loadCSV 函数中添加例如 10,000 个线段来设置一次队列。扫描线算法findIntersection 通过轮询队列中的元素来工作,从而删除段并更改原始队列。每次检查交叉点时,我都不想再从头开始添加 10,000 条线段。是不是清楚一点?
  • 这是不可能的,因为您已从队列中删除了项目。你只能避免再次读取文件(我猜你已经做到了)。您可以插入的最大项目数是多少?您为什么担心这个?
  • 如果您似乎不想要Queue 的语义,为什么要首先使用PriorityQueue?为什么不直接创建一个List,对其进行一次排序,然后根据需要对其进行迭代?
  • 那我有点糊涂了。你知道this "limitation" of PriorityQueue吗?如果在对 checkForCollisions 的调用之间顺序可以更改,您似乎别无选择,只能删除并重新添加元素。

标签: java queue priority-queue


【解决方案1】:

首先,重要的是要知道将现有对象分配给另一个变量不会创建原始对象的副本:

MyObject a = new MyObject();
MyObject b = a; // does NOT create a copy!
// now a and b "point" to the same single instance of MyObject!

关于您的实际问题的一些想法:

您的优先级队列只是用于交集算法的工作数据结构,并且就在算法运行时。完成后(因此已找到交叉点),它是空的或至少已更改,正如您已经写的那样。因此,必须为每个算法运行重新创建优先级队列。

那么你应该怎么做:

  1. 将 CSV 文件中的段加载到您的 ArrayList,但不要将其传递到优先级队列。

  2. 每次调用findIntersection() 之前重新填充(或重新创建)优先级队列。这最好通过将所有段传递给方法并从头开始创建一个新的优先级队列来完成:

    public void findIntersection(Collection<Segment> segments) {
        PriorityQueue<Segment> pQueue = new PrioerityQueue<Segment>(segments);
        while (!pQueue.isEmpty()) {
            p.poll(); //removes element from Queue
            // do something
        }
    }
    

提示:正如我在开头已经写过的,这不会复制单个段或段集合。它只是传递一个引用。当然,优先级队列在构建时需要创建内部结构,所以如果段集合很大,这可能需要一些时间。

如果这个解决方案对您的需求来说太慢了,您将不得不改进您的算法。你真的需要经常检查十字路口吗?如果您只将一个线段添加到列表中,则检查与其他线段的交叉点就足够了,但如果其他线段彼此相交则不行。可能您可以将您的分段存储在类似于 Bentley-Ottmann 算法使用的二叉搜索树中。每当一个新的段“到达”时,可以根据搜索树检查它,这应该是可行的,时间复杂度约为 O(log n)。之后,如有必要,可以将该段插入树中。

或者您可以先添加所有路段,然后只检查一次交叉点。

【讨论】:

  • 谢谢。所以,是的,我真的需要经常检查十字路口。不过,你说得对。 “如果你只将一个段添加到列表中,检查与其他段的交叉点就足够了,但如果其他段相互交叉则不行。”这就是我正在做的事情,但是如果我想检查与其他路段的交叉点,我仍然需要每次都填写整个队列。最后一句话是不可能的,因为我在checkForCollisions 中添加的段可能相互交叉,而不是与原始 CSV 加载的段相交。
  • 我想我必须按照你的建议找到另一种方法,然后研究我的算法
  • @rrz0 我已经添加了一个建议,即维护一个可以非常快速地搜索交叉点的二叉搜索树。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-07
  • 1970-01-01
相关资源
最近更新 更多