文章目录
要点
判别模型 VS 生成模型
判别方法:由数据直接学习决策函数Y=f(X)或条件概率分布P(Y|X)作为预测模型,即判别模型。判别方法关心的是对于给定的输入X,应该预测什么样的输出Y。
生成方法:由数据学习联合概率分布P(X,Y), 然后由P(Y|X)=P(X,Y)/P(X)求出概率分布P(Y|X)作为预测的模型。该方法表示了给定输入X与产生输出Y的生成关系。
生成模型:朴素贝叶斯、隐马尔可夫(em算法)
判别模型:k近邻法、感知机、决策树、逻辑回归、线性回归、最大熵模型、支持向量机(SVM)、提升方法、条件随机场(CRF)
1、生成模型可以还原出联合概率分布(还原数据本身相似度),而判别方法不能;
2、生成方法的学习收敛速度更快,当样本容量增加的时候,学到的模型可以更快的收敛于真实模型;
3、当存在隐变量时,仍可以利用生成方法学习,此时判别方法不能用;
4、判别学习不能反映训练数据本身的特性,但它寻找不同类别之间的最优分类面,反映的是异类数据之间的差异,直接面对预测,往往学习的准确率更高,由于直接学习P(Y|X)或Y=f(X),从而可以简化学习;
5、简单的说,生成模型是从大量的数据中找规律,属于统计学习;而判别模型只关心不同类型的数据的差别,利用差别来分类。
降维算法
https://zhuanlan.zhihu.com/p/43225794
https://zhuanlan.zhihu.com/p/79535725
- 缺失值比率:如果数据集的缺失值太多,我们可以用这种方法减少变量数。
- 低方差滤波:这个方法可以从数据集中识别和删除常量变量,方差小的变量对目标变量影响不大,所以可以放心删去。
- 高相关滤波:具有高相关性的一对变量会增加数据集中的多重共线性,所以用这种方法删去其中一个是有必要的。
- 随机森林:这是最常用的降维方法之一,它会明确算出数据集中每个特征的重要性。
- 前向特征选择和反向特征消除:这两种方法耗时较久,计算成本也都很高,所以只适用于输入变量较少的数据集。
- 因子分析:这种方法适合数据集中存在高度相关的变量集的情况。
- PCA:这是处理线性数据最广泛使用的技术之一。
- ICA:我们可以用ICA将数据转换为独立的分量,使用更少的分量来描述数据。
- ISOMAP:适合非线性数据处理。
- t-SNE:也适合非线性数据处理,相较上一种方法,这种方法的可视化更直接。
- UMAP:适用于高维数据,与t-SNE相比,这种方法速度更快。
线性判别分析(Linear Discriminant Analysis,LDA)
投影后类内方差最小,类间方差最大:将数据在低维度上进行投影,投影后希望每一种类别数据的投影点尽可能的接近,而不同类别的数据的类别中心之间的距离尽可能的大。
局部线性嵌入 (LLE)
Laplacian Eigenmaps 拉普拉斯特征映射
SVM
核函数
- 线性核函数(Linear Kernel):主要用于线性可分的情形。参数少,速度快。
- 多项式核函数
- 依靠升维使得原本线性不可分的数据线性可分。
- 高斯核函数
- 高斯核本质是在衡量样本和样本之间的“相似度”,在一个刻画“相似度”的空间中,让同类样本更好的聚在一起,进而线性可分。
- 主要用于线性不可分的情形。参数多,分类结果非常依赖于参数。有很多人是通过训练数据的交叉验证来寻找合适的参数,不过这个过程比较耗时。
- 径向基内核(RBF)
本质: 核函数是一个低纬的计算结果,并没有采用低纬到高维的映射。只不过核函数低纬运算的结果等价于映射到高维时向量点积的值。
warning:需要对数据归一化处理
选择
- 如果Feature的数量很大,跟样本数量差不多,选用LR或者Linear Kernel的SVM: 如果特征维数很高,往往线性可分(SVM解决非线性分类问题的思路就是将样本映射到更高维的特征空间中)。
- 如果Feature的数量比较小,样本数量一般,选用SVM+Gaussian Kernel。
- 如果Feature的数量比较小,而样本数量很多,需要手工添加一些feature变成第一种情况:如果样本数量很多,由于求解最优化问题的时候,目标函数涉及两两样本计算内积,使用高斯核明显计算量会大于线性核,所以手动添加一些特征,使得线性可分。
聚类算法
- K-means
- 划分聚类
- K-中心点算法
- CLARANS算法
- 用于空间数据库的聚类算法
- K-modes
- K-means算法的扩展,采用简单匹配方法来度量分类型数据的相似度
- K-prototypes
- K-medoids(PAM)
- 在迭代过程中选择簇中的某点作为聚类
- CLARA
- 在PAM基础上采用了抽样技术,能够处理大规模数据。
- Focused CLARAN
- 空间索引技术
- PCM
- 模糊聚类
- 层次聚类:对给定的数据集进行层次似的分解
- DIANA
- BIRCH
- 利用树结构对数据集进行处理,叶结点存储一个聚类,用中心和半径表示,顺序处理每一个对象,并把它划分到距离最近的节点,也可作为其他聚类方法的预处理过程。
- CURE
- 采用抽样技术先对数据集随机抽取样本,再采用分区技术对样本进行分区,然后对每个分区局部聚类,最后对局部聚类进行全局聚类
- ROCK
- 随机抽样技术,在计算两个对象的相似度时,同时考虑了周围对象的影响。
- 两个数据点公有的邻居,当我们考虑是否需要合并聚类X与聚类Y时,通过计算两个聚类中数据点之间的链接数量;
- CHEMALOEN
- SBAC
- 在计算对象间相似度时,考虑了属性特征对于体现对象本质的重要程度,对于更能体现对象本质的属性赋值较高的权重
- BUBBLE
- 把BIRCH算法的中心和半径概念推广到普通的距离空间。
- BUBBLE-FM
- 通过减少距离的计算次数, 提高了BUBBLE效率
- 模糊聚类
- EM算法
- 基于密度的聚类
- OPTICS
- DBSCAN
- 采用空间索引技术来搜索对象的邻域,引用了核心对象和和密度可达等概念,从核心对象出发,把所有密度可达的对象组成一个簇。
- GDBSCAN
- FDC
- DBLASD
- 网格算法
- STING
- WaveCluster
- CLIQUE
- OPTIGRID
- 基于统计方案
- COBWeb
- AutoClass
- CLASSIT
- 基于神经网络
- 自组织神经网络SOM
- 均值漂移聚类
隐马尔可夫HMM
Baum-Welch算法(EM算法)
对hmm模型做参数估计的方法,是EM算法的一个特例。
EM算法步骤:
https://zhuanlan.zhihu.com/p/36331115
- expectation,计算隐变量的概率分布,并得到可观察变量与隐变量联合概率的log-likelihood在前面求得的隐变量概率分布下的期望。
- maximization求得使上述期望最大的新的模型参数。若达到收敛条件则退出,否则回到步骤1。前向后向算法则主要是解决Expectation这步中求隐变量概率分布的一个算法,它利用dynamic programming大大减少了计算量。
求导
MySQL
https://www.cnblogs.com/wyaokai/p/10921323.html
https://zhuanlan.zhihu.com/p/117476959
基本操作
- explain:语句执行情况分析
- SQL LIKE 子句中使用百分号 %字符来表示任意字符,类似于UNIX或正则表达式中的星号 *。
索引
索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。
- 单列索引:一个索引只包含单个列。
- 组合索引:一个索引包含多个列。
https://www.cnblogs.com/rocker-pg/p/11635414.html
数据库事务
MySQL 的 InnoDB 引擎才支持事务,其中可重复读是默认的隔离级别。
数据库事务指的是一组数据操作,事务内的操作要么就是全部成功,要么就是全部失败,什么都不做,其实不是没做,是可能做了一部分但是只要有一步失败,就要回滚所有操作,有点一不做二不休的意思。
事务的基本要素(ACID)
- 原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
- 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
- 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
- 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
事务的并发问题
- 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
- 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
- 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
事务隔离
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
读未提交(read-uncommitted)
任何事务对数据的修改都会第一时间暴露给其他事务,即使事务还没有提交(启动两个事务,分别为事务A和事务B,在事务A中使用 update 语句,修改 age 的值为10,初始是1 ,在执行完 update 语句之后,在事务B中查询 user 表,会看到 age 的值已经是 10 了,这时候事务A还没有提交,而此时事务B有可能拿着已经修改过的 age=10 (脏读)去进行其他操作了。在事务B进行操作的过程中,很有可能事务A由于某些原因,进行了事务回滚操作,那其实事务B得到的就是脏数据了,拿着脏数据去进行其他的计算,那结果肯定也是有问题的)。
不可重复读(read-committed)
一个事务只能读到其他事务已经提交过的数据,也就是其他事务调用 commit 命令之后的数据(同样开启事务A和事务B两个事务,在事务A中使用 update 语句将 id=1 的记录行 age 字段改为 10。此时,在事务B中使用 select 语句进行查询,我们发现在事务A提交之前,事务B中查询到的记录 age 一直是1,直到事务A提交,此时在事务B中 select 查询,发现 age 的值已经是 10 了)。
在同一事务中(本例中的事务B),事务的不同时刻同样的查询条件,查询出来的记录内容是不一样的,事务A的提交影响了事务B的查询结果,这就是不可重复读,也就是读提交隔离级别。
可重复读(repeatable-read)
事务不会读到其他事务对已有数据的修改,即使其他事务已提交。事务开始时读到的已有数据是什么,在事务提交前的任意时刻,这些数据的值都是一样的。但是,对于其他事务新插入的数据是可以读到的,这也就引发了幻读问题。
幻读
系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
不可重复读 vs 幻读
不可重复读侧重于修改,幻读侧重于新增或删除
串行化(serializable)
将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束。
从上往下,隔离强度逐渐增强,性能逐渐变差。采用哪种隔离级别要根据系统需求权衡决定,其中,可重复读是 MySQL 的默认级别。
范式
https://www.cnblogs.com/wsg25/p/9615100.html
https://blog.csdn.net/wenco1/article/details/88077279?utm_medium=distribute.pc_relevant.none-task-blog-baidulandingword-2&spm=1001.2101.3001.4242
1NF:列不能再被分割
2NF:属性必须完全依赖于主键, 消除非主属性对主码的部分函数依赖
- 确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)
eg: 1. 主键:(身高和体重)-> 非主属性:肥胖指数:身高不能决定肥胖指数,体重也不能决定肥胖指数,只有(身高和体重)才能决定 肥胖指数,所以肥胖指数完全函数依赖于(身高和体重)
- (学号和身份证)–>姓名,身份证–>姓名:姓名部分函数依赖于(学号和身份证)
3NF:所有的非主属性不依赖于其他的非主属性,消除传递依赖
BCNF:消除任何属性(主属性和非主属性)对关系键的部分函数依赖和传递函数依赖
ps
https://blog.csdn.net/sumaliqinghua/article/details/85872446
- 键字=码字;主键=主码=主关键字;候选键=候选码=候选关键字
- 外键(foreign key):子数据表中出现的父数据表的主键,称为子数据表的外键。
应用
查找 Person 表中所有重复的电子邮箱
-方法一:select Email from Person group by Email having count(Email)>1
- 方法二:
select distinct a.Email from Person a,Person b where a.Email = b.Email and a.Id != b.Id;
同步 VS 异步
- 同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
- 临界区(管程):通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。
- 互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。互斥量比临界区复杂。
- 信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目 。信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。
- 事件:通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作 。
- 异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
并行 VS 并发
-
并发(Concurrent):一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。
- 并发不是真正意义上的“同时进行”,只是CPU把一个时间段划分成几个时间片段(时间区间),然后在这几个时间区间之间来回切换,由于CPU处理的速度非常快,只要时间间隔处理得当,即可让用户感觉是多个应用程序同时在进行。如:打游戏和听音乐两件事情在同一个时间段内都是在同一台电脑上完成了从开始到结束的动作。那么,就可以说听音乐和打游戏是并发的。
-
并行(Parallel):当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
- 其实决定并行的因素不是CPU的数量,而是CPU的核心数量,比如一个CPU多个核也可以并行。
进程、程序、线程
-
程序: 存储在磁盘上, 包含可执行机器指令和数据的静态实体。 即进程或者任务是处于活动状态的计算机程序。
-
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例,即运行中的程序。一个运行着的程序,可能有多个进程。进程在操作系统中执行特定的任务。
- 程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
-
线程就是程序的执行路线,即进程内部的控制序列,或者说是进程的子任务。一个进程中可以有多个线程并发地运行。它们可以执行相同的代码,也可以执行不同的代码,但至少要有一个主线程。同一个进程的多个线程都在同一个地址空间内活动,因此相对于进程,线程的系统开销小,任务切换快。线程不拥有自己的资源,只拥有从属于进程的全部资源,所有的资源分配都是面向进程的。
PV操作原语
https://www.cnblogs.com/shengguorui/p/11205331.html
https://blog.csdn.net/wangjian530/article/details/80742564
-
一种实现进程互斥与同步的有效方法。
- 同步(等待、协作):谁在等待?等待什么?分析清楚了,同步就出来了;
- 互斥:哪些资源是共享的,共享资源需进一步思考有没有必要互斥;
-
PV操作与信号量的处理相关,p操作表示申请一个资源、v操作表示释放一个资源。
-
PV操作对于每一个进程来说,都只能进行一次,而且必须成对使用。在PV原语执行期间不允许有中断发生。原语不能被中断执行,因为原语对变量的操作过程如果被打断,可能会去运行另一个对同一变量的操作过程,从而出现临界段问题。如果能够找到一种解决临界段问题的元方法,就可以实现对共享变量操作的原子性。
P操作的主要动作
①S减1;
②若S减1后仍大于或等于0,则进程继续执行;
③若S减1后小于0,则该进程被阻塞后放入等待该信号量的等待队列中,然后转进程调度。
V操作的主要动作
①S加1;
②若相加后结果大于0,则进程继续执行;
③若相加后结果小于或等于0,则从该信号的等待队列中释放一个等待进程,然后再返回原进程继续执行****或转进程调度。
例子
S1:是否允许司机启动汽车的变量
S2:是否允许售票员开门的变量
driver()//司机进程
{
while (1)//不停地循环
{
P(S1);//请求启动汽车
启动汽车;
正常行车;
到站停车;
V(S2); //释放开门变量,相当于通知售票员可以开门
}
}
busman()//售票员进程
{
while(1)
{
关车门;
V(S1);//释放开车变量,相当于通知司机可以开车
售票
P(S2);//请求开门
开车门;
上下乘客;
}
}
注意:busman() driver() 两个不停循环的函数
缓存
https://blog.csdn.net/kongtiao5/article/details/82771694
缓存处理流程
- 前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
缓存穿透
-
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
-
解决方案:
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。
缓存击穿
-
缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
-
解决方案:
- 设置热点数据永远不过期。
- 加互斥锁
缓存雪崩
-
缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
-
解决方案:
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 设置热点数据永远不过期。
Linux
vim
替换
[addr]s/源字符串/目标字符串/[option]
-
参数说明:
- [addr]——表示检索范围,省略时表示当前行。
- “%”:表示整个文件,同"1,$";
- “.,$”:从当前行到文件尾;
- s:表示替换操作
- [option]:表示操作类型
- g:全局替换
- c:表示进行确认
- p:表示替换结果逐行显示(Ctrl+L恢复屏幕)
- 省略option:默认仅对每行第一个匹配串进行替换
- 如果在源字符串和目标字符串中有特殊字符,需要使用“\”转义
- [addr]——表示检索范围,省略时表示当前行。
-
全局替换:%s/源字符串/目标字符串/g
chown 改变文件或目录所属的组
chown [-R] 账号名称:组群 文件/目录
:chown myy fileb
--修改fileb的拥有者为myy
chmod 改变文件或目录的访问权限
chmod -R 777 /dir
- 777: 有3位,最高位7是设置文件所有者访问权限,第二位是设置群组访问权限,最低位是设置其他人访问权限。
- r(Read,读取,权限值为4):对文件而言,具有读取文件内容的权限;对目录来说,具有浏览目 录的权限。
- w(Write,写入,权限值为2):对文件而言,具有新增、修改文件内容的权限;对目录来说,具有删除、移动目录内文件的权限。
- x(eXecute,执行,权限值为1):对文件而言,具有执行文件的权限;对目录了来说该用户具有进入目录的权限。
crontab:定期执行
linux 任务调度的工作主要分为以下两类:
- 系统执行的工作:系统周期性所要执行的工作,如备份系统数据、清理缓存
- 个人执行的工作:某个用户定期要做的工作,例如每隔10分钟检查邮件服务器是否有新信,这些工作可由每个用户自行设置
语法:crontab [ -u user ] { -l | -r | -e }
- -u user 是指设定指定 user 的时程表,这个前提是你必须要有其权限(比如说是 root)才能够指定他人的时程表。如果不使用 -u user 的话,就是表示设定自己的时程表。
- 参数说明:
- -e : 执行文字编辑器来设定时程表,内定的文字编辑器是 VI,如果你想用别的文字编辑器,则请先设定 VISUAL 环境变数来指定使用那个文字编辑器(比如说 setenv VISUAL joe)
- -r : 删除目前的时程表
- -l : 列出目前的时程表
时间格式:f1 f2 f3 f4 f5 program
- 其中 f1 是表示分钟,f2 表示小时,f3 表示一个月份中的第几日,f4 表示月份,f5 表示一个星期中的第几天(0 - 7:星期天为0)。program 表示要执行的程序。
- 当 f1 为 * 时表示每分钟都要执行 program,f2 为 * 时表示每小时都要执行程序,其馀类推
- 当 f1 为 a-b 时表示从第 a 分钟到第 b 分钟这段时间内要执行,f2 为 a-b 时表示从第 a 到第 b 小时都要执行,其馀类推
- 当 f1 为 */n 时表示每 n 分钟个时间间隔执行一次,f2 为 */n 表示每 n 小时个时间间隔执行一次,其馀类推
- 当 f1 为 a, b, c,… 时表示第 a, b, c,… 分钟要执行,f2 为 a, b, c,… 时表示第 a, b, c…个小时要执行,其馀类推
进程
所谓进程就是系统中正在运行的程序,进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就是创建了一个进程,在这个过程中操作系统对进程资源的分配和释放,可以认为进程就是一个程序的一次执行过程。
Linux下有三个特殊的进程:idle进程(PID=0),init进程(PID=1),和kthreadd(PID=2)
- idle进程由系统自动创建,运行在内核态。idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换。
- kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间,负责所有内核进程的调度和管理。它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthread的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程 。
- init进程由idle通过kernel_thread创建,在内核空间完成初始化后,加载init程序。init进程由0进程创建,完成系统的初始化,是系统中所有其他用户进程的祖先进程。
- Linux中的所有进程都是由init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成后,init将变成为守护进程监视系统其他进程。
- 所以说init进程是Linux系统操作中不可缺少的程序之一,如果内核找不到init进程就会试着运行/bin/sh,如果运行失败,系统的启动也会失败。
死锁
https://blog.csdn.net/hd12370/article/details/82814348
- 多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
产生死锁的原因
-
竞争资源
- 系统中的资源可以分为两类:
- 可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
- 不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
- 竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
- 竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
- 系统中的资源可以分为两类:
-
进程间推进顺序非法
- 若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
- 当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁。
- 若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
产生死锁的必要条件
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。
避免死锁
- 加锁顺序(线程按照一定的顺序加锁)
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
- 死锁检测: 画出资源分配图 =》 简化资源分配图
- 资源分配图
- 用长方形代表一个进程,用框代表一类资源。由于一种类型的资源可能有多个,用框中的一个点代表一类资源中的一个资源。从进程到资源的有向边叫请求边,表示该进程申请一个单位的该类资源;从资源到进程的边叫分配边,表示该类资源已经有一个资源被分配给了该进程。
- 死锁定理
- 如果资源分配图中没有环路,则系统没有死锁;
- 如果资源分配图中出现了环路,则系统可能有死锁。
- 资源分配图
预防死锁
- 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
- 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
- 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
- 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
解除死锁
- 资源剥夺法
- 挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源,而处于资源匮乏的状态。
- 撤销进程法
- 强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。
- 进程回退法
- 让一(多)个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而不是被剥夺。要求系统保持进程的历史信息,设置还原点。
cookie VS session
Cookie:登录有效期
- Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
session
-
客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。
-
Session是由应用服务器维持的一个服务器端的存储空间,用户在连接服务器时,会由服务器生成一个唯一的SessionID,用该SessionID 为标识符来存取服务器端的Session存储空间。而SessionID这一数据则是保存到客户端,用Cookie保存的,用户提交页面时,会将这一 SessionID提交到服务器端,来存取Session数据。
-
新开的浏览器窗口会生成新的Session,但子窗口会共用父窗口的Session。
-
Session的使用比Cookie方便,但是过多的Session存储在服务器内存中,会对服务器造成压力。
-
session 生存周期到浏览器页面关闭,cookie可以设置失效时间。
-
cookie不是很安全(可以加密),别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。
-
单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。Session对象没有对存储的数据量的限制,其中可以保存更为复杂的数据类型。
TCP vs UDP
- TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。
- 使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。
- 虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。
Shell
传递参数
在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n
。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……
- $# 传递到脚本的参数个数
- $* 以一个单字符串显示所有向脚本传递的参数。如"$*“用「”」括起来的情况、以"$1 $2 … $n"的形式输出所有参数。
- $$ 脚本运行的当前进程ID号
- $! 后台运行的最后一个进程的ID号
- $@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。如"$@“用「”」括起来的情况、以"$1" “$2” … “$n” 的形式输出所有参数。
- $- 显示Shell使用的当前选项,与set命令功能相同。
- $? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
RAID(Redundant Array of Independent Disk 独立冗余磁盘阵列)
- 最常用的四种RAID为 RAID 0、RAID 1、RAID 5、RAID 10
Kubernetes
POD生命周期
- 挂起(Pending):API Server创建了Pod资源对象并已经存入了etcd中,但是它并未被调度完成,或者仍然处于从仓库下载镜像的过程中。
- 运行中(Running):Pod已经被调度到某节点之上,并且所有容器都已经被kubelet创建完成。
- 成功(Succeeded):Pod 中的所有容器都被成功终止,并且不会再重启。
- 失败(Failed):Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。
- 未知(Unknown):因为某些原因无法取得 Pod 的状态,通常是因为与 Pod 所在主机通信失败。
C\C++ 公有、私有、保护成员 && 公有、私有、保护继承
公有:可以被类对象及其所有成员访问
- 外界(类外)能够直接访问该成员,通过"."运算符
- 一般函数成员定义为公有成员
- 通过调用公有函数成员实施规定的操作
- 外界与类之间起着接口的作用
私有:不能被类对象直接访问
- 外界(类外)不能够直接访问该成员
- 一般数据成员被定义为私有成员
- 使得成员被封装隐藏起来,外界不能够随便修改对象的私有数据成员
- 只有通过类中公有函数对数据进行修改,保证数据的安全性
- private可以省略不写,在没有关键字修饰成员类型时默认为私有成员
保护:派生类可访问
- 外界部分区域能够访问该成员,即某些数据或函数成员在类外被有限制的访问
- 私有是对外界完全封闭,公有是完全开放,保护是介于两者之间
继承方式
- 私有继承 和公有继承 的区别 在于 基类的公有成员能否在派生类的外部访问。
- 在保护继承中,基类的公有成员和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员不可直接访问。
- 假设base派生出了push类,而push类又派生出了 push++ 类
- 在私有继承中,base 中的保护成员和公有成员在push中都是私有成员,push++不能访问base中的任何成员
- 而在保护继承中,base 中的保护成员和公有成员在push中都是保护成员,push++可以访问base 中的保护成员和公有成员,但是在push和push++类外都不能访问。
C\C++二维指针初始化
方法一:
int **dp1;
dp1 = (int **)malloc(sizeof(int *) * row);//分配指针数组,计算行的大小
for(int i=0; i<row; i++)
{
dp1[i] = (int *)malloc(sizeof(int) * (column));//分配每个指针所指向的数组,再计算列
}
方法二:
int*ptr[row];
for(int i=0;i<row;i++)
{
ptr[i]=(int*)malloc(sizeof(int)*column);
}
C++ 析构函数 ~func()
- 用在类中的析构函数之前,表示该函数是析构函数。如类A的析构函数。
class A
{
//...
~A(){}//析构函数
};
- 类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
- 析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
C++ vector
操作
- v.push_back(k); 尾插元素;
- v.insert(it,k); 在任意位置插入元素;
- v.eraser(it,it+k); 删除任意元素;
- v.size(); 容量;
- v.clear(); 清空容器;
- v.empty(); 判断容器是否为空;
- reverse(v.begin(),v.end());反转任意段元素
- sort(v.begin(),v.end(),cmp);sort排序默认由小到大,cmp可以自由规定排序规则。
声明
迭代器声明:
vector<int>::iterator it;// vector迭代器
初始化
vector<int> vec = {1,2,3,4,5,6,7}; //c++11的初始化用法
vector<int> vec; //里面没有元素,直接用下标访问会吐核 //需要使用push_back()进行尾添加
vector<int> vec(10); //初始化10个元素为0的动态数组
vector<int> vec(10,100); //初始化10个元素为100的动态数组
string
http://c.biancheng.net/view/400.html
- string.length()
- string.compare()
- string.replace()
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
cout<<"------------[string] 分割线-------------\n"<<endl;
string s;
// 输入读取串 getline(cin,s);
s="z2345";
s+="6789";
string::iterator it; // string迭代器
it=s.begin();
s.insert(it+1,'p'); // string插入字符
s.erase(it+2,it+4); // 删除[it+2,it+4)元素
s.replace(6,2,"golfz"); // 替换下标为6长度为2的串
cout<<s.find("z")<<endl; // 查找子串,返回第一个出现的位置
cout<<s.length()<<endl; // 返回串的长度
cout<<s<<endl;
vector<string> ff; // string对象作为vector对象
ff.push_back("Jack");
ff.push_back("Tom");
ff.push_back("Mchical");
cout<<ff[2]<<endl;
return 0;
}
string.h包
-
int strcmp(const char *str1, const char *str2)
:把 str1 所指向的字符串和 str2 所指向的字符串进行比较。 -
void *memset(void *str, int c, size_t n)
:复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。 -
char *strcpy(char *dest, const char *src)
:把 src 所指向的字符串复制到 dest。char *strncpy(char *dest, const char *src, size_t n)
:把 src 所指向的字符串复制到 dest,最多复制 n 个字符。
-
char *strchr(const char *str, int c)
:在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。char *strrchr(const char *str, int c)
:在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置。void *memchr(const void *str, int c, size_t n)
:在参数 str 所指向的字符串的前 n 个字节中搜索第一次出现字符 c(一个无符号字符)的位置。
-
char *strtok(char *str, const char *delim)
:分解字符串 str 为一组字符串,delim 为分隔符。 -
void *memmove(void *dest, const void *src, size_t n)
:另一个用于从 src 复制 n 个字符到 dest 的函数。 -
size_t strcspn(const char *str1, const char *str2)
:检索字符串 str1 开头连续有几个字符都不含字符串 str2 中的字符。char *strpbrk(const char *str1, const char *str2)
:检索字符串 str1 中第一个匹配字符串 str2 中字符的字符,不包含空结束字符。也就是说,依次检验字符串 str1 中的字符,当被检验字符在字符串 str2 中也包含时,则停止检验,并返回该字符位置。
哈希
- 哈希表中存放指针(下标),指向数据
- 哈希函数是根据关键字设计的。
- 存放位置:(关键字)% (数组大小)
- 数组大小一般设计为质数,因为需要均匀的散布。
- 搜索二叉树O(log2N),而哈希表发挥稳定的话最快可达O(1)。
- 缺点:
- 表越满,冲突越多。
哈希冲突
链地址法
- 加入next指针,遇到冲突的时候,把数据写到next位置。
- 对于相同的值,使用链表进行连接。
开放地址
线性探测法
- 如果遇到冲突:新位置 = 原始位置 + i (i是查找的次数)
- 易产生堆聚现象:存入哈希表的记录在表中连成一片
平方探测法
- 新位置 = 原始位置 + i2
- 不扎堆
伪随机探测
- 按顺序决定值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来值的基础上加上随机数,直至不发生哈希冲突。
散列
Hi=(H(key) + di) MOD m, i=1,2,…, k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列.
- 线性探测再散列: di=1,2,3,…, m-1
- 二次探测再散列: di=12, -12, 22,-22, 32, …, ±(k)2,(k<=m/2)
- 伪随机探测再散列: di=伪随机数序列
双哈希、再哈希法、双散列函数探查法
- 设置第二个哈希函数,hash2(关键字) = R-(关键字 mod R) R取比数组尺寸小的质数(二次哈希的结果在1-R之间,不会等于0)
- 新位置 = 原始位置 + i*hash2(关键字)
- 对于不同数据间距不同。
创建公共溢出区
- 把哈希表分为公共表和溢出表,如果发生了溢出,溢出的数据全部放在溢出区。
哈希表满了、散列表元素太多(装填因子太大)(70%)
再次哈希 =》哈希表扩充
- 新表的尺寸是旧表的2倍以上,选择一个质数
- 把之前的数据再次通过哈希计算搬到新表里
排序
https://www.cnblogs.com/onepixel/p/7674659.html
模板
堆排序(1040)
#include <iostream>
using namespace std;
int arr[1001];
int len;
//大顶堆
void HeapAdjust(int s,int m)
{
int j;
int x = arr[s];
for(j = 2*s; j <= m; j*=2)
{
if(j < m && arr[j] < arr[j+1])j++;
if(x >= arr[j])break;
//x 大于左右子树
arr[s] = arr[j];//交换位置
s = j;
}
arr[s] = x;
}
//建堆
void CreatHeap()
{
for(int i = len/2; i > 0; i--)
{
HeapAdjust(i,len);
}
}
//排序
void HeapSort()
{
CreatHeap();
int i,x;
for(i = len; i > 1; i--)
{
x = arr[1];
arr[1] = arr[i];
arr[i] = x;
HeapAdjust(1,i-1);
}
}
int main()
{
int i;
int T;
cin>>T;
while(T--)
{
memset(arr,0,sizeof(arr));
cin>>len;
for(i = 1; i <= len; i++)
{
cin>>arr[i];
}
//排序
HeapSort();
for(i = 1; i <= len; i++)
{
cout<<arr[i];
if(i < len)cout<<" ";
}
cout<<endl;
}
return 0;
}
Dfs(2102)
#include <iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;
const int MAXX = 10;
char graph[2*MAXX+1][MAXX+1];
int visit[2*MAXX+1][MAXX+1];
char dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};
int n,m,tt;
int ex,ey;
void dfs(int xx,int yy,int step,int &flag)
{
if(xx == ex && yy == ey)
{
if(step <= tt)
{
flag = 1;
return;
}
else
return;
}
for(int i=0; i<4; i++)
{
int tx = xx + dir[i][0];
int ty = yy + dir[i][1];
int tt = step + 1;
if(tx >= m || tx < 0 || ty >= (yy/n+1)*n || ty < (yy/n)*n || visit[ty][tx] || graph[ty][tx] == '*')
continue;
if(graph[ty][tx] == '#')
{
visit[ty][tx] = 1;
if(ty >= n)
ty = ty-n;
else
ty = ty + n;
if(graph[ty][tx] == '#' || graph[ty][tx] == '*' || visit[ty][tx])
continue;
}
visit[ty][tx] = 1;
dfs(tx,ty,tt,flag);
visit[ty][tx] = 0;
if(ty >= n && graph[ty-n][tx] == '#')
{
visit[ty-n][tx] = 0;
}
else if(ty < n &&
graph[ty+n][tx] == '#')
{
visit[ty+n][tx] = 0;
}
if(flag)
return;
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
int sx,sy;
memset(visit,0,sizeof(visit));
cin>>n>>m>>tt;
for(int i=0; i<2*n; i++)
{
for(int j=0; j<m; j++)
{
cin>>graph[i][j];
if(graph[i][j] == 'S')
{
sx = j;
sy = i;
}
else if(graph[i][j] == 'P')
{
ex = j;
ey = i;
}
}
//cout<<endl;
}
int flag = 0;
visit[sy][sx] = 1;
dfs(sx,sy,0,flag);
if(flag)
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
}
return 0;
}
BFS(1242)
#include <iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int MAXX = 200;
int dir[4][2] = {{1,0},{0,1},{-1,0},{0,-1}};
int n,m;
char graph[MAXX+1][MAXX+1];
struct node
{
int x,y,time;
friend bool operator<(node a,node b)
{
return a.time>b.time;
}
}head;
void bfs(int xx,int yy)
{
bool visit[MAXX+1][MAXX+1];
memset(visit,0,sizeof(visit));
visit[yy][xx] = 1;
priority_queue<node>q;
q.push((node){xx,yy,0});
while(!q.empty())
{
head = q.top();
q.pop();
if(graph[head.y][head.x] == 'r')
{
cout<<head.time<<endl;
return;
}
for(int i=0; i<4; i++)
{
int tx = head.x + dir[i][0];
int ty = head.y + dir[i][1];
int tt = head.time;
if(ty >= n || ty<0 || tx >= m || tx < 0 || visit[ty][tx] || graph[ty][tx] == '#')
continue;
if(graph[ty][tx] == 'x')
tt++;
q.push((node){tx,ty,++tt});
visit[ty][tx] = 1;
}
}
cout<<"Poor ANGEL has to stay in the prison all his life."<<endl;
}
int main()
{
while(cin>>n>>m)
{
for(int i=0; i<n; i++)
{
cin>>graph[i];
}
int ex,ey;
for(int i=0; i<n; i++)
{
for(int j=0; j<m; j++)
{
if(graph[i][j] == 'a')
{
ex = j;
ey = i;
}
}
}
bfs(ex,ey);
}
return 0;
}
链表(2019)
#include <iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;
typedef struct node* Link;
struct node
{
int data;
Link next;
};
const int MAXX = 100;
int main()
{
int n,m;
while(cin>>n>>m&&(n||m))
{
Link head = (Link)malloc(sizeof(node));
head->next = NULL;
Link p = head;
for(int i=0; i<n; i++)
{
Link k = (Link)malloc(sizeof(node));
cin>>k->data;
k->next = NULL;
p->next = k;
p = p->next;
}
p = head;
while(p->next != NULL)
{
if(p->next->data < m)
{
p = p->next;
}
else
{
Link k = (Link)malloc(sizeof(node));
k->data = m;
k->next = p->next;
p->next = k;
break;
}
}
p = head->next;
cout<<p->data;
while(p->next != NULL)
{
cout<<" "<<p->next->data;
p = p->next;
}
cout<<endl;
}
return 0;
}
二叉树(3999)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<stack>
using namespace std;
typedef struct node *Binary;
typedef int ElementType;
struct node
{
ElementType data;
Binary lchild,rchild;
};
Binary PreTree;
Binary BuildTree(Binary pT,int pov)
{
if(pT == NULL)
{
pT = new node;
pT->data = pov;
pT->lchild = NULL;
pT->rchild = NULL;
return pT;
}
else
{
if(pT->data < pov)
pT->rchild = BuildTree(pT->rchild,pov);
else
pT->lchild = BuildTree(pT->lchild,pov);
return pT;
}
}
void PreBinTree(Binary pT)
{
Binary T = pT;
stack<Binary>q;
bool flag = false;
while(T!=NULL||!q.empty())
{
while(T!=NULL)
{
if(flag)
cout<<" "<<T->data;
else
{
cout<<T->data;
flag = true;
}
q.push(T);
T = T->lchild;
}
if(!q.empty())
{
T = q.top();
q.pop();
T = T->rchild;
}
}
}
void Destroy(Binary *pT)
{
if(*pT == NULL)
return;
Destroy(&(*pT)->lchild);
Destroy(&(*pT)->rchild);
free(*pT);
}
int main()
{
int n;
int num;
while(cin>>n)
{
for(int i=1; i<=n; i++)
{
cin>>num;
PreTree =
BuildTree(PreTree,num);
}
PreBinTree(PreTree);
cout<<endl;
Destroy(&PreTree);
}
return 0;
}
并查集(1325)
void make_set()
{
for(int i=1; i<=MAXX; i++)
{
pre[i]=i;
}
}
int find_set(int a)
{
int r=a;
while(r!=pre[r])
{
r=pre[r];
}
int i=a;
int j;
while(r!=pre[i])
{
j=pre[i];
pre[i]=r;
i=j;
}
return r;
}
void union_set(int p1,int p2)
{
int s1=find_set(p1);
int s2=find_set(p2);
if(s2==pre[s1])
flag=false;
else if(s1!=s2)
pre[s2]=s1;
}
Prim
#include <iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int INF=0x0ffffff;
const int MAXX_VIL=100;
int n,m;
bool visit[MAXX_VIL+1];
int graph[MAXX_VIL+1][MAXX_VIL+1];
int lowcost[MAXX_VIL+1];
void Init();
void Init_prim();
void updata(int k);
void prim();
int main()
{
while(cin>>n>>m&&n)
{
int start,endd,cost;
Init();
for(int i=1; i<=n; i++)
{
cin>>start>>endd>>cost;
graph[start][endd]=(graph[start][endd]<cost?graph[start][endd]:cost);
graph[endd][start]=graph[start][endd];
}
Init_prim();
prim();
}
return 0;
}
void Init()
{
for(int i=1; i<=m; i++)
{
for(int j=1; j<=m; j++)
{
if(i==j)
graph[i][j]=0;
else
graph[i][j]=INF;
}
}
}
void Init_prim()
{
for(int i=1; i<=m; i++)
{
lowcost[i]=graph[1][i];
visit[i]=0;
}
visit[1]=1;
}
void updata(int k)//更新数据 以k为顶点或保持之前数据
{
for(int i=1; i<=m; i++)
{
if(!visit[i]&&lowcost[i]>graph[k][i])
lowcost[i]=graph[k][i];
}
}
void prim()
{
int sum=0;
for(int i=1; i<m; i++)//最后一个顶点不必再找临近点
{
int minn=INF;
int k=0;
for(int j=1; j<=m; j++)
{
if(!visit[j]&&lowcost[j]<minn)
{
minn=lowcost[j];
k=j;
}
}
if(minn==INF)//数据不完整
{
cout<<"?"<<endl;
return;
}
sum+=minn;
visit[k]=1;
updata(k);
}
cout<<sum<<endl;
}
Dijkstra(2112)
void Init_Dij()
{
for(int i=1; i<=t; i++)
{
dist[i]=cost[1][i];
flag[i]=false;
}
flag[1]=true;
}
int Dijkstra()
{
int k=1;
Init_Dij();
for(int i=1; i<=t; i++)
{
int minn=MAXX_SPOT*MAXX_TIME;
for(int j=1; j<=t; j++)
{
if(!flag[j]&&dist[j]<minn)//查找最小值
{
minn=dist[j];
k=j;
}
}
if(k==2)
return 0;
flag[k]=true;
for(int j=1; j<=t; j++)
{
if(!flag[j]&& minn+cost[k][j]<dist[j]) //以k为起点 更新总时间
dist[j]=minn+cost[k][j];
}
}
return -1;
}