一、簡介
作為一個程序員,算法是一個永遠都繞不過去的話題,雖然在大學里參加過ACM的比賽,沒記錯的話,浙江賽區倒數第二,后來不知怎么的,就不在Care他了,但是現在后悔了,非常的后悔!!!如果當時好好學算法的話,現在去理解一些高深的框架可能會很easy,現在隨着C#基礎和Web技能的提升,發現哪里都用到算法,但是,很無奈.所以,從今天開始,要重新對自己定位,不能做一個工具的使用者.起碼要做到知其所以然.好了,廢話不多說,算法之旅,算是正式開始了.希望這個過程能貫穿我的整個職業生涯.甚至整個人生.
二、隊列
關於隊列,不多說,只要做了一兩年程序員,對他肯定不陌生,可以說哪里都有他.關於他的概念也很簡單.類似於我們生活中的排隊打飯,當然先排隊的肯定先打到飯.專業術語叫做先進先出.下面用基於object數組的C#實現,代碼如下:
/// <summary> /// 自定義隊列 /// </summary> public class Queue { private object[] _array; /// <summary> /// 隊列頭 /// </summary> private int _head; /// <summary> /// 隊列尾 /// </summary> private int _tail; /// <summary> /// 當前數組的長度 /// </summary> private int _size; /// <summary> /// 使用默認構造函數時,給定隊列默認的長度4 /// </summary> public Queue() : this(4) { } /// <summary> /// 初始化指定容量的隊列 /// </summary> /// <param name="capacity"></param> public Queue(int capacity) { if (capacity < 0) { throw new Exception("初始容量不能小於0"); } _array = new object[capacity]; _head = 0; _tail = 0; _size = 0; } /// <summary> /// 入隊 /// </summary> public virtual void Enqueue(object obj) { _array[_tail] = obj; _tail = _tail + 1; _size++; } /// <summary> /// 出隊 /// </summary> /// <returns></returns> public virtual object Dequeue() { if (Count == 0) { throw new InvalidOperationException("當前隊列為空,無法執行Dequeue操作"); } object result = _array[_head]; _array[_head] = null; _head = _head + 1; _size--; return result; } /// <summary> /// 當前隊列的長度 /// </summary> public int Count { get { return _size; } } }
控制台調用代碼如下:
class Program { static void Main(string[] args) { var q = new Queue(); q.Enqueue(1); q.Enqueue(2); q.Enqueue(3); q.Enqueue(4); Console.WriteLine("出隊:{0},{1},{2},{3}", q.Dequeue(), q.Dequeue(), q.Dequeue(), q.Dequeue()); Console.ReadKey(); } }
先進先出,但是有問題,上面給定初始長度為4,所以全局數組的長度為4,當你調用Equeue方法5次,數組會報溢出錯誤,所以,如果當前隊列的長度等於我們給它的初始值時,必須進行一個數組的Copy操作,將當前數組拷貝到一個容量更大的數組中去,這里MS采用的算法時,每次乘以2的遞增.修改代碼如下:
/// <summary> /// 自定義隊列 /// </summary> public class Queue { private object[] _array; /// <summary> /// 隊列頭 /// </summary> private int _head; /// <summary> /// 隊列尾 /// </summary> private int _tail; /// <summary> /// 當前數組的長度 /// </summary> private int _size; /// <summary> /// 使用默認構造函數時,給定隊列默認的長度32 /// </summary> public Queue() : this(4) { } /// <summary> /// 初始化指定容量的隊列 /// </summary> /// <param name="capacity"></param> public Queue(int capacity) { if (capacity < 0) { throw new Exception("初始容量不能小於0"); } _array = new object[capacity]; _head = 0; _tail = 0; _size = 0; } /// <summary> /// 入隊 /// </summary> public virtual void Enqueue(object obj) { if (_array.Length == _size) { int capacity = _array.Length * 2; SetCapacity(capacity); } _array[_tail] = obj; _tail = _tail + 1; _size++; } /// <summary> /// 出隊 /// </summary> /// <returns></returns> public virtual object Dequeue() { if (Count == 0) { throw new InvalidOperationException("當前隊列為空,無法執行Dequeue操作"); } object result = _array[_head]; _array[_head] = null; _head = _head + 1; _size--; return result; } /// <summary> /// 當前隊列的長度 /// </summary> public int Count { get { return _size; } } /// <summary> /// 重新設定原始數組的容量 /// </summary> private void SetCapacity(int capacity) { var newArray = new object[capacity]; Array.Copy(_array,newArray, _array.Length); _array = newArray; _head = 0; _tail = _size; } }
ok,現在每次都會以原數組*2的長度擴展原始數組,但是還是有問題,如果這中間存在出隊,實際的_size會減一,但是數組實際的長度還是為原來的,區別就是出隊的那個元素的位置會被設置為null,會存在以下bug:
var q = new Queue(); q.Enqueue(1); q.Dequeue(); q.Enqueue(2); q.Enqueue(3); q.Enqueue(4); q.Enqueue(5); Console.WriteLine("出隊:{0},{1},{2},{3}", q.Dequeue(), q.Dequeue(), q.Dequeue(), q.Dequeue()); Console.ReadKey();
出隊,導致_size-1,但是原始數組的長度還是為4,千萬不要說,Dequeue的時候,讓第一個元素的內存釋放數組長度變為3,這是不可能的,至少我不知道.所以,這里還需要對算法進行改進.好了,到這里我就做不下去了,看了MS的實現,估計是對數組對了特殊的內存處理,沒有辦法處理出隊后第一個元素為null,但是它還是會算到計算長度里面去,如果引入新的變量去計算實際的長度,不用說,m目測會有內存浪費!mmp.如果你們有好的辦法,請告知.
通過學習鏈表發現,隊列可以使用環形鏈表來實現.關於鏈表請參考隨筆.這里因為MS已經提供了API,所以這里不想繼續下去了.如果你理解了鏈表的原理,通過他來實現隊列和棧很簡單.但是可能無法實現MS原生的隊列那樣的效果.