Вот моя реализация MaxHeap. В большинстве случаев это работает, но иногда просто вызывает сбой некоторых значений. Я хочу, чтобы это было потокобезопасным. Пытался отладить много раз, но не смог определить проблему. Кто-нибудь может указать на проблемы в моей реализации?
using System.Threading;
using Crossbones.Common;
using LanguageExt;
using static LanguageExt.Prelude;
namespace MySpace
{
public static class Operators
{
public static bool XOR(bool x, bool y) => (x && !y) || (!x && y);
public static bool XAND(bool x, bool y) => (x || !y) && (!x || y);
}
public enum CompareResult
{
Left = 1,
Equal = 0,
Right = -1
};
public delegate CompareResult HeapComparer<T>(in T left, in T right);
public class Heap<T> where T : struct
{
private readonly int _capacity;
private readonly IComparer<T> _comparer;
private readonly List<T> _list;
private long _itemCount;
public Heap(int capacity, IComparer<T> comparer)
{
_capacity = capacity;
_comparer = comparer;
_list = new List<T>(capacity);
}
public Option<T> Pop()
{
Option<T> head = None;
lock (_list)
{
if (_list.Any())
{
head = Some(_list[0]);
_list[0] = _list[^1];
_list.RemoveAt(_list.Count - 1);
Count = _list.Count;
ShiftDown(0);
}
}
return head;
}
public void Push(in T item)
{
lock (_list)
{
_list.Add(item);
var itemIdx = _list.Count - 1;
while (itemIdx > 0)
{
var parentIdx = (int)(itemIdx - 1) / 2;
if (Compare(parentIdx, itemIdx) == CompareResult.Right)
{
Swap(parentIdx, itemIdx);
itemIdx = parentIdx;
}
else break;
}
Count = _list.Count;
}
}
public bool IsEmpty => Count ==0;
public bool IsFull => Count == _capacity;
public long Count
{
get => Interlocked.Read(ref _itemCount);
private set => Interlocked.Exchange(ref _itemCount, value);
}
public Option<T> Peek(int position)
{
Option<T> ret = None;
if (position < Count - 1)
{
lock (_list)
{
ret = Some(_list[position]);
}
}
return ret;
}
public IEnumerable<T> ReadAll()
{
var ret = new List<T>((int)Count);
lock (_list)
{
ret = _list;
_list.Clear();
Count = 0L;
}
return ret;
}
void Swap(int leftIdx, int rightIdx)
{
var tmp = _list[leftIdx];
_list[leftIdx] = _list[rightIdx];
_list[rightIdx] = tmp;
}
CompareResult Compare(int idxLeft, int idxRight) => _comparer.Compare(_list[idxLeft], _list[idxRight]) switch
{
-1 => CompareResult.Right,
0 => CompareResult.Equal,
1 => CompareResult.Left
};
int ShiftDown(int itemIdx)
{
var ret = itemIdx;
var maxIdx = _list.Count - 1;
while (itemIdx < maxIdx)
{
var left = (index: 2 * itemIdx + 1, present: (2 * itemIdx + 1) <= maxIdx);
var right = (index: 2 * itemIdx + 2, present: (2 * itemIdx + 2) <= maxIdx);
if (left.present && right.present)
{
var target = Compare(left.index, right.index) switch
{
CompareResult.Left => left,
CompareResult.Right => right,
CompareResult.Equal => left
};
if (target.present)
{
Swap(target.index, itemIdx);
itemIdx = target.index;
}
else break;
}
else if (Operators.XOR(left.present, right.present))
{
var target = left.present ? left : right.present ? right : (index: itemIdx, present: false);
if (target.present && Compare(target.index, itemIdx) == CompareResult.Left)
{
Swap(target.index, itemIdx);
itemIdx = target.index;
}
else itemIdx = target.index;
}
else break;
}
return ret;
}
}
}
Я использую LanguageExt.Core для некоторых функциональных вещей. в основном вариант, некоторые, нет.
Я держу счет отдельно, чтобы чтение Count и IsEmpty , IsFull было атомарной операцией.
2 ответа
Логика для ShiftDown
слишком сложна. Когда присутствуют left
и right
, нет необходимости проверять target.present
. Вы уже знаете, что target
присутствует, поскольку это может быть только left
или right
.
И в случае, когда они оба не присутствуют, вы знаете, что left
это тот, который присутствует. В надлежащей куче, поскольку он заполнен слева, у узла не может быть правого потомка, если у него нет левого потомка.
Гораздо проще реализация ниже. Я сохранил возвращаемое значение, хотя не понимаю, зачем вам нужен этот метод для возврата значения. Вы, конечно, не используете его в своем коде.
int ShiftDown(int itemIdx)
{
var ret = itemIdx;
var maxIdx = _list.Count - 1;
while (itemIdx < maxIdx)
{
var largestChild = itemIdx;
var leftIdx = 2*itemIdx + 1;
if (leftIdx > maxIdx)
{
// node has no children
break;
}
// left child exists. See if it's larger than its parent.
if (_list[itemIdx > _list[itemIdx])
{
largestChild = leftIdx;
}
var rightIdx = leftIdx + 1;
if (rightIdx <= maxIdx && _list[rightIdx] > _list[largestChild])
{
// right child exists and is larger than current largest child.
largestChild = rightIdx;
}
if (largestChild != itemIdx)
{
Swap(itemIdx, largestChild);
itemIdx = largestChild;
}
}
return ret;
}
Я могу указать на несколько вопросов:
Count
,get => Interlocked.Read(ref _itemCount);
Вы не используете замок. В результате это может легко вернуть неправильное значение, если поток вызывает его, пока другой находится вPush
, только между добавлением нового элемента и обновлением счетчика. То же самое касаетсяPop
. Следовательно,IsFull
иIsEmpty
также потерпят неудачу.Peek
- вы читаетеCount
вне блокировки, поэтому сначала решаете, что в куче есть элемент, и пытаетесь его получить, но его еще нет.ReadAll
выглядит разбитым. Если я не ошибаюсь, он всегда вернет пустой список. Вы правильно пытаетесь вернуть копию, но я не вижу, чтобы вы делали копию. Вы копируете ссылку.
В зависимости от ваших вариантов использования вы можете посмотреть и ReaderWriterLockSlim вместо lock
.
Похожие вопросы
Новые вопросы
c#
C# (произносится как «see Sharp») — это высокоуровневый мультипарадигменный язык программирования со статической типизацией, разработанный Microsoft. Код C# обычно нацелен на семейство инструментов и сред выполнения Microsoft .NET, которое включает в себя .NET, .NET Framework, .NET MAUI и Xamarin среди прочих. Используйте этот тег для ответов на вопросы о коде, написанном на C#, или о формальной спецификации C#.