0%

个人理解:

两者虽然都是用芯片组成新的内存条,但是位拓展是逻辑上让这些芯片属于一个新的内存条,拓展了每个存储单元的大小。

而多体交叉编址并未改变其存储单元大小,只是让数据以他的规则进行排序

以本题为例,已经确定为按字节编址,故每个存储单元位数都为一字节,自然不能使用位拓展将存储单元的位数扩大

则必须使用多模块交叉编址

多模块交叉编址的分类

顺序编址(高位交叉)

这种情形下,我们按地址的前几位进行存储数据,如果有4个芯片,则前两位作为区分所在模块的位置如01000000——011111111,将数据顺序存储起来。

但是这样无法让他们并行启动,假如总线周期是1,而一个存期周期是4,那么当总线占用完传完数据后只能等待3才能继续下一个存储单元的使用

交叉编址(低位交叉)

交叉编址则是以后几位为标准进行存储,这样就能把顺序数据均匀的分布在四个芯片上了

如何区分这四个是轮流启动还是一起启动呢?要看总线宽度和DRAM的位数,如果4个DRAM的位数合在一起是32位,而总线宽度也是32,则可以一起启动,四个一起传输到总线上

如果一个DRAM的位数和总线宽度一致,那么每次总线只能传输一个存储单元,则需要流水线的方式来进行传输。模块个数也就是大于等于T/r,否则总线周期已经结束,下一个芯片的存取周期还没结束只能干等着

炫耀一下我新做的布丁蛋糕!

陀翁的最后一舞《卡拉马佐夫兄弟》并未写完,其被视为文学史上的一颗明珠和高峰

老卡拉马佐夫和卡拉马佐夫四兄弟几乎代表了陀翁本人的四个课题

也就是译者荣如德说的四个R

心灵隐秘(Revelation of Man’s secret heart)、革命(Revolution)、俄罗斯(Russia)和宗教(Religion)

Revelation of Man’s secret heart

卡拉马佐夫父子之间的关系可以说是畸形,老卡拉马佐夫为人卑鄙丑陋,几乎极全人类的劣根性为一身,荒淫无度唯利是图的小人模样透过纸笔将其扭曲的哲学思维表现出来。但是老卡拉马佐夫为何却偏爱阿辽沙这个小儿子,阿辽沙并非逢场作戏假意迎合父亲,而是的确怀抱着博爱的精神去看待自己这荒唐的一家子。老卡拉马佐夫对阿辽沙表现出来的微不足道的爱实际上是这个人物正面的另一面卑微胆小渴望得到一部分认可,但是他的丑陋就像译者所说相当一部分就是取自陀翁的老农奴主父亲

再看我们的三位卡拉马佐夫兄弟,老大米剑卡是陀思妥耶夫斯基当兵时真实的写照,无法控制情绪又有相当高的自尊心,他一方面为了与父亲争夺情妇能愤慨地到处宣扬要杀了父亲,为了情妇抛弃未婚妻,而一方面又对自身崇高的爱情保持敬重,对自己用未婚妻的钱去背叛未婚妻感受到前所未有的痛苦。米嘉其人我觉得正是卡拉马佐夫兄弟这部作品的精髓所在,他身为人的两面性,他的善恶在不断的拉扯转变。他为了自身的爱选择了卑鄙的手段。但却又在几乎决定要杀掉父亲的时候转身离去。伊万作为这部书理性的代表,他反对上帝,反对基督,他在的科学带领下的诞生的新哲学深深压迫着灵魂。《宗教大法官》正是他认为的现世战胜了身后生命,恶魔战胜了基督在脑内形成的长诗,他的理性带着他走过10000的4次方公里,所以他感到疯狂,但是又在最后站出来为自己的哥哥作证哪怕自己会被贴上怂恿作案的标签。阿辽沙和佐西马长老几乎代表了宗教中的善,两人都相信世上的爱,对世界万物充满博爱。而私生子老四从小受尽白眼没有兄弟们的继承权,只能做自己父亲的奴才被一个疯女人生下来,他表面唯唯诺诺但是内心狡诈阴险,最后狡诈阴险的自杀却因留下不要怪任何人为终

Religion

卡拉马佐夫兄弟本书讨论的核心即为善恶和宗教。上帝与恶魔对俄罗斯人民的捉弄,当恶魔占领米剑卡的内心时,他为了爱情策划铤而走险敲碎老子的脑壳,但是当真正闯进了其父的房间时,他却犹豫了逃走了。他的恶念和善念时常交叉,脑子发热让他做出诸多可怕的事拿一个生活艰难的上尉出气,花掉了未婚妻给的钱只为了和情妇狂欢。作为这本书中善恶两面最明显的存在,他显然拥有着野蛮主导的一面,他爱的彻底又狂热的可怕,善恶在他心中没有定论,因此善良的天使和丑恶的魔鬼时常接管他的内心,但是又像辩护律师所讲,他绝不可能犯下杀人的案子尤其是杀父即使这称不上是一个父亲,他其实宽容只是野蛮,他其实温情只是焦躁,他其实善良只是凶恶。

