数据结构与算法(python)(数据结构)
文章目录
一、数据结构基本概念
数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中元素之间的关系组成。
简单来说,就是设计数据以何种方式组织并存储在计算机中。
比如:列表、集合和字典都是一种数据结构
N.Wirth:“程序=数据结构+算法”
数据的逻辑结构:数据元素之间的逻辑关系
分为:线性结构(元素存在一对一的相互关系):线性表、栈和队列、串;
非线性结构:集合、树结构、图结构,网状结构。
存储结构:也称物理结构,是指数据结构在计算机中的表示,包括数据元素的表示与关系的表示,
主要包括:顺序存储,链式存储,索引存储,散列存储
线性表的常见实现方式包括顺序存储结构(即数组)和链式存储结构(即链表)
二、线性结构
1.列表(顺序存储)
需要掌握:
(1)列表中元素是如何存储的?
(2)列表的基本操作:按下标查找,插入元素,删除元素…
(3)这些操作的时间复杂度是多少?
扩展:python的列表如何实现?
线性表是抽象的数据结构模型,列表是具体的编程实现。
列表中的元素在内存中是连续存储的。每个元素占据相同的内存空间,并且可以通过列表的起始地址和下标进行快速访问。对于长度为 𝑛的列表,每个元素可以通过其下标 𝑖直接访问,计算其内存地址为:地址=起始地址+i*元素大小
32位机器上,整数占4字节,一个地址占4个字节。
python实现:
Python 列表是基于动态数组实现的,具有连续存储、自动扩容的特点。
Python 列表能够存储不同类型的元素,这是通过存储对象引用(指针)来实现的。
基本操作:
1.按下标查找(索引访问)
操作: 通过下标访问列表中的元素,如 array[i] 时间复杂度: O(1)
2.插入元素
操作: 在列表的任意位置插入一个元素,如 array.insert(i, x)。
时间复杂度:在末尾插入: O(1)
原因: 如果列表末尾有空闲空间,直接在末尾插入即可,无需移动其他元素。
在中间或开头插入:O(n)
原因: 在开头或中间插入元素时,需要将插入位置之后的所有元素向后移动,以腾出空间。因此,最坏情况下需要移动n−1 个元素,时间复杂度为O(n)
3. 删除元素
操作: 从列表中删除元素,如 array.pop(i) 或 array.remove(x)。
删除末尾元素: O(1) 删除中间或开头元素:O(n)
4.遍历列表
操作: 依次访问列表中的每个元素。时间复杂度: O(n)
# 示例列表
lst = [1, 2, 3, 4, 5]
# 使用 for 循环遍历元素
for element in lst:
print(element)
# 示例列表
lst = [1, 2, 3, 4, 5]
#需要访问元素的下标,可以使用 range() 函数结合 len() 方法,通过下标进行遍历。
# 通过下标遍历列表
for i in range(len(lst)):
print(f"Index: {
i}, Element: {
lst[i]}")
# 使用 enumerate() 同时获取下标和元素
for index, element in enumerate(lst):
print(f"Index: {
index}, Element: {
element}")
# 使用列表推导式遍历并生成一个新的列表
squared_lst = [x**2 for x in lst]
print(squared_lst)
#尽管 for 循环更常用,while 循环也可以用于遍历列表,特别是在需要手动控制循环变量时
# 使用 while 循环遍历列表
i = 0
while i < len(lst):
print(lst[i])
i += 1
#map() 函数可以将列表中的每个元素应用一个函数,并返回一个迭代器,可以将其转换为列表或直接遍历。
# 使用 map() 函数遍历并应用函数
squared_lst = map(lambda x: x**2, lst)
# 转换为列表并打印
print(list(squared_lst))
5.更新元素
操作: 修改列表中的某个元素,如 array[i] = x。时间复杂度: O(1)
6.查找元素(线性搜索)
操作: 查找列表中是否包含某个元素,如 x in array。
时间复杂度: O(n)
原因: 需要遍历整个列表,直到找到目标元素或到达列表末尾,最坏情况下需要访问所有 𝑛 个元素。
2.栈
栈(Stack)是一种后进先出(LIFO,Last In First Out)的数据结构。这意味着最后插入栈中的元素会最先被取出。Python 本身没有专门的栈数据结构,但可以使用列表(list)或 collections.deque 来实现栈的功能。
栈的特点:
后进先出(LIFO): 栈的基本原则是后进先出,即最后加入栈的元素最先被移除。
单一操作端: 所有操作都在栈的顶端进行,无论是插入还是删除。
顺序访问: 栈只能访问位于栈顶的元素,不能直接访问其他元素。
栈有两个主要操作:压栈(push) 和 弹栈(pop)。压栈是将元素添加到栈的顶端,弹栈是从栈的顶端移除元素。
#栈的操作实现
class Stack:
def __init__(self):
# 初始化一个空栈
self.stack = []
def push(self,element):
self.stack.append(element) #压栈
def pop(self):
return self.stack.pop() #弹栈
def get_top(self):
if len(self.stack) > 0:
return self.stack[-1]
stack = Stack()
stack.push(10) # 栈: [10]
stack.push(20) # 栈: [10, 20]
stack.push(30) # 栈: [10, 20, 30]
# 查看栈顶元素
print("栈顶元素:", stack.get_top()) # 输出: 30
# 弹栈操作
print("弹出的元素:", stack.pop()) # 输出: 30
栈的应用,括号匹配问题,若栈为空,则匹配
#栈的应用:括号匹配问题
class Stack:
def __init__(self):
# 初始化一个空栈
self.stack = []
def push(self,element):
self.stack.append(element) #压栈
def pop(self):
return self.stack.pop() #弹栈
def get_top(self):
if len(self.stack) > 0:
return self.stack[-1]
else:
return None
#判断空列表
def is_empty(self):
return len(self.stack) == 0
def brace_match(s):
match = {
'}':'{', ']':'[', ')':'('}
stack=Stack()
for ch in s:
if ch in {
'(', '[', '{'}:
stack.push(ch)
elif: #ch in 右括号
if stack.is_empty():
return False
elif stack.get_top() == match[ch]
stack.pop()
else: #如果栈顶不匹配
return False
if stack.is_empty():
return True
else:
return False
3.队列
队列(Queue) 是一种线性数据结构,遵循先进先出(FIFO)的原则。可以将队列想象成排队的场景,最先排队的人最先被服务。
队列的特点:
先进先出(FIFO): 队列遵循先进先出的原则,第一个进入队列的元素最先被移除。
两个操作端: 队列在队尾插入元素,在队首移除元素,两个操作端分别负责不同的操作。
顺序访问: 队列只能访问队首的元素,不能直接访问其他元素。
队列有两个主要操作:入队(enqueue) 和 出队(dequeue)。入队是将元素添加到队尾,出队是从队首移除元素
虽然可以用列表来实现队列,但由于列表在队首插入和删除元素的效率较低(时间复杂度为O(n)),不推荐在生产环境中使用列表来实现队列。
环形队列
##环形队列实现
class Queue:
def __init__(self,size=100):
self.queue = [0 for _ in range(size)]
self.size=size
self.rear = 0 #队尾指针
self.front = 0 #队首指针
def push(self,element):
if not self.is_filed():
self.rear = (self.rear + 1) % self.size
self.queue[self.rear] = element
else:
raise IndexError("Queue is empty.")
def pop(self):
if not self.is_empty():
self.front = (self.front + 1) % self.size
return self.queue[self.front]
else:
raise IndexError("Queue is empty.")
def is_empty(self): #判断队空
return self.rear == self.front
def is_filed(self): #判断队满
return (self.rear+1)%self.size == self.front
q=Queue(5)
for i in range(4):
q.push(i)
print(q.pop())
q.push(4)
双向队列:双向队列的两端都至此进队和出队操作
在 Python 中,推荐使用 collections.deque 或 queue.Queue 来实现队列,因为它们在性能和功能上更适合队列的使用。
from collections import deque
q = deque([1,2