数据结构是以某种形式将数据组织在一起的集合,它不仅存储数据,而且支持访问和处理数据的操作。
算法是求解问题时所需要遵循的、被清楚指定的简单指令的集合,表示的是求解问题的一种实现思路或思想。优秀的算法可以让程序在短时间,消耗资源较少的条件下获得执行结果。
数据结构与算法思想具有广泛的通用性,在任何语言中都可以使用,仅仅是语法存在差异。
计算 a a a、 b b b、 c c c。
a + b + c = 1000 a 2 + b 2 = c 2 a + b + c = 1000 \\ a^2 + b^2 = c^2 a+b+c=1000a2+b2=c2
方法1
for a in range(0, 1001):
for b in range(0, 1001):
for c in range(0, 1001):
if a + b + c == 1000 and a ** 2 + b ** 2 == c ** 2:
print(a, b, c)
方法2
for a in range(0,1001):
for b in range(0,1001):
c = 1000-a-b
if a+b+c == 1000 and a**2+b**2 == c**2:
print(a, b, c)
两种方法计算结果相同,但方法1的运行耗时比方法2的运行耗时长很多,因此在运行耗时的角度上,方法2更加优秀。
操作:向列表中添加元素,获取不同方式所需耗时。
这里timeit模块获取一段代码的耗时。
class timeit.Timer(stmt='pass', setup='pass', timer=)
参数说明
stmt:需要测试的代码;
setup:初始化代码或构建环境的导入语句;
timer:计时函数。
stmt参数和setup参数默认值都是'pass',可以包含多条语句,多条语句之间使用分号或新行分隔。
实例化一个空列表,然后将1000个数字添加到列表中。
def test1():
alist = []
for i in range(1000):
alist.append(i)
def test2():
alist = []
for i in range(1000):
alist = alist + [i]
def test3():
alist = [i for i in range(1000)]
def test4():
alist = list(range(1000))
from timeit import Timer
if __name__ == '__main__':
t1 = Timer('test1()', 'from __main__ import test01')
print(t1.timeit(1000)) # 0.04284289999986868
t1 = Timer('test2()', 'from __main__ import test02')
print(t1.timeit(1000)) # 0.8767927999999756
t1 = Timer('test3()', 'from __main__ import test03')
print(t1.timeit(1000)) # 0.022221100000024308
t1 = Timer('test4()', 'from __main__ import test04')
print(t1.timeit(1000)) # 0.009096400000089488
算法的时间复杂度是一个函数,可以量化算法的执行步骤数量,用于定性地描述算法的运行时间,也可以了解输入值趋近无穷时算法的运行情况。
时间复杂度常用大O符号表示(大O记法)。
例如,上面例子中方法1的时间复杂度为 O ( n 3 ) O(n^3) O(n3),方法2的时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
常见的时间复杂度排序
O ( 1 ) < O ( l o g n ) < O ( n ) < O ( n l o g n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n) O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
数据结构是数据的某种组织方式,主要研究的是数据以何种形式保存或进行存取操作。
算法是为了解决实际问题而设计的,而数据结构是算法需要处理问题的载体。
[{'name': 'name1', 'score': 'score1'},
{'name': 'name2', 'score': 'score2'},
{'name': 'name3', 'score': 'score3'}]
方式1中查询操作的时间复杂度为 O ( n ) O(n) O(n)。
[('name1', 'score1'), ('name2', 'score2'), ('name3', 'score3')]
方式2中查询操作的时间复杂度为 O ( n ) O(n) O(n)。
{'name1': {'score': 'score1'}, 'name2': {'score': 'score2'}, 'name3': {'score': 'score3'}}
方式3中查询操作的时间复杂度为 O ( 1 ) O(1) O(1)。
栈Stack与队列Queue都是用于存储数据的数据结构。
栈:后入先出
队列:后入先出
创建一个栈,需求:
Stack() 创建一个空栈,不需要参数。
push(item) 入栈(压栈)操作,将一个数据添加到栈的顶部。
pop() 出栈(弹栈)操作,将栈中顶部的元素取出。
peek() 返回栈顶元素的下标,下标起始值为0。
isEmpty() 判断栈是否为空。
size() 返回栈中元素的数量。
class Stack():
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item) # 尾部追加
def pop(self):
return self.items.pop() # 尾部弹出
def isEmpty(self):
return self.items == []
def size(self):
return len(self.items)
def peek(self):
return len(self.items) - 1
s = Stack()
s.push(1)
s.push(2)
s.push(3)
print(s.peek()) # 2
print(s.pop()) # 3
print(s.pop()) # 2
print(s.pop()) # 1
创建一个队列,需求:
Queue() 创建一个空队列。
enqueue(item) 入队操作,将一个数据添加到队首。
dequeue() 出队操作,从队尾取出元素。
isEmpty() 判断队列是否为空。
size() 返回队列中元素的数量。
class Queue():
def __init__(self):
self.items = []
def enqueue(self, item):
self.items.insert(0, item) # 首部追加
def dequeue(self):
return self.items.pop() # 尾部移出
def size(self):
return len(self.items)
def isEmpty(self):
return self.items == []
q = Queue()
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)
print(q.dequeue()) # 1
print(q.dequeue()) # 2
print(q.dequeue()) # 3
烫手的山芋
6个孩子围成一个圈,第一个孩子手里有一个烫手的山芋,需要在计时器开始计时1秒后将山芋传递给下一个孩子,以此类推。
计时器每计时7秒钟,手里有山芋的孩子退出游戏,直到只剩下一个孩子。请使用队列实现这个游戏策略,排在第几个位置的孩子会获胜。
# 将6个孩子加入到队列中
kids = ['A', 'B', 'C', 'D', 'E', 'F']
q = Queue()
for kid in kids:
q.enqueue(kid)
while q.size() > 1: # 结束游戏的条件
# 开始一轮游戏
for i in range(6): # 山芋传递的次数=计时器间隔-1
# 保证队首的孩子手中有山芋
kid = q.dequeue()
q.enqueue(kid)
# 一轮游戏结束时需要将队首元素移出队列(将手中有山芋的孩子淘汰)
q.dequeue()
print(q.dequeue()) # E
q1 = Queue()
q2 = Queue()
items = [1, 2, 3, 4, 5, 6]
for item in items:
q1.enqueue(item)
while q1.size() >= 1:
# 将q1中的前n-1个元素取出,依次加入到q2中保存起来
while q1.size() > 1:
item = q1.dequeue()
q2.enqueue(item)
# 剩下的元素是最后加入的元素,将其移出,即实现了栈的后入先出。
print(q1.dequeue())
# 交换队列
q1, q2 = q2, q1
同普通队列相比,双端队列可以在头和尾双端进行数据的插入和删除。
Deque() 创建一个deque。
add_front(item) 将数据添加到deque的头部。
add_rear(item) 将数据添加到deque的尾部。
remove_front() 从deque的头部取出元素。
remove_rear() 从deque的尾部取出元素。
is_empty() 判断deque是否为空。
size() 返回deque中元素的数量。
class Dequeue():
def __init__(self):
self.items = []
def add_front(self, item):
self.items.insert(0, item)
def add_rear(self, item):
self.items.append(item)
def remove_front(self):
return self.items.pop(0)
def remove_rear(self):
return self.items.pop()
def is_empty(self):
return self.items == []
def size(self):
return len(self.items)
双端队列应用案例:回文检查
def is_huiwen(input_str):
d = Dequeue()
for each_char in input_str:
d.add_front(each_char)
flag = True
while d.size()>1:
if d.remove_front() != d.removeRear():
flag = False
return flag
print(is_huiwen('abaa')) # False
将所有元素依次不间断地的存入一组连续的内存空间中,这种存储结构是顺序结构。采用顺序存储结构的线性表称为顺序表(Contiguous List),顺序表内元素是有顺序的。
顺序表分为单数据类型和多数据类型,Python中的列表和元组属于多数据类型的顺序表。
优点
存取操作简单高效,通过下标直接操作元素。
缺点
创建顺序表前需要预先确定待存储数据的数量和类型,以便在内存中申请连续的存储空间;
当需要插入或删除内部的一个元素时,整个顺序表需要遍历并移动元素来重新排序,相当于数据搬迁。
链表(Linked List),是一种物理存储单元上非连续、非顺序的存储结构,元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表中的元素没有下标,存储的每一个元素称为一个结点,每个结点包括两个部分:存储数据元素的数据域和存储下一个结点地址的指针域。
节点数据结构的封装
class Node():
def __init__(self, item):
self.item = item
self.next = None
is_empty():链表是否为空
length():链表长度
travel():遍历整个链表
add(item):链表头部添加元素
append(item):链表尾部添加元素
insert(pos, item):指定位置添加元素
remove(item):删除节点
search(item):查找节点是否存在
class LinkedList():
def __init__(self): # 构建一个空的链表
self._head = None # 只可以指向第一个节点
def add(self, item):
node = Node(item)
node.next = self._head
self._head = node
def traval(self):
cur = self._head
while cur:
print(cur.item)
cur = cur.next
def length(self):
count = 0
cur = self._head
while cur:
count += 1
cur = cur.next
return count
def is_empty(self):
return self._head == None
def search_item(self, item):
find = False
cur = self._head
while cur:
cur_item = cur.item # 遍历到节点对应的数据值
if item == cur_item:
find = True
break
cur = cur.next
return find
def append(self, item): # 向链表尾部添加节点
node = Node(item)
if self._head == None: # 链表为空
self._head = node
return
# 链表为非空
cur = self._head
pre = None # 永远指向cur的前一个节点
while cur:
pre = cur
cur = cur.next
# 循环结束后pre指向链表的最后一个节点,cur指向空
pre.next = node
def insert(self, pos, item): # 向指定位置插入节点
node = Node(item)
pre = None
cur = self._head
if (pos < 0) or (pos > self.length()):
print('位置有误,重新输入!!!')
return
if pos == 0:
node.next = self._head
self._head = node
return
for i in range(pos):
pre = cur
cur = cur.next
pre.next = node
node.next = cur
def remove(self, item): # 删除指定的节点
cur = self._head
pre = None
# 如果删除节点为第一个节点的话
if self._head.item == item:
self._head = cur.next
return
while cur:
pre = cur
cur = cur.next
if cur.item == item:
break
pre.next = cur.next
l = LinkedList()
l.append(1)
l.append(2)
l.append(3)
l.append(4)
l.append(5)
l.remove(5)
l.traval()
class LinkedList():
def reverse(self):
if self.is_empty():
return None
previous_node = None
current_node = self._head
next_node = current_node.next
while True:
# 修改指针指向,即反转链表。
current_node.next = previous_node
# 进行偏移
previous_node = current_node
current_node = next_node
if next_node:
next_node = next_node.next
else:
break
self._head = previous_node