老卡拉马佐夫认识到的是生命的虚无,他不像伊万一样否认上帝又在否认了身后生命的情况下决定即使没有身后生命人也可以行善,他成为了活脱脱的虚无主义者,他像动漫里的瑞克一样,纵情享乐,逃避一切社会带给他的责任,亲情和羁绊,失去一切道德。伊万在想的就是失去了基督对来世的承诺,难道人就不会像父亲一样“法无禁止皆可为”吗,难道就不会烧杀淫掠吗?但是依我所想,信仰即是人类社会存续和发展的基础,无论信仰什么,广义上我们信仰的是社会规则,共同的文化和文字。陀翁借伊万和阿辽沙讨论之口表达的正是自己的担忧,失去了基督的博爱,人类会不会退化成动物,人会不会觉得生命像人文主义者所说的一样是一场旅程,因此再也不在意善恶再也不考虑德行。可是人类社会的信仰并非一定出自于宗教和基督,伊万内心对杀死上帝存在的挣扎正体现在了恶魔对他说的“不相信身后生命的人死后被惩罚走一万的四次方公里”,我们社会存在的信仰可是却并非是上帝,我们依然存在真理,我们信仰的真理虽然是人造的并非像杜撰的上帝一样好像来自于神创造的自然的。而人创造的顺应社会发展,人们和谐共处的原则和社会规则也正是指引人向上的工具

陀翁处在一个基督将死的世界,伊万正是这个害怕但是却以理性为指导最后几乎要疯了的人,但是阿辽沙最后也说即使卡嘉觉得他有事但是他其实最后一定会康复,伊万最后会代替陀翁去一个新时代见证新的思想,上帝虽死但是真理复苏,崇拜科学和真理的时代唤醒新的伦理观。阿辽沙最后和郭立亚送走了伊柳沙,曾经欺侮嘲弄伊柳沙的孩子们此时为善良流出眼泪,为人类最伟大的精神动容。未来的社会主义者郭立亚对他崇拜的基督徒阿辽沙表达了自己一定会追求真理,阿辽沙对孩子们说不管他们以后想去做什么一定要记住善良的今天,记住为伊柳沙流泪的今天,当有一天要行凶作恶的时候回忆起这一天一定会赶走心中的恶魔。仿佛太阳初升一般,阿辽沙和郭立亚两个人定然一定会为今天的善追求真正的真理,不再是基督而是即将到来的全人类的更崇高的理想

Russia

作为一个民族倾向很明显的作家,陀翁显然是一个俄罗斯的民族主义作家,他描写了这个民族的底色,像米剑卡一样冲动敢爱敢恨,像伊万一样理性自我折磨,像阿辽沙一样善良博爱,又像老卡拉马佐夫一样陷入虚无。这些都是Russia,作者以自己的人设阅历写尽自己所知的俄罗斯和对俄罗斯母亲的爱。这些特性显得荒诞不经,但是却

世上太需要荒唐了。这世界就是靠荒唐支撑起来的,要是没有荒唐,世界只是一潭死水

如果得不到肯定的解答,也就永远得不到否定的解答,您知道自己的心有这一特点,而这正是您的心的全部痛苦所在。

最烦的一集,还好学会了,把经验写出来

平衡二叉树的插入

LL&RR情况

LL平衡旋转

RR平衡旋转

不管是LL还是RR,都先找到最先失调的根节点A**(也就是他的左右子树高度差大于2)**

在找到A较高的那棵子树的父亲结点B,然后B朝着A的方向旋转

以LL为例子,右旋,并且将自己的右树给A(向哪里旋转,就将被旋转的结点的 旋转方向的树给出去)

LR&RL情况

LR平衡旋转

RL平衡旋转

若将新的结点插到最先失调结点的左孩子的右树或者右孩子的左树,则使用RL或者LR

以LR为例

我们按之前的方法找到了B,也就是失调结点A的比较深的树的父亲B

再把新结点的父亲且是B的孩子,也就是B的树深最高的树的父节点C

此时将C和B替换,让C先左旋(因为是向左旋转,将CL给B),然后再右旋(因为是向右旋转,此时把CR给A)

平衡二叉树的删除

王道课上的删除步骤

这里不讨论调整完二次失衡的情况,只需要根据以下步骤再来一次即可

LL&RR情况

以此图为例,在其中75就是第一个失调的结点A

