数据结构与算法
引入
程序设计语言基本数据类型:int,float,char Python内置数据结构:list,dict,tuple. Python扩展数据结构:栈,队列 存储一个student的name,age,hometown的方式
列表+元组:[
('zhangsan',24,'beijing'),
('zhangsan',24,'beijing'),
('zhangsan',24,'beijing'),
]
for stu in stus:
if stu[0] == 'zhangsan':
列表+字典 [
{'name':'zhangsan'},
{'age':23},
{'hometown':'beijjing'},
]
字典:{
'zhangsan':{
'age':24,
'hometown':'beijing',
}
}
stu['zhangsan']
经典知识点总结
1.算法与数据结构的概念
算法是指要解决问题的思路
数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。
2.数据结构和算法的区别
(1).数据结构只是静态的描述了数据元素之间的关系
(2).高效的程序需要在数据结构的基础上设计和选择算法
程序 = 算法 + 数据结构
总结:算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体
3.抽象数据类型(Abstract Data Type)
ADT概念:是指一个数学模型以及定义在这个数学模型上的一组操作的集合,即把数据类型和数据类型上的运算绑定在一起,
进行封装。对外只声明存储方式和调用方法,具体实现隐藏。
引入抽象数据类型的目的是把数据类型的表示和数据类型上的运算的实现与这些数据类型和运算在程序中的引用隔开,使他们相互独立。
常用的数据运算有五种:
插入
删除
修改
查找
排序
4.时间复杂度
衡量程序执行效率的快慢,称为时间复杂度。
算法完成任务最少需要多少基本操作,即最优时间复杂度。(最乐观最理想,价值不大)
算法完成任务最多需要多少基本操作,即最差时间复杂度。(提供了一种保证,主要关注)
算法完成任务平均需要多少基本操作,即平均时间复杂度。(算法的全面评价)
时间复杂度的几条基本计算规则
1.基本操作,即只有常数项,认为其时间复杂度为〇(1)
2.顺序结构,时间复杂度按加法计算
3.循环结构,时间复杂度按乘法计算
4.分支结构,时间复杂度取最大值
5.判断一个算法的效率时,往往只需关注操作数量的最高次频,其他次要项和常数项可以忽略
6.在没有特殊说明时,我们所分析的算法的时间复杂度,通常指的都是最坏时间复杂度。
常见时间复杂度比较:〇(1)<〇(logn)<〇(n)<〇(nlogn)<〇(n^2)<〇(n^3)<〇(2^n)<〇(n!)<〇(n^n)
5.线性表:
线性表:一组序列元素的组织形式,一个线性表是某类元素的集合,还记录着元素之间的一种顺序关系,线性表是最基本的数据结构之一。 顺序表:将元素顺序的存放在一块连续的存储区里,元素间的顺序关系由他们的存储顺序自然表示。
链表:将元素存放在通过链接构造起来的一系列存储块中。
6.顺序表
顺序表:数据元素本身连续存储,每个元素所占的存储单元大小固定相同,元素的下标是其逻辑地址,而元素存储的物理地址(实际内存地址)可以通过存储区的起始地址Loc(e0)加上逻辑地址 (第i个元素)与存储单元大小(c)的乘积计算而得,即:Loc(ei) = Loc(e0)+c*i
故,访问指定元素无需从头遍历,通过计算便可获得对应地址,其时间复杂度为〇(1)
注:操作系统最小寻址单位是字节。int类型占四个字节,char类型占1个字节。计算机存储索引从0开始,表示偏移量。
顺序表的存储方式:顺序表的基本方式,顺序表的外置元素方式
顺序表的结构:一部分是表中元素的集合,另一部分是为实现正确操作而需记录的信息
表头信息(表头存储区的容量,当前表中已有元素个数),数据区
顺序表存储结构:一体式,分离式。区别:增加容量时,分离式表头不变,一体式表头改变
7.链表的提出
顺序表的构建需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁,所以使用起来并不是很灵活。
链表结构可以充分利用计算机的内存空间,实现灵活的内存动态管理。
8.链表
8.1-定义:链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是在每一个节点(数据存储单元)里存放
下一个节点的位置信息(即地址)。
8.2-单向链表:也叫单链表,是链表中最简单的一种形式。它的每个节点包含两个域,一个信息域(元素域),一个链接域。这个链接指向链表中的
下一个结点,而最后一个节点的链接则指向一个空值。
表元素域elem用来存放具体的数据
链接域next用来存放下一个节点的位置(python中的标识)
变量p指向链表的头结点(首节点)的位置,从p出发能找到表中的任意节点。
8.3 python变量标识的本质
a = 10,首先会在内存中开启两个内存块,一个存储10,一个存储地址,这个地址指向10,给这个地址取名a。
a,b = b,a 交换的是a,b原来维护的指向内存块的地址。
8.4 双向链表:每个节点有两个链接,一个指向前一个节点(前驱),一个指向后一个节点(后继)。当此节点为第一个节点时,前驱为空,
当此节点为尾节点时,后继为空。
9.链表和顺序表的对比
9.1 顺序表
优点:(1) 方法简单,各种高级语言中都有数组,容易实现。
(2) 不用为表示结点间的逻辑关系而增加额外的存储开销。
(3) 顺序表具有按元素序号随机访问的特点。
缺点:(1) 在顺序表中做插入删除操作时,需要对所有元素进行前后移位操作,只能通过拷贝和覆盖的方式,
平均移动大约表中一半的元素,因此对n较大的顺序表效率低。
(2) 需要预先分配足够大的存储空间,估计过大,可能会导致顺序表后部大量闲置;预先分配过小,
又会造成溢出。
9.2 链表
优点:(1) 在链表中做插入删除操作时,不会影响前面和后面的节点,因此对n较大的链表效率高。
(2) 不需要预先分配足够大的存储空间,避免造成空间闲置或溢出的情况。
缺点:(1) 需要为表示结点间的逻辑关系(指针变量)而增加额外的存储开销。
(2) 只能通过遍历找到某个节点,不能使用下标直接定位节点。
9.3 选择合适的数据结构
9.3.1 基于存储的考虑
顺序表的存储空间是静态分配的,在程序执行之前必须明确规定它的存储规模,也就是说事先对"MAXSIZE"要有合适的设定,过大造成浪费,
过小造成溢出。可见对线性表的长度或存储规模难以估计时,不宜采用顺序表;链表不用事先估计存储规模,但链表的存储密度较低
(存储密度是指一个结点中数据元素所占的存储单元和整个结点所占的存储单元之比,显然链式存储结构的存储密度是小于1的)。
9.3.2 基于运算的考虑
在顺序表中按序号访问 ai的时间性能时O(1),而链表中按序号访问的时间性能O(n),所以如果经常做的运算是按序号访问数据元素,
显然顺序表优于链表;而在顺序表中做插入、删除时平均移动表中一半的元素,当数据元素的信息量较大且表较长时,这一点是不应忽视的;
在链表中作插入、删除,虽然也要找插入位置,但操作主要是比较操作,从这个角度考虑显然后者优于前者。
9.3.3 基于环境的考虑
顺序表容易实现,任何高级语言中都有数组类型,链表的操作是基于指针的,相对来讲前者简单些,也是用户考虑的一个因素。
注:顺序表和链表抓主要用于存储,栈和队列主要用于对数据的操作。
10.栈:只允许在表的一端进行操作的线性表,FILO(先进后出) 11.队列:只允许在表尾插入,表头删除的线性表,FIFO(先进先出) 12.双端队列:具有栈和队列的性质的数据结构,可以在队列的任意一端入队和出队
13:栈(Stack)和队列(Queue)是两种操作受限的线性表。
线性表:线性表是一种线性结构,它是一个含有n≥0个结点的有限序列,同一个线性表中的数据元素数据类型相同并且满足“一对一”的逻辑关系。
“一对一”的逻辑关系指的是对于其中的结点,有且仅有一个开始结点没有前驱但有一个后继结点,有且仅有一个终端结点没有后继但有
一个前驱结点,其它的结点都有且仅有一个前驱和一个后继结点。
这种受限表现在:栈的插入和删除操作只允许在表的尾端进行(在栈中成为“栈顶”),满足“FIFO:First In Last Out”;
队列只允许在表尾插入数据元素,在表头删除数据元素,满足“First In First Out”。
栈与队列的相同点:
1.都是线性结构。
2.插入操作都是限定在表尾进行。
3.都可以通过顺序结构和链式结构实现。、
4.插入与删除的时间复杂度都是O(1),在空间复杂度上两者也一样。
5.多链栈和多链队列的管理模式可以相同。
栈与队列的不同点:
1.删除数据元素的位置不同,栈的删除操作在表尾进行,队列的删除操作在表头进行。 2.应用场景不同;常见栈的应用场景包括括号问题的求解,表达式的转换和求值,函数调用和递归实现,深度优先搜索遍历等;常见的队列的应用场景包括计算机系统中各种资源的管理,消息缓冲器的管理和广度优先搜索遍历等。
3.顺序栈能够实现多栈空间共享,而顺序队列不能。
14.排序
稳定性:两个相同的元素在排序之后相对位置不发生改变称之为稳定排序。
14.1 交换排序:
冒泡排序:每次循环找出最大值或最小值
时间复杂度:最优 〇(n) 序列有序
最差 〇(n2)
稳定性:稳定
快速排序:划分交换排序(partition-exchange sort),通过一趟偶排序将要排序的数据分割成独立的两部分,一部分的元素比另一部分的元素都要小。
然后在按此方法对两部分数据分别进行快速排序,整个排序可以递归进行,以此达到整个数据变成有序序列
时间复杂度:最优 〇(nlogn)
最差 〇(n2)
稳定性:不稳定
14.2 选择排序:每次从无序序列中选择一个最大值或最小值放在一端。
时间复杂度:最优 〇(n2)
最差 〇(n2)
稳定性:不稳定(考虑升序每次选择最大的情况) 2,2,1
14.3 插入排序
14.3.1 直接插入排序:每次从无序序列中按顺序拿一个元素插入有序序列中的正确位置。
时间复杂度:最优 〇(n) 升序排序,序列已经处于升序状态
最差 〇(n2)
稳定性:稳定
14.3.2 希尔排序:缩小增量排序
时间复杂度:最优 根据步长序列的不同而不同
最差 〇(n2)
稳定性:不稳定 2,2,1
14.4 归并排序:分治法,先递归分解数组,再合并数组
时间复杂度: 最优:〇(nlogn)
最差: 〇(nlogn)
稳定性:稳定
15.搜索
二分查找法:又称折半查找。优点是比较次数少,查找速度快,平均性能好。缺点是要求待查表为有序表,且插入删除困难。因此,折半查找适用于不经常变 动而查找频繁的有序列表。
时间复杂度: 最优:〇(1)
最差: 〇(logn)
16.树:由n(n>=1)个有限节点组成一个具有层次关系的集合。一对多。
特点:(1)每个节点有零个或多个子节点
(2)没有父节点的节点称为根节点
(3)每一个非根节点有且只有一个父节点
(4)除了根节点外,每个子节点可以分为多个不相交的子树。
术语:节点的度:一个节点含有子树的个数
树的度:一棵树中,最大节点的度称为树的度。
叶节点或终端节点:度为零的节点。
父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。
子节点:一个节点含有的子树的根节点称为该节点的子节点。
兄弟节点:具有相同的父节点的节点互称为兄弟节点。
节点的层次:从根节点开始定义,根为第一层,根的子节点为第2层,以此类推。
树的深度或高度:树中节点的最大层次。
堂兄弟节点:父节点在同一层次的节点互称为堂兄弟。
节点的祖先:从根到该节点所经分支上的所有节点。
子孙:以某节点为根的子树中任一节点成为该节点的子孙。
森林:由m(m>=0)棵互不相交的树的集合称为森林。
树的种类:
无序树:树中任意节点的子节点之间没有顺序关系,这种关系成为无序树。
有序树:树中任意节点的子节点之间有顺序关系。这种关系称为有序树。
二叉树:每个节点最多含有两个子树的树称为二叉树。
完全二叉树:对于一个二叉树,假设其深度为d.除了第d层外,其他各层的节点数目均已达最大值,且第d层所有节点
从左到右连续紧密排列。对于这样的二叉树称为完全二叉树。
满二叉树:所有叶节点都在最底层的完全二叉树。
平衡二叉树(AVL树):当且仅当任何节点的两颗子树的高度差不大于1的二叉树。
排序二叉树(二叉查找树,二叉搜索树,有序二叉树)
哈夫曼树(用于信息编码):带权路径最短的二叉树称为哈夫曼树或最优二叉树。
B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多余两个子树。
17.树的存储方式
顺序存储:速度快,所占空间大。
链式存储:可以存储。缺点:指针域个数不定。
18.应用场景:
(1)xml,html
(2)路由协议
(3)mysql数据库索引
(4)文件系统的目录结构
(5)很多经典的AI算法,比如机器学习的(decision tree)决策树也是树结构
19.二叉树:每个节点最多含有两个子树的树称为二叉树。
性质1:二叉树第i层最多只有2^(i-1)个节点。
性质2:深度为k的二叉树最多有(2^k)-1个节点
性质3:叶子节点的个数与度为2的节点的个数满足n0=n2+1
性质4: 具有n个节点的完全二叉树的深度必为log2(n+1)
性质5:完全二叉树,从上到下,从左到右,编号为i的节点,其左孩子编号为2i,右孩子编号为2i+1,其父节点为i/2(根节点除外)
20.二叉树的遍历
广度遍历:队列:先进先出
深度优先:栈:后进先出
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
先序:0 1 3 7 8 4 9 2 5 6
中序:7 3 8 1 9 4 0 5 2 6
后序:7 8 3 9 4 1 5 6 2 0
0
1 2
3 4 5 6
7 89
算法实现(基于Python)
# -*- coding: utf-8 -*-
"""
@Datetime: 2018/11/6
@Author: Zhang Yafei
"""
"""
时间复杂度:衡量算法执行效率的快慢
a+b+c=1000,且a^2+b^2=c^2(a,b,c为自然数),如何求出所有a,b,c的自然数组合?
a,b,c
总时间=基本运算数量*执行次数
每台机器执行的总时间不同
但是执行基本运算数量大体相同
"""
import time
start_time = time.time()
# for a in range(1001):
# for b in range(1001):
# for c in range(1001):
# if a+b+c==1000 and a**2+b**2==c**2:
# print('a,b,c:',a,b,c) #222秒
"""
T = 1000 * 1000 * 1000 * 2
T = 2000 * 2000 * 2000 * 2
T = N * N * N *2
T(n) = n^3 * 2
T(n) = n^3 * 10
T(n) = n^3 * k
从数学上,T(n) = k*g(n),在现实中,省去细枝末叶,只剩下最显著特征
T(n) = g(n)
g(n) = n^3
"""
#顺序
#条件
#循环
for a in range(1001):
for b in range(1001):
c = 1000-a-b
if a**2+b**2==c**2:
print('a,b,c:',a,b,c) #1秒
end_time = time.time()
print('times:',end_time-start_time)
print('finished')
"""
T(n) = n * n *(1+max(1,0))
= n^2*2
= 〇(n^2)
"""
# -*- coding: utf-8 -*-
"""
@Datetime: 2018/11/6
@Author: Zhang Yafei
"""
from timeit import Timer
"""
li1 = [1,2]
li2 = [23,5]
li = li1 + li2
li = [i for i in range(10000)]
li = list(range(10000))
"""
def t1():
li = []
for i in range(10000):
li.append(i)
def t2():
li = []
for i in range(10000):
# li = li + [i] #218秒
li += [i] #1.04
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__ import t5")
print('extend:',timer5.timeit(1000))
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('insert:',timer7.timeit(1000))
# -*- coding: utf-8 -*-
"""
@Datetime: 2018/11/11
@Author: Zhang Yafei
"""
import sys
from timeit import Timer
reps = 1000
size = 10000
def forStatement():
res = []
for x in range(size):
res.append(abs(x))
# 列表解析
def listComprehension():
res = [abs(x) for x in range(size)]
# map
def mapFunction():
res = list(map(abs, range(size)))
# 生成器表达式
def generatorExpression():
res = list(abs(x) for x in range(size))
if __name__ == '__main__':
print(sys.version)
timer1 = Timer("forStatement()", "from __main__ import forStatement")
print('for循环append', timer1.timeit(reps))
timer2 = Timer("listComprehension()", "from __main__ import listComprehension")
print('列表生成式', timer2.timeit(reps))
timer3 = Timer("mapFunction()", "from __main__ import mapFunction")
print('map:', timer3.timeit(reps))
timer4 = Timer("generatorExpression()", "from __main__ import generatorExpression")
print('生成器表达式', timer4.timeit(reps))
# -*- coding: utf-8 -*-
"""
@Datetime: 2018/11/8
@Author: Zhang Yafei
"""
class Node(object):
"""节点"""
def __init__(self,elem):
self.elem = elem
self.next = None
class SingleLinkList(object):
"""单链表"""
def __init__(self,node=None):
self.__head = node
def __str__(self):
if not self.__head:
return 'None'
else:
cur = self.__head
list_str = ''
while cur:
cur_str = '{}->'.format(cur.elem) if cur.next else str(cur.elem)
list_str += cur_str
cur = cur.next
return list_str
def is_empty(self):
"""链表是否为空"""
return self.__head is None
def length(self):
"""链表长度"""
#cur游标,用来移动遍历节点
cur = self.__head
#count记录数量
count = 0
while cur: #cur.next == None
cur = cur.next
count += 1
return count
def travel(self):
"""遍历整个链表"""
cur = self.__head
while cur:
print(cur.elem,end=" ")
cur = cur.next
print('')
def add(self,item):
"""链表头部添加元素,头插法"""
node = Node(item)
node.next = self.__head
self.__head = node
def append(self,item):
"""链表尾部添加元素,尾插法"""
node = Node(item)
if self.is_empty():#链表判空
self.__head = node
else:
cur = self.__head
while cur.next:
cur = cur.next
cur.next = node
def insert(self,pos,item): #insert(2,100)
"""指定位置添加元素
:param pos 从0开始
"""
if pos<=0:
self.add(item)
elif pos>(self.length()-1):
self.append(item)
else:
node = Node(item)
pre = self.__head
count = 0
while count < pos-1:
pre = pre.next
count += 1
#当循环退出后,pre指向pos-1位置
node.next = pre.next
pre.next = node
def remove(self,item):
"""删除节点"""
cur = self.__head
prev = None
while cur:
if cur.elem == item:
#先判断此节点是否是头结点
if cur == self.__head:
self.__head = cur.next
else:
prev.next = cur.next
break
else:
prev = cur
cur = cur.next
def search(self,item):
"""查询节点是否存在"""
cur = self.__head
while cur:
if cur.elem == item:
return True
cur = cur.next
return False
def index(self,item):
"""查询节点是否存在"""
cur = self.__head
count = 0
while cur:
if cur.elem == item:
return count
cur = cur.next
count += 1
return '{} is not in linklist'.format(item)
def value(self,index):
"""查找指定位置的值"""
if self.is_empty():
return None
elif index>self.length()-1:
return 'linklist index out of range'
cur = self.__head
count = 0
while count<index:
count += 1
cur = cur.next
return cur.elem
if __name__ == '__main__':
ll = SingleLinkList()
print
