【问题标题】:Sorted TStringList, how does the sorting work?排序的 TStringList,排序是如何工作的?
【发布时间】:2011-02-25 01:58:13
【问题描述】:

我只是很好奇,因为最近我看到了 Java 中 Hashmaps 的使用,想知道 Delphi 的 Sorted String 列表是否完全相似。

TStringList 对象是否生成一个 Hash 以用作每个项目的索引?以及如何通过 Find 函数对照字符串列表检查搜索字符串?

我经常使用 Sorted TStringLists,我只是想进一步了解发生了什么。

请假设我不知道哈希映射是如何工作的,因为我不知道 :)

谢谢

【问题讨论】:

  • 使用的排序算法是快速排序。

标签: delphi tstringlist


【解决方案1】:

我一般将这个问题解释为对列表和字典的概述的请求。

  • list,几乎所有人都知道,是一个由连续整数索引的容器。
  • 哈希映射字典关联数组是一个容器,其索引可以是任何类型。很常见的是,字典是用字符串索引的。

为了争论,让我们将列表称为L,将我们的字典称为D

列表具有真正的随机访问。如果您知道它的索引,则可以在恒定时间内查找一个项目。字典并非如此,它们通常采用基于哈希的算法来实现高效的随机访问。

当您尝试查找值时,排序列表可以执行二进制搜索。查找值 V 是获取索引 I 的行为,即L[I]=V。二分查找非常有效。如果列表未排序,则它必须执行效率低得多的线性搜索。已排序的列表可以使用插入排序来维护列表的顺序——当添加新项目时,它会插入到正确的位置。

您可以将字典视为<Key,Value> 对的列表。您可以遍历所有对,但更常见的是使用索引表示法查找给定键的值:D[Key]。请注意,这与在列表中查找值的操作不同——当您知道索引 I 时,它类似于读取 L[I]

在旧版本的 Delphi 中,从字符串列表中哄骗字典行为是很常见的。表演很糟糕。内容弹性不大。

在现代 Delphi 中,有TDictionary,一个可以容纳任何东西的泛型类。该实现使用哈希,虽然我没有亲自测试过它的性能,但我理解它是值得尊敬的。

有一些常用算法可以优化使用所有这些容器:未排序列表、排序列表、字典。您只需要使用正确的方法来解决手头的问题。

【讨论】:

  • 我从未体验过 TStringList 的性能“糟糕”,但这可能是因为我几乎没有在其中放入超过几十万个项目。很好的解释,虽然它没有在任何地方提到快速排序,这实际上是 TStringList 使用的排序。
  • @Golez 当列表为Sorted 时,它使用插入排序。当您在未排序的列表上调用 Sort() 时,它会使用快速排序。字符串列表的性能在用作列表时非常出色,但在强制表现得像字典时性能较差。
【解决方案2】:

TStringList 将字符串保存在一个数组中。

如果您在其他未排序(排序属性 = false)的字符串列表上调用 Sort,则执行 QuickSort 以对项目进行排序。

如果将 Sorted 设置为 true,也会发生同样的情况。

如果您在未排序的字符串列表上调用 Find(或调用 find 的 IndexOf)(Sorted 属性 = false,即使您明确调用了 Sort,如果 Sorted 属性不为 true,则该列表也被视为未排序),则线性搜索是执行从开始到找到匹配的所有字符串的比较。

