数据结构学习笔记(四):重识数组(Array)
目錄
1 數組通過索引訪問元素的原理
1.1 內存空間的連續性
1.2 數據類型的同一性
2 數組與鏈表增刪查操作特性的對比
2.1 數組與鏈表的共性與差異
2.2 數組與鏈表增刪查特性差異的原理
3 數組與容器的關系
1 數組通過索引訪問元素的原理
數組是用一組連續的內存空間存儲一組具有類型相同的數據的線性序列,其元素包括數據和索引(或稱“下標”)兩大要素。內存空間的連續性和數據類型的同一性是數組得以實現通過索引值快速找到元素值的前提條件。
1.1 內存空間的連續性
顯然易見的是,數組的索引值以0為基礎不斷累加,在我們看不到的內存空間,數組元素的內存地址也是這樣一種累加的關系,只要有了數組首個元素的首地址,后面元素的內存地址都以此為基礎。
了解了這個原理后,就不難理解為什么數組的索引要從0開始。基于日常經驗,我們會將數組的索引看作元素的“編號”,然而究其本質,索引標記的是元素內存地址相對于首地址的偏移(offset)情況,第一個進入數組的元素偏移量是0,第二個進入的是1,第三個是2,以此類推。
當然了,把索引定義為元素“編號”也不是不可以,這樣做雖然減少了我們的思維負擔,但卻增加了系統的運算負擔,系統在通過我們輸入的“編號”尋找數據時,都需要做一次減法運算得出與首地址的偏移量來定位元素。數組的最大優勢就在于隨機訪問元素的極致效率,把“編號”作為索引無疑是背離了這一數據結構追求訪問效率的設計初衷。
1.2 數據類型的同一性
數組元素的數據類型同一性的要求在理論上很好理解,因為不同數據類型的內存占用一般不同,比如在Java的四種數值型數據:byte占1字節,short占2字節,int占4字節,long占8字節。如果我們聲明一個了容量為10的int型數組,那么系統就會分配一個40字節的空間;如果數據類型不同,分配空間時就會有諸多不便。
當然了,Java中也可以定義一個Object類型的數組,往里面存入不同類型的數據,如果我們直接打印這個數組,會發現相比指定了基本數據類型的數組,多打印了一個奇怪的東西:
package com.notes.data_structure4;public class ArrayDemo {public static void main(String[] args) {int[] array1 = {1,2,3,4,5};Object[] array2 = {1,2,"a","b","c"};System.out.println(array1);System.out.println(array2);} }打印結果如下。我們可以這樣理解,array2是一個存儲了對象的數組,字符串和整數被統一成了對象這樣一個類型。
?
2 數組與鏈表增刪查操作特性的對比
2.1 數組與鏈表的共性與差異
在不同的知識體系中,有的將數組和鏈表、隊列、棧一起劃為線性表的下位概念;有的將鏈表、棧和隊列看作線性表,將數組看作與線性表不同的數據結構類型。兩種體系各有優劣,前者著眼于這四種數據結構在數據組織方面的共性,后者著眼于數組與另外三種結構在增刪查特性上的區別。
基于數組和鏈表、棧、隊列一樣都是線性序列,那么數組固然也能被稱為“線性表”。然而,鏈表、棧、隊列之間的差異只是“量”的差異,即增刪操作的靈活度不同;數組和另外三種數據結構的差異則屬于“質”的差異,增刪查特性完全相反。因此,我們傾向于把鏈表、棧和隊列統一命名為線性表,把數組看作與線性表不同的另一種數據結構。關于鏈表、棧和隊列的描述,可以參考“數據結構學習筆記”系列的其他文章,鏈接貼在下面:
鏈表:https://blog.csdn.net/weixin_45370422/article/details/116573863
棧:https://blog.csdn.net/weixin_45370422/article/details/116889212
隊列:https://blog.csdn.net/weixin_45370422/article/details/117376241
由于棧和隊列都是操作受限的線性表,并不適宜直接與數組對比,往往以鏈表作為觀察數組增刪查特性的一面鏡子。對于二者的差異,通常的描述是:鏈表增刪快、查找慢;數組增刪慢、查找快。但是,這種說法有不嚴謹之處。第一、增刪要根據操作位置的分情況討論,數組在末端新增元素的能力和鏈表沒有區別,只有在非末端位置功能才遜于鏈表;第二、查找也有多種類型,數組只是在基于位置的查找上有明顯優勢,這種查找也叫隨機訪問(random access),在基于數值的查找上相對于鏈表沒有任何優勢。
因此,嚴謹的描述是:數組在序列的非末端位置新增和刪除元素的功能弱于鏈表,隨機訪問元素的功能強于鏈表。
2.2 數組與鏈表增刪查特性差異的原理
雖然鏈表和數組都是線性的有序結構,但是如何保持數據的“有序”,鏈表和數組選擇了不同的解決方案。
鏈表依靠的是指針,指針存放的是排在當前結點后面的那個結點的內存地址;數組依靠的是索引,索引標記的是當前元素的內存地址相對于首元素地址的偏移量。兩相對比,我們不難得出這樣的認識:鏈表中除了鏈頭結點,每個結點位置只與它的上一個結點有關;數組中的每一個元素的索引值都與首元素呈線性正相關,即數組元素的位置表達為與基準的相對量,因此我們能夠輕松地通過這個相對量快速地定位到元素,從而實現快速隨機訪問。
在之前的博文中,我們用Java代碼實現的棧和隊列都特意定義了基于位置查找的方法,比如棧里面定義的get(int distance)方法,參數為元素與棧頂的距離,這實際上就是一種隨機訪問。對于鏈表、鏈式棧和鏈式隊列,唯一的辦法是定義一個可移動的指針,一個元素一個元素的找過去,直至到達指定位置。基于數組實現的順序棧和順序隊列,倒是可以用索引來直接定位,不過為了凸顯指針的作用,我們還是寫成了指針移動的方式。
再來看在序列非末端位置的增刪操作。鏈表中結點的關系是兩兩直接相關,增刪操作是通過在特定位置改變結點間的指向關系實現的,這種改變不會對其他數據的指向關系產生影響。如下圖所示:
?
數組就不同了,由于數組中的元素位置只跟首元素有關,因此在中間新增和刪除元素,至少“牽一發而動半身”。自發生增刪操作的位置起,其后所有元素與首元素的偏移量都要重新計算,假設一個容量為n的數組,在索引為k的元素前面新增一個元素,那么新增的元素索引成為k,原來索引為k的元素及其后面的元素索引都要加1,即原來k-n索引的元素全部要后移一位;刪除操作也是同樣的道理,在索引為k的元素前刪除一個元素,刪除后原來k-n索引元素全部要前移一位。
?
3 數組與容器的關系
在大部分的高級編程語言中,都會設計一個容器類封裝數組的操作細節,Java的ArrayList、Python的List都是這樣的封裝好了的容器。與Java不同的是,Python沒有內置對數組的支持,但Java可以直接聲明一個數組。
那么問題來了,既然Java已經有了功能完善的ArrayList,那么為什么不能跟Python一樣取消對數組的支持,用ArrayList完全取代數組不就可以了?況且ArrayList還在其底層實現好了動態擴容功能,初始容量為10,存入的數據超過這個容量,數組自動擴容1.5倍。但是原生的數組也并非一無是處,在有些場景中也有一些的優勢。
如果數據量是確定的,且對數據的操作也不復雜,那么完全可以選擇使用數組來實現。在上一篇筆記“隊列”中實現過一個循環隊列,循環隊列存儲的數據數量就是固定的,如果我們想用循環隊列做一個約瑟夫環這樣的小練習,那大可不必選用ArrayList。
還有一點是,ArrayList指定數據類型時不支持基本數據類型,比如我們不能指定int,而只能指定其包裝類Integer。在想使用基本數據類型的場景下,也可以優先考慮數組,而不是ArrayList。
?
總結
以上是生活随笔為你收集整理的数据结构学习笔记(四):重识数组(Array)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python enumeration_如
- 下一篇: svn中项目管理中ec_Mac中使用sv