一、 大话数据结构
1.1 数据结构绪论
数据组成
数据元素:组成数据的基本单位。
数据项:数据元素可以由若干个数据项构成,是数据不可分割的最小单位。
数据对象:数据相同的元素组成的集合,是数据的子集合。
example:
[类数据:人类, 元素:{张三, 李四,...}, 数据项:{眼睛,鼻子,...}, 数据对象(眼睛):{张三的眼睛, 李四的眼睛...}]
数据结构
不同的元素的数据元素之间不是相互独立的,而是存在关系的,这种关系称之为结构。
逻辑结构:集合、线性、树形、拓扑图
物理结构:数据的逻辑结构在计算机中的存储形式顺序存储结构:数据存储在对地址连续的存储单元,逻辑关系与物理关系一致。
连式存储结构:数据元素位置任意,需要指针存放数据元素的地址信息。
1.2 算法
1.21 时间复杂度
算法的时间复杂度:T(n),随着n的变化,执行次数的变化,仅仅关注T(n)的最高阶。O(1):常数阶、O(n):线性阶、O()平方阶 。下面分别给出不同等差数列求和为例:
# 1+2...+n
# O(1)
def sum_1(n):
return int((1 + n) * n / 2)
# n
def sum_2(n):
count = 0
for i in range(1, n+1):
count += i
return count
# n*n
def sum_3(n):
count = 0
for i in range(1, n+1):
for j in range(i):
count += 1
return count
常用的算法复杂度比较:
最坏复杂度和平均复杂度:n个数顺序存储,现在需要插入某个元素,最好的情况1, 最坏的情况n+1,那么平均复杂度是多少了?,搜寻任意x的期望值(n+2)/2。一般没有特殊说明,复杂度都是指最坏的情况。
1.22 空间复杂度
定义和时间复杂度类似,重要的是我们可以通过空间复杂度来换取时间。
example:设计一个算法,复杂度为O(), 这里把算法的结果顺序存储下来,复杂度O(1)。
1.3 线性表
最大的缺点是插入和删除:算法时间复杂度大。为了解决这个问题就得牺牲下空间---连式存储。
- 连式存储
每个数据储存叫节点Node,Node由数域和指针域,前者存储数值后者存储后继的地址信息。存在头指针,每次必须从头指针开始,最后一个指针域为空。 - 搜寻
想得到某个指定地址数据,每次必须从头开始,往下搜寻下去找到即停止,否者直到指针域为空停止说明数据不存在。时间复杂度O(n). - 插入
p节点后插入s节点,s->next=p->next, p-next=s,p的后继赋值给s后继,p后继改为s - 删除
p->next = p->next->next
ps:静态链表,循环链表,双向链表
1.4 栈
想象下弹夹
- 是线性单链表
- 仅仅允许在链表尾部,即栈顶进行插入和删除操作
- 1-n数依次进栈(仅仅是进去的顺序),出站顺序必须满足任意想编号x后面的编号小于x的必须倒序排列。
- 两个站共享内存,从中间某个位置作为两个栈顶, 两端为栈为。如果容积为n当两个站的顶部地址差为1时代表栈满。
- 链栈不存在栈满的情况???
- 栈的递归运用,斐波那契数列数列为例,每次递归到最底层就把数据压入栈的底部,慢慢向上回溯到顶部得到最后顶部值。
# 方法1
def Fib():
a = 1
yield a
b = a
yield b
while 1:
c = a + b
yield c
a = b
b = c
# 这里采用递归的方法解决,稍微改变下需求,求第i个fib数列
def Fib_i(i):
if i <= 2:
return 1
else:
return Fib_i(i-1) + Fib_i(i-2)
1.5 串(string)
- 朴素的模式匹配方法
001 匹配000000001,从第一个起点index=0开始001,发现前两个00能匹配到第1个不行,然后index=1,直到尾部,如果两个长度分别为m, n (n >= m),则算法复杂度为(n - m + 1)* m。 - KMP模式匹配
abcd和abcabcd进行匹配
step1:
abcabcd
abcd
index=3时不匹配
step2
abcabcd
`abcd
index=1终止
step3
abcabcd
``abcd
index=2终止
!!!!!!其实上面step2 step3都是无效
因为在step1时a=a,向后一个个平移到abcabcd第二个a时才可以进一步可能,因此step2可以直接跳到下面
abcabcd
···abcd
1.6 二叉树
- 二叉树的性质
- 第i层节点数最多可能:
- 深度为n的二叉树至多:个节点
- 叶节点为,度为2 的节点数,则
- n个节点完全二叉树的深度,[]为取整运算
- 把完全二叉树n个节点按层进行编号。如果i=1则为root,i>1这这个节点的父节点为;如果节点就没有左孩子,否则左孩子为;如果则无孩子,否则其有孩子为.
- 满树
n个节点,除了每个叶节点,每个节点都有左右左右子树,并且所有的叶节点都在最底层,层次m与节点n存在 关系。
- 完全二叉树
对应一个二叉树按层次对所以节点编号包括叶节点,如果所有节点编号与同样深度的满二叉树编号结果一致,那么该树为满二叉树。
性质、属性:
- 叶节点只能在最下面2层,倒数第二层存在叶节点则,一定集中在树右侧且连续,二最下面一层则集中在左侧且连续。
- 同样节点数的树,完全二叉树深度最小
满树是完全二叉树的子集
- 二叉树的遍历
- 前序:根节点左孩子右孩子
- 中序:左孩子根节点右孩子
- 后续:左孩子右孩子根节点
- 层次遍历:顾名思义吧
1.7 排序
内排序: 与下相反
外排序: 需要辅助空间
排序方法:
1.冒泡排序:两两比较,不符合则交换,复杂度。
下面例子对比两者的时间:# out:0.34375643730163574; 0.10623884201049805
def buddleSort(L):
n = len(L)
for i in range(n):
count = 0
for j in range(n - 1):
if L[j] > L[j + 1]:
c = L[j]
L[j] = L[j + 1]
L[j + 1] = c
count = 1
else:
pass
if count == 0:
# 如果序列不在发生改变跳出循环
break
return L
print(buddleSort([4, 3, 1, 2]))
# 升级算法:比如:
# 3, 2, 1, 4, 5 初始状态
# 2, 1, 3, 4, 5 第一个外层循环后,内层在j=2时后面的顺序不在改变
# 1, 2, 3, 4, 5 在第二外层,j=2和后面都不要在遍历了,前面一个外层已经知道
def buddleSort2(L):
n = len(L)
m = n
for i in range(n):
count = 1
# print('m', m, i)
flag = None
for j in range(m - 1):
if L[j] > L[j + 1]:
c = L[j]
L[j] = L[j + 1]
L[j + 1] = c
count = 0
flag = j + 1
else:
pass
# print('flag:', flag)
m = flag
if count:
# 如果序列不在发生改变跳出循环
break
return L
Z = [1, 4, 3, 2] + list(range(10, 1000000))
print(Z[:7])
s = time.time()
res = buddleSort(Z)
cost = time.time() - s
print(Z[:7]) # 行数调用的时候吧Z顺序已经改变了,fuck
Z = [1, 4, 3, 2] + list(range(10, 1000000))
s = time.time()
res2 = buddleSort2(Z)
cost2 = time.time() - s
print(cost, cost2)
print(res2[:7])
- 简单排序法:找到最小的排在第一位,在剩下的元素中在找最小的排在第二位。。。。。复杂度
- 直接插入排序: (利用链式存储)复杂度。参考
(bubkoo.com/2014/01/15/…)
**
def insert(i, j, L):
# i < j
c = L[j]
for z in range(j, i - 1, -1):
L[z] = L[z - 1]
L[i] = c
return L
x = [1, 2, 4, 5]
print(insert(0, 3, x))
def InsertSort(L):
n = len(L)
if L[0] > L[1]:
L = insert(0, 1, L)
for i in range(2, n):
for z in range(i - 1, -1, -1):
if (L[i] < L[z]) and (L[i] >= L[z-1]):
L = insert(z, i, L)
return L
x = [1, 4, 1, 3, 4, 6, 2]
print(InsertSort(x))
- 希尔排序: 将数据分为d组,然后组内进行插入排序,d递减步长为1直到循环到1即停止,复杂度。[参考]
- 堆排序: 将数据编号按层次构建完全二叉树,利用满二叉树性质,从叶节点从树的下面向上寻,吧大的数给父节点,最后root就为最大的数,如此反复,就会得到序列。复杂度
- 归并排序:分而治之复杂度
- 快速排序: 参考**复杂度
二、数据结构与算法
2.1 基本概念
2.11 数据结构定义
数据:电脑上存储的东西都是数据。图片是图像数据,歌曲是声音数据。编程语言中用int、double、char定义的变量都是数据。
数据元素:是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理。比如在人类中,数据元素是指一个人 。
结构:简单的理解就是关系。比如分子结构,就是说组成分子的原子之间的排列方式。严格点说,结构是指各个组成部分间相互搭配和排列的方式 。
数据结构是指相互之间存在着特定关系的的数据元素的集合。
2.12 逻辑结构与物理结构
类似于逻辑地址与物理地址。
(1)逻辑结构:是指数据元素之间的相互关系,与他们在计算机中存储的位置无关。 逻辑结构分为以下四种:
- ①集合结构,元素间无关系。类似于数学中集合的概念。
- ②线性结构,元素间一对一。
- ③树形结构,元素间一对多。
- ④图形结构,元素间多对多。
(2)物理结构:指数据元素在计算机磁盘中存储的方式。通常有顺序存储、链式存储、索引存储和哈希存储。
2.2 基本数据结构介绍
2.21 数组
数组(array),线性数据结构,在内存中顺序存储。 特点:高效的访问,低效的插入和删除。
2.22 栈(类似于子弹弹夹)
栈(stack),线性数据结构,元素只能先进后出(First In Last Out,简称FILO)。最早进入的元素存放的位置叫作栈底(bottom),最后进入的元素存放的位置叫作栈顶(top)。
关于栈的操作有入栈(push)、出栈(pop)和遍历栈。
2.23 队列(类似于排队)
队列(queue),线性结构。队列的特性是:先进先出,后进后出。基本操作就是入队和出队。
**补充(循环队列):**由于队列既可以顺序存储又可以链式存储,当顺序存储时我们可以使用循环对列,来提高队列空间的使用效率。下面用动态图来演示下基本过程。
循环对列需要一个头指针front和一个尾指针tail来判断队列的状态。头指针始终指向队首元素,尾指针始终指向队尾元素的后一个位置, 并且尾指针指向的位置不存储元素,所以队列满时,其中的元素个数为capacity - 1。 当front == tail 时,说明队列为空;当front ==(tail + 1)% capacity时,队列满。(**注:**capacity指顺序存储时队列的总容量)
2.24 链表
链表在计算机中的存储方式为随机存储,不像数组是顺序存储。
链表也包括好几种,单链表、单向循环链表、双链表和双向循环链表。这里只介绍两种常用的链表。
(1)单链表 链表是由一个个结点所构成,先介绍单链表的一个结点。
单链表基本结构
基本操作有:插入操作、删除操作、更新操作和查找操作。
(2)双向链表 双向链表的结点比单链表的结点多一个前驱指针,一个结点有两个指针域。
2.25 树
树和图都是非线性数据结构,是由n(n>=0)个节点构成的有限集。n=0时称为空树。n>0时,有限集的元素构成一个具有层次感的数据结构。
基本概念:
- 根节点:树的第一层的节点为根节点,根节点最多只有一个。
- 叶子结点:无子节点的节点。
- 父节点:有子节点的节点,如图中节点2为节点4和节点5的父节点。
- 子节点:图中节点7、8为节点4的子节点,4、5又为节点2的子节点。
- 树的高度(深度):从根开始定义起,根为第一层 , 根的孩子为第二层。根节点到离它最远的叶子节点为树的高度。
- 节点的度:一个节点拥有 的子节点的个数,如节点4的度为2
- 兄弟节点:如同一个父节点的节点彼此间为兄弟节点,如2与3、4与5、7与8
(1)二叉树 二叉树或者为空集,或者由一个根节点和两棵互不相交的、分别称为左子树和右子树的二叉树组成。从定义可以看出一棵二叉树:
二叉树是有序树,区分左子树与右子树,不可以随意交换子树位置。 一个节点的子树数量取值范围为0,1,2。0代表该节点是叶子节点,1代表该节点只有左子树或只有右子树,2代表该节点有左右子树。
满二叉树: 满二叉树的两个条件: ①一个二叉树的所有非叶子节点都存在左右孩子 ②所有的叶子节点都在同一层。 简单点说,满二叉树的每一个分支都是满的。
二叉树的性质:
- 性质一:在二叉树的第i层上至多有2^(i-1)个节点(i>=1)
- 性质二:深度为k的二叉树至多有2^k-1个节点
- 性质三:对任何一棵二叉树T,如果终端节点数为n0,度为2的节点数为n2 ,那么 n0 = n2 +1
- 性质四: 具有n个节点的完全二叉树的高度为至少为log2(n+1)
- 性质五:如果对一棵有n个节点的完全二叉树的节点按层序编号(从第一层开始到最下一层,每一层从左到右编号),对任一节点i有:
-
- 如果i=1 ,则节点为根节点,没有双亲。
-
- 如果2 * i > n ,则节点i没有左孩子 ;否则其左孩子节点为2i . (n为节点总数)
-
- 如果2 * i+1>n ,则节点i没有右孩子;否则其右孩子节点为21+1
-