最近开始学习王争老师的《数据结构与算法之美》,通过总结再加上自己的思考的形式记录这门课程,文章主要作为学习历程的记录。
一、如何实现随机访问
数组是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。(对这句话进行解释,引出了以下几个概念)
1.线性表:数组,队列,栈,链表
非线性表:树和图
2.连续的内存空间和相同类型的数据
由于这两个限制,若想在数组中删除或插入一个数据,为了保证连续性,就需要做大量的数据搬移工作。
将链表与数组进行对比,可以得出结论:链表适合插入和删除,时间复杂度O(1);数组适合查找操作,但查找时间复杂度并不为O(1)。即便是排好序的数组,用二分查找,时间复杂度也是O(logn)。故正确的表述为数组支持随机访问,根据下标随机访问的时间复杂度为O(1)。
二、低效的插入和删除
1.数组的插入操作
若在数组的末尾插入元素,此时不需要移动数据,则最好时间复杂度为O(1)
若在数组的开头插入元素,那所有的数据都需要往后移一位,此时最坏时间复杂度为O(n)
因此平均情况时间复杂度为 ( 1 + 2 + . . . + n ) / n = O ( n ) (1+2+...+n)/n=O(n) (1+2+...+n)/n=O(n)
2.数组的删除操作
若在数组的末尾删除元素,此时不需要移动数据,则最好时间复杂度为O(1)
若在数组的开头删除元素,那所有的数据都需要往前移一位,此时最坏时间复杂度为O(n)
因此平均情况时间复杂度为 ( 1 + 2 + . . . + n ) / n = O ( n ) (1+2+...+n)/n=O(n) (1+2+...+n)/n=O(n)
此外,若执行多次删除操作时,可以记录下已经被删除的数据,每次的删除操作并不是搬移数据,只是记录数据已经被删除,当数组没有更多的存储空间时,再触发一次真正的删除操作。
以LeetCode上第35题搜索插入位置为例,给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
class Solution(object):
def searchInsert(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
flag = 0
for i in range(len(nums)):
if nums[i]==target:
flag = 1
return i
if flag == 0:
if target<=nums[0]:
return 0
elif target>=nums[-1]:
return len(nums)
else:
for i in range(0,len(nums)-1):
if nums[i]<=target and nums[i+1]>=target:
return i+1
三、为何数组从0开始编号
从偏移角度理解a[0],0为偏移量。
a[k]_address = base_address + k * type_size
如果从1计数,计算数组元素 a[k] 的内存地址就会变为下式,会多出k-1
a[k]_address = base_address + (k-1)*type_size
因此,若从1开始计数,则每次访问数据的时候都会多一次进行减法运算,增加cpu的负担。
参考资料:王争《数据结构与算法之美》