更有效的方法来编写这个算法?

时间:2015-12-02 04:23:26

标签: c++ performance function

目前正致力于图书馆模拟器作业。一切都运转良好,但我想知道一些事情只是为了了解它。

在这个程序中有3个类:Book,Patron和Library。库类包含3个私有数据成员:指向书籍的指针向量,指向顾客指针的向量,以及currentDate int。

有问题的功能如下:

SqlConnection connection1 = new SqlConnection(ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString)

该功能的要求如下:

增加当前日期;将每个Patron的罚款增加10美分,用于他们已经检查的每个逾期账簿(使用modifiedFine)

我上面写的方式现在运行正常。由于我刚刚进入计算机科学课程的第一学期,我们不能使用任何我们未涵盖的内容,我知道这很多。话虽如此,使用更高级的c ++编程方法会有更有效的方法来实现这个功能吗?

8 个答案:

答案 0 :(得分:17)

  1. 如果尺寸不是很大,请使用std::vector
  2. 由于所涉及的间接性,指针总是与它们相关的成本。查找地址并在内存中访问它可能无法由编译器优化,因此将涉及访问内存的成本。内存访问通常是系统性能的瓶颈,因此最好尝试将内容放在内存中并尝试构建程序,以便最少访问内存。

    1. 如果数据非常大,请使用SQL等数据库系统。
    2. 另一方面,我们可以放弃所有脏工作并使用已建立的数据库库或程序。像MySQL这样的东西可以使用优秀的编程语言轻松管理大量数据,以便访问和管理它。某些数据库(如PostgreSQL)可以扩展到大型数据集。熟悉它也很有帮助。例如,甚至一些移动应用也可能使用MySQL for Android。

      1. 使用现代C ++ 11或更高版本的for循环迭代语法。
      2. 当前的for循环语法非常不透明,可能有很多错误。 C ++ 11引入了一个更清晰的for循环语法来迭代标准库容器,如mapvector。使用:for(auto it : vector_name)。如果需要修改每个,请使用it的参考限定符 - 迭代器。

        1. 使用预增量语法来实现最小加速。
        2. ++ii++略有不同。 ++i只是在继续评估表达式之前直接修改它在表达式中出现的对象。 i++创建对象的副本,返回它,并递增原始对象。创建值或对象的副本在C ++中有成本,因此在某些情况下避免这种情况会有所帮助,无论如何这是一个很好的约定。

          1. 经过const &。不仅仅是定期参考。
          2. 默认情况下,函数参数在C ++中按值传递。这意味着C ++只是复制了该对象。但是,当对某个对象重复应用突变时,例如,使用函数来改变整数值随时间的变化,您可能希望通过引用传递。参考文献基本上通过" real"对象,意味着您对引用所做的任何更改都是在" real"对象

            现在,为什么要传递一个不可修改的对象?因为它可以带来更好的优化。通过常量引用传递允许编译器对您的代码做出更强的假设(例如,因为引用在程序的过程中不能改变,在函数中多次引用相同的引用并不要求参数的值重新加载,因为它不应该在函数内部发生变化。)

            1. 使用std::unique_ptrstd::shared_ptr
            2. 智能指针也是C ++ 11引入的一个很好的功能,它涉及通过将其生命周期附加到范围来自动释放自身的指针。换句话说,不需要使用newdelete - 只需创建指针,就不应该跟踪释放内存的时间。在某些情况下,这可能会变得复杂,但一般来说,使用智能指针可以提高安全性,减少内存管理问题的变化,这就是为什么它们首先被引入标准库。

答案 1 :(得分:5)

我想在这里有几个问题要回答。第一个是:这个算法可以更有效吗?另一个是:我在c ++中实现算法能更有效吗?

对于第一个问题,我会回答否。基于这个问题,我觉得你没有更多的信息可以让你做得比O(n ^ 2)更好。

正如评论中所提到的,你可以迭代每个人并按截止日期对他们的书进行排序。在实践中,这可以节省一些时间,但理论上书籍查找仍然是线性时间,O(n)。另外,您添加了排序的开销,使您的算法现在成为O(mnlog(n))其中m是顾客的数量,n是书籍的数量。如果你知道你的顾客很少,每本都有很多书,那么排序可能是有益的。如果你有很多书籍的顾客很少,那就不那么有用了。

至于第二个问题:有一些小的调整(以及一些大的调整)可以使你的代码更有效率,虽然我认为绝大多数时候它们是不必要的。 我注意到的一件主要事情是你在每次迭代时重新创建一个矢量对象for循环。通过这样做,您将产生不必要的开销。请尝试使用此伪代码:

currentDate++;
vector<Book*> ptr = members.at(i)->getCheckedOutBooks();
for(....)

另一个可能是大修的优化是删除Vector库。 c ++中的向量具有动态调整大小以及其他不必要的开销(用于您的任务)的能力。简单地使用数组会有更高的内存效率,尽管它具有相当的时间效率。

