演算法基礎<二> 堆
堆
堆:當一棵二叉樹的每個結點都大於等於它的兩個子結點時,它被稱為堆有序。
命題O:根結點是堆有序的二叉樹中的最大結點
二叉堆:一組能夠用堆有序的完全的二叉樹排序的元素,並在陣列中按照層級儲存(不使用陣列的第一個位置)。
命題P:一棵大小為N的完全二叉樹的高度為lgN。
堆有序上浮
由下至上的堆有序(上浮)實現
private void swim(int k)
{
while(k>1&&less(k/2,k))
{
exch(k/2,k);
k=k/2;
}
}
堆有序下沉
由上至下的堆有序(下沉)實現。
命題R:用下沉操作由N個元素構造堆只需少於2N次比較以及小於N次交換。
private void sink(int k)
{
while(2*k <= N)
{
int j=2*k;
if(j<N&&less(j,j+1)) j++;//找到子結點中較大的那個
if(!less(k,j)) break;,//根節點應該比較大的那個大,否則就需要交換。
exch(k,j);
k=j;//一直交換到最底層
}
}
堆有序排序
public class HeapSortNet<TItem> where TItem : IComparable<TItem> { ///注意這邊有一個Bug忽略了陣列的第一個元素。下面的優先佇列消除了這個Bug public static void Sort(TItem[] items) { int n = items.Length; // 左邊堆構造 for (int k = n / 2; k >= 1; k--) Sink(items, k, n);//K的值需要比N的大 // 右邊堆排序 while (n > 1) { Exch(items, 1, n--);//將最大的放到最後 Sink(items, 1, n);//將最大之前的那個找出來放到第一個 } } /// <summary> /// 下沉 /// </summary> /// <param name="items"></param> /// <param name="k"></param> /// <param name="n"></param> private static void Sink(TItem[] items, int k, int n) { while (2 * k <= n) { int j = 2 * k; if (j < n && Less(items, j, j + 1)) j++; if (!Less(items, k, j)) break; Exch(items, k, j); k = j; } } private static bool Less(TItem[] items, int i, int j) { return items[i - 1].CompareTo(items[j - 1]) < 0; } private static void Exch(TItem[] items, int i, int j) { TItem swap = items[i - 1]; items[i - 1] = items[j - 1]; items[j - 1] = swap; } }
命題S:將N個元素排序,堆排序只需要少於(2NlgN+2N)次比較(以及一半次數的交換)
改進:大多數在下沉排序期間重新插入堆的元素會被直接加入到堆底。
Floyd在1964年觀察發現,可以通過免去檢查元素是否到達正確位置來節省時間,
下沉中總是直接提升較大的子節點直至到達堆底,然後再使元素上浮到正確的位置,可以將比較次數減少一半
唯一能夠同時最優地利用空間和時間的方法,最壞情況下也能保證~2NlgN次比較和恆定的額外空間。在嵌入式或底成本的移動裝置中很流行,無法利用快取,很少於其他元素進行比較。
優先佇列
就是用堆的方式排列的陣列。支援查詢最大最小元素和插入元素
最大優先佇列
最大優先佇列:支援刪除最大元素和插入元素
基於堆的優先佇列:儲存在陣列[1……N]中,陣列[0]沒有使用。
命題Q:對於一個含有N個元素的基於堆的優先佇列,插入元素操作只需要不超過(lgN+1)次比較,刪除最大元素的操作需要不超過2lgN次比較。
/// <summary>
/// 優先佇列
/// </summary>
/// <typeparam name="TItem"></typeparam>
public interface IMaxHeapQueue<TItem> : IEnumerable<TItem>, IDisposable
{
bool IsEmpty { get; }
int Length { get; }
void Insert(TItem x);
TItem DeleteMax();
TItem Max();
}
public class MaxPQNet<TItem> : IMaxHeapQueue<TItem> where TItem : IComparable<TItem>
{
private TItem[] _items;
private int _length;
private IComparer<TItem> _comparer;
public MaxPQNet(int capacity)
{
_items=new TItem[capacity+1];
_length = 0;
}
public MaxPQNet():this(1)
{
}
public MaxPQNet(int capacity, IComparer<TItem> comparer):this(capacity)
{
_comparer = comparer;
}
public MaxPQNet(TItem[] items)
{
_length = items.Length;
_items = new TItem[items.Length + 1];
for (int i = 0; i < _length; i++)
_items[i + 1] = items[i];
for (int k = _length / 2; k >= 1; k--)
sink(k);
}
private void sink(int k)
{
while (k*2<=_length)
{
int j = 2 * k;
if (j < _length && less(j, j + 1)) j++;
if(!less(k,j)) break;
exch(k,j);
k = j;
}
}
private void swim(int k)
{
while (k > 1 && less(k / 2, k))
{
exch(k,k/2);
k = k / 2;
}
}
private bool less(int i, int j)
{
if (_comparer == null)
{
return _items[i].CompareTo(_items[j]) < 0;
}
else
{
return _comparer.Compare(_items[i], _items[j]) < 0;
}
}
private void exch(int i, int j)
{
var item = _items[i];
_items[i] = _items[j];
_items[j] = item;
}
private void resize(int capacity)
{
if(capacity<_length)
throw new ConfigurationException("this capacity is error less than Length");
var tempitems = new TItem[capacity];
for (int i = 1; i < _length+1; i++)
{
tempitems[i] = _items[i];
}
_items = tempitems;
}
public IEnumerator<TItem> GetEnumerator()
{
if (!IsEmpty)
{
var temitem = new TItem[_length-1];
for (int i = 0; i < _length; i++)
{
temitem[i] = _items[i + 1];
}
var temmaxpq=new MaxPQ<TItem>(temitem);
while (!temmaxpq.IsEmpty())
{
yield return temmaxpq.DelMax();
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (!IsEmpty)
{
for (int i = 1; i < _length+1; i++)
{
_items[i] = default;
}
}
}
}
public void Dispose()
{
Dispose(true);
}
public bool IsEmpty => Length == 0;
public int Length => _length;
public void Insert(TItem item)
{
if(_length==_items.Length-1) resize(_length*2);
_items[++_length] = item;
swim(_length);
}
public TItem DeleteMax()
{
if (IsEmpty) return default;
var item = _items[1];
exch(1,_length--);//把最小值交換到第一個重新堆排序
_items[_length + 1] = default;
sink(1);
if(_length>0&&_length==(_items.Length-1)/4) resize(_items.Length/2);
return item;
}
public TItem Max()
{
if (IsEmpty)
return default;
return _items[1];
}
}
堆上優先佇列
最小優先佇列
根結點是最小值,其他相同
public class MinPQNet<TItem> : IMinHeapQueue<TItem> where TItem : IComparable<TItem>
{
private TItem[] _items;
private int _length;
private IComparer<TItem> _comparer;
public MinPQNet(int capacity)
{
_items = new TItem[capacity + 1];
_length = 0;
}
public MinPQNet() : this(1)
{
}
public MinPQNet(int capacity, IComparer<TItem> comparer) : this(capacity)
{
_comparer = comparer;
}
public MinPQNet(TItem[] items)
{
_length = items.Length;
_items = new TItem[items.Length + 1];
for (int i = 0; i < _length; i++)
_items[i + 1] = items[i];
for (int k = _length / 2; k >= 1; k--)
sink(k);
}
private void sink(int k)
{
while (k * 2 <= _length)
{
int j = 2 * k;
if (j < _length && greater(j, j + 1)) j++;
if (!greater(k, j)) break;
exch(k, j);
k = j;
}
}
private void swim(int k)
{
while (k > 1 && greater(k / 2, k))
{
exch(k, k / 2);
k = k / 2;
}
}
private bool greater(int i, int j)
{
if (_comparer == null)
{
return _items[i].CompareTo(_items[j]) > 0;
}
else
{
return _comparer.Compare(_items[i], _items[j]) > 0;
}
}
private void exch(int i, int j)
{
var item = _items[i];
_items[i] = _items[j];
_items[j] = item;
}
private void resize(int capacity)
{
if (capacity < _length)
throw new ConfigurationException("this capacity is error less than Length");
var tempitems = new TItem[capacity];
for (int i = 1; i < _length + 1; i++)
{
tempitems[i] = _items[i];
}
_items = tempitems;
}
public IEnumerator<TItem> GetEnumerator()
{
if (!IsEmpty)
{
var temitem = new TItem[_length - 1];
for (int i = 0; i < _length; i++)
{
temitem[i] = _items[i + 1];
}
var temmaxpq = new MinPQ<TItem>(temitem);
while (!temmaxpq.IsEmpty())
{
yield return temmaxpq.DelMin();
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (!IsEmpty)
{
for (int i = 1; i < _length + 1; i++)
{
_items[i] = default;
}
}
}
}
public void Dispose()
{
Dispose(true);
}
public bool IsEmpty => Length == 0;
public int Length => _length;
public void Insert(TItem item)
{
if (_length == _items.Length - 1) resize(_length * 2);
_items[++_length] = item;
swim(_length);
}
public TItem DeleteMin()
{
if (IsEmpty) return default;
var item = _items[1];
exch(1, _length--);//把最小值交換到第一個重新堆排序
_items[_length + 1] = default;
sink(1);
if (_length > 0 && _length == (_items.Length - 1) / 4) resize(_items.Length / 2);
return item;
}
public TItem Min()
{
if (IsEmpty)
return default;
return _items[1];
}
}
索引優先佇列
可以快速訪問已經插入佇列中的項。
不會改變插入佇列中項的資料結構
命題Q(續):在一個大小為N的索引優先佇列中,插入(insert)元素,改變優先順序,刪除,刪除最小元素和刪除最大元素操作所需要的比較次數和LogN成正比。
示例為最大索引優先佇列,簡單改寫就可以變成最小索引優先佇列
/// <summary>
/// 增加一個key,key對應一個索引,實際交換排序是控制key對應的索引
/// </summary>
/// <typeparam name="TItem"></typeparam>
public class KeyMaxPQNet<TItem> : IMaxKeyHeap<TItem> where TItem:IComparable<TItem>
{
private int _length;
private int _capacity;
/// <summary>
/// key對應的索引
/// </summary>
private int[] _keysIndex;
/// <summary>
/// 索引對應的key
/// </summary>
private int[] _indexesKey;
/// <summary>
/// key對應的item
/// </summary>
private TItem[] _items;
public bool IsEmpty => _length == 0;
public int Length => _length;
public int Capacity => _capacity;
public KeyMaxPQNet(TItem[] items)
{
if (items == null || !items.Any()) throw new ArgumentNullException("items is empty");
_length = 0;
_capacity = items.Length;
_items = new TItem[_capacity + 1];
_keysIndex = new int[_capacity + 1];
_indexesKey = new int[_capacity + 1];
for (int i = 0; i <= _capacity; i++)
{
_keysIndex[i] = -1; //初始化所有key對應的index都是-1,index對應的key都是0
}
foreach (var item in items)
{
this.Insert(item);
}
}
public KeyMaxPQNet(int capacity)
{
if(capacity<0) throw new ArgumentException("this argument capacity is error and can not less zero");
this._capacity = capacity;
_length = 0;
_items=new TItem[capacity+1];
_keysIndex=new int[capacity+1];
_indexesKey=new int[capacity+1];
for (int i = 0; i <= capacity;i++)
{
_keysIndex[i] = -1;//初始化所有key對應的index都是-1,index對應的key都是0
}
}
public void Insert(TItem item)
{
var key = _length+1;
Insert(key,item);
}
public void Insert(int key, TItem item)
{
validateKey(key);
if (!Contains(key))
throw new ArgumentException("this index has existed");
_length++;
_keysIndex[key] = _length;
_indexesKey[_length] = key;
_items[key] = item;
swim(_length);
}
/// <summary>
/// if Empty,throw exception
/// </summary>
/// <returns></returns>
public TItem DeleteMax()
{
if (IsEmpty) throw new NullReferenceException("this is empty queue");
int max = _indexesKey[1];
var item = _items[max];
exch(1,_length--);
sink(1);
_keysIndex[max] = -1;
_items[max] = default;
_indexesKey[_length + 1] = -1;
return item;
}
public void Delete(int key)
{
validateKey(key);
if(!Contains(key)) throw new ArgumentException("this index is not in this queue");
var k = _keysIndex[key];
exch(k,_length--);//這邊已經排除最後一項
swim(k);
sink(k);
_items[key] = default;
_keysIndex[key] = -1;
}
public void Replace(int key, TItem item)
{
validateKey(key);
if(!Contains(key)) throw new NullReferenceException("this key not in queue");
_items[key] = item;
swim(_keysIndex[key]);
sink(_keysIndex[key]);
}
public TItem KeyOf(int key)
{
validateKey(key);
if(!Contains(key)) return default;
return _items[key];
}
public int MaxKey()
{
if(IsEmpty)
throw new ArgumentException("this is null queue");
return _indexesKey[1];
}
public TItem Max()
{
if(IsEmpty)
throw new ArgumentException("this is null queue");
return _items[_indexesKey[1]];
}
public bool Contains(int key)
{
validateKey(key);
return _keysIndex[key] != -1;
}
public IEnumerator<TItem> GetEnumerator()
{
if (!IsEmpty)
{
var queue=new KeyMaxPQNet<TItem>(_items);
for (int i = 1; i <= _items.Length; i++)
{
yield return queue.DeleteMax();
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
while (!IsEmpty)
{
DeleteMax();
}
}
}
private bool less(int indexi,int indexj)
{
return _items[_indexesKey[indexi]].CompareTo(_items[_indexesKey[indexj]]) < 0;
}
private void validateKey(int key)
{
if (key < 0 || key > _capacity) throw new ArgumentException("this key is invalid");
}
private void swim(int index)
{
while (index>1 && less(index/2,index))
{
exch(index, index / 2);
index = index / 2;
}
}
private void sink(int index)
{
while (2 * index <= _length)
{
int indexj = 2 * index;
if (indexj < _length && less(indexj, indexj + 1)) indexj++;
if (!less(index, indexj)) break;
exch(index, indexj);
index = indexj;
}
}
private void exch(int indexi, int indexj)
{
//不需要交換實際陣列只需要交換序好對應的key,和key對應的序號
int swap = _indexesKey[indexi];
_indexesKey[indexi] = _indexesKey[indexj];
_indexesKey[indexj] = swap;
_keysIndex[_indexesKey[indexi]] = indexi;
_keysIndex[_indexesKey[indexj]] = indexj;
}
}
}
排序演算法應用
多向並歸
索引優先佇列用例解決多向歸併問題:它將多個有序的輸入流歸併成一個有序的輸出流。
輸入可能來自於多種科學儀器的輸出(按時間排序)
多個音樂網站或電影網站的資訊列表(名稱或藝術家名字)
商業交易(按時間排序)
使用優先佇列,無論輸入有多長都可以把它們全部讀入並排序
交易資料按照日期排序
商業計算
資訊搜尋
運籌學:排程問題,負載均衡
事件驅動模擬
數值計算:浮點數來進行百萬次計算,一些數值的計算使用優先佇列和排序來計算精確度,曲線下區域的面積,數值積分方法就是使用一個優先佇列來儲存一組小間隔中每段的近似精確度。積分的過程就是山區精確度最低的間隔並將其分為兩半。
組合搜尋:一組狀態演化成另一組狀態可能的步驟和步驟的優先順序。
Prim演算法和Dijkstra演算法:
Kruskal演算法:
霍夫曼壓縮:
字串處理:
排序特點
性質T:快速排序是最快的通用排序演算法。
對原始資料型別使用(三向切分)快速排序,對引用型別使用歸併排序。
命題U:平均來說,基於切分的選擇演算法的執行時間是線性級別的。