数据结构与算法
文章目录
1.算法
01算法的概念
'''
算法的特性:
1.输入:算法具有0个或多个输入.
2.输出:算法至少有1个或多个输出.
3.有穷性:算法在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤都在可接受的时间内完成.
4.确定性:算法中的每一步都有确定的含义,不会出现二义性.
5.可行性:算法的每一步都是可行的,也就是说每一步都能够执行有限的次数完成.
'''
import time
start_time=time.time()
#如果a+b=1000,,且a^2+b^2=c^2(abc为自然数),如何求出所有abc可能的组合?
#1列举abc的所有可能的数值
for a in range(1,1001):
for b in range(1, 1001):
for c in range(1, 1001):
#2.判断是否满足条件
if a**2+b**2==c**2 and a+b+c==1000:
print('a b c:',a,b,c)
end_time=time.time()
cost_time=end_time-start_time
print(cost_time)
02算法的时间效率衡量
'''
'''
import time
start_time=time.time()
#如果a+b=1000,,且a^2+b^2=c^2(abc为自然数),如何求出所有abc可能的组合?
#1列举abc的所有可能的数值
for a in range(1,1001):
for b in range(1, 1001):
for c in range(1, 1001):
#2.判断是否满足条件
if a**2+b**2==c**2 and a+b+c==1000:
print('a b c:',a,b,c)
end_time=time.time()
cost_time=end_time-start_time
print(cost_time)
时间的复杂度
时间复杂度可以表示一个算法随着问题规模不断变化的最主要趋势.
大O记法:
算法一T(n)=n^3*10=>T(n)=O(n^3)
计算规则:1.
1.基本操作,认为其时间复杂度为O(1)
2.顺序结构,时间复杂度按加法进行计算
3.循环结构,时间复杂度按乘法计算
4.分支结构,时间复杂度取最大值
5.判断一个算法的效率是时,往往只需要关注操作数量的最高次项,其他次要项和常数项可以忽略
6.在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度.
O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^3)
空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的度量.
2.数据结构
01数据结构的概念
'''
数据结构:
存储,组织数据的方式.数据结构是指相互之间存在一种或多种特定关系的数据元素的集合,通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率,数据结构往往同高效的算法有关.
算法:
为了实现业务目的的各种方法和思路.
是一种独立的存在,并不依附于代码,代码知识实现算法思想的方式而已.
作用:提升程序的性能
算法和数据结构的区别:
数据结构只是静态的描述了数据元素之间的关系
高效的程序需要在数据结构的基础上设计和选择算法
算法是为了解决实际问题而设计的,数据结构的算法需要处理问题的载体.
''''''
02内存的存储结构
内存是以字节为基本的存储单位的(1024b=1kb),每个存储空间都有自己的地址,内存地址都是连续的.
整形(int):4个字节
字符(char):1个字节
03数据结构的分类
- 线性结构
简单的说,线性结构就是表中各个结点具有线性关系.
特点:1:线性结构是非空集.
2.线性结构所有结点都最多只有一个直接前趋结点和一个直接后继结点.
典型的线性结构:栈,队列等.
- 非线性结构
简单的说,非线性结构就是表中各个结点具有多个对应关系.
特点:1:非线性结构是非空集.
2.非线性结构的一个结点可能有多个直接前趋结点和多个直接后继结点.
典型的线性结构:树结构和图结构等.
05线性结构存储方式的分类
顺序表和链表都是线性的存储结构,我们称之为线性表.
顺序表存储数据的两种结构:
一体式结构
分离式结构
顺序表结构:
数据区
信息区,即元素存储区的容量和当前表中已有的元素个数.
顺序表的扩充:
每次扩充增加固定数目的存储位置,如每次扩充增加10个元素位置,这种策略可称为线性增长.
特点:节省空间,但扩充操作频繁,操作次数多.
链表的扩充:
每次扩充容量加倍,如每次扩充增加一倍存储空间.
特点:减少了扩充操作的执行次数,但可能惠康非空间资源,以空间换时间,推荐的方式.
增加(删除)元素:
表尾端插入元素,时间复杂度为O(1)
非保序的插入元素(不常见),时间复杂度为O(1)
保序的插入元素,时间复杂度为O(n)
- 链表:将元素存放在通过链接构造起来的一系列存储块中,存储区是非连续的.
链表:不需要连续的存储空间.
链表的结构:
单向链表是链表的一种形式,他的每个结点包含两个域:元素域和链接域.
这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值.
1.表元素域elem用来存放具体的数据.
2.链接域next用来存放下一节点的位置
3.变量head指向链表的头节点的位置,从head出发能找到表中的任意节点.
03链表的代码实现:
结点代码实现:
如果node是一个结点:
获取结点元素:node.item
单链表的实现:
head:首结点
#链表节点实现
class SingleNode(object):
def __init__(self,item):
# item:存放元素
self.item=item
# next:标识下一个结点
self.next=None
# 单链表的实现:
class SingleLinkList(object):
def __init__(self,node=None):
# head:首结点
self.head=node
#结点
node1=SingleNode(10)
print(node1.item)
print(node1.next)
#链表
link1=SingleLinkList()
print(link1.head)
link2=SingleLinkList(node1)
print(link2.head)
06链表判空-长度-遍历
#链表节点实现
class SingleNode(object):
def __init__(self,item):
# item:存放元素
self.item=item
# next:标识下一个结点
self.next=None
# 单链表的实现:
class SingleLinkList(object):
def __init__(self,node=None):
# head:首结点
self.head=node
#判断链表是否为空
# is——empty()
def is_empty(self):
if self.head is None:
return True
else:
return False
#获取链表长度
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
if __name__ == '__main__':
#结点
node1=SingleNode(10)
print(node1.item)
print(node1.next)
#链表
link1=SingleLinkList()
print(link1.head)
link2=SingleLinkList(node1)
print(link2.head)
#判空
print(link1.is_empty())
print(link2.is_empty())
#长度
print(link1.lenth())
print(link2.lenth())
#遍历
print(link2.travel())
07链表结点增删改查
链表增加结点的三种情况:
add(item)链表头部增加结点
append(item)链表尾部增加节点
insert(pos,item)指定位置增加节点
remove(item)删除节点
search(item)查找结点是否存在
#链表节点实现
class SingleNode(object):
def __init__(self,item):
# item:存放元素
self.item=item
# next:标识下一个结点
self.next=None
# 单链表的实现:
class SingleLinkList(object):
def __init__(self,node=None):
# head:首结点
self.head=node
#判断链表是否为空
# is——empty()
def is_empty(self):
if self.head is None:
return True
else:
return False
#获取链表长度
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
#头部增加结点
#add()
def add(self,item):
#新结点,存储新数据
node=SingleNode(item)
node.next=self.head
self.head=node
#尾部增加结点
#append(item)
def append(self, item):
# 新结点,存储新数据
node = SingleNode(item)
#判断是否为空
if self.is_empty():
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:
# 游标记录当前所在的位置
cur = self.head
#计数
count=0
## 新结点
node = SingleNode(item)
#1.找到插入位置的前一个结点
while count< pos-1:
cur=cur.next
count+=1
#2.完成新结点插入
node.next=cur.next
cur.next=node
#删除结点
#remove(item)
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
#查找结点
# search()
def search(self,item):
cur=self.head
while cur is not None:
#找到了指定的结点
if cur.item==item:
return True
cur=cur.next
return False
if __name__ == '__main__':
#结点
node1=SingleNode(10)
print(node1.item)
print(node1.next)
#链表
link1=SingleLinkList()
print(link1.head)
link2=SingleLinkList(node1)
print(link2.head)
#判空
print(link1.is_empty())
print(link2.is_empty())
#长度
print(link1.lenth())
print(link2.lenth())
#遍历
print(link2.travel())
# 头部增加结点
link2.add(9)
print(link2.travel())
# 尾部增加结点
link2.append(11)
print(link2.travel())
# 在指定位置增加结点
link2.insert(2,0)
print(link2.travel())
# 删除结点
link2.remove(9)
print(link2.travel())
# 查找结点
print(link2.search(9))
print(link2.search(11))
print(link2.search(2))
线性结构:栈和队列
08栈
栈:
栈stack是一种运算受限的线性表,其限制是仅允许在表的一段进行插入和删除运算,这一端被称为栈项,相对地另一端称为栈底,同时栈的结构特点让它在处理数据的时候符合了先进后出的特点.
作用:
栈是计算机系统里面cpu结构的一部分.
特点:
栈是线性表
栈满足先进后出
#尾部插入删除数据
#append(item),pop()
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()
#出栈 3先出去
my_stack.pop()
my_stack.travel()
09队列
队列:
是一种特殊的线性表,只允许在表的头部front进行删除操作,在表的尾部rear进行插入操作,队列也是一种受操作限制的线性表,进行插入操作的端称为队尾,称为删除操作的端称为队头.
作用:
任务处理类的系统:先把用户发起的任务请求接收过来存到队列中,然后后端开启多个应用程序从队列中取任务进行处理,队列起到了缓冲压力的作用.
代码实现:
头部插入删除数据:
insert(0,item) 时间复杂度O(n)
pop(0)时间复杂度O(n)
尾部插入删除数据:
append(item)时间复杂度O(1)
pop()时间复杂度O(1)
队列所需实现的操作:
Queue()创建一个空的队列
enqueue(item)队列尾部添加元素item
dequeue()队列头部删除元素
is_empty()判断队列是否为空
size()返回队列的大小
# Queue()创建一个空的队列
class Queue(object):
def __init__(self):
#存储数据 线性表
self.items=[]
# enqueue(item)队列尾部添加元素item
def enqueue(self,item):
self.items.append(item)
# dequeue()队列头部删除元素
def dequeue(self):
self.items.pop(0)
# is_empty()判断队列是否为空
def is_empty(self):
return self.items==[]
# size()返回队列的大小
def size(self):
return len(self.items)
q=Queue()
#添加数据
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)
q.enqueue(4)
for i in q.items:
print(i)
#删除数据
q.dequeue()
for i in q.items:
print(i)
print(q.is_empty())
print(q.size())
10双端队列
双端队列(deque,全名:deque-ended queue),是一种具有队列和栈的性质的数据结构
双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行.
双端队列可以在队列任意一端入队和出队.
双端队列所需实现的操作:
Dequue() 创建一个空的双端队列
add_front(item) 队列头部添加元素item
add_rear(item) 队列尾部删添加元素item
remove_front() 队列头部删除元素
remove_rear() 队列尾部删除元素
is_empty() 判断双端队列是否为空
size() 返回队列的大小
class Deque(object):
'''双端队列'''
def __init__(self):
self.items=[]
def is_empty(self):
return 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(1)
deque.add_front(2)
deque.add_rear(3)
for i in deque.items:
print(i)
deque.remove_front()
deque.remove_rear()
for i in deque.items:
print(i)
算法:排序算法,冒泡排序,选择排序,插入排序,快速排序
3.排序算法
排序算法:
所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.
排序算法,就是如何使得记录按照要求排列的方法.
算法的稳定性:
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种排序算法是稳定的,否则称为不稳定的.
不稳定的排序算法:选择排序,快速排序,希尔排序,堆排序.
稳定的排序算法:冒泡排序,插入排序,归并排序和基数排序.
01冒泡排序
冒泡排序:
重复的走访过要排序的元素列,依次比较两个相邻的元素,如果顺序错误就把他们交换过来.总分元素的工作是重复的进行直到没有相邻元素需要交换,也就是说该元素已经排序完成.
最差时间复杂度O(n^2)
最优时间复杂度O(n)
算法稳定性:稳定算法
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[1] > alist[i+1]:
alist[i],alist[i+1]=alist[i+1],alist[i]
count+=1
#如果遍历了一遍没有发现数字交换,退出循环,证明数列是有序的
if count==0:
break
if __name__ == '__main__':
alist=[5,3,4,7,2]
bubble_sort(alist)
print(alist)
02选择排序
选择排序简介
选择排序(Selection sort)是一种简单直观的排序算法。
简单来说就是从无序队列里面挑选最小的元素,和无序队列头部元素替换(放到有序队列中),最终全部元素形成一个有序的队列。
选择排序原理
首先在未排序序列中找到最小(大)元素,和无序队列的第一个元素替换位置,(即形成有序队列)
以此类推,直到所有元素全部进入有序队列,即排序完毕。
最差时间复杂度O(n^2)
最优时间复杂度O(n^2)
算法稳定性:不稳定算法
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]
if __name__ == '__main__':
alist=[1,3,5,10,10,1000]
select_sort(alist)
print(alist)
03插入排序
插入排序:
插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的,个数相加的有序数据,算法适用于少量数据的排序.
插入排序的组成:
第一部分是有序的数字,第二部分是无序的数字.
基本思想:每步将一个待排序的记录,按排序要求插入到前面已经排序的数据中适当位置上,直到全部插入完为止.例如:打扑克.
最差时间复杂度O(n^2)
最优时间复杂度O(n)
算法稳定性:稳定算法
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
if __name__ == '__main__':
alist=[1,99,20,11]
insert_sort(alist)
print(alist)
04希尔排序
希尔排序简介:
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,也称分组插入排序。
希尔排序原理:
1、第1次希尔,两两分组,根据队列元素个数获取组内元素之间的偏移量,分组方式是:0-4、1-5、2-6、3-7 或者 1-2,3-4,5-6,7-8 (局限性)也就是说:i和i+4是一组,4称为下标偏移量
2、对组内元素间进行插入排序(即元素替换)
3、第2次希尔,四四一组,组内插入排序。即组内元素间偏移量是上一次标偏移量/2分组方式:0-2-4-6、1-3-5-7
4、第3次希尔,八八一组,组内插入排序。偏移量同上
5、循环下去,直到所有元素为一组(即组内元素下标偏移量为1)。
一句话:两两一组、四四一组、八八一组...,直到所有元素为一组,进行排序
特点:
下标增量分组,对小组元素进行插入排序
下标增量的特点:
第一次分组,gap=n/2 ,
从第二次分组,gap=gap/2,
最后一次分组gap=1
整个分组过程就是:递归
def shell_sort(alist):
n=len(alist)
# 获取 gap 的偏移值
gap = n // 2
# 只要 gap 在我们的合理范围内,就一直分组下去
while gap >= 1:
# 指定 i 下标的取值范围
for i in range(gap, n):
# 对移动元素的下标进行条件判断
while (i - gap) >= 0:
# 组内大小元素进行替换操作
if alist[i] < alist[i - gap]:
alist[i], alist[i - gap] = alist[i - gap], alist[i]
# 更新迁移元素的下标值为最新值
i = i - gap
# 否则的话,不进行替换
else:
break
# 每执行完毕一次分组内的插入排序,对 gap 进行/2 细分
gap = gap // 2
05快速排序
快速排序简介
快速排序,又称划分交换排序,从无序队列中挑取一个元素,把无序队列分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
简单来说:挑元素、划分组、分组重复前两步
def quick_sort(alist, start, end):
# 定义递归条件
if start < end:
# 定义三个标签
mid = alist[start]
left = start
right = end
# 定义拆分条件
while left < right:
# 右推进
while right > left and alist[right] >= mid:
right -= 1
alist[left] = alist[right]
# 左推进
while left < right and alist[left] < mid:
left += 1
alist[right] = alist[left]
# 获取中间值
alist[left] = mid
# 对切割后左边的小组进行快速排序
quick_sort(alist, start, left-1)
# 对切割后左边的小组进行快速排序
quick_sort(alist, left+1, end)
if __name__ == "__main__":
li = [54,26,93,17,77,31,44,77,20]
print(li)
quick_sort(li,0,len(li)-1)
print(li)
06归并排序
- 归并排序简介
归并排序是采用分治法的一个非常典型的应用。
将无序队列拆分成两个小组,组内元素排序,然后组间元素逐个比较把小元素依次放到新队列中。
汉朝 魏蜀吴 朝廷 打仗 晋
关键字:拆分、排序、组间、小、新队列
一句话:分组排序,合并新队列
-
归并排序原理:
归并排序分为两个阶段:
分组排序阶段:
1、 将无序队列alist,拆分成两个小组A和B,
2、分别对两个小组进行同样的冒泡排序
3、用标签left和right,分别对小组A和B进行管理
合并新队列阶段:
4、两个标签所在的元素比较大小,
5、将小的元素放到一个新队列中,然后小元素所在的标签向右移
6、多次执行4和5,最终肯定有一个小组先为空
7、把不为空的小组元素,按顺序全部移到新队列的末尾
8、无序队列中的所有元素就在新队列中形成有序队列了
特点:
两个阶段:分组排序+合并
合并策略:组间比较,新增小,小移标
def merge(zuo, you):
# 准备工作
l, r = 0, 0
result = []
# 获取分组的长度
zuo_len = len(zuo)
you_len = len(you)
# 元素比较条件
while l < zuo_len and r < you_len:
# 左队列移动元素到新队列
if zuo[l] <= you[r]:
result.append(zuo[l])
l += 1
# 右队列移动元素到新队列
else:
result.append(you[r])
r += 1
# 剩余内容添加到 result 表中
result += zuo[l:]
result += you[r:]
# 返回 result 表
return result
if __name__ == "__main__":
li = [54,26,93,17,77,31,44,77,20]
print("处理前: %s" % li)
sortlist = fen_zu(li)
print("处理后: %s" % li)
print("新列表: %s" % sortlist)
07堆排序
-
堆简介
堆是采用顺序表存储的一种近似完全二叉树的结构
-
父节点和子节点关系:
父节点位置 i,找子节点:左子节点位置:
2i + 1 右子节点位置:2i + 2
-
堆排序原理
它是指利用堆这种树结构所设计的一种排序算法。
简单来说,就是将无序列表先构造一个有特点的堆,然后利用列表的特点快速定位最大/小的元素,将其放到一个队列中。
1、根据完全二叉树结构,将无序队列构造成一个大顶堆,
2、将堆顶的根节点移走,与无序列表的末尾元素交换,此时末尾元素就是最大值,相当于进入了一个有序队列。
3、再将剩余的n-1个无序队列重新构造一个同样堆,
4、重复循环1-3步,最终将所有元素都移动到有序队列。
特点:
无序队列构建一个堆,堆顶和堆尾元素替换位置
重新构建堆,堆顶和堆尾元素替换位置,
… 简单来说:头尾替换,恢复堆后再继续
08排序总结
-
技术总结:
以从小到大进行排序为例:
冒泡排序:在无序队列中选择最小的移动到最左侧,
选择排序:定一个有序队列,从无序队列中选择最小的元素追加到有序队列的末尾.
插入排序:定一个有序队列,从无序队列中选择第一个元素,插入到到有序队列的合适位置
希尔排序:通过对无序队列进行分组,然后再采用插入的排序方法
快速排序:指定一个元素,将无序队列拆分为大小两部分,然后层级递进,最终实现有序队列归并排序:是将无序队列拆分,然后小组内排序,组间元素比较后在新队列中进行排序
堆 排 序:顺序表方式构造堆,首尾替换调整堆
总结小诗:
冒小左移选追加,插入合适分希尔,快速两半归新列,顺表构造首尾堆 -
成本上:
-
快速排序 < 归并排序 < 堆 排 序 < 希尔排序 < 选择排序 < 插入排序 < 冒泡排序
4.搜索
搜索是在队列中找到一个特定元素的算法过程。搜索的结果,只有两个:
True(找到)或False(找不到)。
搜索的几种常见方法:顺序查找、二分法查找、二叉树查找、哈希查找
01二分查找法-递归版本
二分查找又称折半查找,它是一种效率比较高的查找方法.
原理:将数组分为三部分,依次是中值前,中值,中值后.
将要查找的值与中值进行比较,若小与中值则在中值前面查找,若大与中值则在中值后查找,等与中值直接返回.
要求:
必须采用顺序存储结构.
必须按关键字大小有序排列
最差时间复杂度O(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)
if __name__ == '__main__':
alist=[1,2,3,4,5]
print(binary_search(alist, 1))
print(binary_search(alist, 100))
02二分查找法非-递归版本
def binary_search(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
if __name__ == '__main__':
alist=[1,2,3,4,5]
print(binary_search(alist,1))
print(binary_search(alist,100))
03数据结构:非线性结构
树的基本概念:
是一种非线性结构.
它是用来模拟具有树状结构性质的数据集合,它是由n(n>=1)个有限节点组成一个具有层次关系的集合.
特点:
1.每个节点都有零个或多个子节点
2.没有父节点的节点称为根节点.
3.每个非根节点只有一个父节点.
4.除根节点外,每个子节点可以分为多个不相交的树.
树的术语:
节点的度:一个节点含有的子节点的个数称为该节点的度.
树的度:一棵树中,最大的节点的度被称为树的度.
叶节点或终端点:度为0的点.
父亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点.
孩子节点或子节点:一个节点含有的子树的根结点称为该节点的子节点.
兄弟节点:具有相同父节点.
节点的层次:从根开始定义起,根在第一层,根的子节点为第二层,依次类推
树的高度或深度:树中节点的最大层次
堂兄弟节点:父节点在同一层的节点互为堂兄弟
节点的祖先:从根到该节点所经分支上的所有节点
子孙:以某节点为根的子树中任一节点都称为该节点的子孙.
森林:由m(m>=0)棵互不相交的树的集合称为森林.
树的种类:
无序树:树中任意节点的子节点之间没有顺序关系.
有序树:树中任意节点的子节点之间有顺序关系.
霍夫曼树:用于信息编码.
B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个的子树.
二叉树:每个节点最多含有两个子树.
二叉树的种类:
完全二叉树:深度为d,除d层外,其他各层节点数目均已达最大值.并且在d层的节点从左向右连续的紧密排列.
满二叉树:所有叶节点都在最底层的完全二叉树.
平衡二叉树:任何节点的两颗子树的高度差不大于1
排序二叉树:包含空树
树的应用场景:
1.xml,html等,编写这些东西解析器的时候,不可避免用到树.
2.路由协议就是使用了树的算法.
3.MysqL数据库索引.
4.文件系统的目录结构.
5.很多ai算法都是树搜索,机器学习的decision tree也是树结构.
04二叉树
二叉树的存储:
顺序存储:
将数据存储在固定的数组中,虽然在遍历速度上有一定的优势,但因所占空间比较大,是非主流二叉树.
链式存储:二叉树通常以链式存储.子节点个数最多为2
二叉树的的性质:
1.在二叉树的第i层上之多有2^i-1次方个结点(i>0)
2.深度为k的二叉树至多有2^k-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==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)
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.breadh_travel()
05深度优先遍历
先序遍历:根 左 右
中序遍历:左 根 右
后序遍历:左 右 根
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 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.preorder_travel(root.lchild)
print(root.item, end='')
self.preorder_travel(root.rchild)
def postorder_travel(self,root):
'''后序遍历 左 右 根'''
self.preorder_travel(root.lchild)
self.preorder_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.preorder_travel(tree.root)
print()
tree.inorder_travel(tree.root)
print()
tree.postorder_travel(tree.root)
print()