然后找到他最高子树的父节点B也就是80,因为是A的右节点B的右树较高破坏了平衡,所以使用RR,直接令80旋转到75的位置并给75一个77的右节点

LR&RL情况

以此图为例,首先找到第一个失衡的A也就是44,他最高的树的父节点是78也就是B

我们这里找到是B的左树过高导致失调,因此是LR

将50右旋送到78的位置并给78一个62的右节点,再左旋到44的位置并给48的右节点

结语

是自己总结的规律,原本的太难记了

我的规律插入和删除是一样的:

  1. 找到失调的结点A(左右树高第一次大于2)
  2. 找A的两棵子树中谁更高,将其A的这个儿子当作B
  3. 如果这个B是A的左孩子,但是是B的左树较高,就将B旋到A的位置,并且将B靠近A的子树(右旋送右,左旋送左)送给A作为子树(旋转的底层逻辑:把自己的靠着被选结点的子树送一个给被旋到的结点)
  4. 如果这个B是A的左孩子,但是是B的右树较高,则将B的右树的儿子设为C(这里就是LR了)
    先让C旋到B的位置,再旋转到A的位置

说明

本文整合自王道和里昂学长的PPT(无盈利侵权必删),是完整的408代码大题解题方案,整合好的pdf文档可以联系我获取

快速排序

代码实现

王道强化课快速排序代码

快速排序算法以huafen()函数为核心

如何划分?

第一趟

首先将左指针L对应的数组元素设为mid值,我们之后要将mid值和其他值对比然后找到mid值该在的位置

先从R向前寻找第一个小于mid的值(如果没有R- -一直找),如果有,就把他和目前存着mid值的A[L]互换位置

再从L向后寻找第一个大于mid的值(如果没有L++一直找),如果有,就把上一步的A[R]换过来

第二趟

上一趟完成了第一次交换,把小于mid的换到了右边,而把大于mid的换到了左边

第二趟又在新的R找到了小于mid的元素,放在之前发现的左边覆盖,以此类推

最后一趟

当最后一趟开始时,若是L++导致L==R则

A[L]换上了右边更小一点的元素。

而下一步L++,已不满足L<R的条件。而这一步的L就是上一步的R(L==R),A[R]的值已经迁移到了其左边。

因此设A[L]=mid,就把两者很好的拆开了

当最后一趟开始时,若是R- -导致L==R则

也就是上一趟将**大于mid的A[L]**的值放到右边时,将较大的值换到了A[R]中。

此时R- -到R==L,因此此时A[L]中的存储的是已经是到了还没有**R–**时的右边。

因此A[L]=mid,即可拆开

快速排序算法

因此在快排中调用这段代码

先将A中的LR成以mid为中间的两段,左边小于mid,右边大于mid

我们的划分传来的是mid的位置,因此要想把整个数组排序好,只需要排好左右一直调用。

将数组的最左边L和mid的前一个位置进行划分,穷举下去即可解决

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Qsort(int A[], int L, int R) {
if(L > R) return; //递归终止
int M = huafen(A, L, R);
Qsort(A, L, M-1); //左半部分快排
Qsort(A, M+1, R); //右半部分快排
}

int hualen(int A[], int L, int R) {
int mid = A[L];
while(L < R) {
while(A[R] >= mid && L < R) R--;//等于号能保证其相对位置不变,稳定性这一块
A[L] = A[R];
while(A[L] <= mid && L < R) L++;
A[R] = A[L];
}
A[L] = mid;
return L;
}

划分函数的妙用

求数组中第k小元素的方法

王道强化课huafen函数

按理说我们只需要快速排序好,再直接利用数组的随机查找特性找到A[k-1]就可以

但是huafen函数可以直接找到某个元素的具体位置,我们可以直接用M接收huafen函数传来的此时的位置

若这个位置比已知位置小,则k-1在M前面,则设L=M+1继续试

1
2
3
4
5
6
7
8
9
10
int func(int A[], int n, int k) {
int L = 0, R = n - 1, M = 0;
while(1) {
M = huafor(A, L, R);
if(M == k - 1) break;
else if(M > k - 1) R = M - 1;
else if(M < k - 1) L = M + 1;
}
return A[k-1];
}

归并排序

归并排序用于两个数组排到一个数组里的排序中

归并排序王道强化课

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int Merge(int A[], int N, int B[], int M, int C[]) {
int i = 0, j = 0, k = 0;
while (i < N && j < M) {
if (A[i] <= B[j])
C[k++] = A[i++];
else
C[k++] = B[j++];
}
while (i < N)
C[k++] = A[i++];
while (j < M)
C[k++] = B[j++];
return 1;
}

链表

