3.1.3 #
解答 #
基于无序链表的官方实现:https://algs4.cs.princeton.edu/31elementary/SequentialSearchST.java.html
有序符号表的 API 见书中表 3.1.4(中文版 P230,英文版 P366)。
在官方实现的基础上修改 Put
方法,先找到合适位置再插入新的键值对,保证链表有序。
为方便插入操作,可以使用双向链表作为基础进行实现。
表中同时维护开头和末尾引用,加快获得最值的速度。
代码 #
public class OrderedSequentialSearchSt<TKey, TValue> : ISt<TKey, TValue>, IOrderedSt<TKey, TValue>
where TKey : IComparable<TKey>
{
/// <summary>
/// 符号表结点。
/// </summary>
private class Node
{
public TKey Key { get; set; } // 键。
public TValue Value { get; set; } // 值。
public Node Next { get; set; } // 后继。
public Node Prev { get; set; } // 前驱。
}
private Node _first; // 起始结点。
private Node _tail; // 末尾结点。
private int _n; // 键值对数量。
/// <summary>
/// 大于等于 key 的最小值。
/// </summary>
/// <returns>大于等于 key 的最小值,不存在则返回 <c>default(Key)</c>。</returns>
public TKey Ceiling(TKey key)
{
var pointer = _first;
while (pointer != null && Less(pointer.Key, key))
pointer = pointer.Next;
return pointer == null ? default : pointer.Key;
}
/// <summary>
/// 键 <paramref name="key"/> 在表中是否存在对应的值。
/// </summary>
/// <param name="key">键。</param>
/// <returns>如果存在则返回 <c>true</c>,否则返回 <c>false</c>。</returns>
public bool Contains(TKey key) => Floor(key).Equals(key);
/// <summary>
/// 从表中删去键 <paramref name="key"/> 对应的值。
/// </summary>
/// <param name="key">键。</param>
public void Delete(TKey key)
{
var pointer = _first;
while (pointer != null && !pointer.Key.Equals(key))
pointer = pointer.Next;
if (pointer == null)
return;
Delete(pointer);
}
/// <summary>
/// 从链表中删除结点 <paramref name="node"/>。
/// </summary>
/// <param name="node">待删除的结点。</param>
private void Delete(Node node)
{
var prev = node.Prev;
var next = node.Next;
if (prev == null)
_first = next;
else
prev.Next = next;
if (next == null)
_tail = prev;
_n--;
}
/// <summary>
/// 删除最大的键。
/// </summary>
public void DeleteMax()
{
if (_n == 0)
throw new Exception("ST Underflow");
Delete(_tail);
}
/// <summary>
/// 删除最小的键。
/// </summary>
public void DeleteMin()
{
if (_n == 0)
throw new Exception("ST Underflow");
Delete(_first);
}
/// <summary>
/// 小于等于 Key 的最大值。
/// </summary>
/// <returns>小于等于 <paramref name="key"/> 的最大值。</returns>
public TKey Floor(TKey key)
{
var pointer = _tail;
while (pointer != null && Greater(pointer.Key, key))
pointer = pointer.Prev;
return pointer == null ? default : pointer.Key;
}
/// <summary>
/// 获取键 <paramref name="key"/> 对应的值,不存在则返回 null。
/// </summary>
/// <param name="key">键。</param>
/// <returns><typeparamref name="TKey"/> 对应的值。</returns>
public TValue Get(TKey key)
{
var pointer = _first;
while (pointer != null && Greater(key, pointer.Key))
pointer = pointer.Next;
if (pointer == null)
return default;
if (pointer.Key.Equals(key))
return pointer.Value;
return default;
}
/// <summary>
/// 符号表是否为空。
/// </summary>
/// <returns>为空则返回 <c>true</c>,否则返回 <c>false</c>。</returns>
public bool IsEmpty() => _n == 0;
/// <summary>
/// 获得符号表中所有键的集合。
/// </summary>
/// <returns>符号表中所有键的集合。</returns>
public IEnumerable<TKey> Keys() => _n == 0 ? new List<TKey>() : Keys(_first.Key, _tail.Key);
/// <summary>
/// 获得符号表中 [<paramref name="lo"/>, <paramref name="hi"/>] 之间的键。
/// </summary>
/// <param name="lo">范围起点。</param>
/// <param name="hi">范围终点。</param>
/// <returns>符号表中 [<paramref name="lo"/>, <paramref name="hi"/>] 之间的键。</returns>
public IEnumerable<TKey> Keys(TKey lo, TKey hi)
{
var list = new List<TKey>();
var pointer = _first;
while (pointer != null && Less(pointer.Key, lo))
pointer = pointer.Next;
while (pointer != null && Less(pointer.Key, hi))
{
list.Add(pointer.Key);
pointer = pointer.Next;
}
Debug.Assert(pointer != null, nameof(pointer) + " != null");
if (pointer.Key.Equals(hi))
list.Add(pointer.Key);
return list;
}
/// <summary>
/// 最大的键。
/// </summary>
/// <returns>最大的键,不存在则返回 <c>default(Key)</c>。</returns>
public TKey Max() => _tail == null ? default : _tail.Key;
/// <summary>
/// 最小的键。
/// </summary>
/// <returns>最小的键,不存在则返回 <c>default(Key)</c>。</returns>
public TKey Min() => _first == null ? default : _first.Key;
/// <summary>
/// 向符号表插入键值对,重复值将被替换。
/// </summary>
/// <param name="key">键。</param>
/// <param name="value">值。</param>
public void Put(TKey key, TValue value)
{
Delete(key);
var temp = new Node
{
Key = key,
Value = value,
Prev = null,
Next = null
};
Node left = null, right = _first;
while (right != null && Less(right.Key, temp.Key))
{
left = right;
right = right.Next;
}
Insert(left, right, temp);
if (left == null)
_first = temp;
if (right == null)
_tail = temp;
_n++;
}
/// <summary>
/// 小于 Key 的键的数量。
/// </summary>
/// <returns>小于 Key 的键的数量。</returns>
public int Rank(TKey key)
{
var counter = 0;
var pointer = _first;
while (pointer != null && Less(pointer.Key, key))
{
pointer = pointer.Next;
counter++;
}
return counter;
}
/// <summary>
/// 获得排名为 insert 的键(从 0 开始)。
/// </summary>
/// <param name="k">排名</param>
/// <returns>获得排名为 insert 的键(从 0 开始)。</returns>
public TKey Select(int k)
{
if (k >= _n)
throw new Exception("insert must less than ST size!");
var pointer = _first;
for (var i = 0; i < k; i++)
pointer = pointer.Next;
return pointer.Key;
}
/// <summary>
/// 获得符号表中键值对的数量。
/// </summary>
/// <returns>符号表中键值对的数量。</returns>
public int Size() => _n;
/// <summary>
/// [<paramref name="lo"/>, <paramref name="hi"/>] 之间键的数量。
/// </summary>
/// <param name="lo">范围起点。</param>
/// <param name="hi">范围终点。</param>
/// <returns>[<paramref name="lo"/>, <paramref name="hi"/>] 之间键的数量。</returns>
public int Size(TKey lo, TKey hi)
{
var counter = 0;
var pointer = _first;
while (pointer != null && Less(pointer.Key, lo))
pointer = pointer.Next;
while (pointer != null && Less(pointer.Key, hi))
{
pointer = pointer.Next;
counter++;
}
return counter;
}
/// <summary>
/// 键 <paramref name="a"/> 是否小于 <paramref name="b"/>。
/// </summary>
/// <param name="a">检查是否较小的键。</param>
/// <param name="b">检查是否较大的键。</param>
/// <returns>如果 <paramref name="a"/> 较小则返回 <c>true</c>,否则返回 <c>false</c>。</returns>
private bool Less(TKey a, TKey b) => a.CompareTo(b) < 0;
/// <summary>
/// 键 <paramref name="a"/> 是否大于 <paramref name="b"/>。
/// </summary>
/// <param name="a">检查是否较大的键。</param>
/// <param name="b">检查是否较小的键。</param>
/// <returns>如果 <paramref name="a"/> 较大则返回 <c>true</c>,否则返回 <c>false</c>。</returns>
private bool Greater(TKey a, TKey b) => a.CompareTo(b) > 0;
/// <summary>
/// 将结点 <paramref name="insert"/> 插入到 <paramref name="left"/> 和 <paramref name="right"/> 之间。
/// </summary>
/// <param name="left">作为前驱的结点。</param>
/// <param name="right">作为后继的结点。</param>
/// <param name="insert">待插入的结点。</param>
private void Insert(Node left, Node right, Node insert)
{
insert.Prev = left;
insert.Next = right;
if (left != null)
left.Next = insert;
if (right != null)
right.Prev = insert;
}
}