我正在寻找一个数据结构,它保留顶部 n 元素,类似于this question,但增加了维护排序顺序的要求。显然我可以在最后排序,但可能有更有效的方法来实现它。只有插入,永远不会删除,然后通过最后的 n 元素进行迭代。
这个问题与语言无关,但它将在C#中,因此最好使用本机.NET集合的答案。
编辑:我应该澄清,排序顺序仅在顶部 n 元素被迭代时最终才重要。在插入过程中,只要保留顶部 n 元素,排序顺序就无关紧要。
答案 0 :(得分:1)
如果您确实需要始终对它们进行排序,则必须使用自平衡二叉搜索树。但是要考虑到这一点(保持元素排序)不是优化,而是具有成本的奢侈品。
自平衡二进制搜索树比隐式堆慢一个常数因子。
您想如何访问已排序的元素?如果您只想迭代排序的序列,那么正常的自平衡二叉搜索树就足够了。
如果您想在任何时间按排序顺序访问任何元素(另一个豪华......),那么您需要扩充树。基本上,每个节点都有一个额外的字段,用于计算其子树中的节点数,包括其自身。
答案 1 :(得分:1)
您必须告诉我们 n 的顺序以及要插入的项目数。
我认为排序顺序是相关的,你怎么知道哪些元素是顶部 n 的一部分?仅仅因为您只希望在所有插入结束时顶部的 n 可能会对结构或算法产生偏差。
为什么要保留不在顶部 n 中的任何项目?您可以使用大小为n + 1的有序集(thinking of Python's deque)。添加时,遍历列表并将新项插入集中的正确位置。当集合达到大小(n + 1)时,每个插入后面都会删除最后一个项目。这样,您始终拥有顶部的 n 项目,而不会使用永远不会检索的项目来增加数据结构。
此外,如果你保留底部元素的值( n 的最后一个,称之为 b ),那么你可以使用它来完全跳过插入。当新项目 x 大于 b 时,这会将比较次数限制为O(1)。
答案 2 :(得分:1)
这个答案类似于Kelly,但有一个经过测试的代码示例。由于N的尺寸小而< 100,我使用了一个简单的插入排序,如果项目数超过某些(非优化的)值(例如20个项目),则使用二进制搜索查找进行修改。我已经包含了一个示例控制台应用程序(C#)来显示其用途。我进行了简单的测试,以确保它有效,但我目前没有对它进行全面分析。此结构已针对减少内存使用进行了优化。
public class TopNStructure<T> : IEnumerable<T> where T : IComparable<T>
{
private const int SizeForLinearOrBinaryInsert = 20;
private int _maxSize;
private int _currentSize;
private T[] _items;
private IComparer<T> _comparer;
/// <summary>
/// The number of items
/// </summary>
public int Count { get { return _currentSize; } }
public TopNStructure(int maxSize, IComparer<T> comparer)
{
if (maxSize <= 0)
{
throw new ArgumentOutOfRangeException("Max size must be a postive, non-zero value");
}
_maxSize = maxSize;
_currentSize = 0;
_items = new T[maxSize];
_comparer = comparer;
}
public TopNStructure(int maxSize)
: this(maxSize, Comparer<T>.Default) { }
/// <summary>
/// Adds an item to this structure
/// </summary>
/// <param name="item">The item to add</param>
/// <returns>True if the item was added, false otherwise</returns>
public bool Add(T item)
{
if (_currentSize == 0)
{
_items[0] = item;
}
else if (_currentSize == _maxSize)
{
if (_comparer.Compare(_items[_currentSize - 1], item) <= 0)
{
return false;
}
else
{
Insert(item);
return true;
}
}
else if (_currentSize == 1)
{
if (_comparer.Compare(_items[0], item) <= 0)
{
_items[1] = item;
}
else
{
_items[1] = _items[0];
_items[0] = item;
}
}
else
{
if (_comparer.Compare(_items[_currentSize - 1], item) <= 0)
{
_items[_currentSize] = item;
}
else
{
Insert(item);
}
}
_currentSize++;
return true;
}
/// <summary>
/// Insert the item into the data structure
/// </summary>
/// <param name="item">The item to insert</param>
private void Insert(T item)
{
int start = 0;
if (_currentSize >= SizeForLinearOrBinaryInsert)
{
start = Array.BinarySearch<T>(_items, 0, _currentSize, item, _comparer);
if (start < 0)
{
start = ~start;
}
ShiftAndInsert(item, start, _currentSize);
return;
}
else
{
for (int i = start; i < _currentSize; i++)
{
if (_comparer.Compare(_items[i], item) > 0)
{
ShiftAndInsert(item, i, _currentSize);
return;
}
}
_items[_currentSize] = item;
}
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
/// <param name="maxIndex"></param>
private void ShiftAndInsert(T item, int index, int maxIndex)
{
if (maxIndex >= _maxSize)
{
maxIndex = _maxSize - 1;
}
for (int i = maxIndex; i > index; i--)
{
_items[i] = _items[i - 1];
}
_items[index] = item;
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)_items).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _items.GetEnumerator();
}
}
static void Main(string[] args)
{
TopNStructure<double> data = new TopNStructure<double>(25);
Random rand = new Random(132151);
for (int i = 0; i < 50; i++)
{
double value = rand.NextDouble();
data.Add(value);
}
int j = 0;
foreach (double value in data)
{
Console.WriteLine("{0} {1}", j, value);
j++;
}
Console.ReadKey();
}
答案 3 :(得分:0)
在迭代它们之前简单地对前k个元素进行排序可能是值得的。如果k足够小(比小于元素总数的一半),那么这将比维护一个你永远不会查询的排序列表便宜。
查看预先构建的部分排序方法,例如STL的部分排序。
创建所有元素的排序列表将在O(n log n)中完成,以进行基于比较的排序,无论它是否在运行中。部分排序会略好一些。
答案 4 :(得分:0)
这是我的第一个类似的数据结构,这次使用内部链接列表来提高插入速度。你失去了一些速度,因为你不能使用二进制搜索来找到插入点,但插入和删除(项目&gt; n)是O(1)并且应该平衡缺少二进制搜索。此结构使用更多内存,因为链接列表有2个额外的指针。
public class TopNStructureLinkedList<T> : IEnumerable<T> where T : IComparable<T>
{
private const int SizeForLinearOrBinaryInsert = 20;
private int _maxSize;
private int _currentSize;
private LinkedList<T> _items;
private IComparer<T> _comparer;
private LinkedListNode<T> _largestItemNode;
/// <summary>
/// The number of items
/// </summary>
public int Count { get { return _currentSize; } }
public TopNStructureLinkedList(int maxSize, IComparer<T> comparer)
{
_maxSize = maxSize;
_currentSize = 0;
_items = new LinkedList<T>();
_comparer = comparer;
_largestItemNode = null;
}
public TopNStructureLinkedList(int maxSize)
: this(maxSize, Comparer<T>.Default) { }
/// <summary>
/// Adds an item to this structure
/// </summary>
/// <param name="item">The item to add</param>
/// <returns>True if the item was added, false otherwise</returns>
public bool Add(T item)
{
if (_currentSize == 0)
{
_largestItemNode = _items.AddFirst(item);
}
else if (_currentSize == 1)
{
if (_comparer.Compare(_largestItemNode.Value, item) <= 0)
{
_largestItemNode = _items.AddAfter(_largestItemNode, item);
}
else
{
_items.AddBefore(_largestItemNode, item);
}
}
else if (_currentSize == _maxSize)
{
if (_comparer.Compare(_largestItemNode.Value, item) <= 0)
{
return false;
}
else
{
Insert(item);
_largestItemNode = _items.Last.Previous;
_items.RemoveLast();
return true;
}
}
else
{
if (_comparer.Compare(_largestItemNode.Value, item) <= 0)
{
_largestItemNode = _items.AddAfter(_largestItemNode, item);
}
else
{
Insert(item);
}
}
_currentSize++;
return true;
}
/// <summary>
/// Insert the item into the data structure
/// </summary>
/// <param name="item">The item to insert</param>
private void Insert(T item)
{
LinkedListNode<T> node = _largestItemNode.Previous;
while (node != null)
{
if(_comparer.Compare(node.Value, item) <= 0) {
_items.AddAfter(node, item);
return;
}
node = node.Previous;
}
_items.AddFirst(item);
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)_items).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _items.GetEnumerator();
}
}
答案 5 :(得分:0)
如果内存不是问题,最好还是对整个数组进行局部排序。即使您使用O(log n)插入数据结构,您可以通过这种方式实现的最佳复杂性是O(n log k):n插入成本为O(log k)。
使用Selection Algorithm查找数组的k个顶部元素会给出O(k log n)的复杂度。 k小于n,所以它更好。
维基百科上的QuickSelect文章有一个实现。此外,使用纯PriorityQueue(以不同的方式,这里提到的大多数人)更容易。一目了然:
create_heap(array) // O(n)
for(int i=0; i<k; i++)
sorted[i] = heap_pop(array) //O(log n)