查找+删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//删除值为 x 的结点
void deletX(LinkList L, int x) {
LNode *pre = L; //pre指向p的前驱结点
LNode *p = pre->next; //p指向下一个要处理的结点
while (p != NULL) {
//对当前结点p进行处理
if (p->data == x) {
LNode *q = p; //删除并释放值为x的结点
p = p->next; //p指向后一个结点
pre->next = p; //修改前驱结点的next指针
free(q);
} else {
pre = p; // pre、p 后移
p = p->next;
}
}
}

查找+插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//在递增有序链表中插入值为 x 的信息
void InsertX(LinkList L, int x) {
LNode *pre = L; //pre指向p的前驱结点
LNode *p = pre->next; //p指向下一个要处理的结点
while (p != NULL) {
//对当前结点p进行处理
if (p->data > x) {
break; //应插入到p的前面
} else {
pre = p; // pre, p 后移
p = p->next;
}
}
LNode *q = (LNode *)malloc(sizeof(LNode));
q->data = x;
q->next = p;
pre->next = q;
}

头插法(原地逆转链表)

在勾画的绿色部分正是逆置的核心出装,将新插入的结点(在原链表中更靠后)不断放前,而实现逆置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void ListReserve(LinkList L){
//分配一个辅助头结点
LinkList head = (LNode *) malloc(sizeof(LNode));
head->next = NULL;

while (L->next != NULL){
LNode *p = L->next;
L->next = L->next->next;
p->next = head->next;
head->next = p;
}
L->next = head->next;
free(head); //释放辅助头结点
}

尾插法保持原序

以下是我自己写的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void ListPart(LinkList C){
LinkList A=(LNode*)malloc(sizeof(LNode))//A和B的头结点来也
LinkList B=(LNode*)malloc(sizeof(LNode))
A->next=NULL;
B->next=NULL;
int count=1;//计数器:判断奇偶结点
LNode *q=A;//A的尾指针
while(C->next != NULL){
if(count%2 == 1)//若为奇数,尾插法
{
LNode*p=C->next;//记录目前要拆掉的结点
C->next=C->next->next;//断开该结点
q->next=p;//插入尾结点之后
p->next=NULL;//断掉和原表最后的联系
q=p;//更新尾指针
count++;//更新计数值
}
if(count%2 == 0)//若为偶数,头插法
{
LNode *p = C->next;//记录目前要拆掉的结点
L->next = C->next->next;//断开该结点
p->next = B->next;//将最新的结点插到最头边,保证逆序
B->next = p;//作为头结点的下一个结点
count++;//更新计数值
}
}
free(C);//正式消灭C表
}

二叉树

前/中/后序遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//二叉树的结点定义(链式存储)
typedef struct BiTNode{
int data; //数据域
struct BiTNode *lchild,*rchild; //左、右孩子指针
}BiTNode,*BiTree;

//先序遍历二叉树
void PreOrder (BiTree root){
if (root==NULL)
return;
visit(root); //访问根节点
PreOrder(root->lchild);
PreOrder(root->rchild);
}

//中序遍历二叉树
void InOrder (BiTree root){
if (root==NULL)
return;
InOrder(root->lchild);
visit(root); //访问根节点
InOrder(root->rchild);
}

//后序遍历二叉树
void PostOrder (BiTree root){
if (root==NULL)
return;
PostOrder(root->lchild);
PostOrder(root->rchild);
visit(root); //访问根节点
}

层序遍历

在层序遍历中需要使用到队列,也就需要队列的操作

1
2
3
4
5
6
7
8
//初始化队列
void InitQueue(Queue &Q)
//判断队列是否为空
bool IsEmpty(Queue Q)
//新元素x入队
void EnQueue(Queue &Q, BiTNode * x)
//队头元素出队,并使用x 返回队头元素
void DeQueue(Queue &Q, BiTNode * &x)//&x 表示引用参数,函数内部对x的修改会影响外部的x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//层序遍历
void LevelOrder(BiTree T){
Queue Q;
InitQueue(Q); //初始化辅助队列
BiTree p;
EnQueue(Q, T); //将根结点入队
while(!IsEmpty(Q)){
DeQueue(Q, p);
visit(p);
if(p->lchild != NULL)
EnQueue(Q, p->lchild); //左孩子入队
if(p->rchild != NULL)
EnQueue(Q, p->rchild); //右孩子入队
}
}

求树高

(方法一)先序遍历

1
2
3
4
5
6
7
8
9
10
/*求树的高度(方法一)*/
int height = 0; //用全局变量记录树的高度
void PreOrder(BiTree T, int n) {
if (T == NULL)
return;
if (n > height)
height = n; //更新树的高度
PreOrder(T->lchild, n + 1); //遍历左子树
PreOrder(T->rchild, n + 1); //遍历右子树
}

调用时用**PreOrder(T, 1**)

原理是每过一层则+1并和目前高度比较

(方法二)后序遍历

