# day11数据结构
day11数据结构
目录:
- 数据结构
- 栈
- 队列
- 双端队列
- 链表
- 二叉树
- 算法:
- 二分查找
- 冒泡
- 选择
- 快速
- 插入
- 希尔
- 排序二叉树
什么是算法分析?
- 刚接触编程的学生经常会将自己编写的程序和别人的程序做比对,获取在比对的过程中会发现双方编写的程序很相似但又各不相同。那么就会出现一个有趣的现象:两组程序都是用来解决同一个问题的,但是两组程序看起来又各不相同,那么哪一组程序更好呢?
- a+b+c = 1000 a2 + b2 = c**2 (a,b,c均为自然数),求出a,b,c可能的组合?
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)
---->
0 500 500
200 375 425
375 200 425
500 0 500
评判程序优劣的方法
- 消耗计算机资源和执行效率(无法直观)
- 计算算法执行的耗时
- 时间复杂度(推荐)
时间复杂度
- 评判规则:量化算法执行的操作/执行步骤的数量
- 最重要的项:时间复杂度表达式中最有意义的项
- 使用大O记法来表示时间复杂度:O(算法执行步骤数量表达式中最有意义的一项)
def sumOfN(n):
theSum = 0
for i in range(1,n+1):
theSum = theSum + i
return theSum
print(sumOfN(10))
---->55
-
案例:计算下列算法的时间复杂度
-
a=5 b=6 c=10 for i in range(n): for j in range(n): x = i * i y = j * j z = i * j for k in range(n): w = a*k + 45 v = b*b d = 33 ########################### 3 + 3*n*n + 2*n + 1 3n**2 +2n + 4 O(n**2)
常见的时间复杂度:
- O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
数据结构
-
概念:对于数据(基本类型的数据(int,float,char))的组织方式就被称作为数据结构。数据结构解决的就是一组数据如何进行保存,保存形式是怎样的。
-
案例: 需要存储一些学生的学生信息(name,score),那么这些数据应该如何组织呢?查询某一个具体学生的时间复杂度是什么呢?(三种组织方式)
[
[name,score],
[name1,score1],
]
O(n)
(
[name,score],
[name1,score1],
)
O(n)
{
{'name':'bobo'},
{'score':100},
}
{
name:{score:100},
name2:{score:99}
}
O(1)
-
三种组织形式基于查询的时间复杂度?
-
使用不同的形式组织数据,在基于查询时的时间复杂度是不一样的。因此认为算法是为了解决实际问题而设计的,数据结构是算法需要处理问题的载体。
list(for in range()) 速度最快
其次是 [] 推到式
然后是???
栈
-
特性:先进后出的数据结构
-
栈顶,栈尾
-
应用:每个 web 浏览器都有一个返回按钮。当你浏览网页时,这些网页被放置在一个栈中(实际是网页的网址)。你现在查看的网页在顶部,你第一个查看的网页在底部。如果按‘返回’按钮,将按相反的顺序浏览刚才的页面。
-
Stack() 创建一个空的新栈。 它不需要参数,并返回一个空栈。
-
push(item)将一个新项添加到栈的顶部。它需要 item 做参数并不返回任何内容。
-
pop() 从栈中删除顶部项。它不需要参数并返回 item 。栈被修改。
-
peek() 从栈返回顶部项,但不会删除它。不需要参数。 不修改栈。
-
isEmpty() 测试栈是否为空。不需要参数,并返回布尔值。
-
size() 返回栈中的 item 数量。不需要参数,并返回一个整数。
class Stack():
def __init__(self):
self.items = []
def push(self,item):
self.items.append(item)
def drop(self):
return self.items.pop()
def isEmpty(self):
return self.items == []
def size(self):
return len(self.items)
s = Stack()
s.push(1)
len(s.items)
print(s.drop())
print(s.isEmpty())
print(s.size())
s.push('第一')
s.push('第二')
s.push('第三')
s.items # ['第一', '第二', '第三']
# print(s.drop()) # 后进先出对的 # '第三
--->
1
True
0
s = Stack()
def getRequest(url):
s.push(url)
def showCurenrUrl():
print('当前页面展示的url:'+s.pop())
def back():
print('回退按钮点击后显示的url:',s.pop())
getRequest('www.1.com')
getRequest('www.2.com')
getRequest('www.3.com')
showCurenrUrl()
back()
back()
---->
当前页面展示的url:www.3.com
回退按钮点击后显示的url: www.2.com
回退按钮点击后显示的url: www.1.com
队列
-
队列:先进先出
-
应用场景:
- 我们的计算机实验室有 30 台计算机与一台打印机联网。当学生想要打印时,他们的打印任务与正在等待的所有其他打印任务“一致”。第一个进入的任务是先完成。如果你是最后一个,你必须等待你前面的所有其他任务打印
-
Queue() 创建一个空的新队列。 它不需要参数,并返回一个空队列。
-
enqueue(item) 将新项添加到队尾。 它需要 item 作为参数,并不返回任何内容。
-
dequeue() 从队首移除项。它不需要参数并返回 item。 队列被修改。
-
isEmpty() 查看队列是否为空。它不需要参数,并返回布尔值。
-
size() 返回队列中的项数。它不需要参数,并返回一个整数。
class Queue():
def __init__(self):
self.items = []
def push(self,index,item):
self.items.insert(index,item)
def drop(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
q = Queue()
q.push(0,'第一')
q.push(0,'第二')
q.push(0,'第三')
q.items # ['第三', '第二', '第一']
# q.drop() # '第一'
q.peek()
烫手的山芋游戏
- 案例:烫手的山芋
- 烫手山芋游戏介绍:6个孩子围城一个圈,排列顺序孩子们自己指定。第一个孩子手里有一个烫手的山芋,需要在计时器计时1秒后将山芋传递给下一个孩子,依次类推。规则是,在计时器每计时7秒时,手里有山芋的孩子退出游戏。该游戏直到剩下一个孩子时结束,最后剩下的孩子获胜。请使用队列实现该游戏策略,排在第几个位置最终会获胜。
- 结论:
- 七秒即时到了之后,山芋被传递了6次。
- 保证手里有山芋的孩子永远站在队列的头部
class Queue():
def __init__(self):
self.items = []
def push(self,index,item):
self.items.insert(index,item)
def drop(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
# 7 秒 ,去除第一个人中手上不传出的1秒
q1 = Queue()
kids = ['A','B','C','D','E','F']
for kid in kids :
q1.push(0,kid) # 把数据添加进去
q1.items
while len(q1.items)>1: # 当数据只有1个时,为游戏的胜者
for i in range(6):
a = q1.drop() # 先删,后添加到最初位置,就是换顺序
q1.push(0,a) # ,一个个往后走,然后保证山芋在第一位
q1.drop() # 删除第一位
q1.items
双端队列
-
同同列相比,有两个头部和尾部。可以在双端进行数据的插入和删除,提供了单数据结构中栈和队列的特性
-
Deque() 创建一个空的新 deque。它不需要参数,并返回空的 deque。
-
addFront(item) 将一个新项添加到 deque 的首部。它需要 item 参数 并不返回任何内容。
-
addRear(item) 将一个新项添加到 deque 的尾部。它需要 item 参数并不返回任何内容。
-
removeFront() 从 deque 中删除首项。它不需要参数并返回 item。deque 被修改。
-
removeRear() 从 deque 中删除尾项。它不需要参数并返回 item。deque 被修改。
-
isEmpty() 测试 deque 是否为空。它不需要参数,并返回布尔值。
-
size() 返回 deque 中的项数。它不需要参数,并返回一个整数。
class Queue():
def __init__(self):
self.items = []
# 队列方式加到栈首
def addfront(self,index,item):
self.items.insert(index,item)
# 栈模式,从前面取,就是双端队列
def addback(self,item):
self.items.append(item)
def dropfront(self):
return self.items.pop()
def dropback(self):
return self.items.pop(0) # 123 先进先出
def isEmpty(self):
return self.items == []
def size(self):
return len(self.items)
def peek(self): #最后一个的位置 因为从零开始
return len(self.items)-1
q = Queue()
q.addfront(0,1)
q.addfront(0,2)
q.addfront(0,3)
q.items
q.dropfront()
q.dropfront()
q.dropfront()
# 首先进先出
q.addback(1)
q.addback(2)
q.addback(3)
q.dropfront()
q.dropfront()
q.dropfront()
q.items
# 栈 删除方式就是栈了 [1]
q.addback(1)
q.addback(2)
q.addback(3)
q.items
q.dropback()
q.dropback()
q.dropback()
# 后进后厨
尾先进新出: 两边都是
双端队列应用案例:回文检查
回文是一个字符串,读取首尾相同的字符,例如,radar toot madam。
抄的老师的:
ab = 'abcdaddcba'
def huiwen(st):
q = Queue()
ex = 'True'
for s in st:
q.addback(s)
while len(q.items)>1:
if q.dropback()!= q.dropfront():
ex = 'False'
break
return ex
print(huiwen(ab))
False
内存
-
计算机的作用
- 用来存储和运算二进制的数据
-
衡量计算机内存大小的单位:
- bit(位):
- 字节:8bit
- kb:1024字节
- mb:1024kb
-
问题:计算机如何计算1+2?
- 必须先在计算机的内存开辟内存空间
- 才将对应的数值存储到该内存中
-
变量的概念
- a = 10 在计算机中表示的含义(内存图)
- 内存空间开辟好了之后,每一块内存空间都会有两个默认的属性
- 内存空间的大小:算机内存大小的单位
- 内存空间的地址:十六进制的数值
- 地址作用:用来定位(追踪)指定内存空间,就可以获取该内存空间中存储的数据
- 变量本质表示的是内存空间的地址,通常变量想要表示的就是内存地址对应的内存空间
-
理解a=10的内存图(引用,指向)
- 引用:变量
- 指向:如果某一个变量/引用存储了某一块内存空间的地址后,则表示该变量指向了该内存地址所对应的内存空间。
-
不同数据占用内存空间的大小
顺序表
-
容器/数据结构中存储的元素是有顺序的,顺序表的结构可以分为两种形式:单数据类型(numpy)和多数据类型(list)。
-
python中的列表和元组就属于多数据类型的顺序表
-
单数据类型顺序表的内存图(内存连续开启)
-
多数据类型顺序表的内存图(内存非连续开辟)
-
顺序表的弊端:顺序表的结构需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁。
链表:
相对于顺序表,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理且进行扩充时不需要进行数据搬迁。
- 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是每一个结点(数据存储单元)里存放下一个结点的信息(即地址)
要求
. is_empty():链表是否为空
. length():链表长度
. travel():遍历整个链表
. add(item):链表头部添加元素
. append(item):链表尾部添加元素
. insert(pos, item):指定位置添加元素
. remove(item):删除节点
. search(item):查找节点是否存在
建立节点
class Node():
def __init__(self,item): #建立一个节点,新节点,后续的还不知道,所以没有next
self.item = item # 添加的值
self.next = None # 链子指向,因为是新值,还没来得及建链,故None
node = Node(3) # 实例化一个
class Link():
def __init__(self):
# 建立一个空链表
self._head = None # 第一个指向None ,还没有添加进来
def add(self,item): # 链表头部添加元素
node = Node(item) # 实例化一个, 没有后续,next=None item=item
node.next = self._head
# 之前的head 1 空, 2次添加是上一个node,因为head被重新赋值了
# 变成了上一个的node(实例化的) 下面一行代码 ,然后一直迭代
self._head = node # 给head指向,实例化的那个Node 第一次
- 这两个函数都是用的一样的逻辑:
- 都是不知道循环次数,所以用while循环,然后,一直next ,直到为空,然后退出
def travel(self): # 遍历整个链表
cur = self._head # 地址
while cur: # 最后为空
print(cur.item) # 值
cur = cur.next # 下一个
def length(self): # 链表长度
cur = self._head # 地址
count = 0
while cur: # 最后为空
print(cur.item) # 值
count += 1
cur = cur.next # 下一个
# print('长度为',count)
return count
- 不要忘了_head就是代表的 节点
def isEmpty(self):
return self._head == None
- for 循环 然后返回 find为True说明找到了 ,找不到,和上面一样的思想,next往下走
def search(self,item): #查找节点是否存在
cur = self._head
find = 'False'
for i in range(self.length()):
if cur.item == item :
find = 'True'
else:
cur = cur.next
return find
- 犯得两个错误,因为copy的,结果冒号是中文,还没发现
- 下面的cur,pre循环,链子的拼接
#加到最后
#最后一个指向为none ,主要是对前一个操作
#因为最后一个next = None,而前一个,指向的是最后一个,所以要链一下最后一个,一条链
def append(self,item):
node = Node(item)
cur = self._head # 地址
# 注意的项:
# 第一次添加,没有前一项 # 的时候直接,没有最后一个,next
if self.isEmpty():
self._head = node
return # 不要忘了,return 退出
pre = None
while cur: # 最后为空
pre = cur # 前一个
cur = cur.next # 下一个
pre.next = node
# 插入
# 没想到不难,会了append之后,改一下链子,别的不大动,
# 值的注意的点,范围插入时,
def insert(self, pos, item):
node = Node(item)
cur = self._head # 地址
if 0 > pos or pos > self.length()+1:
print(pos,'超范围,没法插入,请重新插入')
return
pre = None
# 最后为空
for i in range(1,pos): # 第几个位置 前面添加后天添加建立两个连接
pre = cur # 前一个
cur = cur.next # 下一个
node.next = pre.next # node.next ✔
# 顺序不能错了,先把后面的链子给了中间的
pre.next = node # 然后给前面的链子,中间的
#删除节点
def remove(self,item):
node = Node(item)
cur = self._head # 地址
if self.search(item)=='False': #用搜索,查,在里面然后删
return '不在里面,不用删除'
if self.length()==1 or self.length(): #1个或者多个的时候不走下面循环
self._head == None
return
pre = None
# 最后为空
for i in range(1,self.length()): # 第几个位置 前面添加后天添加建立两个连接
pre = cur # 前一个
cur = cur.next # 下一个
pre.next = cur.next #上一个的链连接下一个的下一个的链,所以下一个断开连接
print('长度',self.length())
结果打印
link = Link()
link.add(1)
link.add(2)
link.add(3)
# link.append(1)
# link.append(2)
# link.append(3)
# link.insert(2,4)
# link.insert(2,4)
# link.insert(4,4)
# link.insert(44,4)# 超范围的不行
# link.travel()
# link.length()
# link.isEmpty()
# link.search(-1)
link.remove(1)
print(link.remove(4))
# link.search(1)
3
2
1
3
2
1
3
2
1
3
2
1
不在里面,不用删除