python 高级
main.py
# This is a sample Python script. # Press Shift+F10 to execute it or replace it with your code. # Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings. def print_hi(name): # Use a breakpoint in the code line below to debug your script. print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint. # Press the green button in the gutter to run the script. if __name__ == '__main__': print_hi('PyCharm') # See PyCharm help at https://www.jetbrains.com/help/pycharm/
mysql数据库.py
''' 视图 视图就是一个能够把复杂SQL语句的功能封装起来的一 个虚表。所以我们在创建视图的时候,主要的工作就落 在创建这条SQL查询语句上 图是对若干张基本表的引用,一张虚表,不存储具体 的数据(基本表数据发生了改变,视图也会跟着改变) 视图的好处:方便操作,特别是查询操作,减少复杂的 SQL语句,增强可读性,复用性; 事务四大特性ACID 原子性(atomicity) 一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功, 要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性 一致性(consistency) 数据库总是从一个一致性的状态转换到另一个一致性的状态。(在前面的例子中,一致性确保了 即使在执行第三、四条语句之间时系统崩溃,支票账户中也不会损失500元,因为事务最终没有 提交,所以事务中所做的修改也不会保存到数据库中。) 隔离性(isolation) 通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。 (在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时 有另外的一个账户汇总程序开始运行,则其看到支票帐户的余额并没有被 减去500元。) 持久性(durability) 一旦事务提交,则其所做的修改会永久保存到数据库。(此时即使系统崩溃,修改的数据也不会丢失。) 怎么使用事务? 开启事务 开启事务后执行修改命令,变更会维护到本地缓存中,而不维护到物理表中 begin; 或者 start transaction; 提交事务 将缓存中的数据变更维护到物理表中 commit; 回滚事务 放弃缓存中变更的数据 表示事务执行失败 应该回到开始事务前的状态 rollback; 什么是索引? 索引的本质 索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个 组成部分),它们包含着对数据表里所有记录的位置信息。 如何使用索引? 查看表中已有索引 show index from 表名; 创建索引 alter table 表名 add index 索引名【可选】(字段名,…); 删除索引 drop index 索引名称 on 表名; 数据库设计三范式 第一、二范式 第一范式(1NF): 强调的是字段的原子性,即一个字段不能够再分成其他几个字段。 第二范式(2NF): 满足 1NF的基础上,另外包含两部分内容 一是表必须有一个主键 二是非主键字段必须完全依赖于主键,而不能只依赖于主键的一部分 第三范式 第三范式(3NF): 满足 2NF 另外非主键字段必须直接依赖于主键,不能存在传递依赖。 即不能存在:非主键字段 A 依赖于非主键字段 B,非主键字段 B 依赖于主键的情况。 范式: 设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式, 各种范式呈递次规范,越高的范式数据库冗余越小。 第一范式(1NF): 强调的是列的原子性,即列不能够再分成其他几列。 第二范式(2NF): 满足 1NF,另外包含两部分内容,一是表必须有一个主键; 二是非主键字段 必须完全依赖于主键,而不能只依赖于主键的一部分。 第三范式(3NF): 满足 2NF,另外非主键列必须直接依赖于主键,不能存在传递依赖。 即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。 E-R模型及表间关系 power designer,db desinger等这些软件来画出实体-关系模型(E-R模型) E-R模型即实体-关系模型 E-R模型就是描述数据库存储数据的结构模型 表现形式 实体: 用矩形表示,并标注实体名称 属性: 用椭圆表示,并标注属性名称 关系: 用菱形表示,并标注关系名称 E-R模型中的三种关系 一对一 一对多(1-n) 多对多(m-n) 开启时间检测 set profiling=1; select * from test; show profiles; ''' ''' Python连接MySQL数据库 安装pymysql第三方包: sudo pip3 install pymysql pymysql使用步骤 导入 pymysql 包 import pymysql 创建连接对象 connect() 获取游标对象 连接对象.cursor() ④ pymysql完成数据的查询操作 游标对象.execute() ⑤ 关闭游标和连接 游标对象.close() 连接对象.close() ''' # # 导入 pymysql 包 # import pymysql # # 创建连接对象 # conn = pymysql.connect(host="192.168.79.130", port=3306, user='root',password='123456',database='python_test_1',charset='utf8') # utf8 # # 获取游标对象 # cs = conn.cursor() # # ④ pymysql完成数据的查询操作 # # 游标对象.execute() # sql = 'select * from student;' # # 获取sql语句影响的行数 # # content = cs.execute(sql) # cs.execute(sql) # content = cs.fetchone() # print(content) # content = cs.fetchall() # print(content) # # ⑤ 关闭游标和连接 # cs.close() # conn.close() # # 导入 pymysql 包 # import pymysql # # 创建连接对象 # conn = pymysql.connect(host="192.168.79.130", port=3306, user='root',password='123456',database='python_test_1',charset='utf8') # utf8 # # 获取游标对象 # cs = conn.cursor() # # # # 增加数据 # # sql = "insert into students(name) values('杨老六')" # 体检创建表,设定主键为自增 # # cs.execute(sql) # # # 删除数据 # # sql = "delete from students where id=5; " # 体检创建表,设定主键为自增 # # cs.execute(sql) # # # 修改数据 # sql = "update students set name = '老王' where id=1; " # 体检创建表,设定主键为自增 # cs.execute(sql) # # sql = 'select * from students;' # cs.execute(sql) # # content = cs.fetchall() # for i in content: # print(i) # # ⑤ 关闭游标和连接 # # # 提交数据 增删改都需要提交 # conn.commit() # cs.close() # conn.close() ''' SQL语句参数化 select * from goods where name='%s'" % find_name 不安全的方式 sql = "select * from goods where name='%s'" % find_name cs.execute(sql) 安全的方式 构造参数列表 params = [find_name] 执行select语句 sql = “select * from goods where name=%s” (这里的%s不需要加引号) cs.execute(sql,params) ''' # 导入 pymysql 包 import pymysql # 创建连接对象 conn = pymysql.connect(host="192.168.79.130", port=3306, user='root',password='123456',database='python_test_1',charset='utf8') # utf8 # 获取游标对象 cs = conn.cursor() # SQL注入 # find_name = '老王' # find_name = "'or 1 or'" # # sql = "select * from students where name=''or 1 or''" # # sql = "select * from students where name='%s'" % find_name # cs.execute(sql) # 安全的方式 find_name = '老王' # find_name = "'or 1 or'" sql = "select * from students where name=%s" cs.execute(sql,[find_name]) content = cs.fetchall() for i in content: print(i) # ⑤ 关闭游标和连接 cs.close() conn.close()
test1.py
class Node(object): """节点类""" def __init__(self, item): self.item = item self.lchild = None self.rchild = None class BinaryTree(object): """完全二叉树""" def __init__(self, node=None): self.root = node def add(self, item): """添加节点""" if self.root == None: self.root = Node(item) return # 队列 queue = [] # 从尾部添加数据 queue.append(self.root) while True: # 从头部取出数据 node = queue.pop(0) # 判断左节点是否为空 if node.lchild == None: node.lchild = Node(item) return else: queue.append(node.lchild) if node.rchild == None: node.rchild = Node(item) return else: queue.append(node.rchild) def breadh_travel(self): """广度优先遍历""" if self.root == None: return # 队列 queue = [] # 添加数据 queue.append(self.root) while len(queue)>0: # 取出数据 node = queue.pop(0) print(node.item, end="") # 判断左右子节点是否为空 if node.lchild is not None: queue.append(node.lchild) if node.rchild is not None: queue.append(node.rchild) def preorder_travel(self, root): """先序遍历 根 左 右""" if root is not None: print(root.item, end="") self.preorder_travel(root.lchild) self.preorder_travel(root.rchild) def inorder_travel(self, root): """中序遍历 左 根 右""" if root is not None: self.inorder_travel(root.lchild) print(root.item, end="") self.inorder_travel(root.rchild) def postorder_travel(self, root): """后序遍历 根 左 右""" if root is not None: self.postorder_travel(root.lchild) self.postorder_travel(root.rchild) print(root.item, end="") if __name__ == '__main__': tree = BinaryTree() tree.add("0") tree.add("1") tree.add("2") tree.add("3") tree.add("4") tree.add("5") tree.add("6") tree.add("7") tree.add("8") tree.add("9") tree.preorder_travel(tree.root) print() tree.inorder_travel(tree.root) print() tree.postorder_travel(tree.root)
高级.py
''' 进程的创建步骤 1. 导入进程包 import multiprocessing 2. 通过进程类创建进程对象 进程对象 = multiprocessing.Process() 3. 启动进程执行任务 进程对象.start() 通过进程类创建进程对象 进程对象 = multiprocessing.Process(target=任务名) target 执行的目标任务名,这里指的是函数名(方法名) name 进程名,一般不用设置 group 进程组,目前只能使用None ''' # import os # import time # import multiprocessing # 编写代码 # def coding(): # for i in range(3): # print('coding......') # time.sleep(0.2) # # def music(): # for i in range(3): # print('music......') # time.sleep(0.2) # # if __name__ == '__main__': # # coding() # # music() # # 通过进程类创建进程对象 # coding_process = multiprocessing.Process(target=coding) # music_process = multiprocessing.Process(target=music) # # 启动进程 # coding_process.start() # music_process.start() import threading ''' 进程执行带有参数的任务 args 以元组的方式给执行任务传参 kwargs 以字典方式给执行任务传参 args参数的使用 # target: 进程执行的函数名 # args: 表示以元组的方式给函数传参sing_process = multiprocessing.Process(target=sing, args=(3,)) sing_process.start() kwargs参数的使用 # target: 进程执行的函数名# kwargs: 表示以字典的方式给函数传参 dance_process = multiprocessing.Process (target=dance, kwargs={"num": 3}) # 启动进程 dance_process.start() ''' # import os # import time # import multiprocessing # # def coding(num): # # 获取进程编号 # print('coding>>>%d' % os.getpid()) # # 获取进程的父进程编号 # print('main>>>%d' % os.getppid()) # for i in range(num): # print('coding......') # time.sleep(0.2) # # def music(count): # print('music>>>%d' % os.getpid()) # print('main>>>%d' % os.getppid()) # for i in range(count): # print('music......') # time.sleep(0.2) # # if __name__ == '__main__': # # 通过进程类创建进程对象 # coding_process = multiprocessing.Process(target=coding,args=(3,)) # music_process = multiprocessing.Process(target=music,kwargs={'count': 3}) # print('main>>>%d' % os.getpid()) # # 启动进程 # coding_process.start() # music_process.start() ''' 进程的注意点 进程之间不共享全局变量 主进程会等待所有的子进程执行结束再结束 ''' # import multiprocessing # import time # # my_list = [] # # def write_data(): # for i in range(3): # my_list.append(i) # print('add:',i) # print(my_list) # # def read_data(): # print('read_data', my_list) # # if __name__ == '__main__': # # 创建写入进程 # wp = multiprocessing.Process(target=write_data) # rp = multiprocessing.Process(target=read_data) # # wp.start() # time.sleep(1) # rp.start() # # 创建读取数据进程 ''' 创建子进程会对主进程资源进行拷贝,也就是说子进程是主进程的一个副本,好比是一对双胞胎,之所以进程之间不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已。 打印结果 add: 0 add: 1 add: 2 [0, 1, 2] read_data [] ''' ''' 主进程会等待所有的子进程执行结束再结束 ''' # import time # import multiprocessing # # def work(): # for i in range(10): # print('工作中。。。') # time.sleep(0.2) # # if __name__ == '__main__': # work_process = multiprocessing.Process(target=work) # # 设置守护主进程 # # work_process.daemon = True # work_process.start() # # 延时1秒 # time.sleep(1) # # 销毁子进程 # work_process.terminate() # print('主进程执行完毕,,,') ''' 结束主进程后,子进程也销毁 设置守护主进程 销毁子进程 ''' ''' 线程的介绍 线程是程序执行的最小单位 , 实际上进程只负责分配资源 , 而利用这些资源执行程序的是线程 , 也就说进程是线程的容器 , 一个进程中最少有一个线程来负责执行程序 . 同时线程自己不拥有系统资源,只需要一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源 . 线程的创建步骤 1. 导入线程模块 import threading 2. 通过线程类创建线程对象 线程对象 = threading.Thread(target=任务名) 3. 启动线程执行任务 线程对象.start() 通过线程类创建线程对象 线程对象 = threading.Thread(target=任务名) target 执行的目标任务名,这里指的是函数名(方法名) name 线程名,一般不用设置 group 线程组,目前只能使用None ''' # import time # import threading # # def coding(): # for i in range(3): # print('coding....') # time.sleep(0.2) # # def music(): # for i in range(3): # print('music....') # time.sleep(0.2) # # if __name__ == '__main__': # coding_thread = threading.Thread(target = coding) # music_thread = threading.Thread(target=music) # coding_thread.start() # music_thread.start() import time import threading # def work(): # for i in range(10): # print('work....') # time.sleep(0.2) # # if __name__ == '__main__': # # wt = threading.Thread(target=work) # # 设置守护信息 # wt = threading.Thread(target=work,daemon=True) # # wt.setDaemon(True) # wt.start() # time.sleep(1) # print('主进程执行完毕') # 线程间执行是无序的 # def get_info(): # current_thread = threading.currentThread() # print(current_thread) # # if __name__ == '__main__': # for i in range(10): # wt = threading.Thread(target=get_info) # wt.start() # 线程间可以共享全局变量 # my_list = [] # # def write_data(): # for i in range(3): # print('add ...',i) # my_list.append(i) # # def read_data(): # print('read:',my_list) # # # if __name__ == '__main__': # wt = threading.Thread(target=write_data) # rd = threading.Thread(target=read_data) # # wt.start() # time.sleep(1) # rd.start() # 线程间共享全局变量,数据出现错误的问题 # g_num = 0 # # def add(): # for i in range(1000000): # global g_num # g_num += 1 # print('g_num',g_num) # # def add2(): # for i in range(1000000): # global g_num # g_num += 1 # print('g_num', g_num) # # if __name__ == '__main__': # wt = threading.Thread(target=add) # rd = threading.Thread(target=add2) # # wt.start() # rd.start() ''' 解决办法: 同步: 就是协同步调,按预定的先后次序进行运行。好比现实生活中的对讲机,你说完后,我再说,不能大家一起说。 使用线程同步: 保证同一时刻只能有一个线程去操作全局变量 线程同步方式: 互斥锁 互斥锁的创建 mutex = threading.Lock() 上锁 mutex.acquire() 释放锁 mutex.release() ''' # g_num = 0 # # def add(): # mutex.acquire() # for i in range(1000000): # global g_num # g_num += 1 # mutex.release() # print('g_num',g_num) # # def add2(): # mutex.acquire() # for i in range(1000000): # global g_num # g_num += 1 # mutex.release() # print('g_num', g_num) # # if __name__ == '__main__': # mutex = threading.Lock() # wt = threading.Thread(target=add) # rd = threading.Thread(target=add2) # # wt.start() # rd.start() ''' 死锁 一直等待对方释放锁的情景就是死锁。 原因:没有及时或者在正确的位置释放锁 注意: 使用互斥锁的时候需要注意死锁的问题,要在合适的地方注意释放锁。 死锁一旦产生就会造成应用程序的停止响应,应用程序无法再继续往下执行了。 ''' # g_num = 0 # # def add(): # print('add....') # mutex.acquire() # for i in range(1000000): # global g_num # g_num += 1 # print('g_num',g_num) # # def add2(): # print('add2....') # mutex.acquire() # for i in range(1000000): # global g_num # g_num += 1 # print('g_num', g_num) # # if __name__ == '__main__': # mutex = threading.Lock() # wt = threading.Thread(target=add) # rd = threading.Thread(target=add2) # # wt.start() # rd.start() ''' 进程和线程对比 区别对比 1. 进程之间不共享全局变量 2. 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步 3. 创建进程的资源开销要比创建线程的资源开销要大 4. 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位 5. 线程不能够独立执行,必须依存在进程中 ''' ''' 网络 将具有独立功能的多台计算机通过通信线路和通信设备连接起来,在网络管理软件及网络通信协议下,实现资源共享和信息传递的虚拟平台。 端口号可以分为两类: 知名端口号和动态端口号 知名端口号的范围是0到1023 动态端口号的范围是1024到65535 知名端口号是指众所周知的端口号,范围从0到1023,这些端口号一般固定分配给一些服务,比如21端口分配给FTP(文件传输协议)服务, 25端口分配给SMTP(简单邮件传输协议)服务,80端口分配给HTTP服务。 动态端口号的范围是从1024到65535,如果程序员开发的程序没有设置端口号,操作系统会在动态端口号这个范围内随机生成一个给开发的应用程序使用。 传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 [1] 定义。 Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。RFC 768 [1] 描述了 UDP。 TCP是一个稳定、可靠的传输协议,常用于对数据进行准确无误的传输,比如: 文件下载,浏览器上网。 他的特点是: 1. 面向连接 2. 可靠传输 TCP采用发送应答机制 超时重传 错误校验 流量控制和阻塞管理 ''' ''' python3编码转换 encoed()和decode()函数可以接受参数,encoding是指在编解码过程中使用的编码方案。 bytes.decode(encoding=“utf-8”) str.encode(encoding=”utf-8”) 导入socket模块 import socket 创建服务端socket对象使用socket类 socket.socket(AddressFamily, Type) ''' ''' TCP客户端程序开发流程 1.创建客户端套接字对象 2.和服务端套接字建立连接 3.发送数据 4.接收数据 5.关闭客户端套接字 TCP客户端程序开发 TCP服务端程序开发流程 ''' import socket # if __name__ == '__main__': # # 创建udp的套接字# AF_INET: ipv4地址类型# SOCK_STREAM: TCP传输协议类型 # tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # tcp_client_socket.connect(('127.0.0.1', 8080)) # tcp_client_socket.send('nihao'.encode(encoding='utf-8')) # # 阻塞等待数据到来 # recv_data = tcp_client_socket.recv(1024) # print(recv_data.decode()) # 默认utf-8 # tcp_client_socket.close() # 下载【网络调试助手】本地开启8080测试 ''' TCP服务端程序开发流程 1.创建服务端套接字对象 2. 绑定IP地址和端口号 3.设置监听 4.等待接受客户端的连接请求 5.接收数据 6.发送数据 7.关闭套接字 ''' # if __name__ == '__main__': # tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # # tcp_server_socket.bind(('127.0.0.1', 8888)) # # 设置端口号复用 # tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # tcp_server_socket.bind(('', 8888)) # '' 则默认为本机ip地址 # # 设置监听128:代表服务端等待排队连接的最大数量 # tcp_server_socket.listen(128) # # 阻塞等待,返回一个泳衣和客户端通信的socket,和客户端的地址 # conn_socket,ip_port = tcp_server_socket.accept() # print('客户端地址:',ip_port) # recv_data = conn_socket.recv(1024) # print(recv_data.decode()) # # conn_socket.send('我服务端已经收到你客户端发送过来的数据了'.encode(encoding='utf-8')) # conn_socket.close() # tcp_server_socket.close() ''' 当客户端的套接字调用close后,服务器端的recv会解阻塞,返回的数据长度为0,服务端可以通过返回数据的长度来判断客户端是否已经下线,反之服务端关闭套接字,客户端的recv也会解阻塞,返回的数据长度也为0。 ''' ''' 案例-多任务版TCP服务端程序开发 ''' # if __name__ == '__main__': # tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # # tcp_server_socket.bind(('127.0.0.1', 8888)) # # 设置端口号复用 # tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # tcp_server_socket.bind(('', 8888)) # '' 则默认为本机ip地址 # # 设置监听128:代表服务端等待排队连接的最大数量 # tcp_server_socket.listen(128) # while True: # # 阻塞等待,返回一个用以和客户端通信的socket,和客户端的地址 # # 如果有新的客户端来连接服务端,那么就产生一个新的套接字专门为这个客户端服务 # conn_socket,ip_port = tcp_server_socket.accept() # print('客户端地址:',ip_port) # recv_data = conn_socket.recv(1024) # print(recv_data.decode()) # # conn_socket.send('我服务端已经收到你客户端发送过来的数据了'.encode(encoding='utf-8')) # conn_socket.close() ''' 以上代码,存在问题;需要第一个客户端服务完成后(连接并且接收数据后),才能服务第二个连接的客户端 ''' ''' 多任务来解决 ''' # def handle_client(conn_socket): # recv_data = conn_socket.recv(1024) # print(recv_data.decode()) # conn_socket.send('我服务端已经收到你客户端发送过来的数据了'.encode(encoding='utf-8')) # conn_socket.close() # print(mystr) # # # if __name__ == '__main__': # mystr = 'sss' # 这个变量可以被其他方法访问; # tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # # tcp_server_socket.bind(('127.0.0.1', 8888)) # # 设置端口号复用 # tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # tcp_server_socket.bind(('', 8888)) # '' 则默认为本机ip地址 # # 设置监听128:代表服务端等待排队连接的最大数量 # tcp_server_socket.listen(128) # while True: # # 阻塞等待,返回一个用以和客户端通信的socket,和客户端的地址 # # 如果有新的客户端来连接服务端,那么就产生一个新的套接字专门为这个客户端服务 # conn_socket,ip_port = tcp_server_socket.accept() # print('客户端地址:',ip_port) # # sub_thread = threading.Thread(target=handle_client,args=(conn_socket,)) # sub_thread.start() ''' python作用域是以函数、类、模块来区分的,而不是块 也就是说if、while,for并不会影响变量的作用域!!!,python中没有块作用域。 这就能解释python的if __name__=='__mian__'中声明的变量同样是全局变量 python 作用域的优先级:LEGB LEGB 表示的是 Local, Enclosing, Global, and Built-in 。同名变量优先访问局部变量,其次是闭包变量,其次是全局变量,最后才是内置变量 ''' ''' http: get请求报文分析 ---- 请求行 ---- GET /a/b/c HTTP/1.1 # GET请求方式 请求资源路径 HTTP协议版本 ---- 请求头 ----- Host: www.itcast.cn # 服务器的主机地址和端口号,默认是80 Connection: keep-alive # 和服务端保持长连接 Upgrade-Insecure-Requests: 1 # 让浏览器升级不安全请求,使用https请求 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 # 用户代理,也就是客户端的名称 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 # 可接受的数据类型 Accept-Encoding: gzip, deflate # 可接受的压缩格式 Accept-Language: zh-CN,zh;q=0.9 #可接受的语言 Cookie: pgv_pvi=1246921728; # 登录用户的身份标识 ---- 空行 ---- post请求报文分析 ---- 请求行 ---- POST /xmweb?host=mail.itcast.cn&_t=1542884567319 HTTP/1.1 # POST请求方式 请求资源路径 HTTP协议版本 ---- 请求头 ---- Host: mail.itcast.cn # 服务器的主机地址和端口号,默认是80 Connection: keep-alive # 和服务端保持长连接 Content-Type: application/x-www-form-urlencoded # 告诉服务端请求的数据类型 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 # 客户端的名称 ---- 空行 ---- ---- 请求体 ---- username=hello&pass=hello # 请求参数 http响应: --- 响应行/状态行 --- HTTP/1.1 200 OK # HTTP协议版本 状态码 状态描述 --- 响应头 --- Server: Tengine # 服务器名称 Content-Type: text/html; charset=UTF-8 # 内容类型 Connection: keep-alive # 和客户端保持长连接 Date: Fri, 23 Nov 2018 02:01:05 GMT # 服务端的响应时间 --- 空行 --- --- 响应体 --- <!DOCTYPE html><html lang=“en”> …</html> # 响应给客户端的数据 浏览器调试工具 Headers选项总共有三部分组成: General: 主要信息 Response Headers: 响应头 Request Headers: 请求头 ''' ''' 搭建静态web服务器 test@ubuntu20:~/python/static$ pwd /home/test/python/static test@ubuntu20:~/python/static$ python3 -m http.server 9000 Serving HTTP on 0.0.0.0 port 9000 (http://0.0.0.0:9000/) ... 浏览器访问 static目录下资源: http://192.168.79.128:9000/index.html ''' ''' 静态服务器 返回固定页面数据 ''' # if __name__ == '__main__': # tcp_serve_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # tcp_serve_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # tcp_serve_socket.bind(('', 8080)) # tcp_serve_socket.listen(128) # while True: # client_socket, client_addr = tcp_serve_socket.accept() # client_request_data = client_socket.recv(1024).decode(encoding='utf-8') # print(client_request_data) # with open('./static/index.html', 'rb') as f: # file_data = f.read() # # response_line = "HTTP/1.1 200 OK \r\n" # # response_header = "Server:pwb\r\n" # # response_body = file_data # # response_data = (response_line + response_header + '\r\n').encode() + response_body # # client_socket.send(response_data) # # client_socket.close() ''' 由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally来实现: try: f = open('/path/to/file', 'r') print(f.read()) finally: if f: f.close() 但是每次都这么写实在太繁琐,所以,Python引入了with语句来自动帮我们调用close()方法: with open('/path/to/file', 'r') as f: print(f.read()) 调用read()会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用read(size)方法, 每次最多读取size个字节的内容。另外,调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。因此,要根据需要决定怎么调用。 ''' ''' 静态Web服务器-返回指定页面数据 ''' # if __name__ == '__main__': # tcp_serve_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # tcp_serve_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # tcp_serve_socket.bind(('', 8080)) # tcp_serve_socket.listen(128) # while True: # client_socket, client_addr = tcp_serve_socket.accept() # client_request_data = client_socket.recv(1024).decode(encoding='utf-8') # print(client_request_data) # request_data = client_request_data.split(' ') # print(request_data) # request_path = request_data[1] # # if request_path == '/': # request_path = '/index.html' # try: # with open('./static' + request_path, 'rb') as f: # file_data = f.read() # except Exception as e: # response_line = "HTTP/1.1 404 OK \r\n" # # response_header = "Server:pwb\r\n" # # response_body = '404 Not Found sorry' # # response_data = (response_line + response_header + '\r\n').encode() + response_body.encode() # # client_socket.send(response_data) # else: # response_line = "HTTP/1.1 200 OK \r\n" # # response_header = "Server:pwb\r\n" # # response_body = file_data # # response_data = (response_line + response_header + '\r\n').encode() + response_body # # client_socket.send(response_data) # finally: # client_socket.close() ''' 静态Web服务器-多任务版 ''' # import threading # # def handle_client_request(client_socket): # client_request_data = client_socket.recv(1024).decode(encoding='utf-8') # print(client_request_data) # request_data = client_request_data.split(' ') # print(request_data) # request_path = request_data[1] # # # 浏览器关闭时,会发送请求 [''], 需要判断客户端是否时发送过来关闭的消息 # if len(request_path) == 1: # client_socket.close() # # if request_path == '/': # request_path = '/index.html' # try: # with open('./static' + request_path, 'rb') as f: # file_data = f.read() # except Exception as e: # response_line = "HTTP/1.1 404 OK \r\n" # # response_header = "Server:pwb\r\n" # # response_body = '404 Not Found sorry' # # response_data = (response_line + response_header + '\r\n').encode() + response_body.encode() # # client_socket.send(response_data) # else: # response_line = "HTTP/1.1 200 OK \r\n" # # response_header = "Server:pwb\r\n" # # response_body = file_data # # response_data = (response_line + response_header + '\r\n').encode() + response_body # # client_socket.send(response_data) # finally: # client_socket.close() # # if __name__ == '__main__': # tcp_serve_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # tcp_serve_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # tcp_serve_socket.bind(('', 8080)) # tcp_serve_socket.listen(128) # while True: # client_socket, client_addr = tcp_serve_socket.accept() # sub_thread = threading.Thread(target=handle_client_request,args=(client_socket,)) # sub_thread.start() ''' 静态Web服务器-面向对象开发 把提供服务的Web服务器抽象成一个类(HTTPWebServer) 提供Web服务器的初始化方法,在初始化方法里面创建Socket对象 提供一个开启Web服务器的方法,让Web服务器处理客户端请求操作 ''' # import threading # # def handle_client_request(client_socket): # client_request_data = client_socket.recv(1024).decode(encoding='utf-8') # print(client_request_data) # request_data = client_request_data.split(' ') # print(request_data) # request_path = request_data[1] # # # 浏览器关闭时,会发送请求 [''], 需要判断客户端是否时发送过来关闭的消息 # if len(request_path) == 1: # client_socket.close() # # if request_path == '/': # request_path = '/index.html' # try: # with open('./static' + request_path, 'rb') as f: # file_data = f.read() # except Exception as e: # response_line = "HTTP/1.1 404 OK \r\n" # # response_header = "Server:pwb\r\n" # # response_body = '404 Not Found sorry' # # response_data = (response_line + response_header + '\r\n').encode() + response_body.encode() # # client_socket.send(response_data) # else: # response_line = "HTTP/1.1 200 OK \r\n" # # response_header = "Server:pwb\r\n" # # response_body = file_data # # response_data = (response_line + response_header + '\r\n').encode() + response_body # # client_socket.send(response_data) # finally: # client_socket.close() # ''' 静态Web服务器-命令行启动动态绑定端口号 ''' # import sys # class HttpWebServer: # def __init__(self,port): # self.tcp_serve_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # self.tcp_serve_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # self.tcp_serve_socket.bind(('', port)) # self.tcp_serve_socket.listen(128) # # def handle_client_request(self,client_socket): # client_request_data = client_socket.recv(1024).decode(encoding='utf-8') # print(client_request_data) # request_data = client_request_data.split(' ') # print(request_data) # request_path = request_data[1] # # # 浏览器关闭时,会发送请求 [''], 需要判断客户端是否时发送过来关闭的消息 # if len(request_path) == 1: # client_socket.close() # # if request_path == '/': # request_path = '/index.html' # try: # with open('./static' + request_path, 'rb') as f: # file_data = f.read() # except Exception as e: # response_line = "HTTP/1.1 404 OK \r\n" # # response_header = "Server:pwb\r\n" # # response_body = '404 Not Found sorry' # # response_data = (response_line + response_header + '\r\n').encode() + response_body.encode() # # client_socket.send(response_data) # else: # response_line = "HTTP/1.1 200 OK \r\n" # # response_header = "Server:pwb\r\n" # # response_body = file_data # # response_data = (response_line + response_header + '\r\n').encode() + response_body # # client_socket.send(response_data) # finally: # client_socket.close() # # def start(self): # while True: # client_socket, client_addr = self.tcp_serve_socket.accept() # sub_thread = threading.Thread(target=self.handle_client_request, args=(client_socket,)) # sub_thread.start() # # def main(): # print(sys.argv) # if len(sys.argv) != 2: # print('格式错误 python xx.py 8080') # return # if not sys.argv[1].isdigit(): # print("格式错误 python xx.py 8080") # return # port = int(sys.argv[1]) # # my_web_server = HttpWebServer(port) # my_web_server.start() # # if __name__ == '__main__': # main() ''' 了解函数也可以作为参数传递 ''' # # def func01(): # print('func01 is show') # # # func01() # # # 函数名存放的是函数所在空间的地址 # # print(func01) # # 函数名也可以像普通变量一样赋值 # # func02 = func01 # # func02() # # def foo(func): # func() # # # 函数参数 # foo(func01) ''' 闭包 闭包的概念 闭包的使用 闭包内修改外部变量 什么是闭包? 在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包 闭包的构成? 在函数嵌套(函数里面再定义函数)的前提下 内部函数使用了外部函数的变量(还包括外部函数的参数) 外部函数返回了内部函数 ''' # def func_out(num1): # # 在函数嵌套(函数里面再定义函数)的前提下 # def func_inner(num2): # # 内部函数使用了外部函数的变量(还包括外部函数的参数) # num = num1 + num2 # print('现在的值:', num) # # 外部函数返回了内部函数 # return func_inner # # # 创建闭包实例 # f =func_out(10) # # 执行闭包 # f(1) # f(2) ''' 结论:通过f = func_out(10) 创建闭包 , 实际上 f() 等价于 内部函数func_inner()即调用闭包就相当于调用内部函数 : 闭包可以对外部函数变量进行保存 ''' ''' 如何自定义闭包? 1. 案例 需求: 根据配置信息使用闭包实现不同人的对话信息,例如对话: 张三: 到北京了吗? 李四: 已经到了,放心吧。 ''' # def config_name(name): # def say_info(info): # print(name + ":",info) # # return say_info # # tom = config_name("tom") # tom('您好') # tom('你在吗?') # jerry = config_name('jerry') # jerry('你好') # jerry('我在呢') ''' 使用nonlocal可以在闭包内部函数中修改外部函数变量 ''' # def func_out(num1): # def func_inner(num2): # # 如果要修改num1,用nonlocal # nonlocal num1 # num1 = num2 +10 # # print(num1) # func_inner(10) # print(num1) # # return func_inner # # # func_out(10) ''' 装饰器 装饰器概念 装饰器的使用 装饰带有参数的函数 装饰带有返回值的函数 装饰带有不定长参数的函数 多个装饰器的使用 带有的参数的装饰器 类装饰器 装饰器的作用 在不改变原有函数的源代码的情况下,给函数增加新功能 装饰器的功能特点 不修改已有函数的源代码 给已有函数增加额外的功能 提示 装饰器复合了开发中的封闭开发原则 ''' # 1 定义一个装饰器(装饰器本质上是闭包) # def check(fn): # def inner(): # print('登陆验证。。。') # fn() # return inner # # 需要装饰的函数 # def comment(): # print('发表评论') # # # 2.使用装饰器装饰函数(增加一个登陆功能) # comment = check(comment) # comment() # 使用语法糖方式来装饰函数 # @check # def comment(): # print('发表评论') # # comment() ''' 装饰器的使用 装饰器的使用场景 函数执行时间的统计 输出日志信息 ''' # import time # def get_time(fn): # def inner(): # start = time.time() # fn() # end = time.time() # print('时间:',end - start) # return inner # # @get_time # def func(): # for i in range(100000): # print(i) # # func() ''' 通用装饰器的使用 1. 装饰带有参数的函数 2. 装饰带有返回值的函数 3. 装饰带有不定长参数的函数 4. 通用装饰器 ''' # def logging(fn): # fn = sum_num # def inner(a, b): # fn(a,b) # 参数需要与被装饰函数一致 # # return inner # 参数需要与被装饰函数一致 # # @logging # def sum_num(a, b): # result = a + b # print(result) # # sum_num(1,2) ''' 装饰带有返回值的函数 ''' # def logging(fn): # fn = sum_num # def inner(a, b): # return fn(a,b) # # return inner # # @logging # def sum_num(a, b): # result = a + b # return result # # result = sum_num(1,2) # print(result) # 3. 装饰带有不定长参数的函数 # def logging(fn): # fn = sum_num # def inner(*args,**kwargs): # fn(*args,**kwargs) # 参数需要与被装饰函数一致 # # return inner # 参数需要与被装饰函数一致 # # @logging # def sum_num(*args,**kwargs): # print(args, kwargs) # # sum_num(1,2,age=18) # 4. 通用装饰器 # def logging(fn): # fn = sum_num # def inner(*args,**kwargs): # result = fn(*args,**kwargs) # 参数需要与被装饰函数一致 # return result # return inner # 参数需要与被装饰函数一致 # # @logging # def sum_num(*args,**kwargs): # print(args, kwargs) # # sum_num(1,2,age=18) ''' 多个装饰器的使用 ''' # 装饰器1 # def check1(fn1): # def inner1(): # print('登陆验证1') # fn1() # # return inner1 # # # def check2(fn2): # def inner2(): # print('登陆验证2') # fn2() # # return inner2 # # # # 被装饰的函数 # 多个装饰器的装饰过程是: 离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程 # @check2 # @check1 # def comment(): # print('发表评论') # # comment() # 登陆验证2 # 登陆验证1 # 发表评论 ''' 带有参数的装饰器 带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数,语法格式: @装饰器(参数,...) ''' # 添加输出日志的功能 # def decorator(fn, flag): # 错误使用方式;decorator只能传入fn参数 # def inner(num1, num2): # if flag == "+": # print("--正在努力加法计算--") # elif flag == "-": # print("--正在努力减法计算--") # result = fn(num1, num2) # return result # return inner # # # 使用装饰器装饰函数 # @decorator("+") # def add(a, b): # result = a + b # return result # # # result = add(1, 2) # print(result) # 正确写法: # # logging 只为了传递参数 # def logging(flag): # # 装饰器 # # 外部函数 # def decorator(fn): # def inner(num1, num2): # if flag == "+": # print("--正在努力加法计算--") # elif flag == "-": # print("--正在努力减法计算--") # result = fn(num1, num2) # return result # return inner # # return decorator # # # 被带有参数的装饰器装饰的函数 # @logging("+") # logging('+') 2 @decorator起到了装饰器的功能 # def add(a, b): # result = a + b # return result # # # result = add(1, 2) # print(result) ''' 带函数装饰器: 装饰器的外部函数只接受一个参数---被装饰的函数 需要给装饰器传参数,需要在装饰器外部再增加一个函数 ''' ''' 类装饰器的使用 _call__方法: 一个类里面一旦实现了__call__方法 那么这个类创建的对象就是一个可调用对象,可以像调用函数一样进行调用 ''' # class Check(object): # def __call__(self, *args, **kwargs): # print('我是call方法') # # c = Check() # c() # class Check(object): # def __init__(self, fn): # self.__fn = fn # # def __call__(self, *args, **kwargs): # print('登陆') # self.__fn() # # # # # 需要装饰的函数 # @Check # comment = Check(comment) # def comment(): # print('发表评论') # # comment() ''' 想要让类的实例对象能够像函数一样进行调用,需要在类里面使用call方法,把类的实例变成可调用对象(callable) 类装饰器装饰函数功能在call方法里面进行添加 ''' ''' property属性 with语句和上下文管理器 生成器的创建方式 深拷贝和浅拷贝 深拷贝和浅拷贝 ''' ''' 定义property属性有两种方式 装饰器方式 类属性方式 ''' ''' property属性就是负责把类中的一个方法当做属性进行使用;这样做可以简化代码使用 ''' # class Person(object): # def __init__(self): # self.__age = 0 # # # 获取属性 # @property # def age(self): # return self.__age # # # 修改属性 # @age.setter # def age(self, new_age): # self.__age = new_age # # p = Person() # # age = p.age() # # print(age) # # p = Person() # print(p.age) # # 修改属性 # p.age = 100 # print(p.age) # 类属性方式 ''' 定义类属性: 类属性 = property(获取值方法, 设置值方法) ''' # class Person(object): # def __init__(self): # # 私有属性 # self.__age = 0 # # def get_age(self): # return self.__age # # def set_age(self, new_age): # self.__age = new_age # # age = property(get_age, set_age) # # p = Person() # print(p.age) # p.age = 100 # print(p.age) ''' with语句和上下文管理器 这种方法虽然代码运行良好,但是缺点就是代码过于冗长,并且需要添加try-except-finally语句,不是很方便,也容易忘记. 在这种情况下,Python提供了 with 语句的这种写法,既简单又安全,并且 with 语句执行完成以后自动调用关闭文件操作,即使出现异常也会自动调用关闭文件操作。 ''' # with open('1.txt', 'r') as f: # data = f.read() # print(data) ''' 上下文管理器 一个类只要实现了__enter__()和__exit__()这个两个方法,通过该类创建的对象我们就称之为上下文管理器。 上下文管理器可以使用 with 语句,with语句之所以这么强大,背后是由上下文管理器做支撑的,也就是说刚才使用 open 函数创建的文件对象就是就是一个上下文管理器对象。 ''' # # class File(object): # def __init__(self,file_name, file_model): # self.file_name = file_name # self.file_model = file_model # # # __enter__() 和 __exit__() # def __enter__(self): # print('这是上文') # self.file = open(self.file_name,self.file_model) # return self.file # # def __exit__(self, exc_type, exc_val, exc_tb): # print('这是下文') # self.file.close() # # with File('1.txt','r') as f : # print(f.read()) ''' 生成器的介绍 根据程序员制定的规则循环生成数据,当条件不成立时则生成数据结束。数据不是一次性全部生成出来, 而是使用一个,再生成一个,可以节约大量的内存。 创建生成器的方式 生成器推导式 yield 关键字 ''' ''' 生成器推导式: 与列表推导式类似,只不过生成器推导式使用小括号 ''' ''' data = ( x for x in range(100)) data <generator object <genexpr> at 0x00000211F6AFFAC8> next(data) 0 next(data) for i in data: print(i) ''' ''' yield 生成器 ''' ''' 代码执行到 yield 会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行 生成器如果把数据生成完成,再次获取生成器中的下一个数据会抛出一个StopIteration 异常,表示停止迭代异常 while 循环内部没有处理异常操作,需要手动添加处理异常操作 for 循环内部自动处理了停止迭代异常,使用起来更加方便,推荐大家使用。 ''' # def generater(num): # for i in range(num): # print('开始') # yield i # print('生成完成') # # g = generater(5) # print(next(g)) # print(next(g)) # print(next(g)) # print(next(g)) # print(next(g)) # print(next(g)) # StopIteration # for i in g: # print(i) ''' 生成器使用场景: 生成一个斐波那契数列 ''' # # 0 1 1 2 3 5 # def fb(num): # a = 0 # b = 1 # # 记录生成了几个数字 # index = 0 # while index < num: # result = a # a, b = b, a+b # # print(result) # yield result # index += 1 # # f = fb(5) # # print(next(f)) # for i in f: # print(i) ''' 深拷贝和浅拷贝 copy函数是浅拷贝,只对可变类型的第一层对象进行拷贝,对拷贝的对象开辟新的内存空间进行存储,不会拷贝对象内部的子对象。 ''' import copy # 普通赋值 # a = [1, 2, 3] # b = [11, 22, 33] # c = [a,b ] # d = c # print(id(c)) # 2940432348808 # print(id(d)) # 2940432348808 # 浅拷贝可变类型 # a = [1, 2, 3] # b = [11, 22, 33] # c = [a,b ] # d = copy.copy(c) # print(id(c)) # 2759196866504 # print(id(d)) # 2759196866760 # 浅拷贝-深层数据 # a = [1, 2, 3] # b = [11, 22, 33] # c = [a,b ] # d = copy.copy(c) # print(id(a)) # 2600813795336 # print(id(c[0])) # 2600813795336 # print(id(d[0])) # 2600813795336 # 浅拷贝不可变类型 # a = (1, 2, 3) # b = (11, 22, 33) # c = (a,b) # d = copy.copy(c) # print(id(c)) # 1872445370120 # print(id(d)) # 1872445370120 ''' 不可变类型的浅拷贝说明: 通过上面的执行结果可以得知,不可变类型进行浅拷贝不会给拷贝的对象开辟新的内存空间,而只是拷贝了这个对象的引用。 ''' ''' 深拷贝 deepcopy函数是深拷贝, 只要发现对象有可变类型就会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储。 ''' import copy # a = [1, 2, 3] # b = [11, 22, 33] # c = [a,b ] # # d = copy.deepcopy(c) # # print(id(c)) # 1931219564488 # # print(id(d)) # 1931219564744 # # print(id(a)) # 2316605397128 # print(id(c[0])) # 2316605397128 # print(id(d[0])) # 2316605589256 # 深拷贝不可变类型 # a = (1, 2, 3) # b = (11, 22, 33) # c = (a,b) # d = copy.deepcopy(c) # print(id(c)) # 1699313344328 # print(id(d)) # 1699313344328 ''' 不可变类型的深拷贝说明: 通过上面的执行结果可以得知: 不可变类型进行深拷贝如果子对象没有可变类型则不会进行拷贝,而只是拷贝了这个对象的引用, 否则会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储 ''' ''' 正则表达式 : 对字符串操作的一种匹配公式 正则表达式的介绍 在实际开发过程中经常会有查找符合某些复杂规则的字符串的需要,比如:邮箱、图片地址、手机号码等,这时候想匹配或者查找符合某些规则的字符串就可以使用正则表达式了 re模块介绍 匹配单个字符 匹配多个字符 匹配开头和结尾 匹配分组 ''' ''' 在python中使用正则表达式需要导入的模块 re # 导入re模块 import re # 使用match方法进行匹配操作 result = re.match(正则表达式,要匹配的字符串) # 如果上一步匹配到数据的话,可以使用group方法来提取数据 result.group() ''' # import re # result = re.match('itc', 'itcast') # info = result.group() # print(info) # # # re.match() 根据正则表达式从头开始匹配字符串数据 ''' 匹配单个字符 代码 功能 . 匹配任意1个字符(除了\n) [ ] 匹配[ ]中列举的字符 \d 匹配数字,即0-9 \D 匹配非数字,即不是数字 \s 匹配空白,即 空格,tab键 \S 匹配非空白 \w 匹配非特殊字符,即a-z、A-Z、0-9、_、汉字 \W 匹配特殊字符,即非字母、非数字、非汉字 ''' import re # # 匹配任意1个字符(除了\n) # # 匹配数据 # # result = re.match('itcast.', 'itcasta') # result = re.match('itcast.', 'itcast\n') # None # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # # [ ] 匹配[ ]中列举的字符 # # 匹配数据 # # result = re.match('itcast.', 'itcasta') # result = re.match('itcast[123abc]', 'itcasta') # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # # \d 匹配数字,即0-9 # result = re.match('itcast\d', 'itcast0') # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # # \D 匹配非数字,即不是数字 # result = re.match('itcast\D', 'itcast-') # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # # \s 匹配空白,即 空格,tab键 # # result = re.match('itcast\s111', 'itcast 111') # result = re.match('itcast\s111', 'itcast\t111') # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # # \S 匹配非空白 # result = re.match('itcast\S111', 'itcast-111') # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # # \w 匹配非特殊字符,即a-z、A-Z、0-9、_、汉字 # result = re.match('itcast\w111', 'itcast_111') # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # \W 匹配特殊字符,即非字母、非数字、非汉字 # result = re.match('itcast\W111', 'itcast*111') # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') ''' 匹配多个字符 代码 功能 * 匹配前一个字符出现0次或者无限次,即可有可无 + 匹配前一个字符出现1次或者无限次,即至少有1次 ? 匹配前一个字符出现1次或者0次,即要么有1次,要么没有 {m} 匹配前一个字符出现m次 {m,n} 匹配前一个字符出现从m到n次 ''' # * 匹配前一个字符出现0次或者无限次,即可有可无 # # result = re.match('itcast1*', 'itcast') # # result = re.match('itcast1*', 'itcast111') # # result = re.match('itcast\d*', 'itcast1234') # # result = re.match('itcast\d*', 'itcast1234itcast') # itcast1234 # result = re.match('itcast\d*', 'itcastitcast') # itcast # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # # + 匹配前一个字符出现1次或者无限次,即至少有1次 # result = re.match('itcast\d+itcast', 'itcast12itcast') # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # # ? 匹配前一个字符出现1次或者0次,即要么有1次,要么没有 # # result = re.match('itcast\d?itcast', 'itcastitcast') # itcastitcast # # result = re.match('itcast\d?itcast', 'itcast1itcast') # itcast1itcast # result = re.match('itcast\d?itcast', 'itcast11itcast') # 没有匹配到 # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # # {m} 匹配前一个字符出现m次 # result = re.match('itcast\d{2}itcast', 'itcast12itcast') # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # # {m,n} 匹配前一个字符出现从m到n次 # # result = re.match('itcast\d{2,5}itcast', 'itcast12itcast') # # result = re.match('itcast\d{2,5}itcast', 'itcast12345itcast') # result = re.match('itcast\d{2,}itcast', 'itcast123456789itcast') # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') ''' 匹配开头和结尾 代码 功能 ^ 匹配字符串开头 $ 匹配字符串结尾 [^指定字符] 表示除了指定字符都匹配 ''' # # ^ 匹配字符串开头 # # result = re.match('^\ditcast', '2itcast') # # result = re.match('^\d.*', '2itcast') # 以数字开头的任意字符串 # result = re.match('.*', '2itcast--') # 匹配任意字符串 # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # # $ 匹配字符串结尾 # result = re.match('.*\d$', 'itcast1') # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # # $ 匹配艺术字为开头,以数字为结尾 # result = re.match('^\d.*\d$', '1itcast1') # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') # ## [^指定字符] 匹配除了指定字符以外的所有字符 # result = re.match('^\d.*[^4]$', '1itcast4') # 没有匹配到 # result = re.match('^\d.*[^4]$', '1itcast1') # 1itcast1 # print(result) # # 获取数据 # if result: # info = result.group() # print(info) # else: # print('没有匹配到') ''' 匹配分组相关正则表达式 代码 功能 | 匹配左右任意一个表达式 (ab) 将括号中字符作为一个分组 \num 引用分组num匹配到的字符串 (?P<name>) 分组起别名 (?P=name) 引用别名为name分组匹配到的字符串 ''' ''' 匹配练习 示例1:| 需求:在列表中["apple", "banana", "orange", "pear"],匹配apple和pear 示例2:( ) 需求:匹配出163、126、qq等邮箱 需求: 匹配qq:10567这样的数据,提取出来qq文字和qq号码 示例3:\num 需求:匹配出<html>hh</html> 示例4:(?P<name>) (?P=name) 需求:匹配出<html><h1>www.itcast.cn</h1></html> | 表示匹配左右任意一个表达式 (ab) 表示将括号中字符作为一个分组 \num 表示引用分组num匹配到的字符串 (?P<name>) 表示分组起别名 (?P=name) 表示引用别名为name分组匹配到的字符串 (分组数据):分组数是从左到右的方式进行分配的,最左边的是第一个分组,依次类推 ''' ''' 示例1:| 匹配左右任意一个表达式 需求:在列表中["apple", "banana", "orange", "pear"],匹配apple和pear ''' # fruit = ["apple", "banana", "orange", "pear"] # # for value in fruit: # result = re.match('apple|pear', value) # if result: # info = result.group() # print('我想吃的水果:',info) # else: # print('这不是我想要吃的水果') # 示例2:( ) 将括号中字符作为一个分组 # 需求:匹配出163、126、qq等邮箱 # # result = re.match('[a-zA-Z0-9_]{4,20}@(163|126|qq).com', 'hello@163Xcom') # hello@163Xcom # result = re.match('[a-zA-Z0-9_]{4,20}@(163|126|qq)\.com', 'hello@163.com') # \. 转义为. # if result: # info = result.group() # print(info) # else: # print('没有匹配的内容') # # 示例2:( ) 将括号中字符作为一个分组 # # 需求: 匹配qq:10567这样的数据,提取出来qq文字和qq号码 # # group(0) 代表匹配所有的数据; 1:第一个分组的数据;2第二个分组的数据;,顺序从左到右依次排序 # result = re.match('(qq):([1-9]\d{4,20})', 'qq:10567') # if result: # info = result.group(0) # qq:10567 # print(info) # num = result.group(2) # 10567 # print(num) # type = result.group(1) # qq # print(type) # else: # print('没有匹配的内容') # # 示例3:\num 表示引用分组num匹配到的字符串 # # 需求:匹配出<html>hh</html> # # result = re.match('<[a-zA-Z1-6]{4}>.*</[a-zA-Z1-6]{4}>', '<html>hh</html>') # <html>hh</html> # result = re.match("<([a-zA-Z1-6]{4})>.*</\\1>", '<html>hh</html>') # \\ 是 \的转义 ; 正则规则最好用"" # if result: # info = result.group() # print(info) # else: # print('没有匹配的内容') # 示例4:(?P<name>) (?P=name) # 需求:匹配出<html><h1>www.itcast.cn</h1></html> # result = re.match("<([a-zA-Z1-6]{4})><([a-zA-Z1-6]{2})>.*</\\2></\\1>", '<html><h1>www.itcast.cn</h1></html>') # if result: # info = result.group() # print(info) # else: # print('没有匹配的内容') ''' (?P<name>) 表示分组起别名 (?P=name) 表示引用别名为name分组匹配到的字符串 (分组数据):分组数是从左到右的方式进行分配的,最左边的是第一个分组,依次类推 ''' # 示例5:(?P<name>) (?P=name) # 需求:匹配出<html><h1>www.itcast.cn</h1></html> result = re.match("<(?P<html>[a-zA-Z1-6]{4})><(?P<h1>[a-zA-Z1-6]{2})>.*</(?P=h1)></(?P=html)>", '<html><h1>www.itcast.cn</h1></html>') if result: info = result.group() print(info) else: print('没有匹配的内容')
数据结构.py
''' 数据结构与算法简介 算法的概念 算法的时间效率衡量 时间复杂度 时间复杂度的计算 最优最坏时间复杂度 常见时间复杂度 空间复杂度 ''' ''' 数据结构: 存储、组织数据的方式 算法是计算机处理信息的本质,因为 计算机程序本质上是一个算法 来告诉计算机确切的步骤来执行一个指定的任务。 一般地,当算法在处理信息时,会从输入设备或数据的存储地址读取数据,把结果写入输出设备或某个存储地址供以后再调用。 算法是独立存在的一种解决问题的方法和思想. ''' ''' 题目: 如果 a+b+c=1000,且 a^2+b^2=c^2(a,b,c 为自然数),如何求出所有a、b、c可能的组合? 解题方法一 : 穷举法 ''' # import time # # 1.列举 a b c 的所有可能数值 # start = time.time() # for a in range(0,1001): # for b in range(0,1001): # for c in range(0, 1001): # # 2.判断是否男足条件 # if (a**2 + b**2 == c**2) and (a+b+c == 1000): # print(f'{a}^2+{b}^2={c}^2') # end = time.time() # print(f'耗时:{end-start}') # 耗时:113秒 ''' 算法是独立存在的一种解决问题的方法和思想 算法的五大特性: ①输入: 算法具有0个或多个输入 ②输出: 算法至少有1个或多个输出 ③有穷性: 算法在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤可以在可接受的时间内完成 ④确定性:算法中的每一步都有确定的含义,不会出现二义性 ⑤可行性:算法的每一步都是可行的,也就是说每一步都能够执行有限的次数完成 ''' ''' 算法时间效率的衡量 解题方法二 知道a、b、c他们三者是有一个关系的,就可以不用对c进行遍历,直接把c列成一个条件即可 ''' # import time # # 1.列举 a b c 的所有可能数值 # start = time.time() # for a in range(0,1001): # for b in range(0,1001): # c = 1000 - a - b # # 2.判断是否男足条件 # if (a**2 + b**2 == c**2): # print(f'{a}^2+{b}^2={c}^2') # end = time.time() # print(f'耗时:{end-start}') # 0.89 ''' 我们假定计算机执行算法每一个基本操作的时间是固定的一个时间单位 , 那么有多少个 基本操作就代表会花费多少时间单位 , 由此可以忽略机器环境的影响而客观的反应算法 的时间效率 时间效率: 代码执行总时间(T) = 操作步骤数量 * 操作步骤执行时间 时间效率: 代码执行总时间(T) = 操作步骤数量 ''' ''' 时间复杂度, 算法一的时间复杂度: T(n)=O(n^3) 算法二的时间复杂度: T(n)=O(n^2) 在比较算法的性能时 最重要的应该是数据量变化时 , 算法随数据量变化的变化程度 大O记法即为算法的 时间复杂度 随 数据量变化 的关系曲线 , 这个关系曲线通常由最高次项决定,当数据量比较高时低次项 的影响相对于最高次项就很小 , 为了方便可以忽略 时间复杂度的计算规则: ①基本操作,认为其时间复杂度为O(1) ②顺序结构,时间复杂度按加法进行计算 ③循环结构,时间复杂度按乘法进行计算 ④分支结构,时间复杂度取最大值 ⑤判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略 ⑥在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度 1. [sum = 1 + 2 ] [sum = 2000**2 + 1000**2] [ a = 100 b = 200 a = a + b ] 都是O(1)不是O(3) 与问题规模的大小无关,执行次数恒定的算法,我们称之为具有O(1)的时间复杂度 ''' ''' 最优最坏时间复杂度 假如我们有一个列表 , 我们要通过一个算法从这个列表中找到10 最坏时间复杂度 my_list = [ 1 , 5 , 6 , 4 , 3 , 2 , 7 , 8 , 9 , 10 ] 最优时间复杂度 my_list = [ 10 , 5 , 6 , 4 , 3 , 2 , 7 , 8 , 9 , 1 ] 对于最优时间复杂度,其价值不大,因为它没有提供什么有用信息 其反映的只是最乐观最理想的情况,没有参考价值。 对于最坏时间复杂度,提供了一种保证,表明算法在此种程度的基 本操作中一定能完成工作 对于平均时间复杂度,是对算法的一个全面评价,因此它完整全面 的反映了这个算法的性质。但另一方面,这种衡量并没有保证,不 是每个计算都能在这个基本操作内完成。而且,对于平均情况的计 算,也会因为应用算法的实例分布可能并不均匀而难以计算。 因此,我们主要关注算法的最坏情况,亦即最坏时间复杂度 ''' ''' 常见的时间复杂度 执行次数函数举例 阶 非正式术语 12 O(1) 常数阶 2n+3 O(n) 线性阶 3n2+2n+1 O(n2) 平方阶 5log2n+20 O(logn) 对数阶 6n3+2n2+3n+4 O(n3) 立方阶 所消耗的时间从小到大: O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) 时间复杂度越低,效率越高 ''' ''' 空间复杂度 类似于时间复杂度的讨论,一个算法的空间复杂度S(n)定义为该算法所耗费的存储空间, 它也是问题规模n的函数。渐近空间复杂度也常常简称为空间复杂度。 空间复杂度是对一个算法在运行过程中临时占用存储空间大小的度量 ''' ''' 数据结构的概念 内存的存储结构 数据结构的分类 顺序表存储方式 顺序表的实现和扩充 顺序表增加与删除元素 ''' ''' 数据结构是存储、组织数据的方式 . 数据结构是指相互之间存在一种或多种特定关系的数据元素的集合 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率 , 数据结构往往同高效的算法有关 算法与数据结构的区别 数据结构和算法都是为了提高程序的运行效率 , 他们的区别是什么? 数据结构只是静态的描述了数据元素之间的关系 高效的程序需要在数据结构的基础上设计和选择算法 算法是为了解决实际问题而设计的,数据结构是算法需要处理问题的载体 数据结构 + 算法 = 程序 ''' ''' 内存的存储结构 内存是以字节为基本存储单位的(1024b = 1kb) , 每个基本存储空间都有自己的地址 整形(int) : 4个字节 字符(char): 1个字节 . 单个字符“a”占1个字节, 字符串“abc”占3个字节 ''' ''' 数据结构的分类 ①线性结构 ②非线性结构 线性结构: 简单地说,线性结构就是表中各个结点具有线性关系 线性结构的特点: ①线性结构是非空集 ②线性结构所有结点都最多只有一个直接前驱结点和一个直接后继结点 非线性结构: 简单地说,非线性结构就是表中各个结点之间具有多个对应关系 非线性结构的特点: ①非线性结构是非空集 ②非线性结构的一个结点可能有多个直接前驱结点和多个直接后继结点 ''' ''' 线性结构的实际存储方式,分为两种实现模型: ①顺序表 : 将元素顺序地存放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示 ②链表 : 将元素存放在通过链接构造起来的一系列存储块中 , 存储区是非连续的 顺序表存储方式 顺序表存储数据的两种情况: ①一体式结构 ②分离式结构 无论①一体式结构还是②分离式结构在获取数据的时候直接通过下标偏移就可以找到数据所在空间的地址 , 而无需遍历后才可以获取地址 . 所以顺序表在获取地址操作时的时间复杂度 : O(1) 顺序表的结构和扩充 顺序表的完整信息包括两部分: ①数据区 ②信息区,即元素存储区的容量和当前表中已有的元素个数 扩充的两种策略 ①每次扩充增加固定数目的存储位置,如每次扩充增加10个元素位置,这种策略可称为线性增长 特点:节省空间,但是扩充操作频繁,操作次数多。 ②每次扩充容量加倍,如每次扩充增加一倍存储空间。 特点:减少了扩充操作的执行次数,但可能会浪费空间资源 , 以空间换时间,推荐的方式 顺序表增加与删除元素 增加元素 a. 尾端加入元素,时间复杂度为O(1) b. 非保序的加入元素(不常见),时间复杂度为O(1) c. 保序的元素加入,时间复杂度为O(n) 删除元素 a. 删除表尾元素,时间复杂度为O(1) b. 非保序的元素删除(不常见),时间复杂度为O(1) c. 保序的元素删除,时间复杂度为O(n) ''' ''' 链表: 不需要连续的存储空间 单链表(单向链表)是链表的一种形式,它的每个节点包含两个域一个元素域和一个链接域 . 这个链接指向链表中的下一个节点 , 而最后一个节点的链接域则指向一个空值 is_empty() 链表是否为空 length() 链表长度 travel() 遍历整个链表 add(item) 链表头部添加元素 append(item) 链表尾部添加元素 insert(pos, item) 指定位置添加元素 remove(item) 删除节点 search(item) 查找节点是否存在 链表增加节点 ① add(item) 链表头部添加结点 ② append(item) 链表尾部添加结点 ③ insert(pos, item) 指定位置添加结点 链表删除节点 ①remove(item) 删除节点 ②search(item) 查找节点是否存在 ''' # # 结点 # class SingleNode(object): # def __init__(self, item): # self.item = item # self.next = None # # # # 单链表 # class SingleLinkList(object): # def __init__(self, node=None): # self.head = node # # # 链表是否为空 # def is_empty(self): # return self.head is None # # # 链表长度 # def lenth(self): # # 游标,记录当前所在的位置 # cur = self.head # # 记录链表的长度 # count = 0 # while cur is not None: # cur = cur.next # count += 1 # return count # # 遍历整个链表 # def travel(self): # cur = self.head # while cur is not None: # print(cur.item) # cur = cur.next # # # 链表头部添加结点 # def add(self,item): # node = SingleNode(item) # node.next = self.head # self.head = node # # # 链表尾部添加结点 # def append(self,item): # node = SingleNode(item) # # if self.head is None: # self.head = node # else: # cur = self.head # while cur.next is not None: # cur = cur.next # # cur.next = node # # # insert(pos, item) # # 指定位置添加结点 # def insert(self,pos,item): # # if pos <= 0: # self.add(item) # elif pos >= self.lenth(): # self.append(item) # else: # node = SingleNode(item) # cur = self.head # count = 0 # while count < pos-1: # cur = cur.next # count += 1 # # node.next = cur.next # cur.next = node # # # 删除节点 # # def remove(self, item): # # 游标 # cur = self.head # # 辅助游标 # pre = None # while cur is not None: # if cur.item == item: # if cur == self.head: # self.head = cur.next # else: # pre.next = cur.next # return # # 没有找到要删除的元素 # else: # pre = cur # cur = cur.next # # # 查找节点是否存在 # def search(self, item): # cur = self.head # # while cur.next is not None: # if cur.item == item: # return True # else: # cur = cur.next # # return False # # node1 = SingleNode(10) # # print(node1.item) # # print(node1.next) # # # 链表 # link1 = SingleLinkList() # # print(link1.head) # # print(link1.is_empty()) # # print(link1.lenth()) # # link1.append(20) # # link1.travel() # # link2 = SingleLinkList(node1) # # print(link2.head.item) # # print(link2.is_empty()) # # print(link2.lenth()) # # link2.travel() # link2.add(9) # link2.append(12) # link2.insert(2,11) # link2.insert(-1,-1) # link2.insert(10,100) # link2.travel() # # print('............') # # link2.remove(9) # # link2.travel() # # print('............') # # link2.remove(11) # # link2.travel() # print('............') # print(link2.search(-11)) ''' 顺序表 查找指定下标元素: O(1) 查找任意元素: O(n) 删除元素: O(n) 尾部删除: O(1) 添加元素: O(n) 尾部添加: O(1) 链表 查找指定下标元素: O(n) 查找任意元素: O(n) 删除元素: O(n) 尾部删除: O(n) 添加元素: O(n) 尾部添加: O(n) ''' ''' 栈 栈(stack)它是一种运算受限的线性表. 其限制是仅允许在表的一端进行插入和删除运算, 这一端被称为栈顶,相对地,把另一端称为栈底. 同时栈的结构特点让它在处理数据的时候 符合了先进后出的特点. 计算机里面的栈其实有着举足轻重的作用, 栈是计算机系统里面CPU结构的一部分. 栈到底有什么用呢? 函数里面有可能要使用到局部变量, 不能总是用全局变量. 而局部变量在函数使用完毕之后 就销毁了, 那么局部变量存储到哪既能不浪费空间又能及时销毁. 栈的特点: ①栈是线性表 ②栈满足先进后出 列表的特点: ①列表是线性表 列表的两端都可以: 头部插入删除数据: insert(0,item), pop(0) 尾部插入删除数据: append(item), pop() 栈(stack)它是一种运算受限的线性表. 其限制是仅允许在表的一端进行插入和删除运算, 这一端被称为栈顶,相对地,把另一端称为栈底. 同时栈的结构特点让它在处理数据的时候 符合了先进后出的特点. ''' # class Stack(object): # """栈:先进后出""" # def __init__(self): # self.__items = [] # # def push(self,item): # """进栈""" # self.__items.append(item) # # def pop(self): # """出栈""" # self.__items.pop() # # def travel(self): # for i in self.__items: # print(i) # # # my_stack = Stack() # my_stack.push(1) # my_stack.push(2) # my_stack.push(3) # my_stack.travel() # print(".......") # my_stack.pop() # my_stack.travel() ''' 队列 是一种特殊的线性表,特殊之处在于它只允许在表的头部(front)进行删除操作, 而在表的尾部(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。 进行插入操作的端称为队尾,进行删除操作的端称为队头 队列的作用 任务处理类的系统:先把用户发起的任务请求接收过来存到队列中,然后后端开启多个应用程序从队列中取任务进行处理 队列起到了缓冲压力的作用 队列需要实现的操作 函数 作用 Queue() 创建一个空的队列 enqueue(item) 队列尾部添加元素item dequeue(): 队列头部删除元素 is_empty() 判断队列是否为空 size() 返回队列的大小 ''' # class Queue(object): # def __init__(self): # self.items = [] # # # 队列尾部添加元素item # def enqueue(self, item): # self.items.append(item) # # def dequeue(self): # self.items.pop(0) # # def is_empty(self): # return self.items == [] # # def size(self): # return len(self.items) # # q = Queue() # q.enqueue(1) # q.enqueue(2) # q.enqueue(3) # # for i in q.items: # print(i) # # q.dequeue() # for i in q.items: # print(i) ''' 什么是双端队列 双端队列(deque,全名double-ended queue),是一种具有队列和栈的性质的数据结构 双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行 双端队列可以在队列任意一端入队和出队 函数 作用 Deque() 创建一个空的双端队列 add_front(item) 从队头加入一个item元素 add_rear(item) 从队尾加入一个item元素 remove_front() 从队头删除一个item元素 remove_rear() 从队尾删除一个item元素 is_empty() 判断双端队列是否为空 size() 返回队列的大小 ''' # class Deque(object): # """双端队列""" # def __int__(self): # self.items = [] # # def is_empty(self): # self.items = [] # # def size(self): # return len(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): # self.items.pop(0) # # def remove_rear(self): # self.items.pop() # # deque = Deque() # # print(deque.is_empty()) # print(deque.size()) # # deque.add_front(2) # deque.add_front(1) # deque.add_rear(3) # deque.add_rear(4) # # for i in deque.items: # print(i) # # print('...') # deque.remove_front() # deque.remove_rear() # # for i in deque.items: # print(i) ''' 排序算法的稳定性 假定在待排序的记录序列中,存在多个具有相同的 关键字的记录,若经过排序,这些记录的相对次序 保持不变,则称这种排序算法是稳定的, 否则称为不 稳定的 不稳定的排序算法: 选择排序、快速排序、希尔排序、堆排序 稳定的排序算法: 冒泡排序、插入排序、归并排序和基数排序 冒泡排序 什么是冒泡排序 冒泡排序:重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从从Z到A)错误 就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成 这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二 氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序” 选择排序 插入排序 快速排序 ''' # def bubble_sort(alist): # """冒泡排序""" # n = len(alist) # for j in range(0,n-1): # # 计数交换次数 # count = 0 # for i in range(0,n-j-1): # if alist[i] > alist[i+1]: # alist[i],alist[i+1] = alist[i+1],alist[i] # count += 1 # # 如果遍历一遍发现没有数字交换,退出循环 # if count == 0: # break ''' 冒泡排序: 最差时间复杂度 : O(n2) 最优时间复杂度 : O(n) 遍历一遍发现没有任何元素发生了位置交换,终止排序 算法稳定性 : 稳定算法 ''' ''' 什么是选择排序 择排序的工作原理: 第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置, 然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾. 以 此类推,直到全部待排序的数据元素的个数为零. ''' # def select_sort(alist): # """选择排序""" # n = len(alist) # for j in range(0,n-1): # # 假定最小值下标 # min_index = j # for i in range(j+1,n): # if alist[i] < alist[min_index]: # min_index = i # if min_index == j: # alist[j], alist[min_index] = alist[min_index], alist[j] ''' 选择排序: 最差时间复杂度 : O(n2) 最优时间复杂度 : O(n2) 算法稳定性 : 不稳定算法 ''' ''' 插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据, 算法适用于少量数据的排序 插入排序的组成 插入算法把要排序的数组分成两部分:第一部分是有序的数字(这里可以默认数组第一个数字为有序的第一部分),第二 部分为无序的数字(这里除了第一个数字以外剩余的数字可以认为是无序的第二部分) ''' # def insert_sort(alist): # """插入排序""" # n = len(alist) # # 控制轮数 # for j in range(1, n): # # [j,j-1,j-2,...1] # for i in range(j, 0, -1): # if alist[i] < alist[i -1]: # alist[i], alist[i-1] = alist[i-1], alist[i] # else: # break ''' 插入排序(Insertion sort)是一种简单直观且稳定的排序算法。如果有一个 已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但 要求插入后此数据序列仍然有序,这个时候就可以使用插入排序法,插入排序 的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个 新的、个数加一的有序数据,算法适用于少量数据的排序 最差时间复杂度:O(n2) 最优时间复杂度:O(n) 算法稳定性: 稳定的排序方法 ''' ''' 什么是快速排序 基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列 快速排序算法通过多次比较和交换来实现排序,其排序流程如下: (1)首先设定一个分界值,通过该分界值将数组分成左右两部分 (2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边 此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值 (3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值, 将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也做类似处理 (4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序 当左、右两个部分各数据排序完成后,整个数组的排序也就完成了 ''' # def quick_sort(alist, start, end): # """快速排序""" # # # 递归结束的条件 # if start >= end: # return # # # 界限值 # mid = alist[start] # # 左右游标 # left = start # right = end # # while left < right: # # 从右边开始,找到一个小于mid的值, 归类到左边; 遇到与mid相等的值>=归类到左边(也可以归类到右边) # while alist[right] >= mid and left < right: # right -= 1 # alist[left] = alist[right] # # 从左边开始,找到一个小于mid的值, 归类到右边 # while alist[left] < mid and left < right: # left += 1 # alist[right] = alist[left] # # # 循环一旦结束,证明找到了mid应该在的位置 # alist[left] = mid # # # 递归操作 # quick_sort(alist,0,left-1) # quick_sort(alist,right + 1,end) ''' 层数代表排序的轮数 : n 每一轮比较n次 最差时间复杂度: O(n2) n/2/2/2... = 1 1*2*2*2*... = n 2的多少次方为n log2n 层数代表排序的轮数 : logn 每一轮比较n次 最优时间复杂度: O(nlogn) 快速排序: 将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的 所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序 过程可以递归进行,以此达到整个数据变成有序序列 最优时间复杂度: O(nlogn) 最差时间复杂度: O(n2) 算法稳定性: 不稳定 ''' # if __name__ == '__main__': # alist = [5, 3, 4, 7, 2] # # bubble_sort(alist) # # select_sort(alist) # # insert_sort(alist) # quick_sort(alist,0, len(alist)-1) # print(alist) ''' 二分查找-递归版本 二分查找-非递归版本 二分查找又称折半查找,它是一种效率较高的查找方法 原理:将数组分为三部分,依次是中值前,中值,中值后 将要查找的值与中值进行比较,若小于中值则在中值前面找,若大于中值则在中值后面找,等于中值时直接返回 第一步: 找到中值(取整数) 第二步: 要查找的数和中值比较 第三步: 若小于中值则在中值前面找,若大于中值则在中值后面找,等于中值时直接返回 二分查找要求: ①必须采用顺序存储结构 ②必须按关键字大小有序排列 最差时间复杂度: O(logn) 1*2*2*2*2 = n O() = log 2 n ~ log 10 n ~ logn 最优时间复杂度: O(1) ''' # # 递归版本 # def binary_search(alist, item): # "“”二分查找”“" # n = len(alist) # # 递归的结束条件 # if n == 0: # return False # # 中间值 # mid = n//2 # # if item == alist[mid]: # return True # elif item < alist[mid]: # return binary_search(alist[0:mid], item) # elif item > alist[mid]: # return binary_search(alist[mid+1:], item) # # # 非递归版本 # def binary_search2(alist, item): # "“”二分查找”“" # # start = 0 # end = len(alist) - 1 # # while start <= end: # mid = (start + end)//2 # # if item == alist[mid]: # return True # elif item < alist[mid]: # end = mid -1 # elif item > alist[mid]: # start = mid + 1 # # # 没有找到 # return False # # alist = [1,2,3,4,5] # # alist = [1] # # print(binary_search(alist,3)) # # print(binary_search(alist,6)) # print(binary_search2(alist,3)) # print(binary_search2(alist,6)) ''' 非线性结构 树 树的概念 树的种类及存储 树的应用场景_数据库索引 二叉树的概念和性质 二叉树的广度优先遍历 二叉树的广度优先遍历实现 二叉树的三种深度优先遍历 二叉树由遍历结果反推二叉树的结构 树(英语:tree)就是一种非线性结构 它是用来模拟具有树状结构性质的数据集合. 它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的. 它具有以下的特点: ①每个节点有零个或多个子节点 ②没有父节点的节点称为根节点 ③每一个非根节点有且只有一个父节点 ④除了根节点外,每个子节点可以分为多个不相交的子树 节点的度:一个节点含有的子节点的个数称为该节点的度 树的度:一棵树中,最大的节点的度称为树的度 叶节点或终端节点:度为零的节点 父亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点 兄弟节点:具有相同父节点的节点互称为兄弟节点 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推 树的高度或深度:树中节点的最大层次 堂兄弟节点:父节点在同一层的节点互为堂兄弟 节点的祖先:从根到该节点所经分支上的所有节点 子孙:以某节点为根的子树中任一节点都称为该节点的子孙 森林:由m(m>=0)棵互不相交的树的集合称为森林 ''' ''' 树的种类及存储 无序树:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树 有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树 有序树: 霍夫曼树(用于信息编码):带权路径最短的二叉树称为哈夫曼树或最优二叉树 B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个的子树 二叉树:每个节点最多含有两个子树的树称为二叉树 二叉树的种类 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树,其中满二叉树的定义是所有叶节点都在最底层的完全二叉树 平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树 排序二叉树(二叉查找树(英语:Binary Search Tree),也称二叉搜索树、有序二叉树) 排序二叉树(BST)的要求: 1.若左子树不空,则左子树上所有节点的值均小于它的根节点的值 2.若右子树不空,则右子树上所有节点的值均大于它的根节点的值 3.左、右子树也分别为二叉排序树 排序二叉树包含空树 二叉树的存储 顺序存储:将数据结构存储在固定的数组中,虽然在遍历速度上有一定的优势,但因所占空间比较大,是非主流二叉树 存储方式.二叉树通常以链式存储 链式存储:由于对节点的个数无法掌握,常见树的存储表示都转换成二叉树进行处理,子节点个数最多为2 多叉树可以转化为二叉树 ''' ''' 树的应用场景 ①xml,html等,那么编写这些东西的解析器的时候,不可避免用到树 ②路由协议就是使用了树的算法 ③mysql数据库索引 ④文件系统的目录结构 ⑤所以很多经典的AI算法其实都是树搜索,此外机器学习中的decision tree也是树结构 二叉树的性质 性质1: 在二叉树的第i层上至多有 2i-1 个结点(i>0) 性质2: 深度为k的二叉树至多有2k - 1个结点(k>0) 性质3: 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1 性质4: 最多有n个结点的完全二叉树的深度必为 log2(n+1) 性质5: 对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子 编号必为2i+1 , 其父节点的编号必为i//2(i=1 时为根,除外) ''' ''' 二叉树的广度优先遍历 深度优先往往可以很快找到搜索路径 广度优先可以找到最短路径 ''' class Node(object): """节点类""" def __init__(self, item): self.item = item self.lchild = None self.rchild = None class BinaryTree(object): "“”完全二叉树“”" def __init__(self,node=None): self.root = node def add(self, item): """"添加节点""" if self.root is None: self.root = Node(item) return # 通过队列,先进先出 # 队列 queue = [] # 从尾部添加数据 queue.append(self.root) while True: # 从头部去除数据 node = queue.pop(0) if node.lchild is None: node.lchild = Node(item) return else: queue.append(node.lchild) if node.rchild is None: node.rchild = Node(item) return else: queue.append(node.rchild) def breadth_travel(self): """广度优先遍历""" if self.root == None: return # 队列 queue = [] queue.append(self.root) while len(queue) > 0: node = queue.pop(0) print(node.item,end="") if node.lchild is not None: queue.append(node.lchild) if node .rchild is not None: queue.append(node.rchild) ''' 能够对二叉树进行先序,中序,后序遍历 ''' def preorder_travel(self,root): """"先序遍历 根 左 有""" if root is not None: print(root.item, end="") self.preorder_travel(root.lchild) self.preorder_travel(root.rchild) def inorder_travel(self, root): """中序遍历 左 根 右""" if root is not None: self.inorder_travel(root.lchild) print(root.item, end="") self.inorder_travel(root.rchild) def postorder_travel(self, root): """h后序遍历 左右根""" if root is not None: self.postorder_travel(root.lchild) self.postorder_travel(root.rchild) print(root.item, end="") if __name__ == "__main__": tree = BinaryTree() # tree.add("A") # tree.add("B") # tree.add("C") # tree.add("D") # tree.add("E") # tree.add("F") # tree.add("G") # tree.add("H") # tree.add("I") # tree.breadth_travel() tree.add("0") tree.add("1") tree.add("2") tree.add("3") tree.add("4") tree.add("5") tree.add("6") tree.add("7") tree.add("8") tree.add("9") tree.preorder_travel(tree.root) # 0137849256 print() tree.inorder_travel(tree.root) # 7381940526 print() tree.postorder_travel(tree.root) # 7839415620 ''' 知道中序遍历 和 先序遍历 或者 后序遍历 就可以推出二叉树的结构 '''