常见算法
分治法
递归法
需求1:阶乘函数
def factorial(n):
if n==0:
return 1
else:
return n*factorial(n-1)
print(factorial(5))
需求2:斐波那契数列(Fibonacci Polynomial)
第0项为0,第1项为1,后面的项均为前两项之和。
def fib(n):
if n==0:
return 0
elif n==1:
return 1
else:
return(fib(n-1)+fib(n-2))
n=int(input("请输入要计算第几项斐波那契数列:"))
for i in range(n+1):
print("fib(%d)=%d"%(i,fib(i)))#%d为整数
贪心法
接下来的每一步走最佳
不能保证求得的最后解是最佳的。只能求满足某些约束条件下可行解的范围。
应用于找图的最小生成树(MST)、最短路径和哈夫曼编码
动态规划
用于研究多阶段决策过程的优化过程与求得一个问题的最佳解。
将大问题拆分成离散的子问题,且每个子问题有被储存起来。
需求:斐波那契数列问题
output=[None]*1000 #fibonacci的缓冲区
def fib(n):
result=output[n]
if result==None:
if n == 0:
result = 0
elif n == 1:
result = 1
else:
result = fib(n - 1) + fib(-2)
output[n] = result
return result
fib(5)
有报错
迭代法
需求:使用for循环来设计一个计算1!~n!阶乘的递归程序。
1
1 2
1 2 3
1 2 3 4
...
sum=1
n=int(input("请输入n="))
for i in range(n+1):
for j in range(i,0,-1):
sum*=j
print("%d!=%5d"%(i,sum))
枚举法
需求1:当1000依此减去1,2,3,…直到哪一个数时,相减的结果开始为负数。
x=1
num=1000
while num>0:
num-=x
x+=1
print(x-1)
需求2:列出1到500之间所有5的倍数
for i in range(1,501):
if i%5==0:
print(i)
回溯法
枚举法中的一种,找出所有(或一部分)解的一般性算法。
走不通就退回再走,例如老鼠走迷宫。
常用的数据结构
分类:
基本数据类型:整数、浮点、布尔、字符。
结构化数据类型(虚拟数据类型):字符串、数组、指针、列表、文件等
抽象数据类型:一个数学模型+一组数学运算或操作堆栈
数据结构的种类
数组
链表
优点:数据的插入和删除都很方便
缺点:设计数据结构时较为麻烦,按序查找
堆栈
1.只能从堆栈的顶端存取数据
2.数据的存取符合“后进先出”的原则
队列
先进先出
树形结构
度数:每个节点所有子树的个数
层数:树的层数
高度:树的最大层数
树叶或终端节点:度数为零的节点
父节点:每一个节点连接的上一层节点。
子节点:每个节点连接的下一层节点
祖先:从树根到该节点路径所包含的所有节点
子孙 :从该节点往下追溯子树中任一节点
兄弟节点:有共同父节点的节点
非终端节点:树叶以外的节点
同代:在同一棵树中具有相同层数的节点
森林:n棵(n≥0)互斥树的集合
n叉树
以链表为主,data+n个链接字段。
假设有m个节点
链接浪费率=[nm-(m-1)]/mn=[m*(n-1)]/m*n
n=2时,为1/2;n=3时,为2/3…
当n=2时,链接浪费率最低。
二叉树
1.树不可为空集合,但二叉树可以。
2.树的度数≥0.二叉树的度数为0≤d≤2
3.树的子树间没有次序关系,为叉树有
图形结构
G
=
(
V
,
E
)
G = (V,E)
G=(V,E),V=顶点,E=边
有向图
(
V
,
E
)
(V,E)
(V,E)
有向图
<
V
,
E
>
<V,E>
<V,E>
哈希表
加载密度:标识符的使用数目/每一个桶内的槽数*桶的数目
加载密度越高,表示哈希空间的使用率越高,碰撞或溢出的概率就会越高。
完美哈希:没有碰撞也没有溢出的哈希函数。
设计哈希函数的原则。
排序算法
在排序的过程中,数据的移动方式可分为“直接移动”和“逻辑移动”
冒泡排序法
从第一个元素开始,比较相邻元素的大小,若大小顺序有误,则对调后在进行下一个元素的比较。
一次扫描后可确保最后一个元素位于正确的顺序。
需求:将 16,25,39,27,12,8,45,63按从小到大的顺序排列
list=[16,25,39,27,12,8,45,63]
for i in range(len(list)-1,-1,-1):
for j in range(i):
if list[j]>list[j+1]:
temp=list[j]
list[j]=list[j+1]
list[j+1]=temp
print(list)
print(list)
时间复杂度 O ( n 2 ) O(n^2) O(n2)
选择排序
枚举法的应用,反复从未排序的数列中取出最小的元素,加入到另一个数列中。
list=[16,25,39,27,12,8,45,63]
def smallest(list):
smallest=list[0]
smallest_index=0#注意这行,数字的初始化也应该是数字,None是字符串
for i in range(1,len(list)):
if list[i]<smallest:
smallest=list[i]
smallest_index=i
return smallest_index
def select_sort(list):
new_list=[]
for i in range(len(list)): # 0~7
smallest_index=smallest(list)
new_list.append(list.pop(smallest_index))#注意这行
return new_list
print(select_sort(list))
时间复杂度 O ( n 2 ) O(n^2) O(n2)
插入排序
将数组中的元素逐一与已排序好的数据进行比较,前两个元素先拍好,再将第三个元素插入适当的位置。
原理:以从小到大排序为例,元素0为第一个元素,插入排序是从元素1开始,尽可能插到前面。插入时分插入位置和试探位置,元素i的初始插入位置为i,试探位置为i-1,在插入元素i时,依次与i-1,i-2······元素比较,如果被试探位置的元素比插入元素大,那么被试探元素后移一位,元素i插入位置前移1位,直到被试探元素小于插入元素或者插入元素位于第一位。
list=[16,25,39,27,12,8,45,63]
def insert_sort(list):
for i in range(1,len(list)):#1~7
temp=list[i]
for j in range(i,-1,-1):
if temp<list[j-1]:
list[j]=list[j-1]
else:
break
list[j]=temp
insert_sort(list)
for item in list:
print(item)
或者
原理2:插入排序原理很简单,讲一组数据分成两组,我分别将其称为有序组与待插入组。
每次从待插入组中取出一个元素,与有序组的元素进行比较,并找到合适的位置,
将该元素插到有序组当中。就这样,每次插入一个元素,有序组增加,待插入组减少。
直到待插入组元素个数为0。当然,插入过程中涉及到了元素的移动。
实现技巧:为了排序方便,我们一般将数据第一个元素视为有序组,其他均为待插入组。
list=[16,25,39,27,12,8,45,63]
def insert_sort(list):
temp=0#临时值
for i in range(1,len(list)):#1~7
temp=list[i]
j=i-1
while j>=0 and temp<list[j]:
list[j+1]=list[j]
j=j-1
list[j+1]=temp
insert_sort(list)
print(list)
时间复杂度 O ( n 2 ) O(n^2) O(n2)
希尔排序
减少插入排序法中数据搬移的次数。
原则:将数据区分成特定间隔的几个小区块,以插入排序法排完区块内的数据后在渐渐减少间隔的距离。
第一次8/2,插入排序。第二次8/2/2,插入排序。第三次8/2/2,插入排序。
和插入排序很像,只是间隔不同。
arr=[16,25,39,27,12,8,45,63]
def shell_sort(arr):
size=len(arr)
jump=size//2
while jump != 0:
for i in range(jump,size):
temp=arr[i]
j=i-jump
while j>=0 and temp<arr[j]:
arr[j+jump]=arr[j]
j=j-jump#控制循环次数
arr[j+jump]=temp
jump=jump//2#控制循环次数
shell_sort(arr)
print(arr)
时间复杂度 O ( n ( 1.3 — 2 ) ) O(n^(1.3—2)) O(n(1.3—2))
合并排序
原理:针对已排序好的两个或两个以上的数列(或数据文件),通过合并的方式,将其组合成一个大的且已排序好的数列(或数据文件)
将两个长度为4的数列合并成为一个长度为8的数列。
20,45,51,88
98,10,23,15
1)在两个序列中,比较当前元素(当前=头一次出现的第一个)
2) 然后取出最小的元素放进8元素序列中
3) 找到(两个)序列的下一个元素,(比较后)取出最小的
arr1=[20,45,51,88,9999]
arr2=[98,10,23,15,9999]
arr3=[]
def find_smallest(arr):
smallest=arr[0]
smallest_index=0
for i in range(1,len(arr)-1):
if arr[i]<smallest:
smallest=arr[i]
smallest_index=i
return smallest_index
def select_sort(arr):
new_arr=[]
for i in range(len(arr)):
smallest_index=find_smallest(arr)
new_arr.append(arr.pop(smallest_index))
return new_arr
def merge_select():
global arr1
global arr2
arr1=select_sort(arr1)
arr2=select_sort(arr2)
index1=0
index2=0
for index3 in range(len(arr1)+len(arr2)-2):
if arr1[index1]<arr2[index2]:
arr3.append(arr1[index1])
index1+=1#因为这里+1,所以数组得再多留个指针
else:
arr3.append(arr2[index2])
index2+=1
return arr3
print(merge_select())
或者
arr1=[20,45,51,88,9999]
arr2=[98,10,23,15,9999]
arr3=[]
def find_smallest(arr):
smallest=arr[0]
smallest_index=0
for i in range(1,len(arr)-1):
if arr[i]<smallest:
smallest=arr[i]
smallest_index=i
return smallest_index
def select_sort(arr):
new_arr=[]
for i in range(len(arr)):
smallest_index=find_smallest(arr)
new_arr.append(arr.pop(smallest_index))
return new_arr
def merge_select(arr1,arr2):
arr1=select_sort(arr1)
arr2=select_sort(arr2)
index1=0
index2=0
for index3 in range(len(arr1)+len(arr2)-2):
if arr1[index1]<arr2[index2]:
arr3.append(arr1[index1])
index1+=1
else:
arr3.append(arr2[index2])
index2+=1
return arr3
print(merge_select(arr1,arr2))
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
快速排序法
目前公认最佳的排序法。
先在数据中找到一个虚拟的中间值,并按此中间值将所有打算排序的数据分成两部分,小的放左边,大的放右边。
分而治之。
arr=[12,45,36,47,68,79]
def quick_sort(arr):
if len(arr)<2:
return arr
else:
pivot=arr[0]
less=[i for i in arr[1:] if i<pivot]
greater=[i for i in arr[1:] if i>pivot]
return quick_sort(less)+[pivot]+quick_sort(greater)
print(quick_sort(arr))
基数排序法
并不需要进行元素间的比较操作,而是一种分配模式排序方式。
按比较的方向可分为最高位优先(Most Significant Digit First,MSD)和最低位优先(Least Significant Digit First,LSD)
方法一:用散列表
arr=[12,45,36,47,68,79,100,123,124,256,780]
def radix_sort(arr):
max_num=max(arr)
max_radix=1
while max_radix<max_num:
max_radix*=10#1000
max_radix//=10#100
n=1
while n<=max_radix:#1,10,100
bucket={}
for i in range(10):
bucket.setdefault(i,[])#否则会报错找不到key的错误
for i in range(len(arr)):
radix=int((arr[i]/n)%10)
bucket[radix].append(arr[i])
#print(bucket)
new_arr = []
j=0
for i in range(10):
if len(bucket[i]) != 0:
for values in bucket[i]:
arr[j]=values
j+=1
n*=10
radix_sort(arr)
#print(radix_sort(arr))
print(arr)
方法二:用二维数组
arr=[12,45,36,47,68,79,100,123,124,256,780]
def radix_sort(arr):
max_num=max(arr)
max_radix=1
while max_radix<max_num:
max_radix*=10#1000
max_radix//=10#100
n=1
while n<=max_radix:#1,10,100
temp=[[0]*100 for row in range(10)]#[0~9位数][数据个数]
for i in range(len(arr)):
m=(arr[i]//n)%10
temp[m][i]=arr[i]
k=0
for i in range(10):
for j in range(len(arr)):
if temp[i][j]!=0:
arr[k]=temp[i][j]
k+=1
n*=10
radix_sort(arr)
print(arr)
以下方法报错:
因为每次更新的都没有被记录到下一次。
arr=[12,45,36,47,68,79,100,123,124,256,780]
def radix_sort(arr):
max_num=max(arr)
max_radix=1
while max_radix<max_num:
max_radix*=10#1000
max_radix//=10#100
n=1
while n<=max_radix:#1,10,100
bucket={}
for i in range(10):
bucket.setdefault(i,[])#否则会报错找不到key的错误
for i in range(len(arr)):
radix=int((arr[i]/n)%10)
bucket[radix].append(arr[i])
#print(bucket)
new_arr = []
j=0
for i in range(10):
if len(bucket[i]) != 0:
for values in bucket[i]:
new_arr.append(values)
n*=10
return new_arr
radix_sort(arr)
print(radix_sort(arr))
print(arr)
二维数组用arr[j]就会超出范围
稳定性、数据搬移量、算法复杂度
排序算法的稳定性大家应该都知道,通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj, Ai原来在位置前,排序后Ai还是要在Aj位置前。
堆排序、快速排序、希尔排序、直接选择排序是不稳定的排序算法,
而基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。
查找
判断一个查找算法的好坏:
比较次数和查找所需要的时间
分类:
静态查找:在查找过程中没有添加、删除或更新等操作
动态查找
顺序查找
优点:在查找时不需要进行任何的处理与排序
缺点:查找速度慢
需求:以随机数生成1~150之间的80个整数,然后实现顺序查找的过程。
import random
data=[0]*80
for i in range(len(data)):
data[i]=random.randint(1,150)#包括1和150
print("数据内容为:")
for i in range(10):
for j in range(8):
print('%2d[%3d] '%(i*8+j,data[i*8+j]),end='')
print()
val=0
while val!=-1:
num=0
val=int(input("请输入范围在0~150之间的被查找的值,输入-1离开:"))#注意加int
for i in range(len(data)):
if val==data[i]:
print('在第%2d的位置查找到键值[%3d]' %(i+1,data[i]))
num+=1
if num==0 and val!=-1:
print('没有找到键值[%3d]' %val)
时间复杂度: O ( n ) O(n) O(n)
二分查找
import random
data=[0]*80
val=1
for i in range(len(data)):
data[i]=val
val=val+random.randint(1,5)
print("数据内容为:")
for i in range(10):
for j in range(8):
print('%2d[%3d] '%(i*8+j,data[i*8+j]),end='')
print()
val=int(input("输入的数为: "))
def bin_search(data,val):
low = 0
high = len(data) - 1
while(low<=high):
mid = (low + high) // 2
if val < data[mid]:
high = mid - 1
elif val>data[mid]:
low = mid + 1
else:
return mid
return None
print(bin_search(data,val))
时间复杂度: O ( l o g n ) O(logn) O(logn)
插值查找
插补查找,是二分查找的改进版。
按照数据位置的分布,利用公式预测数据所在的位置,再以二分法的方式渐渐逼近。
使用插值法是假设数据平均分布在数组中,而每一项数据的差距相当接近或有一定的距离比例。
公式
m
i
d
=
l
o
w
+
k
e
y
−
d
a
t
a
[
l
o
w
]
d
a
t
a
[
h
i
g
h
]
−
d
a
t
a
[
l
o
w
]
∗
(
h
i
g
h
−
l
o
w
)
mid = low + \frac{{key - data[low]}}{{data[high] - data[low]}}*(high - low)
mid=low+data[high]−data[low]key−data[low]∗(high−low)
步骤:
1.将记录从小到大的顺序给予1,2,3,…,n的编号
2.令low=0,high=n
3.当low<high时,重复执行步骤4~7
4.令mid等于如上公式
5.当key<keymid且high≠mid-1,high=mid-1
6.当key=keymid,则成功找到
7.当key>keymid且low≠mid+1,low=mid+1
改下mid的公式即可。
import random
data=[0]*80
val=1
for i in range(len(data)):
data[i]=val
val=val+random.randint(1,5)
print("数据内容为:")
for i in range(10):
for j in range(8):
print('%2d[%3d] '%(i*8+j,data[i*8+j]),end='')
print()
val=int(input("输入的数为: "))
def bin_search(data,val):
low = 0
high = len(data) - 1
while(low<=high):
mid = low+((val-data[low])//(data[high]-data[low]))*(high-low)
if val < data[mid]:
high = mid - 1
elif val>data[mid]:
low = mid + 1
else:
return mid
return None
print(bin_search(data,val))
时间复杂度: O ( l o g n ) O(logn) O(logn)
哈希算法
注意不宜过于复杂
设计原则:
计算速度快、碰撞频率尽量低
除留余数法
将数据除以某一个常数后,取余数当索引。h(key)=key mod B
其中B可以为哈希表中索引的个数。B最好为质数
平方取中法
先计算数据的平方,之后再取中间的某段数字作为索引
取百位数和十位数(再除以10)
折叠法
将数据转换成一串数字后,先将这串数字拆成几个部分 ,再将它们加起来,就可以计算出其桶地址。
移动折叠法:直接相加
边界折叠法:将每一部分数字是奇数的反转或偶数的反转再相加。
数字分析法
适用于数据不会更改,且为数字类型的静态表。
在决定哈希函数时先逐一检查数据的相对位置和分布情况,将重复度高的部分删除。
碰撞与溢出问题的处理
线性探测法
发生碰撞时,以线性的方式往后寻找空的储存位置,一旦找到位置就把数据放进去。
把哈希的位置视为环形结构,当后面的位置都被填满后,可以把数据放前面。
缺点:相类似的键值往往会聚集在一起。
import random
maxnum=7
maxindex=10
data=[0]*maxnum#创建数组
index=[0]*maxindex#创建哈希表
for i in range(maxnum):
data[i]=random.randint(1,20)
print('%d '%data[i],end='')
print()#数组初始化并打印
for i in range(maxindex):
index[i]=-1#哈希表初始化
for i in range(maxnum):
temp = data[i] % maxindex
while True:
if index[temp]==-1:
index[temp]=data[i]
break
else:
temp+=1
print(index)
平方探测法
改进线性探测法的缺点
下一次查找的地址为
(
f
(
x
)
+
i
2
)
 
m
o
d
 
B
(f(x) + {i^2})\bmod B
(f(x)+i2)modB或
(
f
(
x
)
−
i
2
)
 
m
o
d
 
B
(f(x) - {i^2})\bmod B
(f(x)−i2)modB
第一次查找:
(
f
(
k
e
y
)
+
1
2
)
m
o
d
B
(f(key)+1^2)modB
(f(key)+12)modB
第二次查找:
(
f
(
k
e
y
)
−
1
2
)
m
o
d
B
(f(key)-1^2)modB
(f(key)−12)modB
第三次查找:
(
f
(
k
e
y
)
+
2
2
)
m
o
d
B
(f(key)+2^2)modB
(f(key)+22)modB
第四次查找:
(
f
(
k
e
y
)
−
2
2
)
m
o
d
B
(f(key)-2^2)modB
(f(key)−22)modB
。。。
第n次查找:
(
f
(
k
e
y
)
±
(
(
B
−
1
)
/
2
)
2
)
m
o
d
B
(f(key)\pm ((B-1)/2)^2)modB
(f(key)±((B−1)/2)2)modB,其中B必须为
4
j
+
3
4j+3
4j+3的质数,且
1
≤
i
≤
(
B
−
1
)
/
2
1 \le i \le (B - 1)/2
1≤i≤(B−1)/2
再哈希法
一开始先设置一系列的哈希函数。
如果使用第一种哈希函数出现溢出时就改用第二种,如果第二种也出现溢出则改用第三种。。。
数组与链表算法
是重要的结构化数据类型,是典型线性表的应用。
线性表按照内存存储的方式,基本上可分为以下两种:
静态数据结构:数组类型
动态数据结构:链表
矩阵
矩阵相加
A=[[1,3,5],[7,9,11],[13,15,17]]
B=[[9,8,7],[6,5,4],[3,2,1]]
N=3
C=[[0]*N for row in range(N)]#C的创建
for i in range(N):
for j in range(N):
C[i][j]= A[i][j]+ B[i][j]
for i in range(N):
for j in range(N):
print(C[i][j],end=' ')
print()
i*总列数+j
矩阵相乘
A的维数为mn,B的维数为np,
C=AB维数为mp
print("请输入A的维数(M,N):")
M=int(input("M="))
N=int(input("N="))
A=[0]*M*N
for i in range(M):
for j in range(N):
A[i*N+j]=int(input('a%d%d='%(i,j)))
print("请输入B的维数(N,P):")
P = int(input("P="))
B= [0] * N*P
for i in range(N):
for j in range(P):
B[i * P + j] = int(input('b%d%d=' % (i, j)))
C=[0]*M*P
for i in range(M):
for j in range(P):
temp=0
for k in range(N):
temp=temp+A[i*N+k]*B[k*P+j]#这行注意
C[i*P+j]=temp
for i in range(M):
for j in range(P):
print(C[i*P+j],end=' ')
print()
转置矩阵
A=[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]
M=4
B=[[0]*M for row in range(M)]
for i in range(M):
for j in range(M):
print('%d' % A[i][j], end='\t ')
print()
for i in range(M):
for j in range(M):
B[i][j]=A[j][i]
print()
for i in range(M):
for j in range(M):
print('%d' % B[i][j], end='\t ')
print()
注意:
B=[0]*M*M
是一个列表,一维数组
B=[[0]*M for row in range(M)]
是二维数组
建立单向链表
声明一个学生成绩链表节点的结构声明,包含name,score和一个指针字段 。
class Student:
def __init__(self):
self.name=''
self.score=0
self.next=None#指针
步骤:
1.动态分配内存空间给新节点使用
2.将原链表尾部的指针(next)指向新元素所在的内存位置。
3.将ptr指针指向新节点的内存位置,表示这是新的链表尾部。
4.由于新节点当前为链表的最后一个元素,由此将它的指针(next)指向None
需求:建立学生节点的单向链表
class Student:
def __init__(self):
self.name=''
self.score=0
self.next=None
head=Student()
head.next=None
ptr=head
select=0
while select!=2:
print("(1)添加 (2)离开")
#try:
select=int(input("请输入一个选项:"))
#except ValueError:
#print('输入错误,请重新输入')
if select==1:
new_student=Student()
new_student.name=input('姓名:')
new_student.no=input('学号:')
new_student.Math=eval(input('数学成绩:'))#eval() 函数用来执行一个字符串表达式,并返回表达式的值。
new_student.English = eval(input('英语成绩:')) #
ptr.next=new_student
new_student.next=None
ptr=ptr.next
单向链表的连接功能
级联:两个或两个以上链表的连接
python中较长的语句如果一行写不完可以用“\
”来连接多行语句
在(),{},[] 中无需用“\”连接
import sys
import random
#ptr不更新?
#级联函数
def concatenation(ptr1,ptr2):
ptr=ptr1
while ptr.next!=None:
#print('[%2d %1s %3d]=>' % (ptr.num, ptr.name, ptr.salary))
ptr=ptr.next
ptr.next=ptr2
return ptr1
class Employee:
def __init__(self):
self.num=0
self.name=''
self.salary=0
self.next=None
#====data1的初始化
namedata1=['a','b','c','d',\
'e','f','g','h',\
'i','j','k','m']
data=[[None]*2 for row in range(12)]
for i in range(12):
data[i][0]=i+1
data[i][1]=random.randint(51,100)
#===data1的链表形式,head1这组链表已经储存了其内容
head1=Employee()
if not head1:
print('Error!内存分配失败')
sys.exit(0)
head1.num=data[0][0]
head1.name=namedata1[0]
head1.salary=data[0][1]
head1.next=None
ptr=head1
for i in range(1,12):
new_employee=Employee()
new_employee.num=data[i][0]
new_employee.name=namedata1[i]
new_employee.salary=data[i][1]
ptr.next=new_employee
new_employee.next=None
ptr=ptr.next
ptr1=head1
while ptr1!=None:
print('[%2d %1s %3d]=>' %(ptr1.num, ptr1.name, ptr1.salary))
ptr1=ptr1.next
print()
#====data2的初始化
namedata2=['n','o','p','q',\
'r','s','t','u',\
'v','w','x','y']
for i in range(12):
data[i][0]=i+13
data[i][1]=random.randint(51,100)
#===data1的链表形式
head2=Employee()
if not head2:
print('Error!内存分配失败')
sys.exit(0)
head2.num=data[0][0]
head2.name=namedata2[0]
head2.salary=data[0][1]
head2.next=None
ptr=head2
for i in range(1,12):
new_employee=Employee()
new_employee.num=data[i][0]
new_employee.name=namedata2[i]
new_employee.salary=data[i][1]
ptr.next=new_employee
new_employee.next=None
ptr=ptr.next
ptr1=head2
while ptr1!=None:
print('[%2d %1s %3d]=>' %(ptr1.num, ptr1.name, ptr1.salary))
ptr1=ptr1.next
print()
i=0
ptr=concatenation(head1,head2)
while ptr!=None:#注意这行
print('[%2d %1s %3d]=>' % (ptr.num, ptr.name, ptr.salary), end='')
i += 1
if i>=3:
print()
i=0
ptr = ptr.next
单向链表的节点删除
1.删除首节点:
top=head
head=head.next
2.删除末节点
ptr.next=tail
ptr.next=None
2.删除中间节点
Y=ptr.next
ptr.next=Y.next
示例:
import sys
import random
def del_ptr(head, ptr): # 删除节点子程序
top = head
if ptr.num == head.num: # [情形1]:要删除的节点在链表头部
head = head.next
print('已删除第 %d 号员工 姓名:%s 薪资:%d' % (ptr.num, ptr.name, ptr.salary))
else:
while top.next != ptr: # 找到删除节点的前一个位置
top = top.next
if ptr.next == None: # 删除在链表末尾的节点
top.next = None
print('已删除第 %d 号员工 姓名:%s 薪资:%d' % (ptr.num, ptr.name, ptr.salary))
else:
top.next = ptr.next # 删除在串行中的任一节点
print('已删除第 %d 号员工 姓名:%s 薪资:%d' % (ptr.num, ptr.name, ptr.salary))
return head # 返回链表
class Employee:
def __init__(self):
self.num=0
self.name=''
self.salary=0
self.next=None
#====data1的初始化
namedata1=['a','b','c','d',\
'e','f','g','h',\
'i','j','k','m']
data=[[None]*2 for row in range(12)]
for i in range(12):
data[i][0]=i+1
data[i][1]=random.randint(51,100)
#===data1的链表形式,head1这组链表已经储存了其内容
head=Employee()
if not head:
print('Error!内存分配失败')
sys.exit(0)
head.num=data[0][0]
head.name=namedata1[0]
head.salary=data[0][1]
head.next=None
ptr=head
for i in range(1,12):
new_employee=Employee()
new_employee.num=data[i][0]
new_employee.name=namedata1[i]
new_employee.salary=data[i][1]
ptr.next=new_employee
new_employee.next=None
ptr=ptr.next
ptr1=head
while ptr1!=None:
print('[%2d %1s %3d]=>' %(ptr1.num, ptr1.name, ptr1.salary))
ptr1=ptr1.next
print()
while (True):
findword = int(input('请输入要删除的员工编号,要结束删除过程,请输入-1:'))
if (findword == -1): # 循环中断条件
break
else:
ptr = head
find = 0
while ptr != None:
if ptr.num == findword:
ptr = del_ptr(head, ptr)
find = find + 1
head = ptr
ptr = ptr.next
if find == 0:
print('######没有找到######')
ptr1=head#注意这行的head
while ptr1!=None:
print('[%2d %1s %3d]=>' %(ptr1.num, ptr1.name, ptr1.salary))
ptr1=ptr1.next
print()
单向链表的旋转
步骤:
p=head
q=head.next
head.next=None
r=q.next
q.next=p
p=q
q=r
r=q.next
q.next=p
...
示例:
import sys
import random
def reserve(head):
"""反转函数"""
p=head
q=head.next
head.next=None
while(q!=None):
r=q.next
q.next=p
p=q
q=r
return p
class Employee:
def __init__(self):
self.num=0
self.name=''
self.salary=0
self.next=None
#====data1的初始化
namedata1=['a','b','c','d',\
'e','f','g','h',\
'i','j','k','m']
data=[[None]*2 for row in range(12)]
for i in range(12):
data[i][0]=i+1
data[i][1]=random.randint(51,100)
#===data1的链表形式,head1这组链表已经储存了其内容
head=Employee()
if not head:
print('Error!内存分配失败')
sys.exit(0)
head.num=data[0][0]
head.name=namedata1[0]
head.salary=data[0][1]
head.next=None
ptr=head
for i in range(1,12):
new_employee=Employee()
new_employee.num=data[i][0]
new_employee.name=namedata1[i]
new_employee.salary=data[i][1]
ptr.next=new_employee
new_employee.next=None
ptr=ptr.next
print("反转前")
ptr=head
while ptr!=None:
print('[%2d %1s %3d]=>' %(ptr.num, ptr.name, ptr.salary))
ptr=ptr.next
print()
#经过反转
head=reserve(head)
print("反转后")
ptr=head
while ptr!=None:
print('[%2d %1s %3d]=>' %(ptr.num, ptr.name, ptr.salary))
ptr=ptr.next
print()
堆栈与队列算法
抽象数据类型
用数组实现堆栈
堆栈本身大小是变动的,而数组大小只能事先规划和声明好。
需求:使用数组结构,用循环来控制元素压入堆栈或弹出 堆栈,并仿真堆栈的各种操作,此堆栈中最多可容纳100个元素,其中必须包括push函数和pop函数,并最后输出所有元素。
#每个函数里面外来的数字不在函数名后的括号中,就在函数里面加global
MAXSTACK=100#数组的容量
global stack
stack=[0]*MAXSTACK
top=-1#堆栈的顶部
def isEmpty():
if top==-1:
return True
else:
return False
def push(value):
global stack
global MAXSTACK
global top
if top>=MAXSTACK-1:
print("堆栈已满,无法压入")
else:
top=top+1
stack[top]=value
def pop():
global stack
global top
if isEmpty():
print("此堆栈为空")
else:
print('弹出的元素为%d'%stack[top])
top=top-1
i=2
count=0
while True:
i=int(input("1为压入,0为弹出,-1为中止:"))
if i==-1:
break
elif i==1:
value=int(input("要被压入的数字为:"))
push(value)
elif i==0:
pop()
else:
print('重新输入')
print("=======================")
if top<0:
print("堆栈是空的")
else:
i=top
while(i>=0):
print('堆栈弹出的顺序为%d'%stack[i])
count+=1
i=i-1
用链表实现堆栈
优点:随时可以动态改变链表长度,能有效使用内存资源
缺点:设计的算法较为复杂
class Node:
def __init__(self):
self.data=0
self.next=None
top=None
def isEmpty():
if top==None:
return 1
else:
return 0
def push(value):
global top
new_node=Node()
new_node.data=value
new_node.next=top
top=new_node
def pop():
global top
if isEmpty():
print("为空,无法弹出")
else:
ptr=top
top=top.next
return ptr.data
#print('弹出的为%d'%ptr.data)
while True:
i=int(input("1为压入,2为弹出,-1为中断:"))
if i==-1:
break
elif i==1:
value=int(input("被压入的数为:"))
push(value)
elif i==2:
print('弹出的为%d' % pop())
print("======================")
while(not isEmpty()):
print('堆栈弹出的顺序为:%d'%pop())
汉诺塔问题的求解算法
Tower of Hanoi
步骤:
1.将n-1个盘子从木桩1移动到木桩2
2.将第n个盘子从木桩1移动到木桩3
3.将n-1个盘子从木桩2移动到木桩3
用递归方式和堆栈
def hanoi(n,p1,p2,p3):
if n==1:
print(p1+'->'+p3)
else:
hanoi(n-1,p1,p3,p2)
hanoi(1,p1,p2,p3)
hanoi(n-1,p2,p1,p3)
j=int(input("要移动的盘子的数量"))
hanoi(j,'1','2','3')
最小移动次数 2 n − 1 2^n-1 2n−1
八皇后问题的求解算法
N*N棋盘,则为N皇后问题。
用回溯方式和堆栈
global queen
EIGHT=8
queen=[0]*EIGHT
num=0#计算总共有几组解
#输出结果
def print_table():
global num
global EIGHT
num=num+1
x=y=0
print('八皇后的第%d组解'%num)
for x in range(EIGHT):
for y in range(EIGHT):
if x==queen[y]:
print('<q>',end='')
else:
print('<->',end='')
print()
input('按下任意键继续')
#测试放在棋盘上的皇后是否遭受到攻击
#遭到攻击返回1,不遭到攻击返回0
# 这里的col是queen的索引,row是quen里的值
def attack(row,col):
global queen
i=0
atk=0
offset_row=offset_col=0
while (atk!=1)and(i<col):
offset_col=abs(i-col)#新放进去的皇后和之前的对比
offset_row=abs(queen[i]-row)
if row==queen[i] or offset_row==offset_col:#列数肯定不一样
atk=1
i=i+1
return atk
def decide_position(value):
global queen
i=0#列数,索引
while i<EIGHT:
if attack(i,value)!=1:
queen[value]=i
if value==7:
print_table()
else:
decide_position(value+1)# 每当新加的元素没有受到攻击时,才到下一列
i=i+1
decide_position(0)
用数组实现队列
优点:算法相当简单
缺点:数组大小无法根据队列的实际需要来动态申请,只能声明一个固定的大小。
与堆栈的不同:
需要拥有两种基本操作:加入和删除,而且要使用front与real两指针来分别指向队列的前端与末尾。
每加入一个元素,real的值+1
每取出一个元素,front值+1
当real=数组大小-1时,表示队列已满
MAX=4
queue=[0]*MAX
front=-1
real=-1
def enqueue(item):#加元素
global real
global queue
if real==MAX-1:
print("队列已满")
else:
real+=1
queue[real]=item
def dequeue(item):#减元素
global real
global front
global queue
if front==real:
print("队列已空")
else:
front+=1
item=queue[front]
def front_value(Queue):#返回队列前端的值
global real
global front
global queue
if real==front:
print("队列已空")
else:
print(queue[front])
示例:
import sys
MAX=4
queue=[0]*MAX
front=-1
real=-1
choice=''
while real<MAX-1 and choice!='e':
choice=input('[a]表示加入一个数,[d]取出一个数,[e]结束跳出循环:')
if choice=='a':
value=int(input("要被加入的数为:"))
real+=1
queue[real]=value
elif choice=='d':
if real>front:
front += 1
print('取出的数为%d' % queue[front])
queue[front] = 0
else:
print('队列已空')
sys.exit(0)
else:
print()
print('--------------------------')
if real==MAX-1:
print("队列已满")
elif front>=real:
print("队列已空")
else:
while real>front:
front+=1
print('[%d] '%queue[front],end='')
print()
用链表实现队列
只能将front指针放置于队列开头与结尾处,所以判断条件不是简单的加加减减,而是指针的位置。
class Student:
def __init__(self):
self.name=''*20
self.score=0
self.next=None
front=Student()
real=Student()
front=None
real=None
def enqueue(name,score):
global front
global real
new_student=Student()
new_student.name=name
new_student.score=score
if real==None:
front=new_student
else:
real.next=new_student
real=new_student
new_student.next=None
def dequeue():
global front
global real
if real.next==front:#或者if front==None
print("队列为空")
else:
print('被取出的事姓名:%s,分数:%d'%(front.name,front.score))
front = front.next
def show():
global front
global real
if real.next==front:#或者if front==None
print("队列为空")
else:
ptr=front
while(ptr!=None):
print('姓名:%s,分数:%d'%(ptr.name,ptr.score))
ptr=ptr.next
select=0
while True:
select=int(input("1为加,2为取,3为显示,4为跳出"))
if select==4:
break
elif select==1:
name=input('name=')
score=int(input('score='))
enqueue(name,score)
elif select==2:
dequeue()
elif select==3:
show()
双向队列
允许两端中的任意一端都具备删除或加入功能。
两种应用:
1.数据只能从一端加入,但可从两端取出。
2.数据能从两端加入,但只能从一端取出。
第一种示例:(链表)
class Node:
def __init__(self):
self.data=0
self.next=None
front=Node()
real=Node()
front=None
real=None
def enqueue(data):
"""数据的加入,后端加入"""
global real
global front
new_node=Node()
new_node.data=data
if real==None:
front=new_node
else:
real.next=new_node
real=new_node
new_node.next = None#111
def dequeue(action):
"""数据的取出"""
global real
global front
#前端取出
if action==1 and front!=None:
if front==real:
real=None
value=front.data
front=front.next
return value
elif action==2 and real!=None:
value=real.data
#当余下的数据大于1个时,找出最后第二个节点,把real移到这里
startfront=front
tempNode=front
while front.next!=real and front.next!=None:
front=front.next
tempNode=front
real=tempNode
front=startfront
#当余下一个时
if front.next==None and real==None:
real=None
front=None
return value
else:
return -1
print('========================')
choice=''
while True:
choice=input('a为加,b为减,e为结束')
if choice=='a':
value=int(input('输入的数为'))
enqueue(value)
elif choice=='b':
print('从前端取出为%d' % dequeue(1))
print('从后端取出为%d' % dequeue(2))
else:
break
优先队列
不必遵守队列特性(FIFO,先进先出)的有序线性表。
加入元素时可任意加入,但有最高优先级者则最先输出。
如果以输入先后次序的倒序作为优先级,此优先队列即为一个堆栈。
树形结构及其算法
非线性结构
满二叉树:高度为h,树的节点数为
2
h
−
1
,
h
≥
0
2^h-1,h≥0
2h−1,h≥0,第k层具有
2
k
−
1
{2^{k - 1}}
2k−1个节点
完全二叉树:高度为h,树的节点数小于
2
h
−
1
,
h
≥
0
2^h-1,h≥0
2h−1,h≥0。假设 有N个节点,二叉树的层数为
⌊
log
2
(
N
+
1
)
⌋
\left\lfloor {{{\log }_2}(N + 1)} \right\rfloor
⌊log2(N+1)⌋。
斜二叉树:当一个二叉树完全没有右节点或左节点时,就称为左斜二叉树或右斜二叉树 。
严格二叉树:每一个终端节点均有非空的左右子树。
用数组实现二叉树
关系:
1.左子树索引值是父节点索引值2
2.右子树索引值是父节点索引值2+1
特点:
1.可以为空集合,如果不为空,则节点上一定要有键值。
2.每一个树根的值需大于左树根的值 。
3.每一个树根的值需小于右树根的值 。
4.左右子树也是二叉查找树。
5.数的每个节点值都不相同。
def btree_create(data,btree,length):
for i in range(1,length):
level=1
while btree[level]!=0:
if data[i]>btree[level]:
level=level*2+1
else:
level=level*2
btree[level]=data[i]
print("====================")
data=[0,6,3,5,4,7,8,9,2]
length=len(data)
btree=[0]*16
print('数组为:')
for i in range(len(data)):
print('%d '%data[i],end='')
print()
btree_create(data,btree,length)
print("二叉树为:")
for i in range(1,16):
print('%d '%btree[i],end='')
print()
用链表实现二叉树
优点:对于节点的增加与删除相当容易。
缺点:很难找到父节点,除非在每一个节点多增加一个父节点
class Tree:
def __init__(self):
self.data=0
self.left=None
self.right=None
def btree_create(root,data):
new_node=Tree()
new_node.data=data
new_node.left=None
new_node.right=None
if root==None:
root=new_node
return root
else:
current=root
while current!=None:
backup=current
if data < current.data:
current = current.left
else:
current = current.right
if data<backup.data:
backup.left=new_node
else:
backup.right=new_node
return root
print("======================")
ptr=None
data=[5,6,24,8,12,3,17,1,9]
root=None
for i in range(len(data)):
ptr=btree_create(ptr,data[i])
print("左子树:")
root=ptr.left
while root!=None:
print(root.data)
root=root.left
print("右子树:")
root=ptr.right
while root!=None:
print(root.data)
root=root.right
print()
二叉树遍历
访问树中所有的节点各一次
按照二叉树特性。分为以下三种。
中序:BAC
前序:ABC
后序:BCA
记忆方法:树根的位置,遍历方式先左树根后右树根
中序遍历
左中右
def inorder(ptr):
if ptr!=None:
inorder(ptr.left)
print('[%2d] '%ptr.data,end='')
inorder(ptr.right)
后序遍历
左右中
def postorder(ptr):
if ptr!=None:
postorder(ptr.left)
postorder(ptr.right)
print('[%2d] '%ptr.data,end='')
前序遍历
中左右
def preorder(ptr):
if ptr!=None:
print('[%2d] ' % ptr.data, end='')
preorder(ptr.left)
preorder(ptr.right)
示例:
中序排列
class Tree:
def __init__(self):
self.data=0
self.left=None
self.right=None
def inorder(ptr):
if ptr!=None:
inorder(ptr.left)
print("[%2d] "%ptr.data,end='')
inorder(ptr.right)
def btree_create(root,data):
new_node=Tree()
new_node.data=data
new_node.left=None
new_node.right=None
if root==None:
root=new_node
return root
else:
current=root
while current!=None:
backup=current
if data < current.data:
current = current.left
else:
current = current.right
if data<backup.data:
backup.left=new_node
else:
backup.right=new_node
return root
print("======================")
ptr=None
data=[5,6,24,8,12,3,17,1,9]
root=None
for i in range(len(data)):
ptr=btree_create(ptr,data[i])
print("中序排列后的结果")
inorder(ptr)
print()
二叉树的查找
class Tree:
def __init__(self):
self.data=0
self.left=None
self.right=None
def search(ptr,value):
"""查找二叉树中的某个值"""
while True:
if ptr==None:
return None
if ptr.data==value:
return ptr
elif value<ptr.data:
ptr=ptr.left
else:
ptr=ptr.right
def btree_create(root,data):
new_node=Tree()
new_node.data=data
new_node.left=None
new_node.right=None
if root==None:
root=new_node
return root
else:
current=root
while current!=None:
backup=current
if data < current.data:
current = current.left
else:
current = current.right
if data<backup.data:
backup.left=new_node
else:
backup.right=new_node
return root
print("======================")
ptr=None
data=[5,6,24,8,12,3,17,1,9]
root=None
for i in range(len(data)):
ptr=btree_create(ptr,data[i])
print('[%d] '%data[i],end='')
print()
print('==================')
value=int(input("输入的数为 "))
if search(ptr,value)!=None:
print('您要找的值%2d找到了'%value)
else:
print('您要找的值没找到')
二叉树节点的插入
class Tree:
def __init__(self):
self.data=0
self.left=None
self.right=None
def inorder(ptr):
"""中序遍历"""
if ptr!=None:
inorder(ptr.left)
print("[%2d] "%ptr.data,end='')
inorder(ptr.right)
def search(ptr,value):
"""查找二叉树中的某个值"""
while True:
if ptr==None:
return None
if ptr.data==value:
return ptr
elif value<ptr.data:
ptr=ptr.left
else:
ptr=ptr.right
def btree_create(root,data):
new_node=Tree()
new_node.data=data
new_node.left=None
new_node.right=None
if root==None:
root=new_node
return root
else:
current=root
while current!=None:
backup=current
if data < current.data:
current = current.left
else:
current = current.right
if data<backup.data:
backup.left=new_node
else:
backup.right=new_node
return root
print("======================")
ptr=None
data=[5,6,24,8,12,3,17,1,9]
root=None
for i in range(len(data)):
ptr=btree_create(ptr,data[i])
print('[%d] '%data[i],end='')
print()
print('==================')
###插入数
value=int(input("输入的数为 "))
if search(ptr,value)!=None:
print('二叉树已经有该节点了')
else:
btree_create(ptr,value)
inorder(ptr)
print()
二叉树节点的删除
有分以下三种情况:
1.删除的节点为树叶
只要将其相连的父节点指向None即可
2.删除的节点只有一棵子树
如果删除的是左子树,将其有指针字段放到父节点的左指针字段。
3.删除的节点有两棵子树
有两种做法
1)找出中序立即先行者,将欲删除节点的左子树中最大者向上提
2)找出中需立即后续者,将欲删除节点的右子树中最小者向上提
堆积树排序法
是选择排序的改进版,可以减少选择排序法的比较次数。
堆积树是一种特殊的二叉树。可分为以下两种:
最大堆积树:
1.是一个完全二叉树
2.所有节点的值都大于或等于它左右子节点的值。
3.树根是堆积树种最大的。
最小堆积树:
1.是一个完全二叉树
2.所有节点的值都小于或等于它左右子节点的值。
3.树根是堆积树种最小的。
将二叉树转换为堆积树:
可以用数组来存储二叉树所有节点的值。
从二叉树的树根开始从上往下逐一按堆积树的建立原则来改变各节点值。或者从下往上。
堆积树并不唯一。
图的数据结构及其算法
一个图
G
=
(
V
,
E
)
G=(V,E)
G=(V,E)
图遍历的方法有两种,即“深度优先遍历”和“广度优先遍历”
也称为“深度优先搜索”和“广度优先搜索”
深度优先遍历法
类似于前序遍历
结合递归和堆栈两种数据结构
后进先出,从大到小
广度优先搜索
结合递归和队列两种数据结构
先进先出,从小到大
最小生成树
又称”花费树“、”成本树"或“值树”
一个图的生成树(spanning tree)就是以最少的边来连通图中所有的顶点,且不造成回路。
如果在树的边加上一个权重值,那么这种图就成为“加权图”
如果这个权重值代表两个顶点间的距离或成本,这类图就被称为网络。
用贪婪法则为基础来求得一个无向连通图的最小生成树的常见方法,有Prim和Kruskal算法
Prim算法
P氏法
每一步取最佳
在V-U中差集所产生的几何中找出一个顶点 x,该顶点x能从U集合中的某点形成最小成本的边,且不会造成回路。
然后将顶点x加入U集合中。
直到U=V为止。
Kruskal算法
K氏法
将各边按权值大小从小到大排列,接着从权值最低的边开始建立最小成本生成树。
如果加入的边会造成回路则舍弃不用,直到加入了n-1个边为止。
图的最短路径法
Dijkstra算法
求出某一点到其他顶点的最短距离
A*算法
Dijkstra算法的 改进版,预先设置一个推测权重。
适用于可以事先获得或预估各个顶点到终点距离的情况。
Floyd算法
求出图中任意两点甚至所有顶点间最短的距离