数据结构和算法(一)【面试】

数据结构和算法

1.1 第一次尝试

数据结构和算法是一名程序开发人员的必备基本功,主动学习积累
引入:
如果 a+b+c=1000(N) 且 a2+b2=c^2(a,b,c 为自然数),如何求出 a,b,c 可能的组合
枚举法:一个一个去试验

import time
start = time.time()
for a in range(0,1001): #此处一个不动另一个动,用嵌套
    for b in range(0,1001):
        for c in range(0,1001):
              if a+b+c==1000 and a**2+b**2==c**2: #注意这里是双等于号
                    print("a,b,c:%d,%d,%d"%(a,b,c)) 
end = time.time()
print('time:%f'%(end-start))
print('finished')

1.2 算法的提出

算法是计算机处理信息的本质、
算法是独立存在的一种解决问题的方法和思想
算法的五大特性:
输入:可有有无 输出:至少有一个输出 有穷性:必须在有限步骤之内解决问题
确定性:每一步都有确定的含义 可行性:每一步都能执行有限次数完成1.3 第二次尝试

for a in range(0,1001):
    for b in range(0,1001):
         c = 1000-a-b
         if a**2 + b**2 == c**2:
               print("a,b,c:%d,%d,%d"%(a,b,c))
 
end_time =time.time()
print("times:%d"%(end_time-start_time))
print('finished')

算法效率的衡量:时间(不客观,离不开执行环境)
每台机器执行的总时间不同,但执行的基本运算数量大体相同
可以直接用计算步骤的数量来描述两个算法的优劣
时间复杂度:
描述算法时间上面的效率问题,把描述算法时间的快慢称为时间复杂度
用程序最终运算的时候经过基本运算(步骤描述)的数量来描述时间复杂度
大 o 表示法:
对算法分析的越细致代表我们衡量的越准确,分析的时候只要有一个大概的特征
渐进函数 #T(n)=n^3 * a + c 近似→ g(n)=n^3 T(n) = g(n)
n 代表跟问题相关的规模

1.4 算法效率衡量

最坏时间复杂度
对同样算法处理数据不同有可能决定运算步骤有多少
(需要判断算法对数据处理时候最理想的状态和最坏的情况)
/最优时间复杂度:意义不大最理想便是最特殊
/最坏时间复杂度:最多的步骤,是一种保证
/平均时间复杂度:处理问题规模的平均状况
时间复杂度的几条基本计算准则:
1.基本步骤来说只有常数项,认为时间复杂度为 O(1)
2.顺序结构:基本步骤之间的累加
3.循环结构:乘法计算
4.分支结构:时间复杂度取最大值
5.判断一个算法的效率,只关注最高次项
6.没有特殊说明,指最坏时间复杂度1.5 算法分析

for a in range(0,n):
   for b in range(0,n):
     c = n-a-b
     if a**2 + b**2 == c**2:
        print("a,b,c:%d,%d,%d"%(a,b,c))

#计算时间复杂度

T(n) = n * n(循环) * 1(基本步骤) (1(分支结构) + max(1,0)= n^2*2
 = O(n**2)

1.6 常见时间复杂度

12 → O(1) 常数阶
2n +3 → O(n) 线性阶
3n^2+2n+1 → O(n^2) 平方阶 忽略次要项
5log2n+20 → O(log2n) 对数阶
2n + 3nlog2n+19 → O(nlogn) nlogn 阶
O(1)<O(logn)<O(n)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)1.7python 内置类型性能分析
timeit(测试)模块:
可以用来测试一小段 python 代码的执行速度
计时器:
class timeit.Timer(stmt=’pass’,setup=’pass’,timer=)
class timeit.Timer(stmt(测试代码语句)=’pass’,setup(运行语句用到的相关的东西相关设置)
=’pass’,timer(时间计量,跟操作系统有关)=)
timeit.Timer.timeit(numer=1000000)
timer 类中测试语句执行速度的对象方法,number 参数是测试代码时的测试次数,默认为
1000000 次,方法返回执行代码的平均耗时,一个 float 类型的秒数
对代码执行多少次,每次时间是多少,拿出来一个平均值

