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.如果你們有好的辦法,請告知.

通過學習鏈表發現,隊列可以使用環形鏈表來實現.關於鏈表請參考隨筆.這里因為MS已經提供了API,所以這里不想繼續下去了.如果你理解了鏈表的原理,通過他來實現隊列和棧很簡單.但是可能無法實現MS原生的隊列那樣的效果.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM