演算法基礎<一>
Bag(包)
揹包:不支援刪除元素的集合資料型別。
public interface IBag<TItem> : IEnumerable<TItem>, IDisposable
{
bool IsEmpty { get; }
int Length { get; }
void Add(TItem item);
}
陣列實現
public class ResizingArrayBagNet<TItem>:IBag<TItem> { private TItem[] _items; private int _length; public ResizingArrayBagNet() { _items=new TItem[2]; _length = 0; } public IEnumerator<TItem> GetEnumerator() { if (!IsEmpty) { for (var i = 0; i < _length; i++) { yield return _items[i]; } } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public void Dispose() { throw new NotImplementedException(); } protected virtual void Dispose(bool disposing) { if (disposing) { if (!IsEmpty) { for (var i = 0; i < _length ; i++) { _items[i] = default; } } } } public bool IsEmpty => _length == 0; public int Length => _length; public void Add(TItem item) { if(_length==_items.Length) resize(_length*2); _items[_length++] = item; } private void resize(int capacity) { var temitems=new TItem[capacity]; Array.Copy(_items,0,temitems,0,_length); _items = temitems; } }
連結串列下壓棧
public class LinkedBagNet<TItem> : IBag<TItem> { private Node _first; private int _length; private class Node { public TItem Item { set; get; } public Node Next { set; get; } } public LinkedBagNet() { _length = 0; _first = default; } public IEnumerator<TItem> GetEnumerator() { if (!IsEmpty) { var temnode = _first; while (temnode != default) { var item = temnode.Item; temnode = temnode.Next; yield return item; } } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public void Dispose() { Dispose(true); } protected virtual void Dispose(bool disposing) { if (disposing) { if (!IsEmpty) { var temnode = _first; while (temnode != default) { temnode.Item = default; temnode = temnode.Next; } } } } public bool IsEmpty => _length == 0; public int Length => _length; public void Add(TItem item) { Node oldeNode = _first; _first=new Node(); _first.Item = item; _first.Next = oldeNode; _length++; } }
Stack(棧)
棧:後進先出。
遊離:彈出的元素所在的引用必須要置空,不然沒辦法被垃圾回收器回收。
public interface IStack<TItem> : IEnumerable<TItem>, IDisposable
{
bool IsEmpty { get; }
int Length { get; }
void Push(TItem item);
TItem Pop();
}
棧應用:算術表示式求值:
- 將運算元壓入運算元棧
- 將運算子壓入運算棧
- 忽略左括號
- 在遇到右括號時,彈出一個運算子,彈出所需要數量的運算元,並將運算子和運算元的運算結果壓入運算元棧。
陣列實現
public class ResizingArrayStackNet<TItem> : IStack<TItem>
{
private TItem[] _items;
private int _length;
public ResizingArrayStackNet()
{
_items = new TItem[2];
_length = 0;
}
public int Length => _length;
public bool IsEmpty => _length == 0;
private void resize(int capacity)
{
var temitems=new TItem[capacity];
Array.Copy(_items,0,temitems,0,_items.Length);
_items = temitems;
}
public void Push(TItem item)
{
if(_length==_items.Length) resize(2*_length);
_items[_length++] = item;
}
public TItem Pop()
{
if (IsEmpty) return default;
TItem item = _items[--_length] ;
_items[_length] = default;
if(_length>0 && _length==_items.Length/4) resize( _items.Length / 2);
return item;
}
public IEnumerator<TItem> GetEnumerator()
{
if(!IsEmpty)
{
for(var i=0;i<_length;i++)
{
yield return _items[i];
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (!IsEmpty)
{
for (var i = 0; i < _length; i++)
{
_items[i] = default;
}
}
}
}
}
連結串列實現下壓堆疊
public class LinkedStackNet<TItem> : IStack<TItem>
{
private Node _first;
private class Node
{
public TItem Item { set; get; }
public Node Next { set; get; }
}
public LinkedStackNet()
{
_length = 0;
_first = default;
}
public IEnumerator<TItem> GetEnumerator()
{
if (!IsEmpty)
{
var temnode = _first;
while (temnode != default)
{
var item = temnode.Item;
temnode = temnode.Next;
yield return item;
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (!IsEmpty)
{
var temnode = _first;
while (temnode != default)
{
temnode.Item = default;
temnode = temnode.Next;
}
}
}
}
private int _length;
public bool IsEmpty => _length == 0;
public int Length => _length ;
public void Push(TItem item)
{
Node oldfirst = _first;
_first=new Node();
_first.Item = item;
_first.Next = oldfirst;
_length++;
}
public TItem Pop()
{
if (IsEmpty) return default;
var item = _first.Item;//不存在遊離的問題,這個Node被GC回收
_first = _first.Next;
_length--;
return item;
}
}
Queue(佇列)
佇列:先進先出(FIFO)
public interface IQueue<TItem> : IEnumerable<TItem>, IDisposable
{
bool IsEmpty { get; }
int Length { get; }
void Enqueue(TItem item);
TItem Dequeue();
}
陣列實現
public class ResizingArrayQueueNet<TItem> : IQueue<TItem>
{
public ResizingArrayQueueNet()
{
_first = 0;
_last = 0;
_items=new TItem[2];
}
public IEnumerator<TItem> GetEnumerator()
{
if (!IsEmpty)
{
for (var i = _first; i < _last; i++)
{
yield return _items[i];
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (!IsEmpty)
{
for (var i = _first; i < _last; i++)
{
_items[i] = default;
}
_items = null;
}
}
}
private TItem[] _items;
private int _first;
private int _last;
public bool IsEmpty => (_last - _first) == 0;
public int Length => _last-_first;
private void resize(int size)
{
var temitems=new TItem[size];
var temlength = Length;
Array.Copy(_items,_first,temitems,0,Length);
_first = 0;
_last = temlength;
_items = temitems;
}
public void Enqueue(TItem item)
{
if(_last==_items.Length) resize(Length*2);
_items[_last++] = item;
}
public TItem Dequeue()
{
if (IsEmpty) return default;
var item = _items[_first++];
if(Length<_items.Length/4) resize(_items.Length/2);
return item;
}
}
連結串列實現下壓佇列
public class LinkedQueueNet<TItem> : IQueue<TItem>
{
private Node _first;
private Node _last;
private int _length;
public LinkedQueueNet()
{
_first = null;
_last = null;
_length = 0;
}
private class Node
{
public TItem Item { set; get; }
public Node Next { set; get; }
}
public IEnumerator<TItem> GetEnumerator()
{
if (!IsEmpty)
{
var temnode = _first;
while (temnode != default)
{
var item = temnode.Item;
temnode = temnode.Next;
yield return item;
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (!IsEmpty)
{
var temfirst = _first;
while (temfirst != default)
{
temfirst.Item = default;
temfirst = temfirst.Next;
}
_length = 0;
}
}
}
public bool IsEmpty => _length == 0;
public int Length => _length;
public void Enqueue(TItem item)
{
var temnode = _last;
_last = new Node();
_last.Item = item;
_last.Next = null;
if (IsEmpty) _first = _last;
else temnode.Next = _last;
_length++;
}
public TItem Dequeue()
{
if (_length > 0)
{
var temitem = _first.Item;
_first = _first.Next;
_length--;
return temitem;
}
return default;
}
}
排序
比較和交換:
public class AssistSort<T> where T : IComparable<T>
{
public static void Shuffle(T[] seq)
{
int n = seq.Length;
var random = new Random(Guid.NewGuid().GetHashCode());
for (int i = 0; i < n; i++)
{
// choose index uniformly in [0, i]
int r = (int)(random.Next(n));
T swap = seq[r];
seq[r] = seq[i];
seq[i] = swap;
}
}
// does v == w ?
public static bool Eq(T v, T w)
{
if (ReferenceEquals(v, w)) return true; // optimization when reference equal
return v.CompareTo(w) == 0;
}
public static void Exch(T[] seq, int value, int min)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (value > seq.Length - 1 || value < 0)
throw new ArgumentException("value is not in seq");
if (min > seq.Length - 1 || min < 0)
throw new ArgumentException("min is not in seq");
T t = seq[value];
seq[value] = seq[min];
seq[min] = t;
}
public static bool Less(T value, T compare)
{
if (value == null)
throw new ArgumentNullException("value is null");
if (compare == null)
throw new ArgumentNullException("compare is null");
return value.CompareTo(compare) < 0;
}
public static void Show(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
throw new ArgumentException("seq length equal 0");
Console.WriteLine(string.Join(" ", seq));
}
public static bool IsSorted(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
throw new ArgumentException("seq length equal 0");
for (int i = 1; i < seq.Length; i++)
{
if (Less(seq[i], seq[i - 1])) return false;
}
return true;
}
public static bool IsSorted(T[] seq, int lo, int hi)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (seq.Any() || seq.Length < 2)
throw new ArgumentException("seq length is abnormal");
if (lo < 0 || lo > seq.Length - 1 || (lo + 1) == seq.Length)
throw new ArgumentException("lo is abnormal");
if (hi < 0 || hi > seq.Length - 1 || hi < lo)
throw new ArgumentException("hi is abnormal");
for (int i = lo + 1; i <= hi; i++)
if (Less(seq[i], seq[i - 1])) return false;
return true;
}
}
選擇排序
執行時間與初始狀態無關
資料移動是最少的,只用了N次交換,交換次數和陣列的大小是線性關係,
找到剩餘元素中最小數與第一個交換
結論A:對於長度為N的陣列,選擇排序的需要大約N^2/2次比較和N次交換
public class SelectionSort<T> where T : System.IComparable<T>
{
public static void Sort(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
var count = seq.Length;
for (int i = 0; i < count; i++)
{
int min = i;
//找出剩餘項中最小項的位置
for (int j = i + 1; j < count; j++)
{
//if (seq[j].CompareTo(seq[min]) < 0) min = j;
if (AssistSort<T>.Less(seq[j], seq[min])) min = j;
}
//將最小項與min交換
AssistSort<T>.Exch(seq, i, min); //呼叫靜態方法非常消耗效能
//T t = seq[i];
//seq[i] = seq[min];
//seq[min] = t;
}
}
}
插入排序
像打牌一項,將每一張排插入到已經有序的牌中的適當位置。
執行時間取決於元素的初始順序。
結論B:對於隨機不重複的陣列,平均情況下插入排序需要N^2/4次比較和交換。最壞情況下需要N^2/2次交換和交換,最好情況下需要N-1次比較和0次交換。
public class InsertionSort<T> where T : IComparable<T>
{
public static void Sort(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
int count = seq.Length;
for (int i = 0; i < count; i++)
{
for (int j = i; j > 0 && AssistSort<T>.Less(seq[j], seq[j - 1]); j--)
{
AssistSort<T>.Exch(seq, j, j - 1);
}
}
// 5 4 3 1 2
// 4 5 3 1 2
// 4 3 5 1 2
// 3 4 5 1 2
// 1 3 4 5 2
// 1 2 3 4 5
//1 2 4 3 5
//1 2
}
public static void Sort(T[] seq, int lo, int hi)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
if (hi < lo || lo < 0 || hi < 0 || lo > seq.Length || hi > seq.Length)
throw new ArgumentException("Parameter error");
for (int i = lo + 1; i < hi; i++)
{
for (int j = i; j > lo && AssistSort<T>.Less(seq[j], seq[j - 1]); j--)
{
//交換,每個seq[j]相當於被訪問了2次,跟seq[j+1]和seq[j-1]各比較一次
AssistSort<T>.Exch(seq, j, j - 1);
}
}
}
}
插入排序改進減少比較
結論C:插入排序需要的交換操作和陣列中倒置的數量相同,需要比較次數大於等於倒置的數量,小於等於倒置的數量加上陣列的大小再減一。
改進插入排序,只需要在內迴圈中將較大的元素都向右移動而不總是交換兩個元素。這樣訪問陣列的次數能夠減半。
public static class InsertionSortX<T> where T : IComparable<T>
{
public static void Sort(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
int n = seq.Length;
// put smallest element in position to serve as sentinel
int exchanges = 0;
for (int i = n - 1; i > 0; i--)
{
if (AssistSort<T>.Less(seq[i], seq[i - 1]))
{
AssistSort<T>.Exch(seq, i, i - 1);
exchanges++;
}
}
if (exchanges == 0) return;
// insertion sort with half-exchanges
for (int i = 2; i < n; i++)
{
T v = seq[i];
int j = i;
while (AssistSort<T>.Less(v, seq[j - 1]))
{
//右移,seq[j]只跟當前比較的值V比較了1次
seq[j] = seq[j - 1];
j--;
}
seq[j] = v;
}
}
}
插入和選擇比較
插入排序改進二分快速定位
可以看到插入左邊都是已近排號順序的,可以使用二分法快速確定待插入數的位置。
public static class BinaryInsertionSort<T> where T : IComparable<T>
{
public static void Sort(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
int n = seq.Length;
for (int i = 1; i < n; i++)
{
// binary search to determine index j at which to insert a[i]
T v = seq[i];
int lo = 0, hi = i;
while (lo < hi)
{
int mid = lo + (hi - lo) / 2;
if (AssistSort<T>.Less(v, seq[mid])) hi = mid;
else lo = mid + 1;
}
// insetion sort with "half exchanges"
// (insert a[i] at index j and shift a[j], ..., a[i-1] to right)
for (int j = i; j > lo; --j)
seq[j] = seq[j - 1];
seq[lo] = v;
}
}
}
結論D:對於隨機排序的無重複主鍵的陣列,插入排序和選擇排序的執行時間是平方級別的,兩者之比應該是一個較小的常數。
希爾排序
大規模亂序的插入排序很慢,因為它只會交換相鄰的元素,如果最小的元素正好在陣列的盡頭,那麼把它移動到正確位置就需要N-1次移動,希爾排序就是為了解決這個問題,交換不相鄰的元素以對陣列的區域性進行排序。
希爾排序的思想:陣列中任意間隔為h的元素都是有序的,這樣的陣列成為h有序陣列。一個h有序的陣列是h個相互獨立的有序陣列組成的一個數組。
希爾排序高效的原因是:它權衡了子陣列的規模和有序性。
/// <summary>
/// 希爾排序,插入排序的變種
/// 其實就是從h項開始選定,跟之前的h項進行比較交換
/// 然後再縮小h進行重複的插入排序,h縮小的順序,1 4 13 40.。。。
/// 最後一次一定是1,就是插入排序,只不過這時序列已經大部分順序,所以進行了少量交換操作
/// </summary>
/// <typeparam name="T"></typeparam>
public class ShellSort<T> where T : IComparable<T>
{
public static void Sort(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
int count = seq.Length;
int h = 1;
//求出最大限度間隔,
while (h < count / 3) h = 3 * h + 1;//1 4 13 40 121 364 1093
while (h >= 1)
{//將陣列變成h有序
for (int i = h; i < count; i++)
{
//將a[i]插入a[i-h],a[i-2*h],a[i-3*h].....
for (int j = i; j >= h && AssistSort<T>.Less(seq[j], seq[j - h]); j -= h)
{
AssistSort<T>.Exch(seq, j, j - h);
}
}
h = h / 3;
}
//0 1 2 3 4 5 6 h=4
//4:0 5:1 6:2
//h=1
//1:0 2:1 3:2
}
public static void SortPro(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
int count = seq.Length;
int h = 1;
//求出最大限度間隔,
while (h < count / 2) h = 2 * h + 1;//1 3 7 15 31
while (h >= 1)
{//將陣列變成h有序
for (int i = h; i < count; i++)
{
for (int j = i; j >= h && AssistSort<T>.Less(seq[j], seq[j - h]); j -= h)
{
AssistSort<T>.Exch(seq, j, j - h);
}
}
h = h / 2;
}
}
public static void SortPlus(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
int count = seq.Length;
int h = 1;
//求出最大限度間隔,
while (h < count / 4) h = 4 * h + 1;//1 5 21 85
while (h >= 1)
{//將陣列變成h有序
for (int i = h; i < count; i++)
{
for (int j = i; j >= h && AssistSort<T>.Less(seq[j], seq[j - h]); j -= h)
{
AssistSort<T>.Exch(seq, j, j - h);
}
}
h = h / 4;
}
}
}
希爾排序對任意排序的陣列表現都很好。
希爾排序能夠解決一些初級排序演算法無能為力的問題,通過提升速度來解決其他方式無法解決的問題是研究演算法的設計和效能的主要原因。
希爾排序:當一個h有序的陣列按照增幅K排序後,它仍然是h有序的
希爾排序的執行時間不到平方級別,在最後情況下是N^(3/2)
性質E:使用遞增序列1,4,13,40,121,364……的希爾排序所需的比較次數不會超出N的若干倍乘以遞增序列的長度。
如果需要解決一個排序問題,優先先使用希爾,再使用其他,因為它不需要額外的記憶體,其他的高效排序不會比希爾排序快2倍。
歸併排序自頂而下
歸併排序核心思想是:可以先(遞迴地)將它分成兩個陣列f分別排序,然後結果歸併起來。
效能:能夠將任意長度為N的陣列排序所需的時間和NlogN成正比。
分而治之
/// <summary>
/// 歸併排序,自頂而下,使用了分治的思想,NlgN
/// 核心思想就是兩個順序數列合併成一個數列
/// 將數列以中間index為界分為前後,然後遞迴分,直到子陣列只有2個然後合併。
/// 浪費了大量的空間,每次合併都需一個Auxiliary
/// </summary>
/// <typeparam name="T"></typeparam>
public class MergeSortUB<T> where T : IComparable<T>
{
/// <summary>
/// 將數列low-mid和ming-high合併
/// low-mid必須順序
/// mid-high必須順序
/// </summary>
/// <param name="seq">數列</param>
/// <param name="aux">合併需要陣列</param>
/// <param name="lo">low所在index</param>
/// <param name="mid">mind所在index</param>
/// <param name="hi">hi所在index</param>
private static void Merge(T[] seq, T[] aux, int lo, int mid, int hi)//aux:auxiliary備用
{
// 合併前兩個數列必須是順序的,私有方法不需要驗證
//if(!AssistSort<T>.IsSorted(seq, lo, mid))
// throw new ArgumentException("seq is not sort in low to mid");
//if(!AssistSort<T>. IsSorted(seq, mid
//+1, hi))
// throw new ArgumentException("seq is not sort in mid to high");
// 備份
for (int k = lo; k <= hi; k++)
{
aux[k] = seq[k];
}
// 合併演算法,i左半邊開始位,j右半邊開始位
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++)
{
if (i > mid) seq[k] = aux[j++];//左半邊用盡,取右半邊元素
else if (j > hi) seq[k] = aux[i++];//右半邊用盡,取左半邊元素
else if (AssistSort<T>.Less(aux[j], aux[i])) seq[k] = aux[j++];//右半邊元素小於左邊元素,取右半邊元素
else seq[k] = aux[i++];//取左半邊元素
}
}
private static void Sort(T[] seq, T[] aux, int lo, int hi)
{
if (hi <= lo) return;
int mid = lo + (hi - lo) / 2;
Sort(seq, aux, lo, mid);
Sort(seq, aux, mid + 1, hi);
Merge(seq, aux, lo, mid, hi);
}
public static void Sort(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
T[] aux = new T[seq.Length];
Sort(seq, aux, 0, seq.Length - 1);
}
}
通過一棵樹來解釋歸併的效能,每個節點都標識通過Merge()方法並歸成的子陣列,這棵樹正好n層,k表示在K層,K層有2^K個節點,每個節點的長度為2^(n-k),那麼並歸最多需要2^(n-k)次比較。那麼每層需要比較次數2^K*2^(n-k)=2^n,n層總共為n*2^n=NlgN;
命題F:對於長度為N的任意陣列,自頂向下的並歸需要1/2NlgN至NlgN次比較。
命題G:對於長度為N的任意陣列,自頂向下的歸併排序最多需要訪問陣列6NlgN次。
歸併統計交換次數
/// <summary>
/// 統計交換次數,自頂而下並歸方法
/// </summary>
/// <typeparam name="T"></typeparam>
public class Inversion<T> where T : IComparable<T>
{
public static long Count(T[] seq)
{
T[] b = new T[seq.Length];
T[] aux = new T[seq.Length];
for (int i = 0; i < seq.Length; i++)
b[i] = seq[i];
long inversions = Count(seq, b, aux, 0, seq.Length - 1);
return inversions;
}
private static long Count(T[] a, T[] b, T[] aux, int lo, int hi)
{
long inversions = 0;
if (hi <= lo) return 0;
int mid = lo + (hi - lo) / 2;
inversions += Count(a, b, aux, lo, mid);
inversions += Count(a, b, aux, mid + 1, hi);
inversions += Merge(b, aux, lo, mid, hi);
if (inversions != Brute(a, lo, hi))
throw new Exception("inversion is not right");
return inversions;
}
private static long Merge(T[] a, T[] aux, int lo, int mid, int hi)
{
long inversions = 0;
// copy to aux[]
for (int k = lo; k <= hi; k++)
{
aux[k] = a[k];
}
// merge back to a[]
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++)
{
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (AssistSort<T>.Less(aux[j], aux[i])) { a[k] = aux[j++]; inversions += (mid - i + 1); }
else a[k] = aux[i++];
}
return inversions;
}
private static long Brute(T[] a, int lo, int hi)
{
long inversions = 0;
for (int i = lo; i <= hi; i++)
for (int j = i + 1; j <= hi; j++)
if (AssistSort<T>.Less(a[j], a[i])) inversions++;
return inversions;
}
}
歸併排序小陣列用插值排序
使用插入排序處理小規模陣列(長度小於15),一般可以將歸併排序的執行時間減少10%~15%
歸併排序改進a[mid]小於等於a[mid+1]
這樣使得任意有序的子陣列演算法執行時間線性
歸併排序不復制元素到輔助陣列
歸併排序自底向上
/// <summary>
/// 並歸排序,自底而上,使用了分治的思想,NlgN
/// </summary>
/// <typeparam name="T"></typeparam>
public class MergeSortBU<T> where T : IComparable<T>
{
/// <summary>
/// 將數列low-mid和ming-high合併
/// low-mid必須順序
/// mid-high必須順序
/// </summary>
/// <param name="seq">數列</param>
/// <param name="aux">合併需要陣列</param>
/// <param name="lo">low所在index</param>
/// <param name="mid">mind所在index</param>
/// <param name="hi">hi所在index</param>
private static void Merge(T[] seq, T[] aux, int lo, int mid, int hi)
{
// copy to aux[]
for (int k = lo; k <= hi; k++)
{
aux[k] = seq[k];
}
// merge back to a[]
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++)
{
if (i > mid) seq[k] = aux[j++]; // this copying is unneccessary
else if (j > hi) seq[k] = aux[i++];
else if (AssistSort<T>.Less(aux[j], aux[i])) seq[k] = aux[j++];
else seq[k] = aux[i++];
}
}
public static void Sort(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
int n = seq.Length;
T[] aux = new T[n];
for (int len = 1; len < n; len *= 2) // 1 2 4 8 16
{
for (int lo = 0; lo < n - len; lo += len + len)
{
int mid = lo + len - 1;
int hi = Math.Min(lo + len + len - 1, n - 1);
Merge(seq, aux, lo, mid, hi);
}
}
}
}
命題H:對於長度為N的任意陣列,自底向上歸併排序需要1/2NlgN至NlgN次比較,最多訪問陣列6NlgN次。
當陣列長度為2的冪的時候,自頂向下和自底向上的歸併排序所用的比較次數和陣列訪問次數正好相同。
自底向上的歸併排序比較適合連結串列組織的資料。這種方法只需要重新組織連結串列的連結就能將連結串列原地排序
自頂向下:化整為零。
自下向上:循序漸進。
命題I:沒有任何基於比較的演算法能夠保證使用少於lg(N!)~NlgN次比較將長度為N的陣列排序。
命題J:歸併排序是一種漸進最有的基於比較排序的演算法
快速排序
快速排序和歸併排序是互補的,快速排序方式當兩個子陣列都有序時,整個資料就自然有序了。
/// <summary>
/// 快速演算法應該是並歸演算法的一個變種,並歸算是找中點,一切為二
/// 快速演算法也是分治演算法的一種,怎麼將序列切分呢,哪個相當於中點的數是這樣的,這個數的前面都比這個數小,這個數的後面都比這個數大,
/// 這樣不斷切分,最後自然切分完自然序列排序好了。
/// 如果有大量重複的資料這個實現有點問題
/// </summary>
/// <typeparam name="T"></typeparam>
public static class QuickSort<T> where T : IComparable<T>
{
public static void Sort(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
Sort(seq, 0, seq.Length - 1);
}
private static void Sort(T[] seq, int lo, int hi)
{
if (hi <= lo) return;
int j = Partition(seq, lo, hi);
Sort(seq, lo, j - 1);
Sort(seq, j + 1, hi);
}
/// <summary>
/// 切分
/// a[lo..hi] => a[lo..j-1] = a[j] = a[j+1..hi]
/// </summary>
/// <param name="seq"></param>
/// <param name="lo"></param>
/// <param name="hi"></param>
/// <returns></returns>
private static int Partition(T[] seq, int lo, int hi)
{
int i = lo;
int j = hi + 1;
T v = seq[lo];
//確保index k之前的數都比其小,index k之後的數都比其大
while (true)
{
// 找到第一個比第一項大的數index叫largeindex
while (AssistSort<T>.Less(seq[++i], v))
{
if (i == hi) break;
}
// 找到最後一個比第一項小的數index,litindex
while (AssistSort<T>.Less(v, seq[--j]))
{
if (j == lo) break; // redundant since a[lo] acts as sentinel
}
// 檢查第一個和最後一個是否交叉了
if (i >= j) break;
//將第一個項和最後一個項交換,確保比第一項小的數再第一項前面
AssistSort<T>.Exch(seq, i, j);
}
//這個切分點就是第一個數
AssistSort<T>.Exch(seq, lo, j);
// 現在, a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
return j;
}
}
交換部分
可能出現的問題:
- 原地切分:使用輔助陣列,很容易實現切分,但切分後的陣列複製回去的開銷會比較多。
- 別越界:
- 保持隨機性:
- 終止迴圈:
- 處理切分元素值的重複情況
- 終止遞迴
效能優勢:
- 用一個遞增的索引將陣列元素和一個定值比較。很難有排序演算法比這更小的內迴圈。
- 快速排序的比較次數比較少。
- 快速排序最好的情況是:每次都正好將陣列對半分,
命題K:將長度為N的無重複陣列排序,快速排序平均需要2NlnN次比較。
命題L:快速排序最多需要約N^2/2次比較,但隨機打亂陣列能夠預防這種情況。每次切分找到的切分值都是最大值或最小值
快速排序該進熵最優
如果有大量重複的元素,將陣列切成三份,小於,等於和大於
/// <summary>
/// 該演算法主要針對序列中有大量相同的項,將序列分為三部分,小於,等於,大於
/// </summary>
/// <typeparam name="T"></typeparam>
public class QuickSort3WaySort<T> where T : IComparable<T>
{
private static void Sort(T[] seq, int lo, int hi)
{
if (hi <= lo) return;
//切分的lt,gt
int lt = lo, gt = hi;
T v = seq[lo];
int i = lo + 1;
//切分
while (i <= gt)
{
int cmp = seq[i].CompareTo(v);
if (cmp < 0) AssistSort<T>.Exch(seq, lt++, i++);
else if (cmp > 0) AssistSort<T>.Exch(seq, i, gt--);
else i++;
}
//切分完成
// a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi].
Sort(seq, lo, lt - 1);
Sort(seq, gt + 1, hi);
}
public static void Sort(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
//AssistSort<T>.Shuffle(seq);
Sort(seq, 0, seq.Length - 1);
}
}
快速排序改進三取樣切分並小陣列插入
取子陣列的中位數為切分點,取樣大小s設為3並用大小s設為3並用大小居中的元素切分的效果最好。
問題:
- 小陣列時插入排序比快速排序要快
- 因為遞迴,快速排序sort()方法在小陣列中也會呼叫自己。
簡單操作將
if(hi <= lo) return;
替換成
if(hi <= lo+M) { Insertion.Sort(a,lo,hi); return;}
/// <summary>
/// 快速演算法的痛點是在小序列的排序效能並不好,所有該X實現在小序列的時候排序採用插入排序演算法
/// </summary>
/// <typeparam name="T"></typeparam>
public class QuickSortX<T> where T : IComparable<T>
{
private static readonly int INSERTION_SORT_CUTOFF = 8;
private static void Sort(T[] seq, int lo, int hi)
{
if (hi <= lo) return;
// cutoff to insertion sort (Insertion.sort() uses half-open intervals)
int n = hi - lo + 1;
if (n <= INSERTION_SORT_CUTOFF)//少量陣列插入排序
{
InsertionSort<T>.Sort(seq, lo, hi + 1);
return;
}
int j = Partition(seq, lo, hi);
Sort(seq, lo, j - 1);
Sort(seq, j + 1, hi);
}
// partition the subarray a[lo..hi] so that a[lo..j-1] <= a[j] <= a[j+1..hi]
// and return the index j.
private static int Partition(T[] seq, int lo, int hi)
{
int n = hi - lo + 1;
int m = Median3(seq, lo, lo + n / 2, hi);//三取樣點,lo,中點,hi
AssistSort<T>.Exch(seq, m, lo);
int i = lo;
int j = hi + 1;
T v = seq[lo];
// a[lo] is unique largest element
while (AssistSort<T>.Less(seq[++i], v))
{
if (i == hi) { AssistSort<T>.Exch(seq, lo, hi); return hi; }
}
// a[lo] is unique smallest element
while (AssistSort<T>.Less(v, seq[--j]))
{
if (j == lo + 1) return lo;
}
// the main loop
while (i < j)
{
AssistSort<T>.Exch(seq, i, j);
while (AssistSort<T>.Less(seq[++i], v)) ;
while (AssistSort<T>.Less(v, seq[--j])) ;
}
// put partitioning item v at a[j]
AssistSort<T>.Exch(seq, lo, j);
// now, a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
return j;
}
private static int Median3(T[] seq, int i, int j, int k)
{
return (AssistSort<T>.Less(seq[i], seq[j]) ?
(AssistSort<T>.Less(seq[j], seq[k]) ? j : AssistSort<T>.Less(seq[i], seq[k]) ? k : i) :
(AssistSort<T>.Less(seq[k], seq[j]) ? j : AssistSort<T>.Less(seq[k], seq[i]) ? k : i));
}
public static void Sort(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
//AssistSort<T>.Shuffle(seq);
Sort(seq, 0, seq.Length - 1);
}
}
命題M:不存在任何基於比較的排序演算法能夠保證在NH-N次比較之內將N個元素排序,
命題N:三向切分的快速排序需要(2ln2)NH次比較
快速排序中型陣列排序
/// <summary>
/// 該演算法進一步升級,不僅在小序列排序時實現插入排序,在中型序列實現三分排序。
/// </summary>
/// <typeparam name="T"></typeparam>
public class QuickBentleyMcIlroySort<T> where T : IComparable<T>
{
// cutoff to insertion sort, must be >= 1
private static readonly int INSERTION_SORT_CUTOFF = 8;
// cutoff to median-of-3 partitioning
private static readonly int MEDIAN_OF_3_CUTOFF = 40;
/// <summary>
/// Rearranges the array in ascending order, using the natural order.
/// </summary>
/// <param name="seq"></param>
public static void Sort(T[] seq)
{
if (seq == null)
throw new ArgumentNullException("seq is null");
if (!seq.Any())
return;
Sort(seq, 0, seq.Length - 1);
}
private static void Sort(T[] seq, int lo, int hi)
{
int n = hi - lo + 1;
// cutoff to insertion sort
if (n <= INSERTION_SORT_CUTOFF)
{
InsertionSort(seq, lo, hi);
return;
}
// use median-of-3 as partitioning element
else if (n <= MEDIAN_OF_3_CUTOFF)
{
int m = Median3(seq, lo, lo + n / 2, hi);
AssistSort<T>.Exch(seq, m, lo);
}
// use Tukey ninther as partitioning element
else
{//其實就是擴大切分點的尋找範圍,最有希望找到中間點,達到最有的快速排序
int eps = n / 8;
int mid = lo + n / 2;
int m1 = Median3(seq, lo, lo + eps, lo + eps + eps);
int m2 = Median3(seq, mid - eps, mid, mid + eps);
int m3 = Median3(seq, hi - eps - eps, hi - eps, hi);
int ninther = Median3(seq, m1, m2, m3);
AssistSort<T>.Exch(seq, ninther, lo);
}
// Bentley-McIlroy 3-way partitioning
int i = lo, j = hi + 1;
int p = lo, q = hi + 1;
T v = seq[lo];
while (true)
{
while (AssistSort<T>.Less(seq[++i], v))
if (i == hi) break;
while (AssistSort<T>.Less(v, seq[--j]))
if (j == lo) break;
// pointers cross
if (i == j && AssistSort<T>.Eq(seq[i], v))
AssistSort<T>.Exch(seq, ++p, i);
if (i >= j) break;
AssistSort<T>.Exch(seq, i, j);
if (AssistSort<T>.Eq(seq[i], v)) AssistSort<T>.Exch(seq, ++p, i);
if (AssistSort<T>.Eq(seq[j], v)) AssistSort<T>.Exch(seq, --q, j);
}
i = j + 1;
for (int k = lo; k <= p; k++)
AssistSort<T>.Exch(seq, k, j--);
for (int k = hi; k >= q; k--)
AssistSort<T>.Exch(seq, k, i++);
Sort(seq, lo, j);
Sort(seq, i, hi);
}
// sort from a[lo] to a[hi] using insertion sort
private static void InsertionSort(T[] seq, int lo, int hi)
{
for (int i = lo; i <= hi; i++)
for (int j = i; j > lo && AssistSort<T>.Less(seq[j], seq[j - 1]); j--)
AssistSort<T>.Exch(seq, j, j - 1);
}
// return the index of the median element among a[i], a[j], and a[k]
private static int Median3(T[] seq, int i, int j, int k)
{
return (AssistSort<T>.Less(seq[i], seq[j]) ?
(AssistSort<T>.Less(seq[j], seq[k]) ? j : AssistSort<T>.Less(seq[i], seq[k]) ? k : i) :
(AssistSort<T>.Less(seq[k], seq[j]) ? j : AssistSort<T>.Less(seq[k], seq[i]) ? k : i));
}
}