递归思想:依次往下调用(这个调用是调用的自身函数),调用到最后一层时得到的是最里层(即最下层)的数据,然后一层一层往上代(相当于嵌套函数),即可得到最终结果。
ElemType是元素类型。若顺序表中存放的是ABCD,则可写为char;存放的是多项式系数,则写为float。
也可以单独提出来先定义。如typedef char ElemType;
- 数组静态分配:数组的名字中存放的是数组中的首元素data[0]的地址,即此数组的基地址。
- 数组动态分配:*data形式上是指针变量,但是它也可以表示一个数组,可以用它来存放数组中第一个元素的地址。
- malloc()的参数要求是整数,即这个分配的这块空间的字节数。
- MaxSize是根据需要自定义的。
- ElemType决定了动态申请的空间怎样去划分。若是char类型,则是1字节1个空间;若是int类型,则是4字节1空间。。* 代表分配好的这个结果是一个指针。。外面的()表示强制类型转换。
- (ElemType*)表示指向数据元素的指针。
- L.data即获得了数组中这一大片空间的基地址。
- free()中的参数要求是一个指针变量。
swap函数中最后m、n释放了。因此最终结果a、b并没有得到改变。
*m改变的是其中的内容。因此a、b值互换。
交换的是m、n的地址,对a、b没有影响。
- 注意sub()中b[]方括号中不可以指明大小
- 最终结果相当于重新给a赋值。最终输出world
也可以理解为j是一个地址。i和j共同使用同一个空间(即地址相同)。
对m、n操作实际上就是对a、b操作。因此最后a、b值成功互换。
数据结构就是操作对象及操作对象之间的关系。
早期,计算机主要用于数值计算。
现在,计算机越来越多地用于非数值计算。
- 数据是计算机程序加工的原料。
- 数值型数据通常是指可以加减乘除、平方开方等的算数运算的。
- 对于非数值型问题,通常会关心 每个个体的具体信息 以及 每个个体之间的关系。
- 同一个数据对象里的数据元素,可以组成不同的数据结构。
- 不同的数据元素,可以组成相同的数据结构。
例中字符串数组是有先后顺序的。(此顺序用内存中的存储位置来表示——在前面的元素存储时存在前面,即按照元素的位置存储)
指针即地址。
通讯录便是常见的索引存储结构。
可与《查找》一章联系学习。
定义一个ADT,就是在“定义”一种数据结构。
【确定了ADT的存储结构,才能“实现”这种数据结构】
基本操作名(参数表):
- 如求圆的面积,其中半径为r。则记为 area® 。。【这种便是赋值参数】
- 如求乘方,x的y次方。则记为power(x,y)
- 如对一个图像G进行放大n倍的操作。则记为scale(&G,n)。。【注意:加上&则表示最后返回G作为结果,这种便是引用型参数。】
复数构造即加减的具体实现:
#include
typedef struct {
float realpart; //定义实部
float imagpart; //定义虚部
}Complex;
//构造复数
Complex assign(float real,float imag) {
Complex c;
c.realpart = real;
c.imagpart = imag;
return c;
}
//两复数求和
Complex add(Complex A,Complex B){
Complex c;
c.realpart = A.realpart + B.realpart;
c.imagpart = A.imagpart + B.imagpart;
return c;
}
//两复数求差
Complex minus(Complex A, Complex B) {
Complex c;
c.realpart = A.realpart - B.realpart;
c.imagpart = A.imagpart - B.imagpart;
return c;
}
int main()
{
Complex b,c,d,e;
b = assign(2.0, 4.0);
c = assign(1.0, 1.0);
d = add(b, c);
e = minus(b, c);
printf("实部为2虚部为4的复数是:b=%f+%f i\n", b.realpart, b.imagpart);
printf("实部为1虚部为1的复数是:c=%f+%f i\n", c.realpart, c.imagpart);
printf("两复数相加是:d=%f+%f i\n", d.realpart, d.imagpart);
printf("两复数相减是:d=%f+%f i\n", e.realpart, e.imagpart);
return 0;
}
typedef用于声明一个已有的数据类型的新名字。
算法是求解问题的步骤。
- 输入取自于某个特定的对象的集合。
- 输出是与输入有着某种特定关系的量。
矛盾的意思是有时候为了节省时间可能会消耗空间,有时为了节省空间可能会花费更多的时间。【因此需要综合平衡】
【 更多的是选择事前分析法】。
若假设每条语句所需的时间均为单位时间,则可以暂时不考虑。
- 行一 是从1到n,循环变量执行n次。但是还需要最后执行一次来判断,即条件不成立则退出循环。因此执行n+1次。
- 行二 是外层循环的循坏体,循环体要执行n次;每执行1次这句语句,都要执行n+1次。因此执行n(n+1)次。
- 行三 是中层循环的循环体,外层循环每执行一次,中层循环执行n次,而外层循环执行了n次。因此执行了n*n次。
由例可知:很多算法执行时间与输入的数据有关。
一般只考虑最坏和平均。
口诀:【常对幂指阶】
也可直接写成O(lgn)。即不用去考虑底数具体是多少。
单位历年中,第一年6,第二年17…以此构成前后结点的线性关系。
把每一项的系数拿出来存成一个线性表。
系数的下标 i 还可隐含地表示指数。
线性表A中有4项,B中有3项。
- 假设每项指数均不同,则最多可得7项。
- 极端情况下每一项刚好指数一样,系数是相反数,则最少可得0项。
这里指的类型实际上就是抽象数据类型。
元素之间的关系由存储单元的邻接关系来体现。
找地址是O(1)
数组长度定义模板
初始就分配很大的内存空间是不可行的。
左边框是静态,右边框是动态。
画线的较难。
- 行1:&L即通过引用型变量将构造好了的顺序表L返回。
- 若成功构造则是为变量L分配空间。
- 行2:动态分配,获得这个数组中的基地址。
- 行4:若成功分配,注意此时里面没有元素,因此长度为0。
- 行3:异常处理。OVERFLOW是溢出的意思,返回的是-2。
注意:销毁的前提条件是该线性表存在。
length代表线性表元素的个数,将其令为0。则表示线性表内无元素,为空。
返回1表示为空,返回0表不为空。
算法的时间复杂度为O(1)。
详见0.6 结构类型的比较
注意:
- 如果有n个元素,那么插入的位置只能是0~n+1位置上。
- 如果线性表容量已经放满,还要插入数据的话,会存在溢出情况。
【综上,需要首先判断插入位置i是否合法以及存储空间是否已满】
插入中的移动方式:先后面的元素往后移,再前面的元素往后移
算法时间主要耗费在移动元素的操作上。
第2步是可以省略的。即直接删除。
删除中的移动方式:先前面的元素往前移,再后面的元素往前移
算法时间主要耗费在移动元素的操作上。
- 包含两部分数据,可用高级语言中的结构体。这个结构中包含两个成员,即data、next。
- data的数据类型,是由处理问题的本身决定的。如data是多项式中的系数,则是float。因此把需要的元素定义成【ElemType】。
- next是指针型。它的类型取决于它所存放地址的元素是什么类型。如data是int整型,则next应定义为:int *p;(意思是指向整型变量的指针)。p=&a;(意思是p里存放的是a的指针)。
- next指向的是和自己的结构完全一样的变量的地址。【因此出现下一点中嵌套的定义】
- struct Lnode是指向这种类型的指针。(这种类型又包括data、next两个成员的一个指针)。。即用自己来定义自己【嵌套的定义】
- 注意:最后一行中Lnode,*LinkList都是类型。【若要定义指向结点的指针,可以是 Lnode *L; 也可以是LinkList L;】
【销毁单链表是将所有结点,包括头结点和头指针都销毁。内存中不存在该单链表】
- 用指针变量p操作当前想要操作的结点。
- 让p一开始指向头结点,然后把指向的结点释放。【一个变量要想指向某一个空间,就把这个空间的地址赋给他】。如若想指向变量a,就是p=&a ; 。【头结点的地址在头指针里存放】。因此让指针p指向头结点,则是 p=L;
- 得先让L指向下一个结点,才能删除头结点。【头结点的指针域存放了下一节点的地址】,即L=L->next; 。
- delete p; 是c++中语法。在c语言中是free§;
- L!=NULL也可表示成L。
- 第一步p=L->next; 即p指向首元结点。
- 得先把下一个结点确定好,才能释放掉前结点。即引入指针变量q预先保存下一个结点的地址。
- 最后一步p、q指针都指向空后,要将头指针设置为空。
让指针变量p指向首元结点,若不为空,计数+1且指向下一个结点。到结点为空时结束。
- 定义指针变量p可以用 Lnode *p; 这样可读性更好。
- i是元素个数计数。初始为0。
- while§表示p指向的结点不为空。
- 如果输入的值超过了元素个数,当指针所指为空,则不需往后找了。
- 如果i=-1,即小于初始下标的值。一开始便不用找了,因为是错的。
- 初始化一行:首先p指向首元结点,j=1表示这是第一个结点。
- while(p&&j
- 循环条件:p不为空且当前指针所指的结点值同要找的数据不相同时。
- 返回:找到了就是那个结点,找不到则返回空。
- 不可以。先执行2的话p->next指向的地址就变了。
- 如果非要先执行2,则找个暂存指针q存放ai的地址。
- 插入算法中i的合理取值范围是1到n+1,删除算法中i的合理取值是1到n。
- p指向的是ai-1结点,q指向的是ai结点。
也叫前插法。
也叫后插法。
- 查找:最好的情况是第一个元素就找到,即1次;最坏的情况是最后一个元素才找到,即n次。
使用循环链表时,最多的是使用带尾指针的循环链表。
GetElemP_DuL(L,i)是查找链表L中的第i个元素的操作。
双向循环链表是消耗了空间来提升时间效率。
- 基地址+(表长-1)=最后一个元素地址。即pa_last=LA.elem+LA.length-1;
空间复杂度是O(1)
- 栈如枪装子弹的弹夹; 队列如日常生活的排队。
- 栈只能在队尾插入,在队尾删除。
- 队列只能在队尾插入,在队头删除。
- top和base可以定义成整型,用它们存放数组的下标。【这种方式就是用下标来引用数组中的元素】
- top和base还可以定义成指针。两个指针相减即两个指针之间的元素个数。 【注意这两个指针必须指向同一数组才可以相减】
if(S.base)表示当前栈不为空。
函数的调用就是栈。
递归的时间效率较差。
- 定义*base用来指向数组中的首元素。
- front、rear称作指针,但不是指针变量。是用来表示数组中元素的下标位置的。如front指向队头元素的下标。
- Q.base[Q.rear]=x; 将元素放在尾指针所指的位置
- 当Q.rear+1达到MAXQSIZE时,则对MAXQSIZE模后可以得到0。(即回到了队尾)
- x=Q.base[s.front]; 将表头指针所指的元素赋给x,即要删除的值。
分配的是一个数组空间,数组的首元素地址实际上就是一个指针。因此定义的是Q.base。【即base指向数组空间的首地址】
*QuenePtr 指向结点的指针类型。
在内存中分配新结点,实际上很少会内存连一个结点的位置都没有,因此一般很少判断是否溢出。
绿色字是直接使用Q.rear。不额外利用新的空间。
- 空串是里面不包含任何字符,连空格都没有。即""
- 空格串里面有若干个空格。即" "
- 主要d中含有一个空格,因此长度是8。
- d的字串不包含c是因为字串的字符应该是连续的。d中的空格断开了BEIJING。
可以用BF或KMP算法实现。
复制一次,长度变成2m,每次截取m个字符,看是否匹配。匹配了就说明感染了。
匹配的字数,看m等于几。如果是3个字符,则比较3遍就够了:
实际中,顺序串用的更多。因为对字符串进行插入和删除操作是比较少的;多数是匹配和查找运算(用顺序更方便)。
- 顺序:逻辑关系直接映射物理结构。
- 后面在研究串的算法时,通常从1号位置往后存储,0号位置闲置不用。
- 假设一个块放50个。
- 常用下面那种结构,称为块链。
BF即暴力破解法。简单但时间效率相对较差。
子串即查找的内容。
第0位不存储。第一位发现一样,则继续匹配下一位。
发现不匹配后,再往后移动一位。用新的4位来比较。此时需要将i回退到第二个字符的位置,j从头开始。
i=i-j+2:
- +T从1移动到 j,移动了j-1个长度;此时S移动的也是j-1。S目前位置是i,用目前的位置i减去移动的位置j-1得到移动前初始位置1(S也是从1开始移动的)。因此【i-j+1】表示移动前的初始位置。
- 【(i-j+1)+1】就是初始位置的下一个位置(满足了往后移一位再比较)。
回溯就是造成这个算法效率比较低的主要原因。
pos表示位置。
- 这个算法是从第一个位置开始查找。
- 红框中>=还是>的问题见下面一点。
定义了pos,则开始比较的值更为灵活。
分析时间复杂度:(模式T含有m个字符)
- 书上是>,王卓老师讲义是>=。由以下代码实现,表面出现结果一致,均不影响最终答案。
- 原因为:最终j=T.length时,满足了 if的条件。但是while循环还是没有结束的,还是满足while循环条件的。因此还要执行一下while循环。执行一次while循环后,i+1,j+1。然后就j一定大于T.length,此时条件不满足while循环条件,就终止了。因此【=是满足的】
#include
#include
#include
#define MAXSIZE 20
typedef struct SString
{
char ch[MAXSIZE+1];
int length;
}SString;
int GetLength(char *L)//得到字符数组的长度
{
int n = 0;
char *p = L;
while(*p!='\0')
{
n++;
p++;
}
return n;
}
void initSString(SString * L)//初始化串
{
char a[MAXSIZE];//定义一个辅助数组
scanf("%s",a);
char *p= L->ch;//定义一个字符指针,指向串里面的数组
strcpy(++p,a);//在数组的下标为1的位置开始赋值,注意为了方便,我们不采用0开始的下标
L->length = GetLength(a);//顺便给长度赋值
}
int index(SString S,SString T,int pos)//返回模式L2在主串L1中第pos个字符开始第一次出现的位置。如果不存在,则返回值为0
{
int i = pos;//初始化
int j = 1;
while(i<=S.length&&j<=T.length)//俩串都未比较到串尾
{
if(S.ch[i]==T.ch[j])
{
i++;
j++;
}else//指针回退,重新开始比较
{
i = i-j+2;
j=1;
}
}
if(j>=T.length)
{
return i-T.length;
}else
{
return 0;
}
}
int main()
{
SString S;
SString T;
printf("请输入主串\n");
initSString(&S);
printf("请输入模式串\n");
initSString(&T);
// printf("请输入要从主串的第几个元素进行匹配\n");
int a=1;
// int a;
// scanf("%d",&a);
if(index(S,T,a))
{
printf("匹配成功,在主串的第%d个位置匹配成功\n",index(S,T,a));
}else
{
printf("匹配失败\n");
}
return 0;
}
❀天勤率辉-KMP算法(易懂版)与王卓老师结合
建议看 天勤率辉-KMP算法(易懂版) 入门,真的很容易懂!
- KMP算法:快速地从一个主串中找出一个想要的子串。(可以做到仅仅后移模式串,比较指针不回溯)
- 这里指针位置所指元素不匹配。(指针左边元素完全匹配)
- 可看到指针前面已经匹配的串中 左右两端都有AB子串,他们完全匹配。则称为模式串的公共前后缀。
- 这一步是KMP算法核心:直接移动模式串,使得【公共前后缀】的前缀直接移动到原先后缀所在的位置。
- 【最长公共前后缀】:如果模式串中有多对公共前后缀,要取最长的那对。
- 其实找公共前后缀时只用看模式串即可。
- 使公共前后缀的前缀移动到原先后缀所在的位置,这样做保证了当前比较指针所在的位置 左边的串上下是一致的
- 当前移动位置,模式串和主串匹配了,指针继续往后扫描。又发现如下不匹配。
- 再次寻找公共前后缀。
- 移动模式串,将公共前后缀的前缀移到后缀位置上
- 此时发现模式串已经超出主串的范围,就可以判定失败。即主串不含有与模式串相匹配的子串。
上述是未查找到的情况,接下来我们试验正确查找到的情况
❗注意字符串存放在一个字符数组中,数组在内存中不可能移动。上述所说移动只是一个形象化的方式。
我们以如下模式串研究:
- 为了方便处理,我们将该模式串放在数组中,并标出数组下标。【注意模式串是从下标为1的位置开始存储的】→(从0开始也可以,原理一样,只是大多数学校是从1)
- 假设该模式串可能和任意主串进行KMP算法。因此每一个位置上都可能发生不匹配。
- 假设第一个位置就发生了不匹配。
- 则把模式串移一位。重新开始比较。
- 在代码中的描述应该为:让模式串中1号位置的字符与主串下一个位置的字符开始比较。
- 如果二号位发生不匹配。
- 此时指针前的子串长度只有1,公共前后缀长度为0,于是移动后落在模式串中 指针左边的子串长度也为0。
- 在代码中的描述是:比较指针所指的位置就是主串中发生不匹配的位置,称为当前位置。此种情况下,拿 模式串的1号位 与 主串中当前位置 进行比较。
- 若3号位 发生不匹配。
- 继续找公共前后缀,此时指针左边的子串为AB,公共前后缀长度为0。与2号位情况相同。
- 若4号位发生不匹配。
- 寻找到公共前后缀
- 后面的类似前面的。
- 可发现一个规律【若公共前后缀长度为n,则在n+1号为与主串当前位开始比较】
- 可以看到除了第一句话外,其余所有的描述都类似。因此将第一句话标记为0。(当看到0,就按照第一句话描述的方式去处理)
- 则代码中可表示f(0)则按第一句话做。后面的每句话把开头的 号位的数字拿出来作为 这句话所要执行的操作的代号。
- 再结合上面的数组下标,将其放在一个数组中。
- 由此可以判断出模式串中任一位置发生不匹配时的操作。如模式串1位置发生不匹配,数组1位置值为0,则找到之前分析得到的操作(1号位与主串下一位比较)
- 因此 它叫做下一步数组,即next数组。
- next[j]和主串没关系,只和模式串有关系。
- 【从头开始的k-1个元素指的就是前缀】。如一个字符串abcd:a是前缀;ab是前缀;abc是前缀;但是abcd不是前缀。
- 【j前面的k-1个元素指的是后缀】。如一个字符串abcd:d是后缀;cd是后缀;bcd是后缀;但是abcd不是后缀。
- 如果字符串的前缀和j这个位置前面的若干个元素相等的话,则获得k的值。【k的值可能不止一个,因此需要找到最大值作为下一个j的位置】
- 如果上述没有找到k,此时j也不为1,则是其他情况。
可以看成非线性原因:一个元素不止有1个前驱和后继。如a11前驱有a01和a10。
因此把二维数组看成特殊的线性结构,是线性结构的扩展。
typedef array1 arry2[m]
- arry2[m]中有m个元素,这m个元素是array1类型;
- 而array1类型是有n个元素的一维数组;
- 因此这句话是一共有m个 含有n个元素的一维数组。因此也定义出一个二维数组。
画波浪线的地方表示的是前驱后继的序偶关系。
C、Java都是行优先。
- 先分析元素前有几行,如a[ 2 ] [ 1 ]前面有2行。。则a[ i ][ j ]前面有 i 行。 这i行每行都有n个元素,因此总共是in个元素。
再分析元素前有几列,如a[ 2 ] [ 1 ]前面有1列。。则a[ i ][ j ]前面有 j 行。 前面则有 j 个元素。- 因此a[ i ][ j ] 前面总共有 in+j 个元素。
- 这些元素总共占用的空间便是 L*(in+j)。
- a[ i ][ j ] 的地址便是首元素地址+L*(in+j)=a+L*(in+j)。
知道某个元素的位置:先确定他前面存了多少页,在数出该元素所在页前面的行数,再确定所在行前面的个数。
【K值就对应的值在一维数组中存放的位置】。
aij对应的k值如何求?
- 要判断他前面有多少个元素。
- 首先判断前面有多少行:前面有i-1行,第一行1个元素、第二行2个元素…因此前i-1行共有元素1+2+3+…+(i-1);
- 然后判断本行 aij前有多少个元素:j-1。
- 因此aij前面共有【1+2+3+…+(i-1)】+【j-1】。即k值。
树的定义就是个递归嵌套的定义。
凹入表示:用凹入的深度表示层次,凹入得越深表示层次越低。
- 结点:数据元素以及指向子树的分支。
- 一个树中根结点只有一个。
- 结点的度可以直接看后继结点的个数。
- 有共同双亲的结点称为兄弟结点。
- M的祖先:A、D、H。
- D的子孙:H、I、J、M。
- 树的深度:树中结点的最大层次。(也称为树的高度)。
❗二叉树不是树,也不是有序树。
等长会比较浪费。
c是非前缀码,其坏处是如果码中出现一个0便搞不清楚是a、b还是c中的第一个0。
b是前缀码。既不等长又是前缀码 是我们常用的。实现方法:哈夫曼树和哈夫曼编码。
- 叶子结点上是运算数,叶子结点的双亲结点都是运算符。
- 对这个二叉树进行【后序遍历】运算即可得到表达式。
数据元素的说明即根、结点、左孩子右孩子的说明。
- n是结点总数;B是边数;n1是度为1的结点数。
- 第一张图从下往上分析:每一个结点到它的双亲结点都要有一个分支,而只有根节点没有。因此如果有n个结点,那么边数等于结点数-1,即B=n-1。
- 第二张图从上往下分析:度为2的结点会产生2条边,度为1的结点产生1条边,叶子结点不产生边。
- TElemType是根据树的存储来决定的,如可以是int、char…
- bt是一个数组。
- 每个结点都可能有 2个结点,因此最多可能有2n个链域。
- 此题应该倒过来分析,孩子 一定有双亲,即有一条边(每个结点的前驱最多只有一个,而只有根结点没有)。
- 因此一共有n-1个结点有指针域,即一共有n-1个指针域。
重点研究前三种
- 三个的时间算法复杂度是一样的,每个结点都需要路过一次。
- 遇到某一个结点,如果不访问它,就需要找地方先存储,等回来的时候再访问。这时候便需要用到【存储空间】。因此需要有一个【栈】存储暂时不访问的结点。【最坏的情况是一个单支结点,即需要暂时存储n个结点】
前后序可以确定根,中序可确定左右子树。
p指代的是指向根结点的指针。
- 最后发现队列中元素为空了,层次遍历 便结束。
- 只知先序序列,不可以唯一确定二叉树。
- 因此补充好空结点,这样输出的才是唯一的。【可以用 空格符或# 表示空结点】
BiTree &T建立的是以链式存储的二叉树。
和先序遍历是类似的,都是根左右。
+1是根结点。
- 箭头表示执行流程。
- 往下执行函数,到了最后一层可得到值,再一层一层返回,得到最终值。
- 不为空,则又继续下一层,即以左孩子为参数,如果左孩子为空,则以右孩子为参数。
- 左右孩子都为空即为叶子结点。
- 左子树判断完了后,返回上层,开始右子树的逐层调用。
二叉链表中空指针域有n+1个。
- 如果没有后继,就继续为空。
- C为中序遍历第一个结点,因此左孩子的指针域继续为空。后继结点为B(看中序节点),因此右孩子指针结点指向B。
- 树去掉根结点就变成森林。
- 给所有森林加一个共同的根结点即为树。
R没有双亲,即双亲域为-1
- 往右走 可以找到所有兄弟结点
- 于是 往左走,然后一直往右走 可以找到所有孩子。
还是找双亲困难。但是可以根据需要 增加双亲结点的数据域(像(3))。
先序:123
中序:213
后序:312
- 0是根结点到自身的长度。
- 包含结点数相同 的树 的路径长度可能是不相同的。
【路径长度最短的不一定是完全二叉树】
某种含义不一定,具体看树用于某种场合。如五分制成绩中的权代表分数占的比例(5%是<60所占比例)。
- 满二叉树不一定是哈夫曼树。
- 哈夫曼树中权越大的叶子离根越近。
- 具有相同带权结点的哈夫曼树不唯一。
- n-1个新结点,每个结点都是度为2。【所有结点的度都不为1】
- 得到的哈夫曼树最高就是n-1层。因为经过n-1次合并。
- weight是权值。
- H可以表示指针,也可以表示数组。
- 从下往上找并判断是左孩子还是右孩子。(左孩子记为0,右孩子记为1)
- 一直到根结点为止
- 找到的编码需翻转,如G从下往上的为00001,则最终G的哈夫曼编码应该为10000。
得到的一种哈夫曼编码,共需1596位。
- 无向图称为边,有向图称为弧。
- ()表示两个没有先后关系;<>表示两个有先后关系,即序偶。
信息即权。
- 邻接矩阵是n*n的,n是顶点数。
- 邻接矩阵是唯一的
用的头插法
- firstin第一条入弧,firstout第一条出弧
- tailvex弧尾位置,headvex弧头位置,hlink弧头相同的下一条弧,tlink弧尾相同的下一条弧
一条路走到底后回退到上一个位置。(最后回退到顶点的位置)
ps:相关实现过程往下翻
起点是2号顶点,因此辅助数字visited[2]=1
访问1顶点
访问3
访问5
访问5时,发现相邻结点均访问过,因此回退到3
访问3时,也都访问过,回退到1
访问4
访问6
然后发现均访问过,则一步步往后退
从起始点开始,访问所有的邻接点,然后再访问邻接点的邻接点。直到所有顶点都被访问。
此通信网便是生成树。因此解题便需要找到最小生成树。
【点开始:任意取一个点,找连接中最小的边;形成整体后再找最小的边】
【适用于点少、边数多的算法】
【先写出所有顶点,找权值最小的,验证是否构成一个环(若构成一个环,则退回上一步找笔直大一点的环)】
【适用于点多的图】
接下来看v0直接到各个顶点 与 v0经过v2到达各个顶点的 比较,若减少则修改值,若没减少则不变。
(v2找到了最短路径,后面的都不用看了)
有两个一样的,则选择下标小的那个
关键路径就是影响工程进度的关键
最迟发生时间是需要 所有流程需要时间的
汇点的最迟发生时间与最早发生时间相同
e(i):a1是从v1到v2,于是查找v1的ve(v1),即0,因此a1的e为0。
l(i):a1是 从v1到v2,于是查找v2的vl(v1),再减去a1的持续时间(即权值6)。即6-6=0
计算差值
差值为0的就是关键活动。
由关键活动构成的路径就是关键路径(蓝色标志)。
查找过程
块间有序,块内可以无序
把中间的数放在第一层,然后最小的放在左边,最大的放在右边。
线性函数中 自变量和因变量是一一对应的关系。(就不会有冲突的函数值)
3是因为使用了临时变量,交换赋值了3次。
缺点:不是对所有情况都适用