算法的概念
算法是计算机处理信息的本质,因为计算机程序本质上是一个算法来告诉计算机确切的步骤来执行一个指定的任务。
一般地,当算法在处理信息时,会从输入设备或数据的存储地址读取数据,把结果写入输出设备或某个存储地址供以后再调用。
算法是独立存在的一种解决问题的方法和思想。
时间复杂度
算法完成工作最少需要多少基本操作,即最优时间复杂度
算法完成工作最多需要多少基本操作,即最坏时间复杂度
算法完成工作平均需要多少基本操作,即平均时间复杂度
基本操作,即只有常数项,认为其时间复杂度为O(1)
顺序结构,时间复杂度按加法进行计算
循环结构,时间复杂度按乘法进行计算
分支结构,时间复杂度取最大值
判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略
在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度
常见时间复杂度之间的关系
list内置操作的时间复杂度
抽象数据类型(Abstract Data Type)
抽象数据类型(ADT)的含义是指一个数学模型以及定义在此数学模型上的一组操作。即把数据类型和数据类型上的运算捆在一起,进行封装。引入抽象数据类型的目的是把数据类型的表示和数据类型上运算的实现与这些数据类型和运算在程序中的引用隔开,使它们相互独立。
最常用的数据运算有五种:
插入
删除
修改
查找
排序
程序 = 数据结构 + 算法
顺序表的基本形式
图b中的c不再是数据元素的大小,而是存储一个链接地址所需的存储量,这个量通常很小。
图b这样的顺序表也被称为对实际数据的索引,这是最简单的索引结构。
顺序表要求存储空间必须连续,一旦不够就需要动态地改变数据。
链表
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是在每一个节点(数据存储单元)里存放下一个节点的位置信息(即地址)。
链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
单向链表
class Node():
def __init__(self,item,next=None):
self.item=item
self.next=next
class SingleLinkedList():
def __init__(self,node=None):
self.head=node
def travel(self):
cur=self.head
while cur!=None:
print(cur.item,end=" ")
cur=cur.next
print()
def is_empty(self):
return self.head==None
def length(self):
count=0
cur=self.head
while cur !=None:
cur=cur.next
count+=1
return count
def add(self,item):
node=Node(item)
node.next=self.head
self.head=node
def append(self,item):
node=Node(item)
cur=self.head
while cur.next!=None:
cur=cur.next
cur.next=node
def remove(self,item):
if self.head.item==item:
self.head=self.head.next
return
else:
pre=None
cur=self.head
while cur.item!=item:
pre=cur
cur=cur.next
else:
pre.next=cur.next
return
def reverse(self):
cur=self.head
sll=SingleLinkedList()
while cur!=None:
sll.add(cur.item)
cur=cur.next
return sll
if __name__=="__main__":
sll=SingleLinkedList()
sll.add(1)
sll.add(2)
sll.add(3)
sll.add(4)
sll.add(5)
sll.append(6)
sll.append(7)
sll.append(8)
sll.append(9)
sll.remove(9)
print(sll.is_empty())
print(sll.length())
sll.travel()
sll_re=sll.reverse()
sll_re.travel()
双向链表
class Node():
def __init__(self,item,next=None,pre=None):
self.item=item
self.next=next
self.pre=pre
class DoubleLinkedList():
def __init__(self,node=None):
self.head=node
def travel(self):
cur=self.head
while cur!=None:
print(cur.item,end=" ")
cur=cur.next
print()
def is_empty(self):
return self.head==None
def length(self):
count=0
cur=self.head
while cur !=None:
cur=cur.next
count+=1
return count
def add(self,item):
node=Node(item)
if self.head==None:
self.head=node
else:
node.next=self.head
self.head.pre=node
self.head=node
def append(self,item):
node=Node(item)
cur=self.head
while cur.next!=None:
cur=cur.next
cur.next=node
node.pre=cur
def remove(self,item):
if self.head.item==item:
self.head=self.head.next
return
else:
pre_node=None
cur=self.head
while cur.item!=item:
pre_node=cur
cur=cur.next
else:
if cur.next==None:
pre_node.next=None
return
else:
pre_node.next=cur.next
cur.next.pre=pre_node
return
def insert(self,index,item):
node=Node(item)
cur=self.head
count=0
while count!=index-1:
cur=cur.next
count+=1
node.next=cur.next
cur.next.pre=node
cur.next=node
node.pre=cur
def search(self,item):
cur=self.head
while cur!=None:
if cur.item==item:
return True
else:
cur=cur.next
return False
if __name__=="__main__":
sll=DoubleLinkedList()
sll.add(1)
sll.add(2)
sll.add(3)
sll.add(4)
sll.add(5)
sll.append(6)
sll.append(7)
sll.append(8)
sll.append(9)
sll.remove(8)
sll.insert(2,100)
print(sll.is_empty())
print(sll.length())
sll.travel()
print(sll.search(1))
print(sll.search(20))
栈
栈(stack),有些地方称为堆栈,是一种容器,可存入数据元素、访问元素、删除元素,它的特点在于只能允许在容器的一端(称为栈顶端指标, top)进行加入数据(push)和输出数据(pop)的运算。没有了位置概念,保证任何时候可以访问、删除的元素都是此前最后存入的那个元素,确定了一种默认的访问顺序。
由于栈数据结构只允许在一端进行操作,因而按照后进先出(LIFO, Last In First Out)的原理运作。
class Stack():
def __init__(self):
self.list=[]
# Stack() 创建一个新的空栈
# push(item) 添加一个新的元素item到栈顶
# pop() 弹出栈顶元素
# peek() 返回栈顶元素
# is_empty() 判断栈是否为空
# size() 返回栈的元素个数
def push(self,item):
self.list.append(item)
def pop(self):
p=self.list.pop()
return p
def peek(self):
#先进后出,栈顶是最后一个元素
return self.list[len(self.list)-1]
def is_empty(self):
return self.list==[]
def size(self):
return len(self.list)
if __name__ == "__main__":
s=Stack()
s.push(1)
s.push(2)
s.push(3)
s.push(4)
print(s.pop())
print(s.peek())
print(s.size())
#### 队列(queue)
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出的(First In First Out)的线性表,简称FIFO。
冒泡排序
比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
方法一:
alist = [54,226,93,17,77,31,44,55,20]
n=len(alist)
for j in range(n-1,0,-1):
for i in range(j):
#每次遍历后都将最大的数放在序列的最后一位
#所以每次遍历的长度都要减一(减去后端已经排序好的数列)
if alist[i]>alist[i+1]:
alist[i],alist[i+1]=alist[i+1],alist[i]
alist
[17, 20, 31, 44, 54, 55, 77, 93, 226]
方法二:
(两个循环语句的顺序的小变化)
alist = [54,226,93,17,77,31,44,55,20]
n=len(alist)
for j in range(n):
for i in range(0,n-j-1):
if alist[i]>alist[i+1]:
alist[i],alist[i+1]=alist[i+1],alist[i]
alist
[17, 20, 31, 44, 54, 55, 77, 93, 226]
最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。)
最坏时间复杂度:O(n2)
稳定性:稳定
选择排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
alist = [54,226,93,17,77,31,44,55,20]
n=len(alist)
for j in range(0,n):
#记录最小位置
index=j
#从j+1到末位选出最小值
for i in range(j,n):
if alist[i]<alist[j]:
index=i
#进行交换
if index!=j:
alist[index],alist[j]=alist[j],alist[index]
alist
[20, 54, 55, 17, 44, 31, 77, 93, 226]
最优时间复杂度:O(n2)
最坏时间复杂度:O(n2)
稳定性:不稳定(考虑升序每次选择最大的情况)
插入排序
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
alist = [54,226,93,17,77,31,44,55,20]
n=len(alist)
# 从第二个位置,即下标为1的元素开始向前插入
for j in range(1,n):
# 从第i个元素开始向前比较,如果小于前一个元素,交换位置
for i in range(j,0,-1):
if alist[i]<alist[i-1]:
alist[i],alist[i-1]=alist[i-1],alist[i]
alist
[20, 54, 55, 17, 44, 31, 77, 93, 226]
最优时间复杂度:O(n) (升序排列,序列已经处于升序状态)
最坏时间复杂度:O(n2)
稳定性:稳定
快速排序
快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
def quick_sort(arr,start,end):
# 递归的退出条件
if start>=end:
return
low=start
high=end
# 设定起始元素为要寻找位置的基准元素
mid=arr[start]
while low<high:
'''如果设定起始元素为要寻找位置的基准元素,
则从末端先开始'''
#如果设定末端元素为要寻找位置的基准元素,
#则从其实元素先开始循环
while low<high and arr[high]>mid:
high-=1
arr[low]=arr[high]
while low<high and arr[low]<=mid:
low+=1
arr[high]=arr[low]
# 退出循环后,low与high重合,此时所指位置为基准元素的正确位置
# 将基准元素放到该位置
arr[low]=mid
# 对基准元素左边的子序列进行快速排序
quick_sort(arr,start,low-1)
quick_sort(arr,low+1,end)
arr = [55, 99, 33, 69, 36, 39, 66, 44, 22]
quick_sort(arr,0,len(arr)-1)
print(arr)
[22, 33, 36, 39, 44, 55, 66, 69, 99]
计数排序
假定20个随机整数的值如下:
9,3,5,4,9,1,2,7,8,1,3,6,5,3,4,0,10,9 ,7,9。
遍历这个无序的随机数列,每一个整数按照其值对号入座,对应数组下标的元素进行加1操作。
比如第一个整数是9,那么数组下标为9的元素加1:
第二个整数是3,那么数组下标为3的元素加1:
继续遍历数列并修改数组…
最终,数列遍历完毕时,数组的状态:
数组每一个下标位置的值,代表了数列中对应整数出现的次数。
直接遍历数组,输出数组元素的下标值,元素的值是几,就输出几次:
0,1,1,2,3,3,3,4,4,5,5,6,7,7,8,9,9,9,9,10。
桶排序
二分查找
二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。
首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
def binary_search(alist,item):
n=len(alist)
start=0
end=len(alist)-1
mid=alist[(start+end)//2]
while start<=end:
if start==end:
if item==mid:
return True
else:
return False
if item==mid:
return True
elif item<mid:
alist=alist[:(start+end)//2]
elif item>mid:
alist=alist[(start+end)//2+1:]
start=0
end=len(alist)-1
mid=alist[(start+end)//2]
else:
return False
alist = [0, 1, 2, 8, 13, 17, 19, 32, 42,]
print(binary_search(alist, 3))
print(binary_search(alist, 13))
False
True
二叉树
广度优先遍历(层次遍历)
深度优先遍历:
左节点永远在右节点前面
先序遍历:根在前面,根节点->左子树->右子树
中序遍历:根在中间,左子树->根节点->右子树
后续遍历:根在最后,左子树->右子树->根节点
由遍历确认一棵树:
两个序列可以确认一棵树,
需要至少有先序和中序,或者中序和后序(一定要有中序)
class Node():
def __init__(self,elem=-1,lchild=None,rchild=None):
self.elem=elem
self.lchild=lchild
self.rchild=rchild
class Tree():
def __init__(self):
self.root=None
def add(self,item):
node=Node(item)
if self.root==None:
self.root=node
return
else:
queue=[]
queue.append(self.root)
while queue:
cur=queue.pop(0)
if cur.lchild==None:
cur.lchild=node
return
else:
queue.append(cur.lchild)
if cur.rchild==None:
cur.rchild=node
return
else:
queue.append(cur.rchild)
def breadth_travel(self):
if self.root==None:
return
else:
# 仍然是用队列的方式实现遍历,末端按遍历顺序逐个添加节点,首端逐个弹出先读到的节点
queue=[]
queue.append(self.root)
while queue:
cur=queue.pop(0)
print(cur.elem,end=" ")
if cur.lchild!=None:
queue.append(cur.lchild)
if cur.rchild!=None:
queue.append(cur.rchild)
print()
def preorder(self,node):
if node==None:
return
else:
print(node.elem,end=" ")
self.preorder(node.lchild)
self.preorder(node.rchild)
def inorder(self,node):
if node==None:
return
else:
self.inorder(node.lchild)
print(node.elem,end=" ")
self.inorder(node.rchild)
def postorder(self,node):
if node==None:
return
else:
self.postorder(node.lchild)
self.postorder(node.rchild)
print(node.elem,end=" ")
if __name__ == "__main__":
t=Tree()
t.add(1)
t.add(2)
t.add(3)
t.add(4)
t.add(5)
t.add(6)
t.add(7)
t.add(8)
t.add(9)
t.breadth_travel()
t.preorder(t.root)
print()
t.inorder(t.root)
1 2 3 4 5 6 7 8 9
1 2 4 8 9 5 3 6 7
8 4 9 2 5 1 6 3 7