1
2
3
4
5
6
7
8
9
10
int PostOrder(BiTree T){
if (T == NULL)
return 0;
int left = PostOrder(T->lchild);
int right = PostOrder(T->rchild);
if (left > right)
return left + 1;
else
return right + 1;
}

以该图为例,g和h没有左右结点了,故直接返回0+1给e作为e的left和right,e再进行比较以大的深度+1作为自己的深度交给b,b也这么做并和c一起交给a

后序遍历以孩子出发从孩子开始数并返给根节点最长的版本

求树宽

此处先用先序遍历统计每一层的结点数,也就是使用先序遍历先根后孩子的特点来统计

1
2
3
4
5
6
7
8
9
int width[MAX];  //用于统计各层的结点总数

//先序遍历,同时统计各层结点总数
void PreOrder(BiTree T, int level) {
if (T == NULL) return;
width[level]++; //累加该层结点总数
PreOrder(T->lchild, level + 1); //遍历左子树
PreOrder(T->rchild, level + 1); //遍历右子树
}

再找到宽度数组中最大的一项,将其作为树宽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void treeWidth(BiTree T) {
// 初始化宽度数组
for (int i = 0; i < MAX; i++)
width[i] = 0;
PreOrder(T, 0); // 从第0层开始遍历

// 找出最大宽度(某一层最多的节点数)
int maxWidth = 0;
for (int i = 0; i < MAX; i++) {
if (width[i] > maxWidth)
maxWidth = width[i];
}
printf("树的宽度是%d", maxWidth);
}

求树WPL

求树的WPL还是要使用树的先序遍历

使用了求树高和树宽的思想

先找到叶子节点(也就是左右孩子为空)并且将此处的带权路径长度加入WPL

如果不是叶子结点则向下找到其他叶子节点高度

1
2
3
4
5
6
7
void PreOrder (BiTree T, int level){
if (T == NULL) return;
if(T->lchild==NULL && T->rchild==NULL)
WPL += level*T->weight; //累加叶结点带权路径长度
PreOrder(T->lchild, level + 1); //遍历左子树
PreOrder(T->rchild, level + 1);
}

特殊树型判断

二叉排序树判定

中序遍历出来的序列是升序序列,所以二叉排序树序列中下一个结点必然大于前面所有的结点(也就是MAX{前面结点})这里用temp表示

所以只要后面遍历到的小于temp,则直接判定失败

若不小于则继续向右子树遍历,直到为NULL退回

1
2
3
4
5
6
7
8
9
10
int temp=MIN_INT; //记录当前遍历到的最小值
bool isBST=true; //是否为二叉排序树?

void InOrder(BiTree T){
if (T == NULL) return;
InOrder(T->lchild);
if (T->data >= temp) temp=T->data;
else isBST=false;
InOrder(T->rchild);
}

平衡二叉树判定

此树判定和方法二找树深一样

通过后序遍历的特点:先访问孩子再进行操作,由最后的孩子也就是结点A的左右子树都退回来自己的高度之后,再进行左右子树高度判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool isBalance=true;    //二叉树是否平衡
int PostOrder(BiTree T){
if (T == NULL)
return 0;
int left = PostOder(T->lchild);
int right = PostOder(T->rchild);

if (left-right>1) isBalance=false;
if (left-right<-1) isBalance=false;

//树的深度=Max(左子树深度,右子树深度)+1
if (left>right) return left+1;
else return right+1;
}

完全二叉树判定

这里先借用一下层序遍历的图

层序遍历,因为完全二叉树是按顺序排放,所以要看每一层的结点的逐个讨论、

四种情况分别在访问这个结点时讨论一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 特殊树形判定:完全二叉树?

bool isComplete = true; // 是否为完全二叉树
bool flag = false; // flag=true,表示层序遍历时出现过叶子或只有左孩子的分支节点

void visit(BiTNode * p) {
if (p->lchild == NULL && p->rchild == NULL) {
// 当前节点为叶子节点
if (flag) {
isComplete = false; // 不是完全二叉树
}
flag = true;
} else if (p->lchild != NULL && p->rchild == NULL) {
// 只有左孩子,没有右孩子
if (flag) {
isComplete = false; // 不是完全二叉树
}
flag = true;
} else if (p->lchild == NULL && p->rchild != NULL) {
// 只有右孩子,没有左孩子 - 肯定不是完全二叉树
isComplete = false;
} else {
// 有两个孩子
if (flag) {
isComplete = false; // 不是完全二叉树
}
}
}

图的数据结构定义

邻接矩阵

这是邻接矩阵的数据结构

这是邻接矩阵的初始化操作

邻接表

这是邻接表的数据结构,在图ALGraph中存储一个顶点表数组和顶点数和边数

