05|数组:为什么很多编程语言中数组都从0开始编号?
1 # 1.数组的插入、删除、按照下标随机访问操作; 2 # 2.数组中的数据类型是Int 3 # 4 # Author:Lee 5 6 class Array(): 7 8 def __init__(self): 9 '''数组类初始化方法.''' 10 self.__data = [] # 数据存储List 11 12 def find(self, index): 13 '''数组的查找方法. 14 参数: 15 index:将要查找的数据的下标 16 返回: 17 如果查找成功,则返回找到的数据 18 如果查找失败,则返回False 19 ''' 20 if index > len(self.__data) or index < 0: 21 return False 22 else: 23 return self.__data[index] 24 25 def delete(self, index): 26 '''数组的删除方法. 27 参数: 28 index:将要删除的数据的下标 29 返回: 30 如果删除成功,则返回True 31 如果删除失败,则返回False 32 ''' 33 if index > len(self.__data) or index < 0: 34 return False 35 else: 36 self.__data.pop(index) 37 return True 38 39 def insert(self, index, value): 40 '''数组插入数据操作. 41 参数: 42 index:将要插入的下标 43 value:将要插入的数据 44 返回: 45 如果插入成功,则返回True 46 如果插入失败,则返回False 47 ''' 48 if index > len(self.__data) or index < 0: 49 return False 50 else: 51 self.__data.insert(index, value) 52 return True 53 54 def insertToTail(self, value): 55 '''直接在数组尾部插入数据. 56 参数: 57 value:将要插入的数据 58 ''' 59 self.__data.append(value) 60 61 def printAll(self): 62 '''打印当前数组所有数据''' 63 print(self.__data)
1 # 1) Insertion, deletion and random access of array 2 # 2) Assumes int for element type 3 # 4 # Author: Wenru 5 # 6 7 8 class MyArray: 9 """A simple wrapper around List. 10 You cannot have -1 in the array. 11 """ 12 13 def __init__(self, capacity: int): 14 self._data = [] 15 self._capacity = capacity 16 17 def __getitem__(self, position: int) -> object: 18 return self._data[position] 19 20 def __setitem__(self, index: int, value: object): 21 self._data[index] = value 22 23 def __len__(self) -> int: 24 return len(self._data) 25 26 def __iter__(self): 27 for item in self._data: 28 yield item 29 30 def find(self, index: int) -> object: 31 try: 32 return self._data[index] 33 except IndexError: 34 return None 35 36 def delete(self, index: int) -> bool: 37 try: 38 self._data.pop(index) 39 return True 40 except IndexError: 41 return False 42 43 def insert(self, index: int, value: int) -> bool: 44 if len(self) >= self._capacity: 45 return False 46 else: 47 return self._data.insert(index, value) 48 49 def print_all(self): 50 for item in self: 51 print(item) 52 53 54 def test_myarray(): 55 array = MyArray(5) 56 array.insert(0, 3) 57 array.insert(0, 4) 58 array.insert(1, 5) 59 array.insert(3, 9) 60 array.insert(3, 10) 61 assert array.insert(0, 100) is False 62 assert len(array) == 5 63 assert array.find(1) == 5 64 assert array.delete(4) is True 65 array.print_all() 66 67 68 if __name__ == "__main__": 69 test_myarray()
数组不仅仅是一种编程语言中的数据类型,还是一种最基础的数据结构。
如何实现随机访问?
数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
# 关键词 第一是线性表(Linear List)。顾名思义,线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。 第二个是连续的内存空间和相同类型的数据。正是因为这两个限制,它才有了一个堪称“杀手锏”的特性:“随机访问”。

# 数组是如何实现根据下标随机访问数组元素的? 计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。当计算机需要随机访问数组中的某个元素时,它会首先通过下面的寻址公式,计算出该元素存储的内存地址: a[i]_address = base_address + i * data_type_size 其中 data_type_size 表示数组中每个元素的大小。我们举的这个例子里,数组中存储的是 int 类型数据,所以 data_type_size 就为 4 个字节。
注意:正确的表述应该是,数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)。
# 注意: 这里我要特别纠正一个“错误”。我在面试的时候,常常会问数组和链表的区别,很多人都回答说,“链表适合插入、删除,时间复杂度 O(1);数组适合查找,查找时间复杂度为 O(1)”。 实际上,这种表述是不准确的。数组是适合查找操作,但是查找的时间复杂度并不为 O(1)。即便是排好序的数组,你用二分查找,时间复杂度也是 O(logn)。所以,正确的表述应该是,数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)。
低效的“插入”和“删除”
最好情况时间复杂度为 O(1): 如果在数组的末尾插入(或删除)元素,那就不需要移动数据了,这时的时间复杂度为 O(1)。
最坏情况时间复杂度为 O(n): 但如果在数组的开头插入(或删除)元素,那所有的数据都需要依次往后(或前)移动一位,所以最坏时间复杂度是 O(n)。
平均情况时间复杂度也为 O(n): 因为我们在每个位置插入(或删除)元素的概率是一样的,所以平均情况时间复杂度为 (1+2+…n)/n=O(n)。
警惕数组的访问越界问题
C 语言中 代码中的数组越界可以访问非法地址
容器能否完全替代数组?
ArrayList 最大的优势就是可以将很多数组操作的细节封装起来。它还有一个优势,就是支持动态扩容。
数组本身在定义的时候需要预先指定大小,因为需要分配连续的内存空间。如果我们申请了大小为 10 的数组,当第 11 个数据需要存储到数组中时,我们就需要重新分配一块更大的空间,将原来的数据复制过去,然后再将新的数据插入。
如果使用 ArrayList,我们就完全不需要关心底层的扩容逻辑,ArrayList 已经帮我们实现好了。每次存储空间不够的时候,它都会将空间自动扩容为 1.5 倍大小。
解答开篇
现在我们来思考开篇的问题:为什么大多数编程语言中,数组要从 0 开始编号,而不是从 1 开始呢?
从数组存储的内存模型上来看,“下标”最确切的定义应该是“偏移(offset)”。前面也讲到,如果用 a 来表示数组的首地址,a[0]就是偏移为 0 的位置,也就是首地址,a[k]就表示偏移 k 个 type_size 的位置,所以计算 a[k]的内存地址只需要用这个公式:a[k]_address = base_address + k * type_size
浙公网安备 33010602011771号