数据结构与算法(Python语言)
第一章
时间复杂度
时间复杂度的简介:
- 运行时间会随着输入的大小如何变化
- 最好的情况: 运行时间的上限 (最少运行时间)
- 由最简单的输入决定
- 提供了所有输入的最终优化目标
- 最差的情况:运行时间的下限 (最多运行时间)
- 由最复杂的输入决定
- 提供了所有输入的保障时间
- 平均情况: 随机输入的运行时间的期望
- 需要建立随机输入的模型
- 是一种评价算法表现的方法
- 平均情况时间通常很难测定
- 我们通常情况下关注最差情况下的运行时间
数组
-
盛有单一类型固定数量值的容器类
- 以0开始的索引
- 数组长度
- 内存表示: 连续的
- 边界检查
-
Python创建一维数组
题目
找到丢失的数字
现在你手上有n-1个数字,这些数字的范围是 [1 , n]
且这n-1个数字中没有重复的数字。由上述条件可知:
你手上的数字丢失了一个。请编写一段高效的找到该缺
失数字的代码。
-
先求和,然后减去每一个数字
-
异或:
- 0^1 = 1
- 0^0 = 0
- 1^0 = 1
- 1^1 = 0
- A^A = 0
- A^0 = A
- 异或支持交换律
计算机最后去做加减乘除的时候都是做的二进制。
所以如果有n个数字,只缺失一个的话。我们可以这么做:
1 ^ 2 ^ 3 … ^ n ^ a1 ^ a2 ^ a3 … ^ an(其中ai是我们输入的数),最后得出来的就是那个缺失的值。——因为一个数字异或本身会等于0(A ^ A=0),当所有的数字都找到对应的ai了,只有缺失的那个数字会异或0(A ^ 0=A)。
洗牌
一个好的洗牌:每一张牌出现在每一个位置的概率应该是相等的。
-
方法一:调用random.shuffle(cards) ——如果不熟悉random欢迎点击这里查看random的用法
import random def shuffle_system(cards): random.shuffle(cards)
-
方法二:
-
思路:假设是a-j一共10张牌,位置分别在A-J。
- 第一次,从A-J的地方随机取一张牌。和放在第一个位置(也就是A位置)的牌交换,如果恰好抽到的是a,那么就不交换直接到下一次
- 第二次,从B-J的地方随机取一张牌,和放在第二个位置(也就是B位置)的牌交换。如果恰好抽到的是b,那么也就不交换直接到下一次
- 依次类推。直到取完(9次后)。
-
理论:为什么这样是正确的
- 第一次,任何一张牌和第一张交换的概率是1/n。
- 第二次,范围从1-n变成了2-n,这张牌要和第二张交换的前提必须是第一次没有抽到它且第二次抽到它。第一次没有抽到它的概率是n-1/n,第二次抽到的概率是1/n-1,相乘起来也是1/n。
- 所以每张牌在每个位置的概率都是1/n,它是一个好的洗牌方式。
-
代码:
def shuffle_correct(cards): for i in range(len(cards)): randomi = i + random.randint(0, (len(cards) - i - 1)) cards[i], cards[randomi] = cards[randomi], cards[i]
-
-
如何证明自己的洗牌程序是对的?
def test_shuffle(f): result = [[0 for i in range(10)] for j in range(10)] #产生一个十行十列的二维数组 #第i行第j列表示第i张牌在第j列出现了几次,第一行第二列就表示第一张牌在第二个位置出现了几次 for i in range(1000): A = [i for i in range(0, 10)] #生成一副0-9的牌 f(A) #这里f是一个函数,def test_shuffle(f),直接测试函数,A直接就赋给f这个函数了。 '''因为调用的f都是打乱牌顺序的函数,所以f(A)调用完之后就等于已经打乱了牌的顺序了。''' for j in range(len(A)): result[A[j]][j] += 1 # [A[j]][j]——这里的A[j]表示的是打乱顺序后,A的第j个位置是什么牌, #比如j=2 此时A[j] = 10,j=2,result[A[j]][j]=result[10][2],表示第十行第二列(即第十章牌在第二个位置出现的次数) print('\n'.join([''.join(['{:6}'.format(item) for item in row]) for row in result]))
数质数
判断质数
首先看一下如何判断一个数是质数 —— 除以小于它的数,只要有一个数使得余数等于0,那么就不是质数。
在判断的时候其实只要 使n被2-根号n之间的整数除(注意2-根号n是左闭右闭区间),就可判定n是否为素数。
为什么只要除到根号n呢?
根号n * 根号n = n ——也就是说一个数必定是由一个小于根号n和一个大于根号n的数相乘得到的。所以只需判断到根号n即可。
代码:
#判断一个数是否是质数
def judge_prime(n):
i = 2 #要注意如果输入的是2,那么就直接返回True了
while(i * i <= n):
if n % i == 0:
return False
else:
i += 1
return True
计算小于等于n的质数个数
题目描述:给定一个正整数n,计算出小于等于n的质数有多少个。 比如17,则返回7,因为小于等于7的质数有2,3,5,7,13,17
方法一:普通方法
每个小于等于n的数都去判断一下该数是不是质数,是则count+1,最后返回count
代码:
def count_prime_one(n):
"""最普通的方法"""
count = 0
for i in range(2,n):
if judge_prime(i):
print(i,end = " ")
count += 1
return count
方法二:高效做法
思路:
建立一个列表,最开始把这n个数都设为True,即都是质数。
然后进行读取,是True的就划掉该数的倍数,是False的直接跳过。从2开始,2的倍数全部划掉即变为False,3的倍数也全部变为False,4已经是False了不用管,5的倍数也全部变为False,6已经是False了不用管…一直判断到根号n,为什么又是根号n?其实原理和刚刚判断质数那个差不多,这里把是True的倍数都划掉了,看图吧~
最后,返回是True的位置就行了
代码:
def count_prime(n):
is_prime = [True] * (n + 1) #为什么这里是n+1呢。因为python索引是从0开始的,这里后面等于是把下标当做了数字。大家不懂的可以自己做一遍,我一开始没懂做一遍就懂了....
i = 2
while (i * i <= n):
if (is_prime[i]):
j = i
while (j * i <= n):
"""划到什么时候为止呢?划到i*j<=n,因为如果i*j都已经大于n了,也就不用再划了"""
is_prime[i * j] = False
j += 1
i += 1
count = 0
for i in range(2, n+1):
if (is_prime[i]):
count += 1
print(i, end = " ")
return count
第二章:数组和动态数组
未完待续——持续更新——预计1-3天更新一章——欢迎收藏关注加点赞哦~~~