- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
想象一下,我有一个很大的整数列表(> 1000个项目)。我需要能够对此列表执行两项操作:删除下半部分,然后通过插入随机整数再次将列表填充到其原始大小。因为我执行这些操作大约一百万次,所以我需要它尽可能地高效。
我做的第一件事就是使用List
,通过在正确的位置添加新项来对其进行排序。尽管删除排序列表的下半部分非常容易,但是插入需要花费大量时间。
我尝试实现跳过列表,但是经过一些测试,列表的大小似乎至少必须为10000,才算真正重要,否则它的性能甚至比正常列表还要差。
这就是为什么我决定使用AVL树的原因,因此可以更快地插入项目。但是问题是我不知道删除这种二进制搜索树下半部分的任何有效方法。
我的问题是:是否有一种有效的方法来做到这一点?有没有我可以更轻松使用的另一种数据结构?
编辑
根据要求,我做了一个小测试,显示了列表,跳过列表和AVL树之间的性能差异。我在msdn:Skip list tutorial上使用本教程制作了跳过列表。 AVL树来自此处:AVL tree。我在Pastebin上上传了测试:Program。
在测试中,我在计时的同时向每个数据结构添加了100 000个项目。在我的电脑上,列表花费了大约1秒钟,跳过列表花费了0.5秒,AVL树花费了0.045秒。如果我愿意这样做一百万次,则该列表将花费大约11.5天,而AVL树仅需要大约半天。这种时差清楚地表明了我为什么要高效。
最佳答案
我想指出一些有关此问题的内容。首先,让我们直接了解一些有关性能和C#的知识,因为在仍然存在误解的同时,很难解释一些东西。
接下来,我将在这里将所有内容应用于特定问题。
一般在C#中的性能
Big-O表示法
在大学里,您将学习O(n)总是比O(n ^ 2)更好,以及O(n)总是比O(n log n)更好。但是,对此的基本假设是每个操作将花费大致相同的时间量。
现在,当我在1986年首次开始在1802 RISC处理器上进行编程时,情况就是这样:内存操作是1个时钟滴答,加,减等也是如此。换句话说,Big-O在这里可以正常工作。
在现代计算机中,这更加困难:
void set(int[] array, int index)
{
array[index] = 0;
}
index
不会超出范围。换句话说:它必须添加两个检查-并且其中之一将必须加载内存:
if (index < 0 || index >= array.Length)
{
throw new IndexOutOfRangeException();
}
m
的排序列表。排序是一项众所周知的操作,充其量每个插入项的费用为
O(log m)
。由于您正在处理
n
的“随机”项,因此您将获得
O(n log m)
的最佳速度。
using System.Linq
,因此我们知道我们只是在处理具有预期特征的 native 数据结构。
SortedSet
的代码。它使用引用,算术等-基本上是我在(1),(2)和(3)中警告过的所有讨厌的东西。现在,这里的实现中有错误(重复),但是速度几乎是您期望的:
static void Main(string[] args)
{
Random rnd = new Random(12839);
SortedSet<int> list = new SortedSet<int>();
for (int i = 0; i < 5000; ++i)
{
list.Add(rnd.Next());
}
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000; ++i)
{
for (int j = 0; j < 5000; ++j)
{
list.Add(rnd.Next());
}
int n = 0;
list.RemoveWhere((a) => n++ < 5000);
}
Console.WriteLine(sw.ElapsedMilliseconds);
Console.ReadLine();
}
O(n log m)
执行。
O(m log m)
,已执行的
m/n
时间和
O(n)
数据操作。结果是
O(n + n log m)
。
static void Main(string[] args)
{
Random rnd = new Random(12839);
List<int> list = new List<int>();
for (int i = 0; i < 5000; ++i)
{
list.Add(rnd.Next());
}
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000; ++i)
{
for (int j = 0; j < 5000; ++j)
{
list.Add(rnd.Next());
}
list.Sort((a, b) => a.CompareTo(b)); // #1
list.RemoveRange(0, 5000);
}
Console.WriteLine(sw.ElapsedMilliseconds);
Console.ReadLine();
}
list.Sort((a, b) => a - b);
list.Sort((a, b) => (int)((float)a - (float)b));
RemoveRange
。那么,为什么我不专注于此呢?好吧,实际上我可以,但是我可以告诉你,这不会有太大的改变,因为相对而言,它并不那么经常被使用。仍然,测试没有危害,对吗?
static void Main(string[] args)
{
Random rnd = new Random(12839);
int[] list = new int[10000];
for (int i = 0; i < 5000; ++i)
{
list[i] = rnd.Next();
}
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000; ++i)
{
for (int j = 0; j < 5000; ++j)
{
list[j + 5000] = rnd.Next();
}
Array.Sort(list, (a, b) => b - a);
}
Console.WriteLine(sw.ElapsedMilliseconds);
Console.ReadLine();
}
List
的底层数据结构是一个数组,因此,如果我们进行排序,我们将在做同一件事。这就是我们的时间。
List
一样快。事实是:我发现如果是这样,它们实际上在很多情况下会更快。但是,在这种情况下,我们不是在讨论内循环中的优化,也不是在分配过多的内存(所有内容都可能保留在缓存中),并且所有内存访问都已对齐。总而言之,我们可以因此期望差异很小。
List
的解决方案并删除比较操作。当我们这样做时,我们仍然必须复制。你能指望什么?
list.Sort();
Comparer<int>.Default
,这是整数比较器的默认实现。该委托(delegate)将被包装在比较器中,创建2个虚拟调用-这只是1个调用。这意味着我们也可以通过实现自己的比较器类来颠倒顺序,这将是一个更快的解决方案。
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000; ++i)
{
for (int j = 0; j < 5000; ++j)
{
list.Add(rnd.Next());
}
// Sort the second half:
list.Sort(5000, 5000, Comparer<int>.Default);
// Both the lower and upper half are sorted. Merge-join:
int lhs = 0;
int rhs = 5000;
while (list.Count < 15000)
{
int l = list[lhs];
int r = list[rhs];
if (l < r)
{
list.Add(l);
++lhs;
}
else if (l > r)
{
list.Add(r);
++rhs;
}
else
{
while (list.Count < 15000 && list[lhs] == l)
{
list.Add(l);
++lhs;
}
while (list.Count < 15000 && list[rhs] == r)
{
list.Add(r);
++rhs;
}
}
}
list.RemoveRange(0, 10000);
}
RemoveRange
,它以突发方式处理数据。我们还可以使用两个缓冲区并交换它们。基本上,我们在merge-join期间编写第二个
list2
并将
RemoveRange
替换为:
list.Clear();
var tmp = list;
list = list2;
list2 = tmp;
关于C#最有效的数据结构,可插入和删除下半部分,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36331129/
我目前正在尝试基于哈希表构建字典。逻辑是:有一个名为 HashTable 的结构,其中包含以下内容: HashFunc HashFunc; PrintFunc PrintEntry; CompareF
如果我有一个指向结构/对象的指针,并且该结构/对象包含另外两个指向其他对象的指针,并且我想删除“包含这两个指针的对象而不破坏它所持有的指针”——我该怎么做这样做吗? 指向对象 A 的指针(包含指向对象
像这样的代码 package main import "fmt" type Hello struct { ID int Raw string } type World []*Hell
我有一个采用以下格式的 CSV: Module, Topic, Sub-topic 它需要能够导入到具有以下格式的 MySQL 数据库中: CREATE TABLE `modules` ( `id
通常我使用类似的东西 copy((uint8_t*)&POD, (uint8_t*)(&POD + 1 ), back_inserter(rawData)); copy((uint8_t*)&PODV
错误 : 联合只能在具有兼容列类型的表上执行。 结构(层:字符串,skyward_number:字符串,skyward_points:字符串)<> 结构(skyward_number:字符串,层:字符
我有一个指向结构的指针数组,我正在尝试使用它们进行 while 循环。我对如何准确初始化它并不完全有信心,但我一直这样做: Entry *newEntry = malloc(sizeof(Entry)
我正在学习 C,我的问题可能很愚蠢,但我很困惑。在这样的函数中: int afunction(somevariables) { if (someconditions)
我现在正在做一项编程作业,我并没有真正完全掌握链接,因为我们还没有涉及它。但是我觉得我需要它来做我想做的事情,因为数组还不够 我创建了一个结构,如下 struct node { float coef;
给定以下代码片段: #include #include #define MAX_SIZE 15 typedef struct{ int touchdowns; int intercepti
struct contact list[3]; int checknullarray() { for(int x=0;x<10;x++) { if(strlen(con
这个问题在这里已经有了答案: 关闭 11 年前。 Possible Duplicate: Empty “for” loop in Facebook ajax what does AJAX call
我刚刚在反射器中浏览了一个文件,并在结构构造函数中看到了这个: this = new Binder.SyntaxNodeOrToken(); 我以前从未见过该术语。有人能解释一下这个赋值在 C# 中的
我经常使用字符串常量,例如: DICT_KEY1 = 'DICT_KEY1' DICT_KEY2 = 'DICT_KEY2' ... 很多时候我不介意实际的文字是什么,只要它们是独一无二的并且对人类读
我是 C 的新手,我不明白为什么下面的代码不起作用: typedef struct{ uint8_t a; uint8_t* b; } test_struct; test_struct
您能否制作一个行为类似于内置类之一的结构,您可以在其中直接分配值而无需调用属性? 前任: RoundedDouble count; count = 5; 而不是使用 RoundedDouble cou
这是我的代码: #include typedef struct { const char *description; float value; int age; } swag
在创建嵌套列表时,我认为 R 具有对列表元素有用的命名结构。我有一个列表列表,并希望应用包含在任何列表中的每个向量的函数。 lapply这样做但随后剥离了列表的命名结构。我该怎么办 lapply嵌套列
我正在做一个用于学习目的的个人组织者,我从来没有使用过 XML,所以我不确定我的解决方案是否是最好的。这是我附带的 XML 文件的基本结构:
我是新来的 nosql概念,所以当我开始学习时 PouchDB ,我找到了这个转换表。我的困惑是,如何PouchDB如果可以说我有多个表,是否意味着我需要创建多个数据库?因为根据我在 pouchdb
我是一名优秀的程序员,十分优秀!