如果您在已排序的字符串列表(Sorted 属性 = true)上调用 Find,则执行二进制搜索(有关详细信息,请参阅 http://en.wikipedia.org/wiki/Binary_search)。

如果将字符串添加到已排序的字符串列表中,则会执行二进制搜索以确定正确的插入位置,数组中的所有后续元素都移动一并插入新字符串。

由于这种插入性能会随着字符串列表的增大而变差。如果要将大量条目插入已排序的字符串列表,通常最好关闭排序,插入字符串,然后将 Sorted 设置回 true 以执行快速排序。

这种方法的缺点是您将无法防止重复插入。

编辑:如果您想要哈希映射,请使用单元 Generics.Collections 中的 TDictionary

【讨论】:

  • +1 表示“在大列表中插入许多项目时进行排序”。人们往往会忘记这一点,并在插入数千个项目时抱怨字符串列表的性能。
  • 您可以通过设置容量属性分配足够的空间来更快地实现这一点。这将防止字符串列表在新项目不适合时必须重新分配整个数组。 Stringlist 确实会大块增加容量,但是如果你知道项的数量,最好自己设置容量。
  • 在添加时保留插入的问题实际上是每次插入时的内存副本。添加到列表的末尾基本上是免费的。插入中间成本。替代排序的时间复杂度是相同的。
  • 添加到列表的末尾基本上是免费的,但不是 TStringList。那是因为数据不在列表中,而是在数组中,这意味着添加项目需要重新分配整个数组。 TStringList 试图通过在达到当前分配的数组的限制时重新分配大块来解决这个问题。这解决了速度问题,但仍然留下了未分配内存的大漏洞,这导致以前的内存管理器出现问题(后来 Delphis 有了一个更智能的新内存管理器)。
  • Goleztrol:这不仅仅是重新分配,主要是如果你将第 i 项插入到 n 个项目的数组中,你需要移动 n-i 个项目。这使得插入成为 O(n) 操作。一个简单的解决方案是使主数组成为数组数组。我过去这样做过,然后插入几乎是恒定的时间( O(total n/avg nr items in subarray)),直到顶层数组接近十万。然后你需要额外的间接级别。
【解决方案3】:

您可以查看源代码,因为它是 Delphi 自带的。 Ctrl-单击代码中的“排序”调用。

它在非 Unicode Delphi 中是一种简单的字母排序,在后来的版本中是一种稍微复杂的 Unicode。您可以为自定义排序顺序提供自己的比较。不幸的是,我没有最新版本的 Delphi,因此无法确认,但我希望在后台有适当的 Unicode 感知和区域设置感知字符串比较例程。 Unicode 排序/字符串比较并非易事,稍作网络搜索就会指出其中的一些缺陷。

当您在字符串或附加到它们的对象(Objects 属性)中分隔文本时,通常会提供您自己的比较例程。在这些情况下,您通常会按对象的属性或字符串中第一个字段以外的其他内容进行排序。或者它可能就像想要对字符串进行数字排序一样简单(所以“2”在“1”之后而不是在“19”之后)

【讨论】:

  • 谢谢,是的,我曾想过查看源代码,但我认为(很糟糕)它会比遍历列表进行比较稍微复杂一些。
  • @CatalystNZ,如果您没有将 Sorted 设置为 true,它只会进行线性搜索。将 Sorted 设置为 true 时,执行的二进制搜索非常快。
  • 默认的 TStringList.Sort 使用 QuickSort 算法,没有散列或任何涉及的内容。如果将字符串添加到已排序的列表中,TStringList 使用二进制搜索 Find 方法来定位正确的位置,然后使用 System.Move “打开”一个间隙以添加新项目。在一个长列表中,内存移动可能是最慢的部分,除非您使用的是慢速自定义比较方法。
  • P.S. IniFiles 单元中还有一个简单的 THashedStringList
【解决方案4】:

还有一个THashedStringList,这可能是一个选项(尤其是在较旧的 Delphi 版本中)。

【讨论】:

    【解决方案5】:

    顺便说一句,TStringList 的 Unicode 排序例程非常慢。如果您覆盖 TStringList.CompareStrings 方法,那么如果字符串仅包含 Ansi 字符(如果您专门使用英语,它们会),您可以使用自定义的 Ansi 字符串比较。我使用自己定制的 TStringList 类来执行此操作,它比 TStringList 类快 4 倍,用于从列表读取和写入字符串的排序列表。

    【讨论】:

      【解决方案6】:

      Delphi 的字典类型(在支持泛型的 Delphi 版本中)是最接近于 Delphi 附带的 hashmap 的东西。 THashedStringList 使查找比在排序的字符串列表中更快。您可以在已排序的字符串列表中使用二进制搜索进行查找,因此它比暴力搜索快,但不如哈希快。

      散列的一般理论是它是无序的,但在查找和插入时非常快。排序列表在插入时相当快,直到列表的大小变大,尽管它不如插入字典效率高。

      列表的最大好处是它是有序的,但哈希查找字典不是。

      【讨论】:

        猜你喜欢
        • 2021-05-15
        • 1970-01-01
        • 2015-12-07
        • 2011-07-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多