數據結構基礎溫故-1.線性表(上)

開篇:線性表是最簡單也是在編程當中使用最多的一種數據結構。例如,英文字母表(A,B,C,D...,Z)就是一個線性表,表中的每個英文字母都是一個數據元素;又如,成績單也是一個線性表,表中的每一行是一個數據元素,每一個數據元素又由學號、姓名、成績等數據項組成。順序表鏈表做爲線性表的兩種重要的存在形式,它們是堆棧、隊列、樹、圖等數據結構的實現基礎。html

1、線性表基礎

1.1 線性表的基本定義

  線性表:零個或多個數據元素的有限序列。線性表中的元素在位置上是有序的,相似於儲戶去銀行排隊取錢,人們依次排着隊,排在前面的先取,排在後面的則後取。這種位置上的有序性就是一種線性關係。由此能夠看出:線性表的先後兩個元素存在一一對應關係編程

PS:須要注意的是,這種先後關係是邏輯意義上而非物理意義上的,就比如若是銀行作了改革,使用排隊機進行排隊,全部儲戶分散在銀行的各個角落,他們取錢的順序是根據儲戶從排隊機獲取的紙條上的號碼來決定的。數組

1.2 線性表的存儲結構

  (1)順序表

  線性表的順序存儲結構是指【用一塊地址連續的存儲空間依次存儲線性表中的數據元素】。就好像咱們剛剛提到的改革以前的銀行,須要在業務窗口前排隊等候辦理。由此能夠看出:在順序表中,邏輯上相鄰的元素在物理上也是相鄰的安全

  (2)鏈表

  相比順序表須要預先佔用一塊事先分配好的存儲空間,鏈表就靈活一些。鏈表中邏輯上相鄰的元素在物理上能夠不相鄰。這就好像改革以後的銀行,人們辦理業務的順序是由手上的小紙條的號碼來決定。在某些特定場合,鏈表的使用優先於順序表。數據結構

2、順序表基礎

2.1 靜態順序表之數組

  在平常編程中,在處理一組數據時,最常使用的數據類型就是數組。它是線性表的順序存儲結構在程序語言中最直接的表現形式性能

  數組是最基礎也是存取速度最快的一種集合類型,在.NET中它是引用類型,也就是說它所需的內存空間會在託管堆上分配,一旦數組被建立,其中的全部元素會被初始化爲它們的默認值。this

PS:另外須要注意的是,當數組元素爲值類型時,數組對象存放的是值類型對象自己。而當元素爲引用類型時,數組對象存放的則是對象的引用(指針)。spa

  (1)數組元素爲值類型時:指針

int[] arrInt = new int[5];
arrInt[2] = 5;
arrInt[4] = 3;

  下圖展現了上面的數組arrInt在內存(這裏如未說明都指在.NET中的內存分配)中的分配形式,能夠看到值類型數組在被建立的同時就擁有了默認值0。code

  (2)數組元素爲引用類型時:

// System.Windows.Forms.Control
Control[] arrCtrl = new Control[5];
arrCtrl[0] = new Button();
arrCtrl[3] = new Label();

  下圖則展現了上面的數組arrCtrl在內存中的分配,能夠看到在託管堆中劃分了一塊可以存放5個指針的內存區域,而且每一個元素都被初始化爲null。若是某個元素被賦值,那麼會存放一個指向實際對象存儲區域的指針。

總結:數組優勢不少,缺點也很明顯:在實際編程中,沒法動態改變集合的大小

2.2 動態順序表之ArrayList與List<T>

  若是須要動態地改變數組所佔用的內存空間的大小,則須要以數組爲基礎作進一步的抽象以實現這個功能。在C#中,ArrayList被稱爲動態數組,它的存儲空間能夠被動態地改變,同時還有添加、刪除元素的功能。

  (1)簡單好用但不是類型安全的ArrayList

  ①Add-添加新元素

        // 在數組末尾順序添加指定元素
        public virtual int Add(object value)
        {
            // 當容量達到最大值時
            if (this._size == this._items.Length)
            {
                // 調整存儲空間大小
                this.EnsureCapacity(this._size + 1);
            }

            this._items[this._size] = value;
            return this._size++;
        }    

  能夠看到,在添加新元素時會進行數組容量的判斷,若是達到最大值則會調用方法動態調整數組大小。

  ②RemoveAt-移除指定元素

        // 移除指定索引的元素
        public virtual void RemoveAt(int index)
        {
            if (index < 0 || index > this._size)
            {
                throw new ArgumentOutOfRangeException("index", "索引超過範圍");
            }
            // 插入位置後的元素向前移動一位
            for (int i = index + 1; i < this._size; i++)
            {
                this._items[i - 1] = this._items[i];
            }

            this._size--;
            this._items[this._size] = null; // 最後一位空出的元素置爲空
        }

  能夠看到,在移除老元素時會進行大量的元素移動操做。這裏的ArrayList採用的元素類型是object,因此最後將空出的元素置爲null。

  ③EnsureCapacity-動態調整數組大小

        // 動態調整數組空間大小
        private void EnsureCapacity(int min)
        {
            if (this._items.Length < min)
            {
                // 新空間大小=原空間大小*2
                int num = (this._items.Length == 0) ?
                    _defaultCapacity : (this._items.Length * 2);
                if (num < min)
                {
                    num = min;
                }
                // 調整數組空間大小
                this.Capacity = num;
            }
        }

  事實上,內存空間一旦分配是沒有辦法更改大小的。ArrayList其實使用「搬家」的方法來實現這個功能的,即當房子住不下這麼多人的時候,那麼換一個更大的新房子就好了。這裏,ArrayList須要擴容時,會在內存空間中開闢一塊新區域,容量爲原來的2倍,並把全部元素都複製到新內存空間中。

  (2).NET2.0出現的泛型版本:List<T>

  因爲ArrayList實際存放的是object對象(在.NET中object是萬物之宗,即全部類型的父類),在進行存取操做時須要進行大量的裝箱和拆箱操做(若是你不知道裝箱和拆箱,那麼請閱讀.NET中六個重要的概念),下降程序性能。因而,從.NET 2.0開始出現了泛型版本的List<T>,它完美取代了ArrayList。

List<int> intNumList = new List<int>();
intNumList.Add(1);
intNumList.Add(2);

int num1 = intNumList[0];
int num2 = intNumList[1];

  能夠看到,在集合建立的時候就把元素類型限定爲int類型,它是安全的,而且還避免了裝箱和拆箱操做。所以,在實際編程中通常都會使用List<T>。

3、.NET中的ArrayList與List<T>

  在.NET中已經爲咱們提供了兩個強有力的順序表結構類型,咱們能夠經過Reflector來查看其具體實現。

3.1 ArrayList的實現

  經過查看源碼,其關鍵就在於EnsureCapacity方法動態地調整數組大小。

3.2 List<T>的實現

  經過查看源碼,其關鍵就在於使用了泛型,而其餘的方法如Add、Remove以及EnsureCapacity都和ArrayList沒有多大區別。

參考資料

(1)程傑,《大話數據結構》

(2)陳廣,《數據結構(C#語言描述)》

(3)段恩澤,《數據結構(C#語言版)》

 

相關文章
相關標籤/搜索