在顶点表中存储这个结点的数据和第一个依附该点的边

边表则是指出这条边指向的点和下一条边的指针以及该边权值

这个表依旧要执行初始化操作

先将顶点表数组中的第一条边全部置为NULL

再将其顶点数和边数先更新

用封装好的AddEdge(图,出点,入点,权值)函数添加边

AddEdge函数是这么写的

首先先创建一个边结点,将其的权值和指向的结点j配置好

再将他连接到i结点第一条边之前,让这条新边指向老的第一条边(类似头插法将靠后的插在头边)

最后让i的点表连接到这条边作为first

最后可以看到他的逻辑结构长这样

图的遍历

深度优先遍历

里昂学长的pdf

1
2
3
4
5
6
7
8
9
10
//邻接矩阵实现深度优先搜索
void DFS(MGraph G,int i){
visit(i);
//访问初始顶点
visited[i]=true;//对做已访问标记
for(int j=0;j<G.G.numVertices;j++)
if(visited[j]==false&&G.edge[i][j]==1){
DFS(G,j);//为的尚未访问的邻接点,递归访问
}
}

首先先把第一个访问的结点打上true,对第一结点比如A,找他向别人发射过的边,直到j<G.vexnum(穷尽了所有点)

当遇到没有遍历到过的且存在的邻接点时,将其投入DPS

然后一直DPS递归下去,直到再没有邻接的点,逐层返回没有访问完邻接点的结点中继续访问他们邻接点

1
2
3
4
5
6
7
8
9
10
11
12
13
//邻接表实现深度优先搜索
void DFS(ALGraph G,int i){
visit(i);
//访问初始顶点
visited[i]=true;//对做已访问标记
for(EdgeNode* p=G.VertexList[i].first ; p!=NULL; p=p->next){
//检测的所有邻接点
int j=p->index;
if(visited[j]==false){
DFS(G,j);//为的尚未访问的邻接点,递归访问
}
}
}

前面步骤一样,依旧从i点开始遍历。先找到i点的第一个边,将其位置存放到p中

j就是此时p指向的点,若此点没有被访问过则开始深度遍历

直到遍历到最后一个能遍历到的点,就往上返回,继续访问之前没有访问完其他边的结点。

广度优先遍历

里昂学长的pdf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//邻接矩阵实现广度优先搜索
void BFS(MGraph G, int v){
visit(v); //访问初始顶点
visited[v]=true; //对v做已访问标记
EnQueue(Q, v); //顶点v入队
while(!isEmpty(Q)){
DeQueue(Q, v); //从队首取出顶点v
for(int w=0; w<G.numVertices; w++){
//检测v的所有邻接顶点
if(visited[w]==false && G.edge[v][w]==1){
visit(w); //w为v的尚未访问的邻接点,访问w
visited[w]=true; //标记w为已访问
EnQueue(Q, w); //顶点w入队
}
}
}
}

广度优先遍历类似于图的层序遍历

从v出发,先将v存入队列中

当队列不为空时就出队一个结点,并且从第0个结点开始遍历,寻找v指向的且没有被访问过的结点,找到一个入队一个直到找不到为止

然后从i结点找到的第一个结点开始继续重复这个步骤,直到所有结点都找完使队列为空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//邻接表实现广度优先搜索
void BFS(ALGraph G, int v){
visit(v); //访问初始顶点
visited[v]=true; //对v做已访问标记
EnQueue(Q, v); //顶点v入队
while(!isEmpty(Q)){
DeQueue(Q, v); //从队首取出顶点v
for(EdgeNode *p=G.VertexList[v].first; p!=NULL; p=p->next){
//检测v的所有邻接顶点
int w = p->index;
if(visited[w]==false){
visit(w); //w为v的尚未访问的邻接点,访问w
visited[w]=true; //标记w为已访问
EnQueue(Q, w); //顶点w入队
}
}
}
}

套路和上面一样,入队之后先找到第一个边指向的结点看是否被访问过,若没有则全部入队直到v结点没有邻接的点为止。

然后又从v第一条边邻接点继续广度优先遍历

拓扑排序

先按一下思路分析

拓扑排序的步骤:

  1. 选择入度为0的顶点并输出
  2. 从图中删除该顶点与所有以它为弧的边
  3. 重复步骤1、2,直到不存在入度为0的顶点
拓扑序列 条件
存在且唯一 每次选择结点都只有一个结点可选
存在不唯一 选择结点时不止一个结点可选
不存在 图中有环,环中顶点未被访问

先拿王道的流程图说一下思想

首先我们需要循环找到入度为0的点并且全部入队。

此处如果队列里元素个数超过1,

  • 入度为0的点肯定超过2个也就是拓扑排序不唯一。
  • 若是有多个连通分量,一个连通分量多个入度为0,另一个连通分量是有环图,则很明显不存在拓扑排序

