数组
数组的特点
数组是可以再内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组下标进行访问,数组下标从0开始。
优点:
1、按照索引查询元素速度快
2、按照索引遍历数组方便
缺点:
1、数组的大小固定后就无法扩容了
2、数组只能存储一种类型的数据
3、添加,删除的操作慢,因为要移动其他的元素。
适用场景:
频繁查询,对存储空间要求不大,很少增加和删除的情况。
数组的基本操作
python中没有数组的说法,但是在学习中可以使用列表来代替之。
创建:
# 创建一个包含汽车品牌的数组
cars = ["Porsche", "Volvo", "BMW"]
访问:
# 通过索引号来引用数组元素。
x = cars[0]
修改:
# 修改首个数组项目的值
cars[0] = "Audi"
数组长度:
# 返回 cars 数组中的元素数。
# 数组长度总是比最高的数组索引大一个。
len(cars)
数组方法:
Python 提供一组可以在列表或数组上使用的内建方法。
方法 | 描述 |
---|---|
append() | 在列表的末尾添加一个元素 |
clear() | 删除列表中的所有元素 |
copy() | 返回列表的副本 |
count() | 返回具有指定值的元素数量。 |
extend() | 将列表元素(或任何可迭代的元素)添加到当前列表的末尾 |
index() | 返回具有指定值的第一个元素的索引 |
insert() | 在指定位置添加元素 |
pop() | 删除指定位置的元素 |
remove() | 删除具有指定值的项目 |
reverse() | 颠倒列表的顺序 |
sort() | 对列表进行排序 |
链表
链表的特点
链表的优点:
链表是很常用的一种数据结构,不需要初始化容量,可以任意加减元素;
添加或者删除元素时只需要改变前后两个元素结点的指针域指向地址即可,所以添加,删除很快;
缺点:
因为含有大量的指针域,占用空间较大;
查找元素需要遍历链表来查找,非常耗时。
适用场景:
数据量较小,需要频繁增加,删除操作的场景
常见链表
链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内存空间),另一个是指向下一个结点地址的指针域。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等。
单链表:
双向链表:
补充拓展:双向链表,每个node节点都有一个前指针和后指针,一般这种情况下,还会增加一个虚拟的尾节点用做尾指针,增加了寻址的速度。
循环链表:
链表的基本操作
链表插入元素
在一个上述的单链表中,index为2的地方,插入节点15。
步骤如下:
第一步,先找到index为1的node节点23;
第二步,然后把节点15的next指针指向节点10(index为3);
第三步,把节点23的next指针指向15;
这里需要先把15节点的next指向10,然后再把23节点的next指向15,顺序不能乱
链表删除元素
第一步,找到待删除元素前一个元素;
第二步,把待删除元素前一个元素的指针指向待删除元素的后一个元素。
第三步,把删除元素返回,删除元素的下一个元素指针置空,脱离链表。
利用python创建建一个静态单链表:
class item (object):
def __init__(self, data):
self.data = data
self.next = None
self.space = None
class SLinkList(object):
'''
静态链表,就是用数组来模拟链表,那么数组的下标就当做是节点的地址,
这个概念是静态链表的核心
'''
def __init__(self, size = 100):
'''
初始化主要是用于初始化链表的大小,而非创建链表
'''
self.link = [item(None) for i in range(size + 1)] # 申请size大小的节点空间[0,1,2,...,size],其中下标0的节点作为头结点
self.link[0].next = None # 表示空表
self.link[0].space = 1 # 指向第一个节点,因为初始化时第一个节点为空闲节点
i = 1
while i < size:
self.link[i].next = i+1 # 利用空闲节点连成一个新的表,并且头结点的space始终指向下一个空闲的节点
i += 1
self.link[i].next = None # 空闲表尾指向None
self.length = 0 # 链表长度
self.rear = 0 # 表尾指针
def CreateSLinkList_R(self, data):
'''
用尾插法创建链表
'''
if self.length > 0: # 非空表无需创建
print("THIS SLINKLIST IS NOT NULL")
return
for each in data:
if not self.Append(each):
print("CreateR: NO SPACE!")
return
def CreateSLinkList_F(self, data):
'''
用头插法创建链表
'''
if self.length > 0: # 非空表无需创建
print("THIS SLINKLIST IS NOT NULL")
return
self.Append(data[0]) # 先添加第一个节点
for each in data[1:]: # 在第一个位置前插入
if self.InsertBefore(each, 1) == 'NS':
print("CreateF: NO SPACE!")
def LocateElement(self, data):
'''
定位第一个与data值相同的节点,并返回该节点的下标
'''
i = self.link[0].next
while i and self.link[i] != data:
i = self.link[i].next
return i
def Malloc_SL(self):
'''
类似于C中malloc函数申请空间,返回空闲节点的下标
'''
i = self.link[0].space
if self.link[0].space:
self.link[0].space = self.link[i].next
return i
def Free_SL(self, k):
'''
释放空间,并返回下标k节点的值
'''
self.link[k].data = None
self.link[k].next = self.link[0].space
self.link[0].space = k
def DeleteElement(self, data):
'''
删除第一个匹配到的节点,并返回删除节点的下标
'''
prior = index = 0
while index != None and self.link[index].data != data:
prior = index
index = self.link[index].next
if not index:
print("DELETE:\tNot Found!")
return False
if not self.link[index].next: # 判断是否是删除表尾元素,是则改变表尾指针
self.rear = prior
self.link[prior].next = self.link[index].next
self.length -= 1
self.Free_SL(index)
return index
def InsertBefore(self, data, k):
'''
在第k个元素前插入元素, 并返回新节点下标
'''
if self.link[0].space == None: # 表满
print("no space!!!")
return "NS"
if k <= 0 or k > self.length: # 超出当前链表可插入范围
print("out of range!!!")
return "OFR"
count = 0
index = 0
while count < k - 1 : # 找到第k-1个节点,直接在改节点后插入新的元素
index = self.link[index].next
count += 1
node_index = self.Malloc_SL() # 申请一个新的节点
self.link[node_index].data = data # 写入数据
self.link[node_index].next = self.link[index].next # 将新节点的next指向第k个节点
self.link[index].next = node_index # 将第k-1个节点的next指向新节点node_index
self.length += 1
return node_index
def Append(self, data):
'''往链表表尾添加元素, 并返回新添加元素的下标'''
node_index = self.Malloc_SL()
if not node_index:
print("Append: NO SPACE!")
return False
self.link[node_index].data = data
self.link[node_index].next = None
self.link[self.rear].next = node_index
self.rear = node_index
self.length += 1
return node_index
def Walk(self):
'''打印链表中所有元素'''
print("WALK:\t", end = '')
index = self.link[0].next
while index:
print(self.link[index].data, end = '')
index = self.link[index].next
print("")
def Detail(self):
'''输出链表中各元素的详细信息'''
print("\nDETAIL:")
# print("序号\t数据\t当前节点\t下一节点")
index = self.link[0].next
count = 1
while index:
# print("%d\t\t%s\t\t%d\t\t%s" % \
# (count, self.link[index].data, index, str(self.link[index].next)))
print("SN:\t%d\tDATA:\t%s\tADDR:\t%d\tNEXT:\t%s" % \
(count, self.link[index].data, index, str(self.link[index].next)))
index = self.link[index].next
count += 1
print("Length:\t%d\nRear:\t%d\n" % (self.length, self.rear))
if __name__ == '__main__':
SL = SLinkList()
while True:
opt = input("请选择创建链表的方式: 1. 尾插法 2. 头插法\nOPTION:\t")
if opt == '1':
data_str = input("请输入链表数据,以空格隔开:\t")
data_str = str(data_str).split(' ')
SL.CreateSLinkList_R(data_str)
break
elif opt == '2':
data_str = input("请输入链表数据,以空格隔开:\t")
data_str = str(data_str).split(' ')
SL.CreateSLinkList_F(data_str)
break
else:
print("请做出正确选择!")
continue
while True:
opt = input("\n请选择操作: 1. 插入数据 2. 删除数据 3. 遍历链表 4. 查看链表详细结构 5. 在链表末尾添加数据 6. 退出程序\nOPTION:\t")
if opt == '1':
data_str = input("请输入插入数据:\t")
pos = input("请输入插入位置:\t")
node_addr = SL.InsertBefore(data_str[0], int(pos))
if node_addr != 'NS' and node_addr != 'OFR':
print("成功删除元素%s,元素地址为:%d" % (data_str[0], node_addr))
else:
print("INSERT ERROR!!!")
elif opt == '2':
data_str = input("请输入删除数据:\t")
data_pos = SL.DeleteElement(data_str[0])
if data_pos:
print("成功删除元素%s,元素地址为:%d" % (data_str[0],data_pos))
elif opt == '3':
print('')
SL.Walk()
elif opt == '4':
SL.Detail()
elif opt == '5':
data_str = input("请输入数据:\t")
node_addr = SL.Append(data_str[0])
print("成功添加元素:\t%s\t元素地址为:\t%d" % (data_str[0], node_addr))
elif opt == '6':
break
else:
print("请做出正确选择!")
数组与链表的比较
从逻辑结构上来看,数组必须实现定于固定的长度,不能适应数据动态增减的情况,即数组的大小一旦定义就不能改变(python中列表可以改变大小,所以列表只能用来当成学习数组的一种手段,并不是等同于数组)。当数据增加是,可能超过原先定义的元素的个数;当数据减少时,造成内存浪费;链表动态进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。
从内存存储的角度看;数组从栈中分配空间(用new则在堆上创建),对程序员方便快速,但是自由度小;链表从堆中分配空间,自由度大但是申请管理比较麻烦。
从访问方式类看,数组在内存中是连续的存储,因此可以利用下标索引进行访问;链表是链式存储结构,在访问元素时候只能够通过线性方式由前到后顺序的访问,所以访问效率比数组要低。