你提到你的第一个学期,所以你可能还没有被介绍到Big O notation

答案 2 :(得分:5)

如果这是您要优化的唯一操作,那么保持tuple <int, Book *, Patron * >的向量按int排序,表示evey的截止日期已签出 然后迭代直到截止日期大于当前日期,应用程序罚款相关的Patron。

答案 3 :(得分:4)

如果您n已检出书籍,m已逾期,您的算法需要O(n)时间来添加罚款。这是因为您的数据结构存储了这样的信息

member -> list(checked out books)
book -> check-out length // presumably the due date for returning the book

如果除了members集合外,您还会存储以下信息:

check-out length -> list(checked out books with that due date)
book -> member who checked it out

然后您可以使用排序树,在截止日期之前存储所有已签出的书籍,以查找O(log n)中所有已过期的书籍。因此,算法的总渐近运行时间会从O(n)减少到O(log n + m)

答案 4 :(得分:3)

您可以考虑将vector替换为std::map容器。地图存储为已排序的树。如果您定义比较结果长度(或更可能是“过期日期”)的比较器功能,则无需每次都扫描整个列表。

更复杂的解决方案是将所有书籍存储在按其到期时间排序的单个指针树中。那么你根本不需要迭代成员。而是迭代书籍,直到找到一本尚未过期的书。

这更复杂,因为现在为每个成员添加/删除书籍(甚至迭代成员所拥有的所有书籍)更加困难,并且可能需要为每个用户维护一个指针向量作为当前方法(除了全球图书地图)。

答案 5 :(得分:2)

自从我使用C ++以来已经有一段时间了,但几乎总是标准库比你自己的实现更快。供您参考,请查看与std::vector相关的标准函数(此站点非常有用)。

您可以通过其他一些过滤逻辑来减少ptr.size(),这样您就不必迭代没有迟到的人(可能会对书籍和截止日期进行一些排序?)

答案 6 :(得分:2)

现在你要修改O(n)中的罚款(n是getCheckOutLength()。size()),但是你可以在 O(log(n))中进行罚款,因为你只需要迟到的书籍数量,而不是他们的罚款对象,如果你有这个数字然后你乘以.01并使用一个修正精细函数来完成所有。

以下是我建议的方式:
如果你将getCheckOutBooks()按照它们的getCheckOutLength()在向量中排序,那么你可以通过在向量中找到std :: upper_bound来找到哪个日期超过curDate,该向量为你提供了大于currentDate的第一个元素,所以从那个元素索引到向量的结尾是应该被罚款的书的数量,这里是代码:

int checkedDateComparator(Patron & leftHand, Patron & rightHand){
    return leftHand.getCheckedOutLength() < rightHand.getCheckOutLength();  
}
bool operator==(Patron & a, Patron & b){
    return a.getCheckedOutLength() < b.getCheckOutLength();
}
void Library::incrementCurrentDate()
{
    currentDate++;

    for (int i = 0; i < members.size(); i++)
    {
        vector<Book*> ptr = members.at(i)->getCheckedOutBooks();
        Book dummy; //dummy used for find the fines 
        dummy.setCheckedOutLength(currentDate);
        int overdue = ptr.end() - upper_bound(ptr.begin(), ptr.end(), dummmy, checkedDateComparator);
        members.at(i)->amendFine(overdue* .01);
   }
} 

答案 7 :(得分:1)

让我们退一步看看要求。当你去图书馆看一些可能迟到的书籍时,你可能会问图书管理员你欠了什么。图书管理员会查找您的帐户并告诉您。就在那时你应该计算费用。你现在正在做的是每隔午夜重新计算费用(我假设)。这是效率低下的部分。

让我们改为使用这个用例:

  1. 图书管理员试图查看赞助人的书籍
  2. 系统计算费用
  3. 赞助人支付任何未付费用
  4. 图书管理员检查书籍
  5. 您问题的相关部分将是第2步。这是伪代码:

    float CalculateFees(Patron patron)
    {
        float result = 0;
        foreach(checkedOutBook in patron.GetCheckedOutBooks())
        {
            result += CalculateFee(checkedOutBook.CheckOutDate(), today);
        }
        return result;
    }
    
    float CalculateFee(Date checkOutDate, Date today)
    {
        return (today.Day() - checkOutDate.Day()) * 0.10;
    }
    

    整个用例可以简单如下:

    void AttemptCheckout(Patron patron, BookList books)
    {
        float fees = CalculateFees(patron);
        if(fees == 0 || (fees > 0 && PatronPaysFees(patron, fees)))
        {
            Checkout(patron, books);
        }
        else
        {
            RejectCheckout(patron);
        }
    }
    

    我写这篇文章的方式很容易改变费用公式。某些类型的材料与其他类型的材料不同。罚款可能会被限制在一定数额。