def t2():
   li = []
   for i in range(10000):
   #li = li+[i] li +=[li]
 
def t3():
   li = [i for i in range(10000)]
 
def t4():
   li = list(range(10000))
 
def t5():
   li =[]
   for i in range(10000):
     li.extend([i]) #接受列表或者可迭代对象
 
 
timer1 = Timer('t1()','from __main__ import t1')
print('append:',timer1.timeit(1000))

#不能直接传函数,而是把代码放到字符串当中

timer2 = Timer('t2()','from __main__ import t2')
print('+:',timer2.timeit(1000))
timer3 = Timer('t3()','from __main__ import t3')
print("[i for i in range]+: ",timer3.timeit(1000))
timer4 = Timer('t4()','from __main__ import t4')
print('list(range):',timer4.timeit(1000))timer5 = Timer('t5()','from __main__ impo
print('extend:',timer4.timeit(1000))
append: 0.9948754999999991
+: 1.3602791999999937
[i for i in range]+: 0.644943099999999
list(range): 0.20950530000000356
extend: 0.21939779999999587
from timeit import Timer
def t6():
   li = [ ]
   for i in range(10000):
     li.append(i) #从尾部添加
def t7():
   li = [ ]
   for i in range(10000):
      li.insert(0,i) #从头部添加
 
 
timer6 = Timer('t6()','from __main__ import t6')
print('append:',timer6.timeit(1000))
timer7 = Timer('t7()','from __main__ import t7')
print('append:',timer7.timeit(1000))
append: 1.0283230999999944
insert: 27.8007298
从头部取元素比从尾部取元素慢
indexx[] append → o(1)
pop(i) insert(1,item) contains(in) → o(n)

1.8 数据结构

数据结构:计算机中表示(存储)的,具有一定逻辑关系和行为特征的一组数据,其中的每
个数据元素称为这个结构的一个结点(数据的组成方式)
数据结构作用是一组结构如何保存
三要素:逻辑结构,存储结构,操作
数据结构的分类:前驱,后继,开始结点,终端结点
抽象数据类型 ADT 是具有一定行为的抽象类型,它抽象掉了类型中值得具体表示和数据类
型定义的各种操作的实现方法
先规定数据怎么样保存,然后看一下应该定义哪些操作,具体实现的不用管
数据结构是抽象数据类型的物理实现,数据结构只要解决两个问题:一个是选择存储结构,
另一个是实现抽象数据类型的各种操作
抽象类型中元素间内在的依赖关系称为逻辑结构
按照逻辑结构来进行分类:
线性结构,树形结构,复杂结构
按照存储结构来进行分类:
各种逻辑结构在计算机的存储或表示
顺序表示,链接表示,散列表示,索引表示
结点分为两类:
初等类型和组合类型
初等类型:
字符,整数,浮点型,布尔型
数据结构讨论的基本单位是结点
在学习数据结构的时候,主要研究结点之间的逻辑结构,存储结构和各种行为的具体实现,
不关心每个结点的具体类型,通常假设为初等类型
文件是逻辑记录的集合,逻辑记录是应用程序需要进行内外存交换的逻辑单位,每个记录可
以包含若干个数据项,其中能够唯一表示该记录的数据项称为关键码

算法时由有穷规则构成的,为解决某一特东类型问题确定的运算序列
算法的性质:有穷性,确定性,可行性
算法的设计:
贪心法,分治法,回溯法,动态规划法,分枝界限法
算法+数据结构=程序
需求:
我们用 python 语言保存班级里面的学生信息
集合:列表 元组 字典
数据结构就是对基本数据类型的一次封装
数据是一个抽象的该你那,将其进行分类之后又得到程序设计语言中的基本类型
如:int float char 等 数据元素之间不是独立的存在特定的关系,这些关系便是数据结构,数
据结构指数据对象中数据元素之间的关系。
算法与数据结构的区别:
算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题具体02:顺序表

2.1 内存,类型本质,连续存储

int = 1,2,3,4,5 现有五个数字是整型
怎么保存这一组数据?(当成整体)
既然都是整数,那么就连续的去存,那么地址也就连续起来了
一组数据相同类型,按顺序进行存储
引入内存的概念:
内存以一个字节作为基本索引单位(一个字节八位)
内存是一个连续的存储空间
如果是一个整型,那要占多少个单元存储呢?
虽然说有一个整数 int a =1,先把整型转化成二进制 0000 0001,又因为是整型,要占四个字
节 0000 0000 0000 0000 0000 0000 0000 0001
char 理解为字符串当中的一个字符,一个字符占一个字结,也就一个存储单元
类型决定在计算机中到底占多少个类型单元
所有高级的数据结构都是由基本的数据类型构成
顺序表:按顺序去存放,而且假设出相当于表格的形式2.2:基本顺序表与元素外围顺序表
li = [ 12, ‘ab’ ] → 12 是整型占四个字结,ab 占两个字结
所占用的存储空间不一样,但都可以用地址来标识,那我们设置两个地址
一个地址会占用四个字结,申请两个字结,连续的存储,而我们内部存储的是地址
元素外置:那就可以用列表的方式存储不同的数据类型2.3:顺序表的一体式结构与分离式结构
顺序表的结构:表头信息和数据区
构造 li = [ ] 一开始就要去预估规模
容量:能存多少 元素个数:当前存储了多少个
基本实现方式:
一体式结构:表头信息和数据区以连续的方式安排在一块存储区里,两部分数据的整体形成
一个而完整地顺序表对象
分离式结构:表对象里只保存与整个表有关的信息(即容量和元素个数),实际数据元素存
放在另一个独立的元素存储区,通过连接与基本表对象关联
为了考虑数据的动态变化,那一般考虑使用分离式2.4:顺序表数据区替换与扩充
重新扩充的时候到底要扩充多大的问题
扩充的两种策略:
2.采用倍增的方式(减少扩充次数,用空间换时间)
动态顺序表:支持扩充的顺序表

2.5:顺序表的添加和删除元素

增加元素:
表尾:时间复杂度 o(1)
非保序的元素插入: 时间复杂度 o(1) 不太用
保序的元素插入:时间复杂度为 o(n)→起始位置
删除元素
表尾:时间复杂度 o(1)
非保序的元素插入: 时间复杂度 o(1) 不太用
保序的元素插入:时间复杂度为 o(n)→起始位置
python 中的 list 和 tuple(不可变型)采用了顺序表的实现技术
list 的行为特征:
1.按照下标位置进行索引,时间复杂度 o(1)
2.允许任意加入元素,加入元素的过程中,表对象标识(id)不变→ 分离式存储
python 官方实现中,list 就是一种采用分离式技术实现的动态顺序表
扩充的两种策略:
在建立空列表的时候,系统分配的一个空间是八个元素的存储区;在往里面进行插入,如果
元素区满了,采用倍增,如果已经很大了,就改变策略,进行一倍一倍的添加03 链表
3.1 链表的提出
链表:将元素存放在通过连接构造起来的一系列存储块中
结点与结点之间靠链连接起来
线性表分为顺序表和链表
3.2 单链表的 ADT 模型
单链表中间有结点,一个是数据元素区,另一个是下一个结点的链接去区
还必须有一个保存第一个结点的位置的数据,尾结点的下一个区域指向空
第一个结点叫做头结点,最后一个结点叫做尾结点
节点实现:数据保存问题

is_empty()是否为空 length() traval() 遍历整个链表
add(item) append(item) insert(pos,item) 
remove(item) search(item)查找节点是否存在3.3python 中变量标识的本质
a=10 (产生一个变量,把 10 放进去) b=20 
a,b=b,a
a,b = 20,10(改变 a,b 的地址导向,达到交换的效果)
def f(): #函数皆是对象
   pass
a = f
python 中变量的本质有链接在,等于谁表明这个区域指向那个节点3.4 单链表及节点的定义代码
#定义节点
class Node(object):
 '''节点'''
    def __init__ (self,elem): #需要构造函数
       self.elem = elem #元素区保存数据
       self.next = None #next 还不知道,那就设置空
 
#node = Node(100) 有一个节点,有一个这样的对象
class SingleLinkList(object): 
 #定义单链表
     def __init__ (self,node):
         self._head = None 
 #自己内部函数去作用对外不暴露(私有属性)
 
 def is_empty(self): #具体对象方法而不是类方法
     pass
 
 def length(self): #链表长度
     pass
 
 def traval(self): #遍历整个链表
     pass
 
 def add(self,item): #在头部添加元素
     pass
 
 def append(self,item): #在链表尾部添加
     pass
 
 def insert(self,pos,item): #在指定位置添加
     pass
 
 def remove(self,item): #删除节点
     pass
 
 def search(self,item): #查找节点是否存在
    pass

3.5:单链表的判空,长度,遍历与尾部添加结点的代码实现

#定义节点
class Node(object):
 '''节点'''
    def __init__ (self,elem): #需要构造函数
       self.elem = elem #元素区保存数据
       self.next = None #next 还不知道,那就设置空
 
#node = Node(100) 有一个节点,有一个这样的对象
class SingleLinkList(object): 
 #定义单链表
     def __init__ (self,node = None):
          self._head = node 
 #自己内部函数去作用对外不暴露(私有属性)
 
 def is_empty(self): #具体对象方法而不是类方法
      return self._head == None
 
 def length(self): #链表长度
      #cur 游标,用来移动遍历结点
     cur = self._head
     #count 记录数量
     count = 0
 while cur != None:
     count +=1
     cur = cur.next
 return count 
 
 def travel(self): #遍历整个链表
     cur = self._head
     while cur != None:
     print(cur.elem)
     cur = cur.next
 
 
 def add(self,item): #在头部添加元素
     pass
 
 def append(self,item): #在链表尾部添加
      node = Node(item)
      if self.is_empty():
          self._head = node
      else: 
          cur = self._head
 while cur.next !=None:
      cur = cur.next
      cur.next = node
 
 
 def insert(self,pos,item): #在指定位置添加
     pass
 
 def remove(self,item): #删除节点
     pass
 
 def search(self,item): #查找节点是否存在
     pass
 
 
 
if __name__ == '__main__':
   ll = SingleLinkList()
   print(ll.is_empty())
   print(ll.length())
 
 ll.append(1)
 print(ll.is_empty())
 print(ll.length())
 
 ll.append(2)
 ll.append(3)
 ll.append(4)
 ll.append(5)
 ll.append(6)
 ll.travel()

3.6 单链表尾部添加和在指定位置添加

class Node(object):
‘’‘节点’’’
def init (self,elem): #需要构造函数
self.elem = elem #元素区保存数据
self.next = None #next 还不知道,那就设置空

#node = Node(100) 有一个节点,有一个这样的对象
class SingleLinkList(object):
#定义单链表
def init (self,node = None):
self._head = node
#自己内部函数去作用对外不暴露(私有属性双下划线)
def insert(self,pos,item): #在指定位置添加
#关于 pops 参数–从 0 开始索引
if pos < 0: #认为在列表头部插入元素
self.add(item)
elif pos > self.length() - 1: #pos 大于最后一个坐标
self.append(item)
else:
pre = self._head
count = 0
while count < pos - 1:
count += 1
pre = pre.next
node = Node(item)
#当循环退出后,pre 指向 pos-1 位置
node.next = pre.next
pre.next = node3.7 单链表查找和删除元素
def search(self,item): #查找节点是否存在
cur = self._head
while cur != None:
if cur.elem == item:
return True
else:
cur= cur.next
return False

def remove(self,item): #删除节点
cur = self._head
pre = None
while cur != None:
if cur.elem == item:
#先判断当前节点是不是头结点
#头结点
if cur == self._head:
self._head = cur.next
else:
pre.next = cur.next
else:
pre = cur
cur = cur.next3.8 单链表与顺序表的对比
链表只记录的头结点,要看其他结点要从头往下走
链表的主要耗时操作是遍历查找,删除和插入操作本身的复杂度是 o(1)
顺序表查找很快,主要耗时的操作是拷贝覆盖。
因此除了目标元素在尾部的特殊情况,顺序表进行插入和删除元素时需要对操作点之后的所
有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行
顺序表的一个优点在于存取元素的时候,可以通过 o(1)的方式一次性定位
缺点在于对于顺序表空间必须连续,如果一旦动态改变,整个存储区都要改变,数据大的时
候,如果没有这么多的存储空间,那么顺序表就达不到要求
链表的特点对于离散的内存空间达到充分的利用,但在利用的同时,额外的开销也大,利用
链表存取元素没办法达到 o(1)的效果,需要明确,在插入这部分,虽然时间复杂度都是 o(n),
链表 n 花费在遍历,顺序表表明 n 花费在数据搬迁上面3.9 双向链表及添加元素
后继结点:当前结点的下一个
前驱结点:当前结点的前一个
链接区:自己起名 prev next(只是一种代表)
双向列表的操作和单向链表一样
3.10 双向链表删除元素

3.11 单向循环链表遍历和求长度

与单链表惟一的区别是 next 区域指向头结点
def length(self): #链表长度
if self.is_empty(): #链表是空链表
return 0
#cur 游标,用来移动遍历结点
cur = self._head
#count 记录数量
count = 1
while cur.next != self._head:
count +=1
cur = cur.next
return count
def travel(self): #遍历整个链表
if self.is_empty():
return
cur = self._head
while cur.next != self._head:
print(cur.elem,end = ’ ')
cur = cur.next
print(cur.elem) #退出循环,cur 指向尾结点,但尾结点元素未打印3.12 单向循环链表添加元素
先构造结点
node.next = self._head
self._head = node
(cur.next = node.next 先找到尾结点 cur.next = node)
cur = self_head
如果是空链表,不可对 cur 取 next
尾插法
node.next = self._head = cur.next
cur.next = node
or
cur.next = node
node.next = self._head
任意位置
和单链表一个效果,不改代码3.13 单向循环链表查找和删除元素
def search(self,item): #查找节点是否存在
if self.is_empty(): #空链表
return False
cur = self._head
while cur.next != self._head:
if cur.elem == item:
return True
else:
cur= cur.next
#退出循环,current 指向尾结点
if cur.elem == item:
return True
return False
删除
def remove(self,item): #删除节点
if self.is_empty():
return
cur = self._head
pre = None
while cur.next !=self._head:
if cur.elem == item:
#先判断当前节点是不是–头结点(最复杂)
#头结点
if cur == self._head: #头结点的情况,找尾结点
rear = self._head
while rear.next != self._head:
rear = rear.next
self._head = cur.next
rear.next = self._head

self._head = cur.next

else:#中间结点
pre.next = cur.next
break
else:
pre = cur
cur = cur.next
#退出循环 cur 指向–尾结点
if cur.elem == item:
if cur == self._head:
#链表只有一个结点
self._head = None
else:
pre.next = cur.next3.14 单向循环链表删除元素复习及链表扩展
1.双向链表也可以扩充到循环
2.链表保存的只有头结点的信息

4.1 栈与队列的概念

栈是一种容器,可存入数据元素
特点:后进先出
栈数据结构描述的是操作,顺序表描述数据怎么存放
队列:先进先出
栈的操作:
stack(创建一个新的空栈)
push(item)添加一个新的元素 item 到栈顶
pop(弹出栈顶元素)
peek(返回栈顶元素)
is_empty(判断栈是否为空)
size(返回栈的元素个数)4.2 栈的实现
class Stack(object):
‘’‘栈’’’
def init(self):
self.__list = []

def push(self,item):
‘’‘添加一个新的元素 item 到栈顶’’’
self.__list.append(item) #使用顺序表尾部 o(1)

def pop(self):
‘’‘弹出栈顶元素’’’
return self.__list.pop()

def peek(self):
‘’‘返回栈顶元素’’’
if self.__list != None:
return self.__list[-1]
else:
return None

def is_empty():

‘’‘判断栈是否为空’’’
return self.__list == []

def size():
‘’‘返回栈的元素个数’’’
return len(self.__list)

if name == ‘main’:
s = Stack()
s.push(1)
s.push(2)
s.push(3)
print(s.pop())
print(s.pop())4.3 队列
取得端叫队头,添加的一端叫做队尾
queue 创建一个空的队列
enqueue(item) 往队列中添加一个 item 元素
dequeue() 从队列头部删除一个元素
is_empty() 判断一个队列是否为空
size() 返回队列的大小
双端队列:
元素可以从两端弹出,其限定插入和删除操作在表的两端进行
双端队列可以在队列的任意一端入队和出队
Deque 创建一个空的双端队列
add_front(item) 队头加入
add_rear(item) 队尾加入
remove_front() 队头删除
remove_rear() 队尾删除
is_empty() 判断一个队列是否为空
size() 返回队列的大小04 排序与搜索

4.1:排序算法的稳定性

排序算法:是一种能将一串数据依照特定顺序进行排列的一种算法
稳定性:稳定性排序算法会然后原本相等键值的纪录维持相对次序,也就是如果一个排序算法是稳定的,当有两个相等键值的纪录 R 和 S,且在原本的列表中 R 出现在 S 之前,在排序
过的列表中 R 也将会在 S 之前

4.2 冒泡排序

冒泡排序:
简单的排序算法,重复遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到再没有再需要交换,也就是说该数列已经排序完成,这个算法的名字由来是因为越小的元素会景由交换慢慢“浮”到数列顶端
符合常规思路,让大家排队,一个一个做对比(比较游标附近两个元素)
n 个数据就要进行 n-1 次比较,走一趟最大的落在最后面,之后再从头开始走,继续前后比较,选出剩余序列的最大值,经过 n-2 次,接着走… …

def bubble_sort(alist): # 冒泡
    n = len(alist)
    for j in range(n-1):
        for i in range(0, n-1-j):
            if alist[i] > alist[i + 1]:
                alist[i], alist[i + 1] = alist[i +1], alist[i]

if __name__ == "__main__":
    li = [54,26,93,17,77,31,44,55,20]
    print(li)
    bubble_sort(li)
    print(li)

#第二种表示:

def bubble_sort(alist): # 冒泡
    n = len(alist)
    for j in range(n-1):
        count = 0
        for i in range(0, n-1-j):
            if alist[i] > alist[i + 1]:
                alist[i], alist[i + 1] = alist[i +1], alist[i]
                count += 1
        if 0 == count:
            return

if __name__ == "__main__":
    li = [54,26,93,17,77,31,44,55,20]
    print(li)
    bubble_sort(li)
    print(li)

#最优时间复杂度是内层循环的 n→ o(n)
稳定性:遇到相同的数字,不动顺序,稳定的

4.3 选择排序算法及实现

从未排序序列中找到最小(大)的元素,放到序列起始位置,然后在剩下的元素中继续找,
依次类推,平均时间复杂度为 o(n^2)最坏时间复杂度相同

alist = [[ ]54,226,93,17,77,31,44,55,20]
mix = 17
alist = [17, 226,93,54,77,31,44,55,20]
mix = 20
alist = [17,20, 93,54,77,31,44,55,226]
mix = 31
alist = [17,20,31, 54,77,93,44,55,226]
alist = [54,226,93,17,77,31,44,55,20]
min = 3
alist[0],alist[3] = alist[3],alist[0]
alist = [17 ,226,93,54,77,31,44,55,20]
min = 8
alist[1],alist[8] = alist[8],alist[1]
alist = [17,20 ,93,54,77,31,44,55,226]
从后面的未排序中选择一个最小的
#j = [0,1,2, ... n-2] j = range(0,n-1)
#i = [1,2,3 ...,n-1] i = range(0,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[min_index] > alist[i]:
                min_index = i
        if j != mid_index:
            alist[min_index], alist[i] == alist[i], alist[min_index]


if __name__ == "__main__":
    li = [54,26,93,17,77,31,44,55,20]
    print(li)
    select_sort(li)
    print(li)

选择排序的时间复杂度 o(n^2) 最优时间复杂度同 o(n^2)
稳定性:不稳定 li = [26,1,12,13,10,26,7,8] 26 经过排序后颠倒4.4 插入排序
def insert_sort(alist):

插入排序

def insert(nums):
    n = len(nums)
    for i in range(1, n): # 从第二个开始遍历
        j = i #  #内层循环代表从右边的无序序列中取出第一个元素,然后将其插入到前面的正确
位置中
        while j > 0:
            if nums[j] < nums[j-1]: # 如果后面的比前面的小
                nums[j], nums[j-1] = nums[j-1], nums[j] # 交换
                j -= 1
            else: # 如果后面的比前面的大,直接退出循环
                break
    return nums
lst = insert([5, 5, 2])
print(lst)

最坏时间复杂度 o(n^2)
最优时间复杂度 o(n) #后面的元素比前面的大,整个内层循环不需要操作
插入算法的稳定性:稳定
插入排序:
主要是构建有序序列,对未排序的数据,从已排序序列从后向前扫描,插入
在任何时刻,新序列都是有序的,平均时间度 o(n^2)最坏时间复杂度相同
从第一个元素开始,该元素可以认为已经被排序,取出下一个元素,在已经排序的元素序列
中从后向前扫描;如果该元素(已排序)大于新的元素,则钙元素移到下一个位置,一直进
行重复,知道最后元素

4.5 希尔排序

对于插入排序的一个改进版
认为整个序列由好多个无序序列组成,取间隔,取 gap
将待排序序列的拷贝序列分成若干个子序列分别进行直接插入排序,不断缩减步长进行循
环对比大小,如果符合排序大小,就下一个元素,不符合就交换元素

def shell_sort(alist):
 '''希尔排序'''
 #n = 9
 n = len(alist)
 #gap= 4
 gap = n // 2
 
 #gap 变化到 0 之前插入算法执行的次数
 while gap >=1:
 #插入算法与普通的插入算法的区别是 gap 步长
 for j in range(gap,n):
 # j = [gap,gap+1,gap+2,'..,n-1]
 i = j
 #内循环控制子序列
 while i >0:
 #插入算法与普通的插入算法的区别是 gap 步长
 if alist[i] < alist[i-gap]:
 alist[i],alist[i-gap] = alist[i-gap],alist[i]
 i -= gap
 else:
 break
 #缩短 gap 步长
 gap //= 2
 
 
 
if __name__ == '__main__':
 li = [54,226,93,17,77,31,44,55,20]
 print(li)
 shell_sort(li)
 print(li)
最优时间复杂度:不确定
最坏时间复杂度 o(n^2)
稳定性:不稳定4.6 快速排序!!!!!(重要)
如果当前列表 high 所在值小于中间值,让当前 low 的位置等于 high 的值
if alist[high] < mid_value:
 alist[low] = alist[high]
 low += 1
elif alist[high] > alish[low]
 high -= 1
if alist[low] < mid_value
 low += 1
elif alist[low] > mid_value:
 alist[high] = alist[low] #右边的位置等于 93
 high -= 1

快速排序

#原理:选取第一个元素值 mid_value,从左边有一个 low 指标,从右边(最后)有一个 high
指标,左右向两边进行移动
#如果左边的值大于 mid_value 值,交换次数,high 左移,如果右边小于 mid_value,交换到
右边,low 右移
#知道 alist[low] = alist[high] 时,此处的位置就是 mid_value 所要留在的位置
#然后用 mid_value 把列表分成两部分,运用递归继续对左右两部分进行快速排序
#最优时间复杂度 o(nlogn)!!!
#最坏时间复杂度 o(n^2)
#稳定性:不稳定

def quick_sort(alist, first, last):
    # 快速排序
    if first >= last:
        return
    mid_value = alist[first]
    low = first
    high = last

    while low < high:
    # high 游标左移,停止条件遇到比中间值小的
        while low < high and alist[high] >= mid_value:
            high -= 1
        alist[low] = alist[high]  # low += 1

        while low < high and alist[low] < mid_value:
            low += 1
        alist[high] = alist[low]
        # high -= 1
        # 从循环退出时,low = high
    alist[low] = mid_value
    # 包含等于情况尽量放在一边
    # 对 low 左边的列表执行快速排序
    quick_sort(alist, first, low - 1)
    # 对 low 右边的列表执行快速排序
    quick_sort(alist, low + 1, last)


if __name__ == '__main__':
    li = [4,2,7,5,9,0]
    print(li)
    quick_sort(li, 0, len(li) - 1)
    print(li)

最优时间复杂度 o(nlogn)
最坏时间复杂度 o(n^2)
稳定性:不稳定4.7 归并排序
先从中间对半分,然后再分,直到分成每个独立的个体,然后左右比较排序合并,从最
底层又一步步的回到最上层
将有序序列的子序列合并,得到完全有序的序列

def merge_sort(alist):
 '''归并排序'''
 n = len(alist)
 if n <= 1:
 return alist
 mid = n//2
 #采用归并排序后形成的有序的新的列表
 left_li = merge_sort(alist[:mid]) #上面不取 mid
 right_li = merge_sort(alist[mid:])
 
 #将两个有序的子序列合并为一个新的整体
 #merge(left ,right)
 left_pointer,right_pointer = 0,0
 result = [ ]
 
 while left_pointer <len(left_li) and right_pointer < len(right_li):
 if left_li[left_pointer] < right_li[right_pointer]:
 result.append(left_li[left_pointer])
 left_pointer += 1
 else:
 result.append(right_li[right_pointer])
 right_pointer += 1
 
 result += left_li[left_pointer:]
 result += right_li[right_pointer:]
 return result
if __name__ == '__main__':
 li = [54,26,93,17,77,31,44,55,20]
 print(li)
 sorted_li = merge_sort(li)
 print(li)
 print(sorted_li)

4.8 归并排序时间复杂度及排序算法复杂度比较

真正产生时间复杂度的就是最后合并的过程,两两合并,时间复杂度相加
最优、坏时间复杂度 o(nlogn)

归并排序

稳定
归并算法要产生一块同等空间来保存处理,别的始终在原列表移动排序,不涉及额外的开销
写一个或者写几个你所熟知的排序算法
必须要知道:快速排序!!!!!4.9 二分查找
搜索:在一个序列中去查找某个元素是否存在
二分查找:又称折半查找,优点是比较次数少,查找速度快,平均性能好
缺点是要求待查表为有序表,且插入删除困难
对于操作对象,支持下标索引(有序顺序表)

def binary_search(alist, item):
    n = len(alist)
    if n > 0:
        mid = n//2
        if alist[mid] == item:
            return True
        elif item > alist[mid]:
            return binary_search(alist[mid+1:], item)
        else:
            return binary_search(alist[:mid], item)
    return False


if __name__ == "__main__":
    li = [54,26,93,17,77,31,44,55,20]
    print(li)
    print(binary_search(li, 29))
    print(li)

#非递归版本,不需要形成一个新的列表
if name == ‘main’:
li =[17, 20, 26, 31, 44, 54, 55, 77, 93]
print (binary_search(li,44))
print (binary_search(li,100))def binary_search_2(alist,item):
‘’‘二分查找,非递归’’’
n = len(alist)
first = 0
last = n -1
while first <= last:
mid = (first + last)/2
if alist[mid] == item:
return True
elif item < alist[mid]:
last = mid - 1
else:
first = mid+1
return False
if name == ‘main’:
li =[17, 20, 26, 31, 44, 54, 55, 77, 93]
print (binary_search(li,44))
print (binary_search(li,100))树
每一个非根节点有且只有一个父节点
节点的度:节点的子树个数
树的度:最大的节点的度
树的深度:树中节点的最大层次
树的种类:
有序树:节点之间没有顺序关系
无序树:节点之间有顺序关系(二叉树:度不可超过两个)
二叉树:{
完全二叉树:除了最底层外,其余各层都达到了最大的个数(1,2,4,6)
满二叉树:所有层必须达到最大的数量,不缺任何一个
平衡二叉树:任意二叉树,两个子树:数的深度差不可超过 1
排序二叉树:数的节点遍历的时候是有序的
}
数的存储和表示:
顺序存储:按照顺序来存(用的不多)
链式存储二叉树:
每个结点最多有两个子树的结构,左子树和右子树
先序:根左右
中序:左中右
后续:左右根
给数写顺序
和给序列画

猜你喜欢

转载自blog.csdn.net/weixin_44697051/article/details/115058922