目录
首先了解很很很基础的一些数据基本概念
数据:能被计算机识别的符号集合[ 数值类型(整型等)、非数值类型(字符/声音/图像/视频等)]
数据的处理:
- 数值类型进行数值计算
- 字符数据类型进行非数值处理
- 声音、图像、视频等通过编码手段变成字符型处理
数据抽象类型(ADT):对数据的某种抽象,定义了数据的取值范围、结构形式以及对数据操作的集合
数据对象:性质相同的数据元素的集合,是数据的子集 ;简称为数据
数据项:一个数据元素可以由若干个数据项组成;是数据不可分割的最小单位
但数据元素才是数据结构中建立数据模型的着眼点
数据结构:带有结构特性的数据元素的集合
逻辑结构面向问题,物理结构面向计算机,目标是将数据及其逻辑关系存入计算机内存
算法:解决特定问题的特定步骤(计算机中:指令的有限序列)
一个程序的运行时间依赖于算法的好坏和问题的输入规模(输入量),基本操作数量必须表示成输入规模的函数
T(n)时间复杂度 = O[f(n)](大O记法)
计算时间复杂度(推导大O):
- 基本操作:只有常数项则为O(1)
- 顺序结构:按加法计算
- 循环结构:按乘法计算
- 分支结构:取两个分支最大值
- 只关注最高次项(因为渐进函数),一般指最坏时间复杂度
a = 5 #1
b = 6 #1
c = 10 #1
for i in range(n):
for j in range(n): # n*n
x = i * i
y = j * j
z = i * j
# n*n*3
for k in range(n):
w = a * k + 45
v = b * b
# n*2
d = 33 #1
#T(n)=3n*n+2n+4 O(n*n)
S(n)空间复杂度 = O[f(n)]
python:list和dict是已经封装好的类型
注意:函数是对基本步骤的封装,函数的时间复杂度,不是出现一次就是常数项,函数体中的代码才能代表该函数的时间复杂度
下列表的补充说明:
pop(i):从指定位置弹出
insert(i,item):在i位置插入item
del operation:删除
iteration:迭代使用for操作
reverse:反转
concatenate:队尾相加
copy:所有元素再生成一遍,所以是O(n)
get item:通过键立马取到值
delete item:删除的不是字典是里面的键
contains(in):字典可根据键直接看一下有没有这个值
❶ 线性表:0/多个数据元素的有限序列(非空表中每个元素都有自己的小板凳)
- python的内置类型list和tuple都可以看做线性表的实现
顺序表:由一维数组实现
顺序表的三个属性:
- 存储空间的起始位置:数组data
- 线性表的最大存储容量:数组长度maxsize
- 线性表当前长度:length
数组长度是存放线性表的存储空间的长度(存储分配后长度不变);线性表长度是线性表元素的个数,随着插入和删除这个长度会变化;任何时刻,线性表的长度都小于等于数组长度!
地址计算法计算存储位置:
获取元素操作思路:将线性表第i个位置元素返回(返回数组第第i-1下标的值)
顺序表插入算法思路:
- 若插入位置不合理,抛出异常
- 若线性表长度大于数组长度,抛出异常/动态增加容量
- 从最后一个元素开始向前遍历到第i个位置,分别将其向后移动一个位置
- 将要插入元素填入位置i处
- 表长加1
顺序表删除算法思路:
- 如果删除位置不合理,抛出异常
- 取出删除元素
- 从删除元素开始向前遍历到最后一个位置,分别将其向前移动一个位置
- 表长减1
存、读数据时,不管哪儿个位置,时间复杂度都是O(1);而插入、删除为O(n)。
链表:数据元素可以存在内存未被占用的任意位置;除了存数据元素还要存后继元素的存储位置
链表的存储结构图:
头结点与头指针的区别:
单链表:核心思想为工作指针后移
- 结构中没有定义表长,所以不能事先知道循环多少次,不便用for循环
获得第i个元素算法:
- 声明一个结点p指向链表第一个结点,初始化j从1开始
- j<i时,遍历链表,让p的指针不断后移到下一个结点,j累加1
- 若到链表末尾p为空,则说明第i个元素不存在
- 否则查找成功,返回结点p的数据
单链表第i个数据插入结点算法:
- 声明一个结点p指向链表第一个结点,初始化j从1开始
- j<i时,遍历链表,让p的指针不断后移到下一个结点,j累加1
- 若到链表末尾p为空,则说明第i个元素不存在
- 否则查找成功,在系统中生成一个空结点s
- 将数据元素e赋值给s->data
- 单链表的标准插入语句:s->next = p->next; p->next = s
- 返回成功
单链表第i个数据删除结点的算法:
- 声明一个结点p指向链表第一个结点,初始化j从1开始
- j<i时,遍历链表,让p的指针不断后移到下一个结点,j累加1
- 若到链表末尾p为空,则说明第i个元素不存在
- 否则查找成功, q = p->next
- 单链表删除标准语句:p->next = q->next
- 将q结点中的数据赋值给e,作为返回: *e = q->data;
- 释放q结点,返回成功:free(q); return ok
- 单链表的插入和删除均是先遍历查找i元素,再插入/删除
- 需要频繁查找,很少插入和删除时:顺序表;若频繁插入或删除:单链表。
- 线性表元素个数变化较大或根本不知道多大时:单链表
顺序表的创建:数组初始化(声明数组并赋值)
单链表的整表创建:动态生成链表(从空表开始依次建立元素结点并逐个插入链表)
- 声明一结点p和计数器变量i
- 初始化一空链表L
- 让L的头结点指针指向null
- 循环:生成一新结点就赋给p;随机生成一数字赋给p->data;将p插头结点与前一新结点之间
以上为头插法,还有尾插法
单链表的整表删除:
- 声明一结点p和q
- 将第一个结点赋值给p
- 循环:将下一个结点赋值给q;释放p;将q赋值给p
静态链表:用数组描述的链表(游标实现法)
- 数组通常建大一点,防止插入溢出
- 备用链表:数组的第一个和最后一个元素不存数据
循环链表:单链表终端结点指针由空指针改为指向头结点,头尾相连接的单链表
- 任意结点开始都可以访问全部结点
- 单链表循环判断条件:p->next是否为空;循环链表:p->next不等于头结点,循环结束
循环列表的合并:
双向链表:在单链表每个结点中设置一个指向其前驱结点的指针域
- 可以反向遍历查找
- 插入和删除时需要更改两个指针变量
❷ 栈:限定仅在表尾进行插入和删除的线性表
- 保证是栈顶元素出栈
- 生活中的栈:弹式手枪、网页浏览
- LIFO(last in first out):后进先出结构
顺序栈的进栈、出栈:
两栈共享空间:
- 解决事先预定的数组大小不够用问题
链栈:栈顶在单链表头部(不需要头结点)
- 基本不存在栈满情况
- 空栈:top = NULL
栈的应用:
1.递归:直接调用自己/通过一系列的调用语句间接调自己的函数
- 至少有一个条件,满足时递归不再进行返回值
- 对于每一层递归,函数的局部变量、参数值、返回地址都被压入栈,返回调用时再将他们弹出
- 与迭代的区别:迭代用的是循环结构;递归用的是选择结构;但是递归会产生大量副本,而迭代不需要反复调用函数和占用额外内存
2.四则运算表达式求值
后缀(逆波兰)表示法:不需要括号的后缀表示法(左括号进栈,出现右括号让栈顶的左括号出栈,直到为空栈)
中缀表达式:标准的四则运算表达式
- 中缀转后缀规则:从左到右遍历每个数字和符号,遇到数字就输出,符号就进栈,若进的符号优先级低于栈顶符号,则高优先级栈顶元素依次出栈;若进的符号是右括号则匹配到左括号一起出栈为止,最终输出后缀表达式。 (栈中进出符号)
- 9+(3-1)*3+10/2 变成9 3 1 - 3 * + 10 2 / +
- 后缀转中缀规则:从左到右遍历每个数字和符号,遇到数字就进栈,遇到符号就将栈顶的两个数字出栈进行运算,运算结果进栈,一直到最后得到结果(栈中进出数字)
- 9 3 1 - 3 * + 10 2 / +变成20
❸队列:只允许在一端进行插入,另一端进行删除的线性表
- FIFO结构(first in first out):先进先出
- 生活中的队列:键盘输入记事本、网页卡后弹出、排队买票
顺序队列:容易造成假溢出(队列还有空闲,但rear缺指向了队列之外)
- 引用两个指针:front指针指向队列头元素;rear指向队尾元素的下一个位置
- front=rear时,此队列不是还剩一个元素而是空队列
循环队列(顺序队列):头尾相接的顺序存储结构的队列
解决顺序列表假溢出问题(rear超过队列时指向下标为0的位置)
如何front=rear时如何判断是空队列还是满队列
方法一:设置一个标志变量flag,当front==rear且flag=0时为空队列;当front==rear且flag=1时为满队列
方法二:front=rear依旧是空队列的条件;满队列的条件更改为(rear+1)%QueueSize==front;(%是为了整合front和rear大小问题)
- 因此通用计算队列长度的公式为:(rear-front+QueueSize)%QueueSize
(缺点:)循环队列数组可能会溢出=>链队列
链队列:只能尾进头出的单链表
- 空队列是front和rear都指向头结点
链队列入队:其实就是在链表尾部插入结点
链队列出队:
在可以确定队列长度最大值时建议用循环队列;如果无法估计队列的长度时,建议用链队列
❹ 字符串:0/对个字符组成的有限序列
空字符串:""
空格串:" "
字符串的比较:长度相等通过ASCII/Unicode编码比较
- 长度不等时,但编码相同,则长度长的大
- 长度不等时,若k<=min(m,n),使ai=bi(i=1,2…k-1),ak<bk,则包含a序列的大
字符串顺序存储结构:值为字符的顺序表
字符串链式存储结构:一个结点可存放1/多个字符,最后一个结点未占满可用#等非串值字符补全
字符串链式存储只在连接字符串与字符串时方便,其余不如顺序结构
朴素模式匹配算法:子串的定位操作通常叫做串的模式匹配
def naive_matching(t, p):
m, n = len(p), len(t)
i, j = 0, 0
while i < m and j < n: # i==m说明找到匹配
if p[i] == t[j]: # 字符相同,考虑下一对字符
i, j = i+1, j+1
else: # 字符不同,考虑t中下一位置
i, j = 0, j-i+1
if i == m: # 找到匹配,返回其开始下表
return j-i
return -1 # 无匹配,返回特殊值
KMP模式匹配算法:
KMP算法详解(python实现)_吮指原味鸡!的博客-CSDN博客_kmp python
❺ 树:n(n>=0)个结点的有限集;(n=0为空树)
- n>0时,有且仅有一个根节点
- 子树个数没有限制,但树的子树间一定是互不相交的
有序树:将树中结点的各子树看成从左至右有次序的不能互换,否则为无序树
度(degree):结点拥有的子树个数
树的度:树内各结点的度的最大值
树的深度(depth):树中结点的最大层次
森林(forest):子树的集合
树的存储结构:
- 双亲表示法:每个结点附设一个指示器指向双亲在链表中的位置
找双亲和孩子:根节点位置域为-1;没有孩子的结点,长子域为-1
找双亲和兄弟:根节点位置域为-1;没有右兄弟的结点,右兄域为-1
- 孩子表示法:每个结点的孩子结点排列起来,以单链表存储
双亲孩子表示法:
- 孩子兄弟表示法: 两个指针分别指向第一个孩子和此结点的右兄弟
在孩子兄弟表示法的基础上想找双亲:二叉树
二叉树(binary tree):
- 每个结点最多两颗子树
- 左右子树有顺序,次序不能颠倒
- 即使树中某结点只有一颗子树也要区分是左子树还是右子树
二叉树的五种形态:
空二叉树、只有根节点、根节点只有左子树、根节点只有右子树、根节点有左右子树
特殊二叉树:
- 斜树(左斜树、右斜树)
- 满二叉树:叶子只在最后一层、非叶子度一定为2、同深度二叉树中,满二叉结点和叶子最多
- 完全二叉树:按层序和左右顺序排号的二叉树(同节点数的二叉树,完全二叉深度最小)
满二叉树一定是完全二叉树,但完全二叉树不一定是满的
二叉树的性质
- 二叉树第层上最多有个结点()
- 深度为的二叉树最多有个结点()
- 若二叉树叶子数为,度为2的结点数为,则
- 具有个结点的完全二叉树的深度为
- 对二叉树结点按层序编号,对任意结点:
- , 结点为根
- , 双亲结点为
- , 无左孩子,否则左孩子为
- , 无右孩子,否则右孩子为
二叉树的顺序存储:一维数组存二叉树结点,且下标要体现出结点间的逻辑关系
顺序存储结构一般只用于完全二叉树
二叉链表:一个数据域两个指针域
二叉树遍历
前序遍历:根、前序遍历左子树、再前序遍历右子树
中序遍历:中序遍历左子树、根、再中序遍历右子树
后序遍历:先叶子后结点遍历左子树、先叶子再结点遍历右子树、根
推导遍历结果:
根据前序和中序遍历序列,可以唯一确定一颗二叉树
根据后序和中序遍历序列,可以唯一确定一颗二叉树
前序和后序不能确定一颗二叉树
二叉树的建立:递归
线索二叉树(threaded binary tree):加上线索(指向前驱和后继的指针)的二叉链表
- 解决了只有亲子关系而无顺序关系的问题
- 相当于把一棵二叉树变成一个双向链表(便于查找和插入删除结点)
- 线索化:将二叉链表中的空指针改为指向前驱或后继的线索 (在遍历过程中修改空指针)
- ltag为0时指向该结点左孩子,为1时指向该结点的前驱
- rtag为0时指向该结点的右孩子,为1时指向该结点的后继
若二叉树需要经常遍历或查找结点时需要某种遍历序列中的前驱和后继,应用线索二叉链表
树转换为二叉树
森林转换为二叉树
二叉树转换为树
二叉树转换为森林
如何判断转换成树还是森林:二叉树根节点有右孩子就是森林,没有就是树
树的遍历
- 先根遍历:根、再依次先根遍历
- 后根遍历:依次遍历每棵子树、再根
森林的遍历
- 前序遍历:第一棵树(根、依次先根遍历每棵子树)、再依次遍历除第一棵树的剩余树
- 后序遍历:第一棵树(依次先根遍历每棵子树、根)、再依次遍历除第一棵树的剩余树
赫夫曼树(最优二叉树):带权路径长度WPL最小的二叉树
路径长度:从根节点到该结点间经过几条线
树的路径长度:所有结点的路径长度之和
结点带权路径长度:该结点路径长度*该结点上权值
树的带权路径长度:所有叶子结点的带权路径长度之和
赫夫曼编码:将赫夫曼树的左支权值全改为0,右支全改为1,则从根节点到叶子结点所经过的路径分支组成的仅包含0和1的序列
- 最基本的压缩编码方法
❻ 图:由顶点的有穷非空集合和顶点之间的边的集合组成(任意两个元素之间都可能相关)
- 线性表的元素叫元素;树叫结点;图叫顶点
- 线性表和树可为空表/空树;图不允许没顶点
- 线性表相邻元素间为线性关系;树相邻两层结点有层次关系;图任意两顶点都可能有关系,顶点间逻辑关系用边表示,边集可以是空的
简单图:无重复的边或顶点到自身的边 的图
图按照有无方向
无向图:图中任意顶点间都为无向边
- G=(V,{E}),顶点集合V={A,B,C,D};边集合E={(A,B),(B,C),(C,D),(D,A),(A,C)}
- 无向图顶点边数叫度
- 边的两个顶点间互为邻接点、边依附于两个顶点
有向图:图中任意顶点间都为有向边(弧:有弧头、弧尾之分)
- G=(V,{E}),顶点集合V={A,B,C,D};边集合E={<A,D>,<B,A>,<C,A>,<B,C>}
- 有向图顶点分入度和出度
- 弧头顶点邻接到弧尾顶点,弧尾顶点邻接自弧头顶点;弧和两个顶点相关联
完全图:任意两顶点间都存在边
- 完全无向图:若个顶点则条边
- 完全有向图:若个顶点则条边
按边(弧)的多少
- 稀疏图
- 稠密图
图上的边(弧)带权则称为网。
图中顶点间存在路径,路径的长度是路径上的边/弧的数目;两顶点间存在路径则说明是连通的,如果路径最终回到起始点则为回路/环,序列中顶点不重复出现的路径叫简单路径。
若任意两顶点都是连通的则为连通图,有向则称强连通图。图中有子图,极大连通子图就是连通分量,有向的则称强连通分量。
- 要是子图
- 子图要是连通的
- 连通子图含有极大顶点数
- 具有极大顶点数的连通子图包含依附于这些顶点的所有边
若一个图有n个顶点和小于n-1条边,则为非连通图;若多于n-1边,必定构成一个环
无向图中连通且 n 个顶点 n-1 条边叫生成树。有向图中一顶点入度为0其余顶点入度为1的叫有向树。一个有向图的生成森林由若干棵有向树构成。
图的存储结构
邻接矩阵(adjacency matrix)存储方式:用两个数组表示图
- 一维数组:存顶点信息
- 二维数组(邻接矩阵):存图中的边/弧的信息
无向图的邻接矩阵 :
有向图的邻接矩阵:
网图的邻接矩阵:
邻接矩阵作用:
- 判断顶点间有无边
- 顶点的度:顶点在矩阵中第行/列的元素之和
- 顶点的所有邻接点:就是邻接点
若图有n个顶点,则邻接矩阵为n*n的方阵
存在边/弧则记为1,反之为0
无向图的邻接矩阵为一个对称矩阵()
缺:对于边数相对顶点较少的图,浪费存储空间
邻接表(adjacency list):数组与链表相结合的存储方法
- 无向图的邻接表
- 有向图的逆邻接表
- 对于网图:在adjvex与next之间加一个weight数据域即可
缺:对于有向图邻接表关注出度,了解入度就必须遍历整个图;逆邻接表关注入度,解决不了出度
十字链表(orthogonal list):结合邻接表与逆邻接表
- 多应用于有向图
邻接多重表:
关注无向图顶点:选邻接表
关注无向图的边:邻接多重表
邻接多重表与邻接表区别:同一条边在邻接表中用两个结点表示,为在邻接多重表中只有一个结点
边集数组:两个一维数组构成(一个存顶点,一个存边)
图的遍历(traversing graph):
- 深度优先遍历(DFS):类似树的前序遍历
- 广度优先遍历(BFS):类似树的层序遍历
时间复杂度相同,仅仅是访问顺序不同;DFS更适合目标明确,以找到目标为主的情况,BFS更适合在不断扩大遍历范围时找到相对最优解的情况。
最小生成树(minimum cost spanning tree):构造连通网的最小代价生成树
Prim算法:以顶点为起点,逐步找各顶点上最小权值的边来构造最小生成树
- 邻接矩阵
- 时间复杂度
kruskal算法: 以边为目标去构建,直接去找最小权值的边来构建生成树,构建时要考虑是否会形成环路;
- 边集数组
- 时间复杂度
kruskal算法主要是针对边展开,边数少效率非常高,所以对于稀疏图有很大优势
prim算法对稠密图,即边数非常多的情况会好一些
最短路径
dijkstra算法:按路径长度递增的次序产生最短路径;解决了从某个源点到其余各顶点的最短路径问题
- 时间复杂度
floyd算法
拓扑排序:对一个有向图构造拓扑序列的过程
AOV网(activity on vertex network):顶点表活动,弧表活动间制约关系,表示工程的有向无环网
- 若此网全部顶点都被输出:无环AOV网
- 若输出顶点少了:有环,不是AOV网
对AOV网拓扑排序算法思路:从AOV网中选一个入度为0的顶点输出,然后删去此顶点,并删除以此顶点为尾的弧 ,继续重复此步骤,直到输出全部顶点或AOV网中不存在入度为0的顶点为止。
拓扑排序的数据结构:
- 邻接表
- 栈(存入度为0的顶点,避免每次查找遍历顶点表找有没有入度为0的顶点)
时间复杂度 :
主要为了解决一个工程能否顺利进行的问题
关键路径
AOE网(activity on edge network):顶点表事件, 边表活动,权值表活动持续时间的有向无环网
- 路径长度:路径上各个活动所持续时间之和
- 关键路径:从源点到汇点具有最大长度的路径
- 关键活动:关键路径上的活动
AOE网要以活动间制约关系没矛盾为前提,分析完成工程至少需要的时间,或为缩短工时应加快哪儿些活动等问题。
关键路径算法思想:比较所有活动的最早开始时间和最晚开始时间,若相等则为关键活动,活动间路径为关键路径,不等则不是。
关键路径数据结构
- 邻接表:弧链表增加weight域,存储弧的权值
- 栈:同拓扑排序
时间复杂度 :
主要解决工程完成需要最短时间问题
关于查找与排序
【数据结构总结笔记(二)----查找与排序】__Carpediem的博客-CSDN博客
参考资料:
《大话数据结构》