然后当我们出队时,我们将计数器+1,来记录已经出队的数

出队之后将所有出队的点指向的点,把他们的入度-1,然后继续寻找入度为0的点循环这个过程

当队空之后我们根据count数进行判断

  • 如果count等于顶点数,那么说明从顶点到最后一个点只有一条路径。没错,拓扑排序唯一
  • 若count数不等于也就是小于顶点数,说明存在环路,始终有点入度不为0互相堵着
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//邻接表实现
int uniquely1(MGraph G){
int inDegree[MAXV] = {0}; // 存储每个顶点的入度
int queue[MAXV]; // 用于存储入度为0的顶点
int front = 0, rear = 0; // 队列的前后指针
int count = 0; // 拓扑序列中的顶点计数

// 计算每个顶点的入度
for (int i = 0; i < G.vexnum; i++)
{
ArcNode *p = G.AdjList[i].firstarc;//从i的第一条边开始找
while (p != NULL)
{
inDegree[p->adjvex]++;
p = p->nextarc;
}
}

// 将所有入度为0的顶点入队
for (int i = 0; i < G.vexnum; i++)
{
if (inDegree[i] == 0)
queue[rear++] = i;
}

// 进行拓扑排序
while (front < rear)//非空队列
{
// 如果队列中有多个顶点,说明图的拓扑序列不唯一
if (rear - front > 1)
return 0;
int v = queue[front++]; // 取出队首顶点
count++;
// 更新该顶点的所有邻接顶点的入度
ArcNode *p = G.AdjList[v].firstarc;
while (p != NULL)
{
inDegree[p->adjvex]--;
// 如果入度变为0,则加入队列
if (inDegree[p->adjvex] == 0)
queue[rear++] = p->adjvex;
p = p->nextarc;
}
}

if (count == G.vexnum)
return 1; // 当前拓扑序列包含所有顶点,则图的拓扑序列唯一
else
return 0; // 当前拓扑序列不包含所有顶点,则图的拓扑序列不存在
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//邻接矩阵实现
int uniquely1(MGraph G)
{
int inDegree[MAXV] = {0}; // 存储每个顶点的入度
int queue[MAXV]; // 用于存储入度为0的顶点
int front = 0, rear = 0; // 队列的前后指针
int count = 0; // 拓扑序列中的顶点计数

// 计算每个顶点的入度
for (int i = 0; i < G.numVertices; i++)
for (int j = 0; j < G.numVertices; j++)
if (G.Edge[i][j] != 0)
inDegree[j]++;

// 将所有入度为0的顶点入队
for (int i = 0; i < G.numVertices; i++)
if (inDegree[i] == 0)
queue[rear++] = i;


// 进行拓扑排序
while (front < rear)
{
// 如果队列中有多个顶点,说明图的拓扑序列不唯一
if (rear - front > 1)
return 0;
int v = queue[front++]; // 取出队首顶点
count++;
// 更新该顶点的所有邻接顶点的入度
for (int i = 0; i < G.numVertices; i++)
if (G.Edge[v][i] != 0)
{
inDegree[i]--; // 如果入度变为0,则加入队列
if (inDegree[i] == 0)
queue[rear++] = i;
}
}

if (count == G.numVertices)
return 1; // 当前拓扑序列包含所有顶点,则图的拓扑序列唯一
else
return 0; // 当前拓扑序列不包含所有顶点,则图的拓扑序列不存在
}

DPS的实现

首先DPS的实现较为容易,这里给出伪代码和思想

借鉴自图——深度优先遍历(DFS)实现有向无环图的逆拓扑排序_dfs实现逆拓扑排序-CSDN博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//返回有向图G中顶点v的第一个邻接点,如没有返回-1
int FirstNeighbor(Graph G, int v)
{
//(......具体实现细节)
return w;
}

//假设图G中w是v的一个邻接点,返回除w外的v的下一个邻接点,若没有,返回-1
int NextNeighbor(Graph G, int v, int w)
{
//(......具体实现细节)
return w;
}

//访问v顶点
void visit(int v);

//标记顶点被访问状态的数组,初始时全为false
bool visited[MAX_NUM];

//以上是DPS代码需要的函数
void DFS(Graph G, int v)
{
visit(v);
visited[v] = true; //顶点v已被访问,也就是入度为0的点
//这里开始循环遍历,先从G图的v点的邻接表的第一根弧开始,若该点尚未访问则访问并递归该点,寻找该点的边表,以此寻找到其邻接结点
//在其邻接表的边表后没有边后,递归结束并退回上一个递归,上一个递归便可以继续寻找他的其他邻接节点
for (w = FirstNeighbor(G, v); w != -1; w = NextNeighbor(G, v, w))
{
if (visited[w] == false)
DFS(G, w);
}

这就是DPS的内核,找到源点第一个邻接节点,然后通过递归寻找这个节点的邻接节点,当穷尽最深后,便会退回上一个节点,寻找该节点的第二个邻接节点

DPS实现拓扑排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//为保证所有顶点都被排序,需遍历所有顶点
for (int i = 0; i < MAX_NUM; i++){
if (!visited[i])
DFS(G, i);
}

void DFS(Graph G, int v){
visited[v] = true;
for (w = FirstNeighbor(G, v); w != -1; w = NextNeighbor(G, v, w)){
if (!visited[w])
DFS(G, w);
}
print(v); //输出v信息进行逆拓扑排序
}

这里就是内核所在,在DPS退出之后才输出,因此祖宗结点反而要在最后输出,因为他只有穷尽所有邻接节点的邻接节点后,**DPS(祖宗)**才会在栈中被放出来

以该图为例,a检查到邻接结点b,b未被访问过

于是调用DPS(B),B也找自己的邻接节点,故只有当调用DPS(d)返回并输出d,才能解放c乃至最下面的a

王道书上没有解释,我自己理解如下:

先给出一个无向图的邻接矩阵,我们先探讨中的元素的意义,是A中的第二行×第三列。我们可以知道当某两项对应元素相乘时不为零的情况下是都为1,也就是点2到某点有边,且某点到3也有边

因此我们可以得出结论,这一行×一列的结果便是2到3路径为2的路径数

推广到一般结论便是:

设图 G 的邻接矩阵为 A,的元素等于由顶点i到顶点j的长度为 n 的路径的数目

准备食材

需要准备的有:

  1. 老酸奶两盒约250g

  2. 淡奶油一盒约250g

  3. 吉利丁片

  4. 抹茶粉

  5. 白砂糖(木糖醇)

  6. 饼干85g(我这里选的好吃点蔬菜饼干,根据自己喜好选择)

  7. 黄油30g

蛋糕底部制作

将我们买的饼干放入袋子或者裱花袋,用擀面杖碾成颗粒状

之后我们拿出提前用热水泡化的液体黄油和饼干混合

这步是为了饼干不松散能成为蛋糕的基底

最后我们把搅拌好的饼干底搅拌均匀,使其都有粘合在一起的状态

放入我们的模具中铺平

这样我们这部就算完成了

慕斯糊糊制作

先将老酸奶倒入两罐,并且搅拌至酸奶状(把酸奶搅拌至酸奶状这块),也就是搅拌顺滑

然后我们把冷水中泡软的吉利丁片再倒入热热的牛奶里,并搅拌至完全融合

再将我们的牛奶不明体倒入老酸奶中,将他们搅拌混合

再将我们冷藏了24h(其实没有这么久,因为刚从超市保鲜柜买回来)的淡奶油打发至蓬松状

将其倒入我们的糊糊,现在糊糊已经完整了,分成两份,一份倒入抹茶粉一份不倒入,这样倒在一起会形成颜色层次

最后把两份慕斯糊糊分别倒入模具,送入冰箱冷藏4h

END成品图

首先,两个结论:

前不能前:前序线索二叉树没有有效算法能够通过指针找到前驱

后不能后:后序线索二叉树没有有效算法能够通过指针找到后继

然后讨论中序线索二叉树找后继

1
2
3
4
ThreadNode *Nextnode (ThreadNode *p) {
if(p->rtag==0) return Firstnode(p->rchild); //右子树中最左下结点
else return p->rchild; // rtag==1 则直接返回后继线索
}

若A有右孩子,这个函数将会找到A的右孩子并一直往左走,停下的瞬间就是他的后继节点

若没有便会直接访问他的后继指针

寻找前驱也很简单,只需要寻找左孩子的最后一个右super孙子结点,无需赘述。也就是中序线索二叉树可以完美找到自己的前驱后继

我们再以B为当前节点讨论,若是先序线索二叉树,B(因为有左孩子)则无法找到他的大爹结点,也就是前驱A

若是后序线索二叉树,B(因为有右孩子)则无法找到他的亲爹指点,也就是后继A

因此前不能前后不能后,中都中!

  • 高等数学积分公式大全

    一、基本积分公式

    1. 幂函数积分


    说明:n=-1时为特殊情况,转化为对数函数积分

    2. 指数函数积分


    二、三角函数积分

    1. 基本三角函数


    2. 衍生三角函数




    3. 正割余割平方积分

    4. 正割余割乘积积分

    三、反三角函数积分

    1. 反正切类


    2. 反正弦类


    四、其他重要积分

    1. 双曲函数类


    2. 分式积分

    五、积分技巧公式

    1. 平方三角函数


    2. 其他实用公式