Lyf凤

靡不有初,鲜克有终

扩大
缩小

05|数组:为什么很多编程语言中数组都从0开始编号?

Python 语言代码实现:代码 1 代码 2

 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 # 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()
代码 2

 

数组不仅仅是一种编程语言中的数据类型,还是一种最基础的数据结构。

如何实现随机访问?

数组(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

历史原因:C 语言设计者用 0 开始计数数组下标,之后的 Java、JavaScript 等高级语言都效仿了 C 语言。

 

posted on 2020-05-07 15:01  Lyf凤  阅读(167)  评论(0)    收藏  举报

导航

levels of contents