数组与链表
一、数组
(一)概述
数组是一种线性结构,将元素存储在连续的内存空间当中,在数组中的元素会有一个位置,我们用索引去记录该位置,这就是一个基本数组的构成。
在python中,数组的概念被列表所替代,内部可以存储不同数据类型的元素,不过实际工作当中,一个列表基本只存储一种数据类型。
(二)一维数组
1、定义数组
一维数组是最简单也是最常用的一种,一般是对一组数据进行存储,每个数据都有他的索引,可以快速的访问该数字。例如:
# 初始化数组(因为python中没有数组概念,所以用列表代替)
list_1 = list(int)
list_2 = []
list_3 = [1, 2, 3, 4, 5]
以上就是一维数组的正常创建格式,是不是很简单方便。如果我们想要访问某个数组,就可以**数组名[下标]**直接得到目标值。注意:首个元素的索引为0,因为其本质上是内存地址的偏移量,而首个元素的内存地址偏移量为0,所以他的索引为0也是非常合理的。如果对地址计算非常感兴趣,可以自行搜索下内存地址计算公式,本篇不做过多赘述。
2、操作数组
数组属于可变容器类型,这意味着我们可以对数组内的元素做操作,一般来说,我们对数组做的操作无非与增删改查和遍历五个动作。
首先是最简单的遍历:
# 一般采用for或者while来实现对数组的遍历
list_1 = [1, 2, 3, 4, 5]
# 这就是一个最简单的遍历,会把所有的元素全部走一遍
for i in list_1:
print(i)
while i < len(list_1):
print(list_1[i])
i += 1
利用循环可以把整个数组都遍历一遍,这样的话我们就能得到数组中的每个元素了,接下来就是给数组增加元素
'''
增:
append(要添加的元素) 将元素添加到列表的末尾, 一次只能添加1个元素.
extend(容器类型) 将元素添加到列表的末尾, 一次可以添加多个元素.
insert(索引, 元素) 在列表的指定位置(索引), 插入元素.
'''
# 1. 定义列表
list1 = [10, 20, 30]
# 2. 演示 append() 函数
list1.append(40)
# 3. 演示 extend() 函数
list1.extend(50) # 这里传入的类型是可迭代(可遍历)的类型.
list1.extend([15, 25, 35, 45])
# 4. 演示 insert() 函数
list1.insert(1, 60)
list1.insert(10, 90) # 这个索引, 最大, 最小我们写谁都行.
list1.insert(-30, 100) # 这个索引, 最大, 最小我们写谁都行.
list1.insert(2, 150)
# 5. 打印列表
print(list1)
需要注意的是,append是一个一个加,extend是可以做到多个一起加的,而insert可以加到自己想加的索引后。说完了增加,接下来就是删除。
'''
删:
remove(元素) 根据元素(内容), 从列表中删除指定的元素, 只删除第一个匹配到的, 不存在就报错.
pop(索引) 根据索引, 从列表中删除指定的元素, 不存在就报错.
del 如果跟的是索引, 就删除该元素, 如果跟的是列表, 就删除该列表(内存中没有了)
clear 清空列表中所有的元素, 但是列表还在.
'''
# 1. 定义列表, 记录元素.
list1 = [1, 2, 3, 4, 5, 2]
# 2. 测试 remove() 函数
# list1.remove(20) # 报错
list1.remove(2) # 删除元素2
# 3. 测试 pop() 函数
list1.pop(3) # 删除索引为3的元素
# list1.pop(30) # 报错
# 4. 测试 del() 函数
del list1[3]
del list1[-3]
del list1 # 不仅删除元素, 还把list1从内存中删除
# 5. 测试 clear() 函数
list1.clear()
# 6. 打印集合
print(list1)
删除元素有多种方式,要注意使用del时,删除的数组是从内存中删除,就像从来没有存在一样,后续调用也会报错。当我们想确定删除的元素到底消失没有时,我们需要用到查询函数。
'''
查:
index(元素, 开始索引, 结束索引) 查找元素在列表中的位置, 不存在就: 报错.
count(元素) 统计元素在列表中的总次数.
元素 in 列表 判断列表中是否包含该元素, 包含:True, 不包含: False
元素 not in 列表 判断列表中是否不包含该元素, 不包含:True, 包含: False
'''
# 1. 定义列表
list1 = [10, 20, 30, 30, 30]
# 2. 演示index函数
print(list1.index(30)) # 2
# print(list1.index(50)) # 报错
# 3. 演示count(元素)
print(list1.count(30)) # 3
# 4. 演示 in 的用法.
print(30 in list1) # True
print(50 in list1) # False
# 5. 演示 not in 的用法, 有点像 双重否定表肯定
print(30 not in list1) # False
print(50 not in list1) # True
以上就是查询的几种方法,而我们在平时的编程过程中,难免会遇到需要修改数据的时刻,前文提到数组是一种可变的容器,那么修改数组的值就显得格外简单了。
'''
列表[索引] = 修改后的值 根据索引, 修改列表中对应元素的值, 索引不存在, 就 报错
sort() 排序的意思, 里边可以写参数, reverse=False(默认的, 升序), reverse=True(降序)
reverse() 反转元素.
'''
# 1. 定义列表, 记录元素.
list1 = [1, 2, 11, 5, 3]
# 2. 测试修改列表的元素值.
list1[2] = 20
# list1[20] = 20 # 报错
# 3. 测试对列表元素 反转.
list1.reverse() # [3, 5, 20, 2, 1]
# 4. 测试对列表元素 排序.
list1.sort() # 默认, 升序
list1.sort(reverse=False) # 同上
list1.sort(reverse=True) # 降序, 即: 先升序排列, 然后反转.
# 降序排列分解动作.
list1.sort() # 升序
list1.reverse() # 反转
# 5. 打印列表
print(list1)
以上就是对数组的修改动作,几种基本的操作种类就在这里,当然还有很多对数组操作的动作和函数,这需要读者自己在实际运用中慢慢探索。
(二)二维数组
有一维就会有二维,二维数组通常会运用到矩阵方面,也是常用的一种类型。其实他就是数组的嵌套,一个数组对另一个数组进行嵌套,一个一维数组就是外面嵌套数组的一个元素。
# 定义一个二维数组
list1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
二维数组与一维数组的区别就在于索引,list1[外部索引] [内部索引],这样标识就可以取到目标元素,那么很多问题就可以解决了,比如:
# 定义一个二维数组
list1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# 取第一个数组的第一个元素
list1[0][0] = 1
只要你了解了二维数组的索引定义,下面的操作就可以和一维数组类似了,所以本文不再过多赘述。
三维数组,其实也就是在二维数组上再去嵌套一层数组,实际意义不大。一般实际编程中,数组的维度最大维持在二维,很少能有三维数组的用武之地,所以基本了解到二维数组即可。
二、链表
(一)概述
链表是一种线性结构,将元素储存在非连续非顺序的存储结构中。链表由一系列的结点组成,每个节点包括两部分:数据域与指针域,数据域存储数据元素,指针域存储下一个结点的指针。
由于链表存储的特性,链表没有办法像数组一样快速访问某个元素,只能通过遍历来寻找元素位置,而且由于必须要存储指针,所以所用空间相较数组来说也会较高。不过他的元素可以散落在内存中的各个位置,所以比较灵活。还有更多的区别下文会单独来说。
(二)单向链表
单向链表是最基础的一种链表,是链表中最简单的形式,每个节点包括两个域,链接域和信息域。链接指向链表的下一个节点,而最后一个节点指向None
head -> item.1 next -> item.2 next -> .... -> item.n next -> None
head保存首地址,item存储数据,而next指向下一个节点地址。
那么问题来了,我们要怎么去定义一个完整的链表,并对其做一些动作呢?
1、定义节点
节点数据包括数据元素和指针
'''
单链表节点
item存放数据元素
next是下一个节点的标识,也就是指针
'''
class Node(object):
def __init__(self, item):
self.item = item
self.next = None
此时我们就已经创建了一个链表的类
2、定义单向链表
链表需要首地址,有首地址后才能一步一步往后加元素
(1)定义头指针
# 定义单链表的首地址指针head
class SingleLinkList(object):
def __init__(self):
self._head = None
有了这两个类后就可以正常的创建链表了
例如:
if __name__ == '__main__':
# 创建列表
link_list = SingleLinkList()
# 创建节点
node1 = Node(1)
node2 = Node(2)
# 将结点加到链表中
link_list._head = node1
# 将第一个结点的next指针指向下一个结点
node1.next = node2
# 访问链表第一个数据
print(link_list._head.item)
# 访问链表第二个数据
print(link_list._head.next.item)
这样就算是创建了一个列表,并且加入了一个元素,如果数据量很少,还可以接受,但是当数据量非常大时,会显得代码十分的臃肿,所以我们需要在链表中加入一些操作方法,来实现快捷的增删改查。
(2)增删改查等操作
一般链表中我们会去增加一些常用的操作方法,例如:
-
is_empty() 判断是否为空
-
lenth() 获取链表的长度
-
items() 获取链表数据迭代器
-
add(item) 链表头部添加元素
-
append(item) 链表尾部添加元素
-
insert(position, item) 在任意位置添加元素
-
remove(item) 删除节点
-
find(item) 查找结点是否存在
实现代码如下:
class SingleLinkList(object):
def __init__(self):
self._head = None
# 判断链表是否为空
def is_empty(self):
return self._head is None
# 计算链表长度
def lenth(self):
# 开局指针指向head
cur = self._head
count = 0
# 指到None说明到了尾部
while cur is not None:
count += 1
# 指向下一个
cur = cur.next
return count
# 遍历链表获取元素
def items(self):
# 开局指针指向head
cur = self._head
while cur is not None:
# 返回生成器,可以理解为DIY的range
yield cur.item
# 指向下一个
cur = cur.next
# 向链表头部加元素
def add(self, item):
# 先创建要加的元素的结点
node = Node(item)
# 新元素结点指向原来头部结点
node.next = self._head
# 头部结点改成新结点
self._head = node
# 向链表尾部加元素
def append(self, item):
# 先创建要加的元素的结点
node = Node(item)
# 判断是否是空链表
if self.is_empty():
# 如果是空的,那头指针直接指向新结点
self._head = node
else:
# 不是空指针的话,就找到尾部,把尾结点next指向新结点
cur = self._head
while cur.next is not None:
cur = cur.next
cur.next = node
# 向指定位置插入元素
def insert(self, index, item):
# 指定位置在第一个元素之前,在头部插入
if index <= 0:
self.add(item)
# 指定位置超过尾部,在尾部插入
elif index > (self.lenth() - 1):
self.append(item)
# 剩下都是合法的
else:
node = Node(item)
cur = self._head
# 找到需要插入的位置
for i in range(index - 1):
cur = cur.next
# 指向下一个元素
node.next = cur.next
# 之前这个位置的元素指向插入元素
cur.next = node
# 删除某个元素(元素)
def delete_item(self, item):
cur = self._head
pre = None
# 遍历到结束
while cur is not None:
# 找到指定元素后
if cur.item == item:
# 如果开头就是
if not pre:
self._head = cur.next
# 如果是后面的
else:
pre.next = cur.next
return True
# 没找到的话就朝后继续遍历
else:
pre = cur
cur = cur.next
# 删除某个元素(索引)
def delete_index(self, index):
cur = self._head
if index == 0:
cur.next = cur.next.next
return
if index > self.lenth():
print('超出范围')
return
for i in range(index):
if cur.next is not None:
cur = cur.next
cur.next = cur.next.next
# 寻找元素
def find(self, item):
# 遍历
return item in self.items()
上面就一些常用的操作,可以针对具体使用场景稍加改造,而当我们写好这些后,我们后续的一些使用就可以直接利用里面的函数了,例如:
if __name__ == '__main__':
# 创建列表
link_list = SingleLinkList()
# 创建节点
node1 = Node(1)
node2 = Node(2)
# 将结点加到链表中
link_list._head = node1
# 将第一个结点的next指针指向下一个结点
node1.next = node2
# 向头部加数据
link_list.add(0)
# 向尾部加数据
for i in range(3, 6):
link_list.append(i)
# 插入数据
link_list.insert(5, 10)
# 查看列表所有数据
for i in link_list.items():
print(i, end=' ')
print()
# 删除某个元素(元素)
link_list.delete_item(3)
for i in link_list.items():
print(i, end=' ')
print()
# 删除某个元素(索引)
link_list.delete_index(2)
for i in link_list.items():
print(i, end=' ')
print()
# 查看某个数据
print(link_list.find(3))
'''
结果:
0 1 2 3 4 10 5
0 1 2 4 10 5
0 1 2 10 5
False
'''
到这里,基础的单向链表我们就完成了
(三)、环形链表
所谓环形链表,顾名思义,就是链表的首尾相连,形成了一个环形的链表。其余部分与单向链表无太多变化。
head -> item.1 next -> item.2 next -> .... -> item.n next -> None
/\ ||
||---<-----------------<-------------<----------------------<---\/
head保存首地址,item存储数据,而next指向下一个节点地址
1、定义结点
'''
环形链表节点
item存放数据元素
next是下一个节点的标识,也就是指针
'''
class Node(object):
def __init__(self, item):
self.item = item
self.next = None
2、定义环形链表
因为与单向链表类似,所以定义头指针和增删改查动作放到一起,方便读者阅读。重点关注涉及到头指针和尾指针的动作,这也是环形和单向的主要区别。
class SingleCycleLinkList(object):
def __init__(self):
self._head = None
# 判断链表是否为空
def is_empty(self):
return self._head is None
# 计算链表长度
def lenth(self):
# 链表为空
if self.is_empty():
return 0
# 开局指针指向head
cur = self._head
count = 1
# 指到None说明到了尾部
while cur.next != self._head:
count += 1
# 指向下一个
cur = cur.next
return count
# 遍历链表获取元素
def items(self):
# 链表为空
if self.is_empty():
return 0
# 开局指针指向head
cur = self._head
while cur.next != self._head:
# 返回生成器,可以理解为DIY的range
yield cur.item
# 指向下一个
cur = cur.next
# 最后输出头指针
yield cur.item
# 向链表头部加元素
def add(self, item):
""" 头部添加结点"""
node = Node(item)
if self.is_empty(): # 为空
self._head = node
node.next = self._head
else:
# 添加结点指向head
node.next = self._head
cur = self._head
# 移动结点,将末尾的结点指向node
while cur.next != self._head:
cur = cur.next
cur.next = node
# 修改 head 指向新结点
self._head = node
# 向链表尾部加元素
def append(self, item):
# 先创建要加的元素的结点
node = Node(item)
# 判断是否是空链表
if self.is_empty():
# 如果是空的,那头指针直接指向新结点
self._head = node
node.next = self._head
else:
# 不是空指针的话,就找到尾部,把尾结点next指向新结点
cur = self._head
while cur.next != self._head:
cur = cur.next
cur.next = node
node.next = self._head
# 向指定位置插入元素
def insert(self, index, item):
# 指定位置在第一个元素之前,在头部插入
if index <= 0:
self.add(item)
# 指定位置超过尾部,在尾部插入
elif index > (self.lenth() - 1):
self.append(item)
# 剩下都是合法的
else:
node = Node(item)
cur = self._head
# 找到需要插入的位置
for i in range(index - 1):
cur = cur.next
# 指向下一个元素
node.next = cur.next
# 之前这个位置的元素指向插入元素
cur.next = node
# 删除某个元素(元素)
def delete(self, item):
if self.is_empty():
return
cur = self._head
pre = Node
# 第一个元素为需要删除的元素
if cur.item == item:
# 链表不止一个元素
if cur.next != self._head:
while cur.next != self._head:
cur = cur.next
# 尾结点指向 头部结点的下一结点
cur.next = self._head.next
# 调整头部结点
self._head = self._head.next
else:
# 只有一个元素
self._head = None
else:
# 不是第一个元素
pre = self._head
while cur.next != self._head:
if cur.item == item:
# 删除
pre.next = cur.next
return True
else:
pre = cur # 记录前一个指针
cur = cur.next # 调整指针位置
# 当删除元素在末尾
if cur.item == item:
pre.next = self._head
return True
# 寻找元素
def find(self, item):
# 遍历
return item in self.items()
细心的读者可以发现,和单向链表相比,环形链表的多余动作就是将尾指针指向头指针。
if __name__ == '__main__':
# 创建列表
link_list = SingleCycleLinkList()
# 判空
print(link_list.is_empty())
# 加入数据,组成一个环形链表
node1 = Node(1)
node2 = Node(2)
link_list._head= node1
node1.next = node2
node2.next = link_list._head
# 向头部加数据
link_list.add(0)
# 向尾部加数据
for i in range(3, 6):
link_list.append(i)
# 插入数据
link_list.insert(5, 10)
# 查看列表所有数据
for i in link_list.items():
print(i, end=' ')
print()
# 删除某个元素(元素)
link_list.delete(3)
for i in link_list.items():
print(i, end=' ')
print()
# 删除某个元素(索引)
link_list.delete(2)
for i in link_list.items():
print(i, end=' ')
print()
# 查看某个数据
print(link_list.find(3))
'''
结果:
True
0 3 4 5 10
0 4 5 10
0 4 5 10
False
'''
(四)、双向链表
双向链表相较于单向列表更为复杂,他在存储一份数据的同时,会存储两个指针,分别指向前方和后方,让链表的表现更加灵活。
head <-> item.1 next prev <-> item.2 next prev <-> … <-> item.n next prev <-> None
head 保存首地址,item 存储数据,next 指向下一结点地址,prev 指向上一结点地址
1、定义结点
'''
双向列表
有一个元素两个指针
两个指针分别指向前和后
'''
class Node(object):
def __init__(self, item):
# item存放数据元素
self.item = item
# next 指向下一个节点的标识
self.next = None
# prev 指向上一结点
self.prev = None
2、定义双向链表
双向链表相比于单向链表,就是加了一个向前的指针prev
# 双向链表
class BilateralLinkList(object):
# 定义头指针
def __init__(self):
self._head = None
# 判空
def is_empty(self):
return self._head is None
# 算长度
def lenth(self):
# 初始指针指向head
cur = self._head
count = 0
# 指针指向None 表示到达尾部
while cur is not None:
count += 1
# 指针下移
cur = cur.next
return count
# 遍历所有元素
def items(self):
# 获取head指针
cur = self._head
# 循环遍历
while cur is not None:
# 返回生成器
yield cur.item
# 指针下移
cur = cur.next
# 在链表头加元素
def add(self, item):
node = Node(item)
if self.is_empty():
# 头部结点指针修改为新结点
self._head = node
else:
# 新结点指针指向原头部结点
node.next = self._head
# 原头部 prev 指向 新结点
self._head.prev = node
# head 指向新结点
self._head = node
# 在链表尾加元素
def append(self, item):
node = Node(item)
if self.is_empty(): # 链表无元素
# 头部结点指针修改为新结点
self._head = node
else: # 链表有元素
# 移动到尾部
cur = self._head
while cur.next is not None:
cur = cur.next
# 新结点上一级指针指向旧尾部
node.prev = cur
# 旧尾部指向新结点
cur.next = node
# 插入元素
def insert(self, index, item):
if index <= 0:
self.add(item)
elif index > self.lenth() - 1:
self.append(item)
else:
node = Node(item)
cur = self._head
for i in range(index):
cur = cur.next
# 新结点的向下指针指向当前结点
node.next = cur
# 新结点的向上指针指向当前结点的上一结点
node.prev = cur.prev
# 当前上一结点的向下指针指向node
cur.prev.next = node
# 当前结点的向上指针指向新结点
cur.prev = node
# 删除结点
def delete(self, item):
if self.is_empty():
return
cur = self._head
# 删除元素在第一个结点
if cur.item == item:
# 只有一个元素
if cur.next is None:
self._head = None
return True
else:
# head 指向下一结点
self._head = cur.next
# 下一结点的向上指针指向None
cur.next.prev = None
return True
# 移动指针查找元素
while cur.next is not None:
if cur.item == item:
# 上一结点向下指针指向下一结点
cur.prev.next = cur.next
# 下一结点向上指针指向上一结点
cur.next.prev = cur.prev
return True
cur = cur.next
# 删除元素在最后一个
if cur.item == item:
# 上一结点向下指针指向None
cur.prev.next = None
return True
# 查找元素
def find(self, item):
return item in self.items()
例如:
if __name__ == '__main__':
# 创建列表
link_list = BilateralLinkList()
# 判空
print(link_list.is_empty())
# 加入数据,组成一个环形链表
node1 = Node(1)
node2 = Node(2)
link_list._head= node1
node1.next = node2
# 向头部加数据
link_list.add(0)
# 向尾部加数据
for i in range(3, 6):
link_list.append(i)
# 插入数据
link_list.insert(5, 10)
# 查看列表所有数据
for i in link_list.items():
print(i, end=' ')
print()
# 删除某个元素(元素)
link_list.delete(3)
for i in link_list.items():
print(i, end=' ')
print()
# 查看某个数据
print(link_list.find(3))
'''
True
0 1 2 3 4 10 5
0 1 2 4 10 5
False
'''