Python中常用的数据结构

常用的数据结构有数组、链表(一对一)、栈和队列、哈希表、树(一对多)、图(多对多)等结构。
在本目录下我们将讲解,通过python语言实现常用的数据结构。

1.数组

在python语言中,并没有直接使用数组这个概念,而是使用列表(list)和元组(tuple)这两种集合,它们的本质都是对数组的封装。其中,列表是一个动态可扩展的数组,支持任意的添加、删除、修改元素;而元组是一个不可变集合,一旦创建就不支持修改,即不支持添加、删除、修改操作,只支持查看。
下面我们提到的数组概念,对应的是python语言的列表。
(1)读取元素

#初始化列表
my_list = [3, 1, 2, 5, 4, 9, 7, 2]
#随机读取元素
print(my_list[2])
#结果为:2

(2)更新(修改)元素

#初始化列表
my_list = [3, 1, 2, 5, 4, 9, 7, 2]
#更新元素
my_list[3] = 10
print(my_list[3])
#结果为:10

注意:数组读取元素和更新元素的时间复杂度都是O(1)
(3)插入元素
插入数组元素存在3中情况:1、尾部插入,2、中间插入,3、超范围插入。
3.1尾部插入,3.2中间插入
对于尾部和中间插入在Python底层已经为我们做了很好的解释,调用起来很方便。

#初始化列表
my_list = [3, 1, 2, 5, 4, 9, 7, 2]
#尾部插入元素
my_list.append(6)
#中间插入元素
my_list.insert(5, 11)
print(my_list)
#结果为:[3, 1, 2, 5, 4, 11, 9, 7, 2, 6]

但是为了更好的理解数组的工作方式,我们自己实现一段插入操作的代码。

class MyArray:
    def __init__(self, capacity):
        self.array = [None] *capacity
        self.size = 0

    def insert_v1(self, index, element):
        #判断访问下标是否超出范围
        if index < 0 or index > self.size:
            raise Exception("超出数组实际元素范围")
        #从右向左循环,逐个元素向右移一位
        for i in range(self.size, index, -1):
            self.array[i] = self.array[i-1]
        #腾出位置放入新元素
        self.array[index] = element
        self.size += 1
    
    def output(self):
        for i in range(self.size):
            print(self.array[i])
        #print(self.array)

array = MyArray(4)
array.insert_v1(0, 10)
array.insert_v1(0, 20)
array.insert_v1(0, 30)
array.output()
#结果为:30
         20
         10

注意:for循环中参数的设置,应该如何变化。
3.3超范围插入
超范围插入就涉及数组的扩容了,我们这里的思想是:再创建一个新数组,长度是旧数组的2倍,再把旧数组的元素统统复制过去,这样就实现了数组的扩容。
只需更改.insert_v2()方法就可以实现,并将.output()改为print(self.array).

def insert_v2(self, index, element):
        #判断访问下标是否超出范围
        if index < 0 or index > self.size:
            raise Exception("超出数组实际元素范围")
        #如果实际元素达到数组容量上限,数组扩容
        if self.size >= len(self.array):
            self.resize()
        #从右向左循环,逐个元素向右移一位
        for i in range(self.size, index, -1):
            self.array[i] = self.array[i-1]
        #腾出位置放入新元素
        self.array[index] = element
        self.size += 1

array = MyArray(4)
array.insert_v2(0, 10)
array.insert_v2(0, 20)
array.insert_v2(0, 30)
array.insert_v2(0, 40)
array.insert_v2(0, 1110)
array.output()
array.insert_v2(2, 60)
array.output()
#结果为:[1110, 40, 30, 20, 10, None, None, None]
        [1110, 40, 60, 30, 20, 10, None, None]

(4)删除元素
数组的删除操作和插入操作的过程相反,如果删除的元素位于数组中间,其后的元素都需要向前挪动一位。
同样对于删除操作,虽然python底层已经做好了实现,但我们仍然需要尝试自己来实现一下。由于不涉及扩容问题,所以删除操作的代码实现比插入操作要简单。

def remove(self, index):
     # 判断访问下标是否超出范围
     if index < 0 or index > self.size:
       raise Exception("超出数组实际元素范围")
     #从左到右,逐个元素向左移动一位
     for i in range(index, self.size - 1):
         self.array[i] =self.array[i+1]
     self.size -= 1

array = MyArray(4)
array.insert_v2(0,10)
array.insert_v2(0,11)
array.insert_v2(0,12)
array.insert_v2(2,13)
array.output() #结果为:[12, 11, 13, 10]
array.remove(1)
array.output() #结果为:[12, 13, 10, 10]
array.remove(3)
array.output() #结果为:[12, 11, 13, 10]

注意:for循环中range中的参数,如果是self.size则i+1就会越界,如果是self.size-1则最后一个元素无法删除。
时间复杂度:(1)插入操作,数组的扩容时间复杂度是O(n),插入并移动元素的时间复杂度也是O(n),综合起来插入操作的时间复杂度是O(n).(2)删除操作,其只涉及元素的移动,时间复杂度也是O(n)。

总结

数组的优势和劣势:
(1)优势:数组拥有非常高效的随机访问能力,只要给出下表,就可以用常量时间找到对应元素。有一种高效查找元素的算法叫二分查找,就是利用数组的这个优势。
(2)劣势:数组的劣势体现在插入和删除元素方面。由于数组元素连续紧密地存储在内存中,插入和删除元素都会导致大量元素被迫移动,影响效率。
总的来说,数组适合的是读操作多,写操作少的场景。而链表则相反。