1. 程式人生 > >C# 算法系列一基本資料結構

C# 算法系列一基本資料結構

一、簡介

作為一個程式設計師,演算法是一個永遠都繞不過去的話題,雖然在大學裡參加過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.如果你們有好的辦法,請告知.