哈佛-CS50-计算机科学导论笔记-五-
哈佛 CS50 计算机科学导论笔记(五)
哈佛CS50-AI | Python人工智能入门(2020·完整版) - P4:L0- 搜索算法 3 (极大极小算法,剪枝,深度限制) - ShowMeAI - BV1AQ4y1y7wy
在这种对抗情境中,我是一个试图做出智能决策的代理,而有另一个人正在对抗我,有着相反的目标。换句话说,我试图成功,而另一个人希望我失败,这种情况在游戏中非常常见。
就像井字棋一样,我们有一个3x3的网格,X和O轮流在这些方格中写下X或O,目标是如果你是X玩家,得到三个连续的X,或者如果你是O玩家,得到三个连续的O,计算机在玩游戏时变得相当出色。
井字棋非常简单,但更复杂的游戏又是怎样的呢?那么,智能决策在游戏中会是什么样子呢?!
那么,在这种情况下,智能的X应该如何移动呢?最终结果是有几种可能性,但如果AI以最佳方式玩这个游戏,那么AI可能会选择上右方的位置,因为此时O的目标与X相反。
试图赢得游戏,目标是在这里获得三个连续的棋子,而O则试图阻止这个目标。因此,O会在这里下棋来阻挡,但现在X可以做出一个相当聪明的移动,X可以进行这样的移动,现在X有两个可能的方式赢得游戏。
游戏的目标是在这里横向得到三个连续的棋子,或者X可以通过纵向得到三个连续的棋子来赢得游戏,因此O的下一步移动无关紧要。例如,O可以在这里下棋来阻挡横向的三个连续棋子,但随后X将赢得游戏。
游戏的目标是纵向得到三个连续的棋子,因此需要有相当的能力,以便计算机能够解决问题。这在精神上与我们目前所看到的问题相似,棋盘的状态和从一个动作到下一个动作的某种转变。
这与传统搜索问题不同,因为这现在是一个对抗性搜索问题。我是一个玩家,试图找到最佳移动,但我知道有对手在阻止我。
我们要查看的搜索情况和算法是一个叫做 minimax 的算法,它在这些确定性游戏中效果很好,其中有两个玩家,它也可以适用于其他类型的游戏,但我们现在将关注我先走一步,然后我的对手再走一步的游戏。
我在试图赢,而我的对手也在试图赢,换句话说,我的对手在努力让我输。那么我们需要什么才能让这个算法运行良好呢?每当我们尝试将这种人类概念转化为计算机时,就像是玩游戏、赢和输,我们想要的是。
将其翻译为计算机可以理解的术语,最终计算机只是理解数字。所以我们想要某种方式将井字棋游戏转化为数值形式,以便计算机可以理解,计算机通常不理解。
这种算法虽然不能直接判断输赢,但能理解大小的概念。所以我们想要的是白色的财富,我所做的是。
我们可能会考虑井字棋游戏的所有可能展开方式,并给每一种方式分配一个值或效用。在井字棋游戏以及许多类型的游戏中,可能的结果有三种:O 赢、X 赢或无人获胜。因此,玩家 1 胜、玩家 2 胜或无人获胜。
现在,让我们给这些可能结果赋予不同的值:O 胜利的值为负 1,无人获胜的值为 0,X 胜利的值为 1。这样我们就为这三种可能的结果分配了数字。
我们有两个玩家,X 玩家和 O 玩家,我们将把 X 玩家称为最大化玩家,把 O 玩家称为最小化玩家,原因在于,在 minimax 算法中,最大化玩家(在这种情况下是 X)旨在最大化分数。
可能的分数选项为负,1 0 和 1 X 想要最大化分数。这意味着如果可能,X 希望在这种情况下赢得比赛,我们给它一个分数为 1,但如果这不可能,X 需要在这两个选项之间选择:负 1,表示获胜,或 0,表示无人获胜。
胜利时,X 宁愿无人获胜,分数为 0,而不是负 1 O 胜利。因此,赢、输和平局的概念在数学上被简化为尝试最大化分数的想法,X 玩家总是希望分数更大,而反过来,最小化玩家则希望。
O 的目标是最小化分数,O 玩家希望分数尽可能小。所以现在我们已经将这个 X 和 O 的游戏,以及赢和输,转变为某种数学模型,X 正在尝试最大化分数,O 正在尝试最小化分数。现在让我们看一下所有的可能性。
游戏的各个部分,我们需要将其编码到AI中,以便。
AI可以玩像井字棋这样的游戏,因此游戏需要一些东西,我们需要某种初始状态,在这种情况下我们称之为s0。这就是游戏开始的方式,比如一个空的井字棋棋盘。例如。
我们还需要一个名为玩家的函数,玩家函数将以状态作为输入,这里用s表示,玩家函数的输出将是轮到哪个玩家。我们需要能够将一个井字棋棋盘传递给计算机,通过一个函数运行它,然后。
函数告诉我们轮到谁了,我们需要一些可以采取的行动的概念。稍后我们会看到这些例子,我们需要一个与之前相同的转换模型。如果我有一个状态并采取一个行动,我需要知道作为结果会发生什么,我需要某种方式来了解何时。
游戏结束,因此这等同于一种目标测试,但我需要一些终端测试,以检查一个状态是否是终端状态,终端状态意味着游戏结束。在经典的井字棋游戏中,终端状态意味着某人已经三连胜,或者所有方块都被填满。
井字棋棋盘被填满,这两种条件使其成为终端状态。在棋类游戏中,终端状态可能是当出现将死或如果将军不再可能时,这就成为终端状态。最后,我们还需要一个效用函数,接受一个状态。
并为那个终端状态提供一个数值,如果X赢得游戏,则该值为1,如果O赢得游戏,则该值为负1。如果没有人赢得游戏,则该值为0。让我们逐一查看这些,在初始状态下,我们可以简单地表示为。
井字棋作为空的游戏棋盘,这是我们开始的地方,是我们开始搜索的起点。我将以视觉方式表示这些,但你可以想象这实际上就像一个数组或一个。
二维数组中包含所有可能的方块,我们还需要一个玩家函数,它同样接受一个状态并告诉我们轮到谁,假设X先走。如果我有一个空的棋盘,那么我的玩家函数是。
将返回X,如果我有一个棋盘,其中X已经移动,那么我的玩家函数将返回O。玩家函数接受一个井字棋棋盘并告诉我们下一个轮到谁。接下来,我们将考虑动作函数。动作函数就像在经典搜索中那样,接受一个状态并给出。
所有可能的行动集合,我们可以在该状态下采取的行动,所以让我们想象在一个看起来像这样的棋盘上总是轮到我们行动。当我们将其传入行动函数时会发生什么,因此行动函数以游戏状态作为输入,输出是一组可能的。
行动是一组我可以在左上角移动,或者我可以在底部中间移动,这就是我在这个特定状态开始时的两个可能行动选择。和之前一样,当我们有状态和行动时,我们需要某种转移模型来告诉我们,当我们在此状态下采取此行动时会发生什么。
表明我们得到的新状态是什么,这里我们使用结果函数来定义,结果函数接受状态作为输入以及一个行动,当我们将结果函数应用于这个状态时,假设让 O 在左上角下棋,我们得到的新状态是这个结果状态,其中 O 在左上角。
这对懂得如何玩井字棋的人来说似乎显而易见,当然你会在左上角下棋,那是你得到的棋盘,但所有这些信息都需要编码到井字棋中,直到你告诉 AI 井字棋的规则是如何工作的,这个函数定义允许我们。
告诉 AI 这个游戏是如何实际运作的,以及行动如何真正影响游戏。
游戏的结果,因此 AI 需要知道游戏是如何运作的,AI 还需要知道游戏何时结束,这通过定义一个称为终止的函数来实现,该函数以状态 s
为输入,如果我们考虑一个尚未结束的游戏,传入终止函数的输出为假,游戏尚未结束,但。
如果我们考虑一个已经结束的游戏,因为 X 在对角线上已经连成三连,那么传入终止函数的输出将为真,因为游戏实际上已经结束,最后我们告诉 AI 游戏的运作方式,包括可以采取的行动以及当你采取行动时会发生什么。
按照我们告诉 AI 的步骤进行操作,游戏现在结束了,我们需要告诉 AI 每个状态的价值,我们通过定义这个效用函数来实现,它接受一个状态 s
并告诉我们该状态的得分或效用。
1 而如果 X 再次获胜,那么效用为负 1,AI 需要知道每个终止状态的效用,即游戏已结束时该状态的效用,因此我可以给你一个像这样的棋盘,其中游戏实际上已经结束,我询问 AI 这个状态的价值。
这可以实现,所以状态的价值是 1,但事情变得有趣的是,如果游戏还没有结束,假设一个像这样的游戏棋盘。我们处于游戏中间,轮到我们采取行动,那么我们怎么知道总是轮到我们行动呢?我们可以通过计算玩家来判断。
我们可以说,玩家的状态传递给O的答案,所以我们知道现在是他们的回合。这个棋盘的价值是什么?我应该采取什么行动?这将取决于我们必须进行一些计算,而这就是minimax算法真正发挥作用的地方。
X正试图最大化得分,这意味着O正试图最小化得分。我们希望最小化最终游戏的总值,因为这个游戏尚未结束,我们还不清楚这个棋盘的价值。我们需要进行一些计算。
为了弄清楚这一点,我们该如何进行计算呢?为此,我们将考虑就像在经典搜索中一样,下一步可能会采取哪些动作,以及这些动作会把我们带到什么状态。结果发现,在这个位置只有两个开放的方格。
这意味着只有两个开放位置,O可以出手,O可以选择在左上角出手,或者在下中间出手。Minimax不知道哪一个移动更好,所以它会考虑两个选项,但现在我们需要考虑。
现在进入同样的情况,我有两个游戏棋盘,它们都还没有结束。接下来会发生什么?在这个意义上,minimax可以称为递归算法,它将重复相同的过程,虽然现在是从相反的角度来看,就好像我现在是。
如果我是O玩家,我会站在对手的角度,考虑如果我是X玩家在这个位置,我的对手会怎么做?那么会发生什么呢?
我的对手,X玩家,正试图最大化得分,而我作为O玩家则在尽力最小化得分。因此,X正试图找到最大可能的值!
从这个棋盘位置来看,X只有一个选择,X将会在这里出手,他们会得到三连。在这种情况下,X赢得比赛的棋盘价值是1,如果X赢得比赛,那么这个棋盘的价值就是1。
从这个位置来看,如果这个状态只能导致这个状态,这是唯一的选择,而这个状态的价值是1,那么对手玩家可以从这个棋盘获得的最大可能值也是1。从这里,唯一能得到的就是一个价值为1的游戏棋盘,所以这个棋盘也有这个价值。
现在我们考虑这个局面,X需要进行一个移动,X唯一可以选择的移动是在左上角,所以X会走到那里。在这个游戏中没有人获胜,没有人连续三次,所以这个棋盘的价值是零,没有人赢。
相同的逻辑,如果从这个棋盘位置,我们唯一能到达的地方是一个价值为零的棋盘,那么这个状态也必须有一个价值为零。现在来了选择的部分,作为O玩家的我现在知道,如果我做出这个选择,移动到左上方。
导致一个价值为1的游戏,假设每个人都以最优方式进行游戏。如果我选择中下方,选择这条分叉,将导致一个价值为0的游戏板,我有两个选项可以选择,1和0,我需要做出选择,作为最小玩家我会。
我更倾向于选择价值最小的选项,因此每当一个玩家有多个选择时,最小玩家将选择价值最小的选项,而最大玩家将选择在1和0之间的最大值,0更小,意味着我宁愿平局也不愿输掉游戏。
游戏板上将显示这个游戏的价值为0,因为如果我在最优情况下,我会选择这条分叉,我会在这里放置我的O来阻止X三个一排,X会在左上角移动,游戏将结束,而没有人会赢得游戏。所以,这就是现在的最小最大逻辑。
考虑所有我可以采取的可能选项,以及我可以采取的所有行动,然后将自己置于对手的选择中。我决定现在要进行的移动,通过考虑我对手下一轮会做什么移动来决定,并且为了做到这一点,我还考虑了我在之后的回合会做什么移动。
继续进行,直到我一直走到游戏结束,达到这些所谓的终端状态。实际上,在这个决策点,我作为O玩家正在决定的事情,可能只是我对手X玩家在我之前所用的逻辑的一部分。
它将是某个更大树的一部分,在这种情况下,X需要选择三个不同的选项来做出决策。离游戏结束越远,这棵树就必须越深,因为这棵树的每个层级。
这将对应我进行的一次移动,或者说是我对手采取的行动,以决定发生什么。实际上,如果我在这个位置是X玩家,并且我递归地进行逻辑运算,我会发现我有三个选择,其中一个会导致一个结果。
如果我在这里进行游戏,并且如果每个人都以最优方式进行游戏,结果将是平局。如果我在这里玩,我总会赢,或者在这里,我作为X玩家可以赢得胜利,在0和负1以及1之间,我宁愿选择一个价值为1的棋盘,因为那样。
我能获得的最大值,因此这个棋盘也将具有一个最大值。这个树可以变得非常非常深,尤其是随着游戏开始有越来越多的走法。这一逻辑不仅适用于井字棋,也适用于任何这些类型的游戏,在这些游戏中,我先走一步,然后对手再走一步。
最终我们有这些对抗性的目标,我们可以将图示简化为看起来像这样的图,这更抽象地表现了。
这是极小极大树,其中每个状态都在这里,但我不再表示它们。这就像井字棋的棋盘,这仅仅表示一些通用的游戏,可能是井字棋,也可能是其他完全不同的游戏。所有这些指向上的绿色箭头代表一个。
在最大化状态中,我希望得分尽可能大,而所有这些指向下方的红色箭头则是最小化状态,在这里玩家是最小化玩家,他们试图让得分尽可能小。因此,如果你想象在这种情况下,我是最大化玩家,这个玩家在这里。
我有三个选择,一个选择给我得分五,一个选择给我得分三,还有一个选择给我得分九。那么在这三种选择中,我最好的选择是选择这个九,它是最大化我所有三种选择的得分。
这给这个状态一个值为九,因为在我的三个选择中,这是我能获得的最佳选择,所以这是我的决策。现在你可以想象这就像离游戏结束还有一步,但你也可以问一个合理的问题。
我的对手在游戏结束前两步可能会做什么呢?我的对手是最小化玩家,他们试图让得分尽可能小。想象一下,如果他们必须选择一个选择会发生什么。一种选择引导我们进入这个状态,而我,作为最大化玩家,将会选择。
对于九,我能获得的最大得分,而一则引导我们进入这个状态,作为最大化玩家我会选择八,而这就是我能获得的最大得分。现在最小化玩家被迫在9和8之间选择,将会选择尽可能小的得分,在这种情况下是8。
然后这个过程将如何展开,但在这种情况下最小化玩家会考虑他们的两个选择,以及因此产生的所有选项。所以这现在是一个极小极大的整体图景。
算法看起来是这样的,现在我们尝试用一点伪代码将其形式化。那么在极小极大算法中究竟发生了什么呢?在给定状态s的情况下,我们需要决定。
如果轮到最大化的玩家,最大化的玩家将选择在我们之前提到的行动集合中一个行动a,回忆一下,行动是一个函数,它接受一个状态并给我返回所有可能的行动,它告诉我所有可能的移动,最大化的玩家将会。
特别是选择在行动集合中能够给我最高的最小值结果的行动a,这意味着我想选择能够让我在所有行动中获得最高分的选项a,但这个分数究竟是什么,我需要知道我的。
最小化的玩家会怎么做,如果他们试图最小化所得到状态的值,我们需要考虑在我采取这个行动后,哪个状态会产生,以及当最小化的玩家试图最小化那个状态的值时会发生什么。我考虑了所有我的可能选项,并在考虑之后。
对于我所有可能的选项,我选择了价值最高的行动a,类似地,最小化的玩家也会这样做,但他们是反向思考,他们会考虑如果轮到他们,他们可以采取的所有可能的行动,并选择具有最小可能值的行动a。
所有的选项,以及他们如何知道所有选项中最小的可能值是通过考虑最大化的玩家会做什么,问一下将这个行动应用于当前状态的结果,然后最大化的玩家会尝试做什么,最大化的玩家会为此计算出什么值。
那个特定状态使每个人的决策都基于试图估计对方会做什么,现在我们需要将注意力转向这两个函数:最大值和最小值,如何实际计算一个状态的值,如果你试图最大化它的价值,又该如何操作。
如果你试图最小化价值,计算一个状态的值,如果你能做到这一点,那么我们就拥有这个最小最大算法的完整实现。让我们尝试一下,试着实现这个最大值函数,它接受一个状态并返回作为输出的值,如果我试图最大化。
关于状态的值,我可以首先检查游戏是否结束,因为如果游戏结束,换句话说,如果状态是终局状态,那么这很简单,我已经有这个效用函数,它告诉我棋盘的值,如果游戏结束,我只需检查,比如说x是否获胜。
赢了是平局吗?这个效用函数只知道状态的值,更棘手的是,如果游戏没有结束,因为那样我需要进行递归推理,思考我的对手在下一步会做什么,我想计算这个状态的值。
状态的值尽可能高,我会在一个名为V的变量中跟踪这个值。如果我想让值尽可能高,我需要给V一个初始值,最初我会将其设定为尽可能低,因为我不知道可用的选项是什么。
一开始,我会将V设为负无穷,这似乎有点奇怪,但这里的想法是我希望初始值尽可能低,因为在考虑我的行动时,我总是会尝试做得比V更好。如果我将V设为负无穷,我知道我总能做得更好。
所以现在我考虑我的行动,这将是某种循环,对于状态中的每个行动,回忆一下,actions是一个函数,它接受我的状态并给我所有可能的行动。
因此,对于每一个行动,我想将其与V进行比较,并说好的,V将等于V和这个表达式中的最大值。那么这个表达式是什么呢?首先,它是获取在该状态中采取行动的结果,然后获取该结果的最小值,换句话说,假设我想找出。
从这个状态来看,最小玩家能做的最好选择是什么,如果他们想要尽量减少分数,那么无论结果的分数是多少,都要将这个最小值与我当前的最佳值进行比较,然后选择这两者中的最大值,因为我正在尝试。
简而言之,这三行代码所做的就是遍历我所有可能的行动,问这个问题:在我的对手尝试在这整个循环之后做什么的情况下,我如何最大化分数。最终我可以通过返回来得到那个特定状态的值。
对于最小玩家来说,正好与此相反,逻辑相同,只不过是反过来计算一个状态的最小值,首先我们检查它是否是一个终止状态,如果是,就返回效用,否则我们将尝试尽量减少该状态的值,考虑到我所有可能的行动,因此我需要一个。
V的初始值是状态的值,最初我将其设定为正无穷,因为我知道它总能得到小于正无穷的东西。因此,通过从V等于正无穷开始,我确保我找到的第一个行动。
这个值将小于V的值,然后我做同样的事情,遍历我所有可能的行动,对于每个结果,当最大玩家做出决策时,让我们取最小值和当前值e。因此,经过一切,我得到可能的最小值。
最后返回的V的值。
用户实际上是伪代码,用于最小最大算法,这就是我们如何处理一个游戏并通过递归使用这些最大值和最小值函数来确定最佳移动,最大值调用最小值,最小值再调用最大值,来回进行,直到我们达到终端状态。
在这个点上,我们的算法可以简单地返回那个特定状态的效用,你可能会想象,这将开始变成一个漫长的过程,尤其是随着游戏开始变得更复杂,我们开始添加更多的动作和更多的可能选项,以及可能持续更长时间的游戏。
所以接下来的问题是,我们可以在这里进行什么样的优化,我们如何能够更好地使用更少的空间或更少的时间来解决这种问题,我们将查看几种可能的优化,但对于一种,我们将再次回顾这个例子。
这些向上和向下的箭头,让我们想象我现在是最大玩家,这个绿色箭头我试图让得分尽可能高,这是一场简单的游戏,我只需进行两个动作,我选择这三种选项之一,然后我的对手也选择这三种选项之一。
我做出什么移动,因此我们得到了某个值,让我们看看我进行这些计算的顺序,并弄清楚是否有任何优化可以对这个计算过程进行改进,我必须逐个查看这些状态,所以假设我从左边开始。
说好,现在我将考虑我的对手最小玩家会尝试做什么。好吧,最小玩家会查看他们的三个可能的行动,并查看它们的值,因为这些都是终端状态,它们是游戏的结束,所以他们会看到这个节点的值是四,值是八。
值为五,而最小玩家会说好吧,在这三个选项中,八和五,我会选择最小的那个,我会选择四。因此这个状态现在的值是四,然后作为最大玩家的我说好吧,如果我选择这个行动,它将有一个值为四,这是我所能得到的最好结果。
由于最小玩家会试图最小化我的得分,那么如果我选择这个选项,我们将接下来探索这个,接下来探索如果我选择这个行动,最小玩家将会说好吧,最小玩家有的三个选项是九、三和七。
所以三是在九和七中是最小的,因此我们可以说这个状态的值是三,所以现在作为最大玩家的我已经探索了我的三个选项中的两个,我知道我的一个选项将至少保证我得分四,而我的一个选项将保证我得分三,而现在我。
让我考虑我的第三个选项,想想这里会发生什么,采用完全相同的逻辑。最小化玩家会查看这三个状态:2、4和6,最小可能的选项是2,因此最小化玩家想要2,而我作为最大化玩家已经通过向下看两层深来计算了所有信息。
通过查看所有这些节点,我现在可以说,在4、3和2之间,你知道我宁愿选择前者,因为如果我选择这个选项,如果我的对手最佳游戏,他们会试图让我达到4,但这就是我能做到的最好了。我不能保证更高的得分,因为如果我选择其中一个。
2个选项,我可能会得到3,或者得到2,确实在这里是9,这是所有分数中最高的,所以我可能会想说,你知道吗?也许我应该选择这个选项,因为我可能会得到9,但如果最小化玩家聪明地在玩,他们会考虑我的对手对此的反应。
最佳的动作和他们在做选择时所拥有的每一个可能选项。我会留下3,而如果我能以最佳方式进行游戏,我本可以确保获得4,这就是我作为一个迷你最大化玩家所使用的逻辑,试图从那个节点最大化我的得分,但这反过来了。
他们花了我相当多的计算时间才能弄清楚这一点,我必须理清所有这些节点才能得出这个结论,这对于一个相当简单的游戏来说,我有三个选择,而我的对手有三个选择,然后游戏结束。所以我想做的是想出一些方法。
为了优化这个,也许我不需要进行所有这些计算就能得出结论,你知道吗?这个向左的动作是我能做的最好选择。让我们再试一次,试着在做这件事情时更加聪明,所以我首先以完全相同的方式开始。
我最初不知道该怎么做,因此我只需考虑其中一个选项,想想最小化玩家可能会怎么做。最小化玩家有8和5的三个选项,而在这三个选项中,最小化玩家认为4是他们能做的最好选择,因为他们想要尽量减少得分。现在我,作为最大化玩家,会考虑我的第二个选项。
那么最小化玩家会怎么做呢?最小化玩家会从那个状态查看他们的选项,我会说,好的,9是一个选项,3也是一个选项。如果我从这个初始状态进行数学计算,进行所有这些计算,当我。
看到3时,这对我来说应该立即是一个红色警告,因为当我在这个状态下看到3时,我知道这个状态的值最多为3。它将是3或者低于3,即使我还没有查看这个最后的动作,甚至是几个后续动作,如果还有更多的话。
我可以有效地剪除这个叶子节点,并且在这里可以采取的行动,我怎么知道这一点呢?我知道最小玩家将试图将我的得分降低,如果他们看到一个3,唯一能使这个值不等于3的,就是我尚未查看的剩余状态小于3,这意味着没有办法。
值不能超过3,因为最小玩家已经能保证得分为3,并且他们正在努力将我的得分降低。那么这告诉我什么呢?这告诉我如果我选择这个行动,我的得分将是3,或者如果运气不好,甚至可能低于3,但我已经知道这个行动将。
保证我得到4。因此,考虑到我知道这个行动保证我得分为4,而这个行动意味着我不能得到超过3的得分,如果我试图最大化我的选择,那就没有必要考虑这个三角形。这里没有任何值,任何数字能改变我在这之间的想法。
我总是会有两个选择,一个是让我得到4的路径,另一个是让我得到3的路径,前提是我的对手以最佳策略进行游戏。这对于我未来考虑的所有状态都是成立的,但如果我看一下最小玩家可能会做的事情,如果我看到这个状态是一个。
2,我知道这个状态至多是2,因为这个值如果不等于2,就必须有其中一个剩余状态小于2,因此最小玩家会选择那个状态,而不是这个。因此,即使不考虑这些剩余状态,作为最大化玩家的我也能知道,选择。
然后选择这两个路径中的任一个都不如右边的那个,因为这个路径不能优于3,那个路径不能优于2,因此在这种情况下,这是我能做到的最好选择。为了做这个,我现在可以说这个状态的值是4。因此,为了进行这种类型的计算,我会认为。
需要更多的记账,随时跟踪事情,跟踪我能做的最好和最坏的事情,对于每一个状态,我会说,好吧,如果我已经知道我能得到4,那么如果我考虑的最佳结果是。
从树中剪除任何低于它的值,因此这个方法,这种最小化的优化称为阿尔法-贝塔剪枝,阿尔法和贝塔代表这两个值,你必须跟踪你目前为止能做到的最好结果。
到目前为止你能做的最糟糕的事情,因此剪枝的理念是,如果我有一个很长很深的搜索树,如果我不需要搜索所有内容,我可能会更高效地搜索它。如果我能删除一些节点,以优化我查看整个搜索空间的方式,那么阿尔法-贝塔。
剪枝确实可以为我们节省很多时间,在搜索过程中通过提高搜索效率,但即便如此,当游戏变得更复杂时,这仍然不是很好。幸运的是,井字棋是一个相对简单的游戏,我们或许可以合理地问,究竟有多少种。
总共有多少可能的井字棋游戏呢?你可以考虑一下,尝试估算在任何特定时刻,有多少步骤。有多少步长,游戏持续多长时间。
结果是,约有二十五万可能的井字棋游戏可以进行,但与更复杂的游戏相比,比如国际象棋,棋子更多,移动更多,游戏持续时间更长,有多少呢?
总共有多少可能的国际象棋游戏呢?结果是,仅仅经过四步,每位白方选手四步,黑方选手四步。
从这种情况下,可能会有2880亿种可能的国际象棋游戏。仅仅在四步之后,进一步分析整个国际象棋游戏,会有多少种可能的国际象棋游戏呢?
因此,可能会有超过十的二十九次方的国际象棋游戏,远比任何可能被考虑的国际象棋游戏要多。这对极小化算法来说是一个相当大的问题,因为极小化算法从初始状态开始,考虑所有可能的动作和所有可能的。
之后的所有行动,直到我们到达游戏结束。如果计算机需要查看如此多的状态,这将是一个问题,这远远超过了任何计算机在合理时间内能够做到的。那么我们该如何解决这个问题,而不是层层深入,每一步都往前走,直到游戏结束呢?
对于计算机来说,遍历所有这些状态是完全无法处理的,我们需要一些更好的方法,结果显示这种更好的方法通常采取一种称为深度的方法。
限制的极小化算法,通常极小化算法是深度无限的,我们只需不断进行下去。
深度有限的极小化算法会说,你知道吗,在某个步数之后,也许我会看十步前。
也许我会看十二步之后,但到那时我会停止,不再考虑可能的额外步骤。
在计算上,考虑所有可能的选项是不可行的,所以我们在深入十或十二个棋步后,会遇到游戏尚未结束的情况,minimax仍然需要一种方法为棋盘或游戏状态分配分数,以确定其当前价值,这在游戏结束时很简单,但如果游戏还未结束,就不那么容易。
为此,我们需要为深度限制的minimax添加一个额外的特征,称为评估函数,它只是一个估计预期效用的函数。
在一个游戏中,比如国际象棋,如果想象游戏值为1意味着白方胜利,而0表示平局,那么你可以想象0.8的分数意味着白方非常可能获胜,这当然不是绝对保证。你会有一个评估函数来估计游戏状态的好坏。
这取决于评估函数的质量,这最终将约束AI的能力。AI越擅长评估任何游戏状态的好坏,它在玩游戏时就会越出色。如果评估函数较差,则表现不佳。
作为对预期效用的估计,它会变得更复杂,因此想出这些评估函数会更加困难。在国际象棋中,你可能会根据你拥有的棋子数量与对手的棋子数量来编写评估函数,因为每个。
每个都有一个值,你的评估函数可能需要比这更复杂,以考虑其他可能出现的情况,还有许多其他的minimax变体以及添加其他特征,以帮助在这些更复杂的环境中表现更好。
在计算上不可行的情况下,我们无法探索所有可能的走法,因此需要弄清楚如何使用评估函数和其他技术,以便能够最终更好地玩这些游戏。但这现在是对这种对抗搜索的一种观察。
我们面临的问题是,在与某种对手对战时,这些搜索问题在整个人工智能领域中随处可见。今天我们讨论了更多经典的搜索问题,比如尝试找到从一个位置到另一个位置的方向。
但每当眼睛面临决策时,比如我现在该怎么做,以便做出理性或智能的行为,或者尝试玩游戏,像是想出该做什么棋步时,这些算法实际上会非常有用。
井字棋的解决方案相对简单,因为这是一个小型游戏,xkcd著名地制作了一部网络漫画,在其中他会告诉你确切的最佳动作。
无论你的对手做什么,这种情况在像跳棋或国际象棋这样的大型游戏中并不太可能,因为国际象棋对大多数计算机来说在计算上是不可解的,因此我们确实需要我们的AI变得更加智能。
它们如何聪明地处理这些问题,以及它们如何在这个环境中寻找解决方案,所以这就是我们对人工智能中搜索的探讨,下次我们将研究知识。
思考我们的AI是如何获取信息、推理这些信息并得出结论的。
哈佛CS50-AI | Python人工智能入门(2020·完整版) - P5:L1- 知识系统知识 1 (知识,逻辑) - ShowMeAI - BV1AQ4y1y7wy
好的,欢迎大家回到《Python人工智能入门》的介绍。上次我们特别讨论了搜索问题,我们有人工智能代理。它们试图解决某种问题,在某种环境中采取行动,而这个环境就是。通过在游戏中进行移动或其他某种行动来采取行动。
尝试找出在哪里转弯,以获取从A点到B点的行车路线。这次我们将更普遍地关注知识这个概念。
很多智能都是基于知识,特别是如果我们考虑到智能。人们知道信息,我们知道关于世界的事实,并利用这些已知的信息。
能够得出结论,推理我们所知道的信息。为了弄清楚如何做某事或得出其他信息,我们进行推理。
基于我们已经拥有的信息,我们希望现在关注的是。能够将知识这一概念进行推理并应用为想法。人工智能,尤其是我们将要构建的。被称为知识型代理,能够通过内部表示知识来推理和行动的代理。
在Rai内部,我对知道某事的含义有一定理解。理想情况下,某些算法或技术可以基于它们所知道的知识来使用,以解决问题,找出额外的信息。这在某种程度上是有帮助的,我们所说的基于知识的推理。
能够得出结论,让我们看一个来自《哈利·波特》世界的简单例子。我们拿出一句我们知道是真实的句子,假设今天下雨。哈利今天访问了海格,这是一个事实,我们可能知道的另一个事实是哈利今天访问了海格或邓布利多,但不是两者。
这告诉我们一些关于世界的信息:哈利访问了海格,但没有访问邓布利多。或者哈利访问了邓布利多,但没有访问海格,现在我们有第三条关于世界的信息。哈利访问了邓布利多,我们现在有三条信息,三个事实在知识库中。我们所知道的信息,现在我们作为人类可以尝试推理并根据这些信息进行分析。
我们可以开始得出什么额外信息,看看这最后两条陈述。哈利要么拜访了海格,要么邓布利多,不能同时拜访,而我们知道佩里今天拜访了邓布利多。基于这两条陈述的结合,我们可以合理得出结论,你知道的。哈利今天一定没有去拜访海格。
你可以得出这个结论,哈利今天没有去看足球。事实证明,我们甚至可以做得更好,获取更多信息。我查看了这个第一条陈述并进行推理。第一条陈述说如果没有下雨,哈利今天就去拜访了海格。
这意味着在所有没有下雨的情况下,我们知道哈利拜访了海格。如果我们现在也知道,哈利没有拜访海格,这就告诉我们一些关于我们最初思考的前提的信息。特别是,骨盆,今天确实下雨了,因为我们可以推理如果没有下雨。哈利就会去拜访海格,我们确实知道,哈利今天没有去海格那里,这种推理。
一种逻辑推理的方式,我们基于我们所知道的信息使用逻辑。以便获取信息,并得出结论。今天我们讨论的重点就是这个,我们如何。让人工智能,逻辑,培根执行,同样的推理。
我们一直在进行的推理,有时我们在用人类语言进行推理,就像我刚才说的。用英语谈论这些句子并尝试推理它们之间的关系,当我们将注意力转向计算机并能够编码时,我们需要稍微正式一些。逻辑和真理与虚假的概念,在机器内部。
我们需要介绍几个术语,侄子符号。这将帮助我们推理逻辑的概念,在人工环境中。我们将从句子的概念开始。
现在,像英语这样的自然语言中的句子只是。我正在说的某些内容,就像我现在所说的。在人工智能的背景下,句子。是对世界的断言,我们称之为知识表征。一种表示知识的方式,内部是哈金剧院。
等等,我们今天大部分时间都会花在对知识的推理上。通过一种称为命题的逻辑。
有多种不同类型的逻辑,其中一些将涉及到命题逻辑。基于关于世界的命题陈述,命题逻辑的开始。命题符号的定义。
人们使用某些符号,通常是字母,比如P或Q,这些符号将代表某些概念。有趣的是,关于世界的一个句子,比如P代表“正在下雨”。Poppy将作为一个符号,代表这个概念,而Q可能代表“哈利今天拜访了海格”,这些命题符号代表一些句子。
关于世界,我们只是听到个别的关于世界的事实。
我希望能有某种方式将命题符号连接在一起,以便更复杂地推理。但在我们推理的世界中,可能还存在其他事实。
为了做到这一点,我们需要引入一些附加符号,通常被称为逻辑联结词。这些逻辑联结词有很多,我们今天关注的五个最重要的就是上面这五个。由逻辑符号表示的“非”,用这个符号表示。
这个概念被表示为一种倒V形,或者用V形的蕴涵表示,我们稍后会讨论这意味着什么。由箭头表示,双条件我们也会在稍后讨论其含义。这些是五种逻辑联结词,我们将重点关注的主要内容是,考虑计算机如何处理这些。
基于事实进行推理,但为了达到这个目的,我们需要查看这些逻辑联结词中的每一个,并建立对它们实际含义的理解。让我们开始吧。这不是简单的内容,我们将展示每一个逻辑连接词的情况。
我们将称之为“真值表”,这表明“非”这个词的含义。当我们将其附加到命题符号或我们的逻辑语言中的任何句子时,真值表便产生。这里是一个命题符号或其他句子的示例,如果是假的,那么“非p”就是真的。如果p是真的,那么“非p”,你可以想象将这个“非”符号放在某个命题逻辑句子前面。
相反的,如果例如说“正在下雨”,那么“非”将代表这个想法,即“没有下雨”。如果p为假,意味着句子“正在下雨”,那么“非p”就代表“没有下雨”,因此这个句子是成立的。
“非”可以想象为取反p中的内容。
真实的,真正的,类似于英语中的“非”,即对后面的内容进行反转,意味着相反的意思。接下来,类似于英语的概念,用这个倒V形状来表示。在不是单一的论点的情况下,我们有p,也有“非p”。
然后我要结合两个不同的命题逻辑句子,所以我可能有一个句子p,另一个句子Q,我想把它们结合在一起,表示P和Q。对于p和q的含义,一般逻辑上,它意味着两个都是真,并且还包括。这就是当我们有两个变量p和Q时的真值表。
当我们有两个变量,每个变量可以处于两个可能状态时,真或假。这导致了2的平方或4种可能的真与假的组合。袭击者Incubus Falls,P为真时Incubus为假,P和Q都为真。这是P和Q可能的四种情况。
第五列这里的pnq告诉我们关于它实际含义的一点信息。Pnq,唯一的情况是P为真,PA恰好为真,Q也恰好为真,所有其他情况,B和Q都将评估为假。这与我们直觉相符,如果我说P和Q,可能意味着。
我期望P和Q,也可能与我们所指的这个“或”一致。由这个倒V形符号表示,或者。顾名思义,当其任意一个参数为真时,它就为真,只要P为真或Q为真。当P或Q为假时。
只有在它的两个操作都是假时,PS为假,Kiwi酱,P或Q将为假。但在所有其他情况下,至少有一个操作数为真。也许它们都是为真,那么P或Q将评估为真。这与大多数人可能使用这个词的方式基本一致。
或者在普通英语中说“或”的无辜性。有时我们可能会说“或”,而我们指的是“任意一个”,但不是两个。在这种情况下,它们只能是一个或另一个,重要的是要注意这个符号,这里的“或”。意味着P或Q,只要其中一个或两个都为真,这都是完全可以的。
任何“或”的评估也将为真,只有在所有操作都为假时,P或Q最终评估为假,逻辑上还有另一个符号。请注意排他“或”,它编码了排他性,即像一个或另一个,而不是两个。我们今天不打算关注这一点,每当我们谈论“或”时。
总是讨论在这种情况下的任意一个或两个,表格表示,现在不是“与”和“或”。接下来是我们可能称之为的蕴含,着迷于这个箭头符号。我们有p和Q,这个句子通常会读作P蕴含Q。P蕴含Q意味着,如果P为真,那么可以说“如果下雨”。
然后我会在室内,会议,如果下雨,意味着我会在室内,这是一个不合逻辑的句子。有些地方,这个真值表有时会有点棘手,显然如果P为真,而Q为真,结果是正确的,这肯定是有意义的。而且,当P为真而Q为假时,PM v Q为假,也是合乎逻辑的。
如果我对你说:“如果下雨,那么我会在室内”,而实际上正在下雨,但我并不在室内。那么看来我的原始陈述是不真实的,Pm5k,FPS为真。BenQ也必须为真,如果不是,那么这个陈述就是假的。不过值得注意的是,当p为……时,PS4,蕴含则完全不作任何声明。
如果我说类似“如果下雨,那么我会待在室内”,而事实证明并没有下雨。这并不意味着我没有陈述我是否会待在室内,倒是。这只意味着如果皮亚(Pia)说你必须是真的,50就不是真的。我们不能对Q是否成立做出任何声明,如果p是假的。
无论Q是否为假,它的真假都无关紧要。我们并没有对你做出任何声明,但我们仍然可以评估这个邀请。这个蕴含的唯一错误的方式是,如果p是真的,而结论是Q。我们会说,在这种情况下,不成立,就像你一样。
最后要讨论的连接是这个双条件。你可以将双条件视为一种双向的条件。最初当我说“如果下雨,那么我会待在室内”时,我并没有说明如果没有下雨会发生什么,也许我会待在室内,也许我会在户外。
这个双条件可以读作“即使是”,也只有在……时。我可以说:“如果下雨,那么我会待在室内,如果且仅如果它在下雨。”这意味着如果下雨,那么我会待在室内,而如果我待在室内,那么可以合理地得出结论。这个双条件仅在p和Q都为真时成立。
丹尼斯的双条件也为真p.m.5q,但反向也是成立的Q也为5p。因此。如果Pnq都为假,你仍然会说它为真,但在其他两种情况下,p如果且仅如果Q最终会评估为假。这里涉及许多真理和盐,这五个基本逻辑连接符将构成命题逻辑的核心语言。
我们将用来描述思想的语言。我们将用来推理这些思想的语言,以便进行推导。让我们来看一些需要了解的附加术语,以便尝试形成这种命题逻辑的语言。
写下一个实际上能够理解这种逻辑的人,接下来我们需要的就是关于什么是真实的概念。关于这个世界,我们有一堆命题符号p、Q、R,甚至其他的。我们需要某种方式来知道,世界上实际上什么是真实的,p是真的还是假的,Q是真的还是假的,等等。为此我们将引入模型的概念,它分配一个真值,真值要么是对的,要么是错的。
每个命题符号,否则它创建的我们可以称之为一个可能的世界。举个例子,如果我有两个命题符号,P表示下雨,Q表示今天是星期二。一个模型只需将这些符号取其真值,找到今天的真值,要么是真,要么是假。这是一个样本模型,这个模型,在这个可能的世界中,这是可能的。
PS4表示下雨,假设为假则表示不下雨。还有其他可能的世界或其他模型,也有一些模型中这两个变量都为真。N个变量,像这样的命题符号,要么为真,要么为假,在可能的模型数量中,每个可能的模型。
在我的模型中,可能的变量,要么是真的,要么是假的,如果我不知道任何信息。所以现在我有了这些符号,并且不能在我将要使用的连接符中使用这些符号。为了构建这些知识的部分,需要某种方式来表示。我们将允许Rai访问我们称之为的知识库。
你的知识库其实只是,代替句子,Rai,最近的海滩。命题逻辑中的一组句子,AI知道的事情,工作路线。我们可能会告诉Rai一些信息,关于它可能面临的情况,关于一个问题,发生在我身上试图解决的情况。
我们会将这些信息提供给AI,AI将会在其知识库中存储这些信息。接下来发生的是AI想利用知识库中的信息来得出关于世界其他部分的结论。这些结论是什么样子的。要理解这些结论,我们需要引入一个新的概念和一个新的符号。
这就是蕴涵的概念。所以,这里这句话带有这个双向转阀,使用希腊字母,这个是希腊字母阿尔法和希腊字母贝塔。读作:阿尔法,蕴涵,阿尔法和贝塔只是句子,命题逻辑。这意味着,阿尔法蕴涵贝塔,意味着在每个模型中。换句话说,在每一个可能的世界中,如果这个句子是真的,阿尔法就是真的。
在句子贝塔中也是真的,如果阿尔法蕴涵贝塔。意味着如果我知道阿尔法是真的,贝塔也必须是真的。如果我的阿尔法是这样的。我知道今天是1月的星期二,那么一个合理的贝塔可能是。我知道这是真的,因为在所有1月的星期二的世界中。
我确信它必须是1月,这是关于世界的第一个陈述或句子的定义。
蕴涵,第二个陈述,我们可以合理地使用推理。基于第一个句子来推导出第二个句子。最终,这是蕴涵的概念,我们将尝试为计算机编码,我们希望Rai代理能够弄清楚可能的蕴涵是什么。
蒙特雷的眼睛能够处理这三句话。哈利没有去找海格,哈利去找了海格或邓布利多,但不能同时去。而哈利去找了邓布利多,仅凭这些信息,像Rai一样进行推理。或者通过使用这些三句话在知识库中得出结论。
我们可以在这里得出结论,1,哈利今天没有去阿格拉。我们可以推导出,今天是否下雨,这个过程称为推理。今天我们将专注于这个过程,推导新的句子。我给你这三句话,你将它们放入IIA的知识库中。
任何人工智能都能够使用某种推理算法来理解这两句句子。这就是我们定义的方式,让我们来看一下推理的例子,看看我们实际上是如何推理的。人类的理解,在我们采取更算法化的方法之前是相似的。我们将看到,有许多方法可以在人工智能中编码推理的概念。
我们实际上可以实现,所以将再次处理几个命题符号。
我将处理p、q和r,他是星期二,Kiwis是下雨。我们的陈述是哈利会去跑步,命题sandbothe,我们只是定义。还没有说明它们是否为真或假,只是在定义。现在我们将为自己或人工智能提供知识库,缩写为KB。
我们对世界的知识,陈述。在这里的括号只是用来表示前提,所以我们可以看到什么与什么关联。但你会这样读,和,不是,意味着把b逐步放入。这里是它是星期二,Q是下雨,不是它是不下雨。
这意味着哈利会去跑步,阅读这整个句子的自然语言释义。如果今天是星期二,并且没有下雨,那么哈利将去跑步。所以如果今天是星期二且没有下雨,那么哈利将去跑步。这些现在都在我们的知识库中,接下来想象我们的知识库还有其他两个信息。
p为真,今天是星期二,并且我们还有信息,非q。它不下雨,这个句子Q“下雨”恰好是假的。我们拥有的三句话中,没有关于p和非q蕴涵r的信息。利用这些信息,我们应该能够推导出一些推论,关键是非q。
仅在p和非q都为真时,整个表达式才为真。我们知道p为真,且我们知道非q为真,因此我们知道整个表达式为真。蕴涵的定义是,如果左侧的整个内容为真,那么右侧的内容也必须为真。
由此推导出的推理也必须是真的,哈利去跑步。我们知道这一点,利用我们知识库中的知识进行推理。
哈佛CS50-AI | Python人工智能入门(2020·完整版) - P6:L1- 知识系统知识 2 (推断,知识工程) - ShowMeAI - BV1AQ4y1y7wy
基于这个想法推理,因此这最终是我们可能认为某种推理算法的开始,这是一种我们可以用来试图判断是否能够得出某种结论的过程。而这些推理算法最终要回答的是。
关于蕴含的核心问题,给定对世界的一些查询,我们想了解世界的某些事情,我们将把这个查询称为 alpha。我们想问的使用这些推理算法的问题是,知识库 K 是否蕴含 alpha,换句话说,使用我们所知道的信息。
在我们的知识库中,我们可以得出结论,这个句子 alpha 是真的,这最终是我们希望做的。那么我们该如何做到这一点?我们该如何写一个算法,能够查看这个知识库并确定这个查询是否为真?
alpha 实际上为真,结果证明有几种不同的算法可以做到这一点,其中一种最简单的算法被称为模型检查。现在请记住,模型只是将我们语言中所有命题符号分配给真值真或假的某种赋值。
你可以将模型视为一个可能的世界,那里有许多可能的世界,其中不同的事情可能为真或假,我们可以枚举所有这些,而模型检查算法正是如此。那么我们的模型检查算法是如何工作的呢?如果我们想确定我们的。
知识库包含一些查询 alpha,那么我们将枚举所有。
考虑所有可能的模型,换句话说,考虑我们变量的所有真和假的可能值,所有我们世界可能存在的状态。如果在每个模型中,知识库为真,alpha 也为真,那么我们知道知识库蕴含 alpha,所以,让我们仔细看看。
尝试理解这句话的实际含义,如果我们知道在每个模型中,换句话说,在每个可能的世界中,不管你给定的变量真值是什么,如果我们知道每当我们的知识为真时,所知的真实情况也是正确的,那么这个查询 alpha 也是真,那么。
理所当然,只要我们的知识库是真实的,那么 alpha 也必须为真,这将构成我们的模型的基础。我们将枚举所有可能的世界,问自己在知识库为真时 alpha 是否为真。
如果是这样,那么我们知道 alpha 为真,否则知识库并不蕴含 alpha。好的,这有点抽象,但让我们看看一个例子,试着将实际的命题符号应用到这个想法上,再次我们将使用相同的例子 P。
假设是星期二,Q 是下雨,R 是哈利将去跑步。我们的知识库包含这些信息,P 和非 Q 推导出 R。我们也知道 P 是星期二,且非 Q 不是下雨,我们的查询即我们想问的事情是,是否保证为真。
哈利将去跑步,因此第一步是列举所有可能的模型,我们有三个命题符号 P、Q 和 R。这意味着我们有二的三次方,即八个可能的模型,都是假。
可以为所有这些模型分配真和假,我们可能会询问在每一个模型中,知识库在这里是否为真,我们所知道的事情在哪些世界中,这个知识库可能适用。在哪个世界这个知识库为真。
我们知道 P,像我们知道今天是星期二,这意味着我们知道前四行中 P 为假,因此没有一个将为真或适用于这个特定知识库,在那些世界中我们的知识库不为真。同样,我们也知道非 Q,即我们知道它不。
比如在知识库中是下雨,所以在 Q 为真的任何这些模型中(例如这两个和这两个)都不会有效,因为我们知道 Q 为假。最后我们也知道 P 和非 Q 推导出 R,这意味着当 P 为真或这里的 P 为真,且 Q 为假,Q 为假时,这两个则 R 必须为真。
如果 P 为真,Q 为假,但 R 也是假,这并不满足这个推导。在这种情况下,推导并不成立。因此,我们可以说,对于我们的知识库,我们可以得出结论,在哪些可能的世界中我们的知识库为真,以及在那些可能的世界中。
我们的知识库是假的,结果发现只有一个可能的世界使我们的知识库实际上为真。在某些情况下,可能会有多个可能的世界使知识库为真,但在这种情况下,恰好只有一个可能的世界。
假 假 真 假 真 假 假 真 真等,总共有八种可能的方式可以明确说出我们的知识库情况,在这种情况下我们会查看查询,R 为真,作为结果我们可以得出结论。这就是模型检查的思想,列举所有可能的模型,并在这些模型中查看。
我们的知识库为真的查询是否也为真。现在我们来看看如何在编程语言(如 Python)中实现这一点,查看一些实际代码,以编码这个命题符号和逻辑及这些联接词,比如与和。
或者和不是一个蕴含,等等,看看这段代码实际可能是什么样子。所以我提前写了一个逻辑库,比我们今天需要担心的要详细得多,但重要的是我们有一个类来表示我们可能拥有的每种类型的逻辑符号或连接词。
所以我们只有一个逻辑符号的类,每个符号都将表示并存储该特定符号的某个名称。我们还有一个取反的类,它接受一个操作数,所以我们可能会说某个符号不是对的,或者某个其他句子不是真的,我们有。
1/4和1/4,等等,我将演示一下这是如何工作的。你可以稍后查看实际的逻辑图,但我会先调用这个文件 hairy pie,我们将存储有关这个哈利·波特世界的信息,例如,所以我将从我的逻辑模块中导入。
在这个库中,一切都是有序的,创建一个符号时,你使用大写字母 S。符号,我将创建一个表示下雨的符号,意味着正在下雨,例如。然后我会创建一个表示海格的符号,意味着哈利拜访了海格,这个符号的意义就是这样。这个符号意味着正在下雨,这个符号意味着哈利。
拜访海格,我将添加另一个符号,叫做邓布利多,表示哈利拜访了邓布利多。现在我想保存这些符号,以便稍后在进行逻辑分析时使用,所以我会将它们每一个保存到一个变量中,例如下雨、海格和邓布利多。
变量都在这里,现在我有了这些逻辑符号,我可以使用逻辑连接词将它们组合在一起。例如,如果我有一个句子像。
和下雨和海格,例如,这不一定是真实的,只是为了演示。我现在可以尝试打印出句子公式,这是一种函数,我写的这个函数接受一个句子,使用命题逻辑并简单地将其打印出来,这样我们程序员就可以现在看到它。
理解它实际上是如何工作的,所以如果我运行 Python Harry PI,我们将会。
看到的是这个句子和命题逻辑下雨和海格,这是我们在 Python 程序中表示“和”的逻辑表示,其参数是下雨和海格,所以我们通过编码这个想法来表示下雨和海格,这在 Python 面向对象编程中是相当常见的。
你有许多不同的类,你传递参数给它们,以创建一个新的对象,例如,以表示这个想法。但现在我想做的是以某种方式编码我对这个世界的知识,以便从类的开始解决这个问题。
谈到试图弄清楚哈利拜访了谁,以及试图弄清楚。是否在下雨,或者没有下雨,那么我有什么知识我将。继续创建一个新的变量,叫做知识,我知道的是什么。我知道的第一句话是我们谈论的想法,如果它不是。
下雨那么哈利将拜访海格。那么,我如何编码它?我可以使用否定,然后是雨的符号。这里是我在说的。它没有下雨,现在,含义是,如果没有下雨。然后哈利拜访了海格,所以我将把它包裹在一个含义内,表示如果。
如果没有下雨,这个前提对于,含义,那么哈利拜访了海格。所以我在说,含义的前提是,天气没有下雨,如果没有。下雨,那么哈利拜访了海格,我可以打印出知识公式。看看那个想法的逻辑公式等价,所以我运行Python,哈利。
PI,这是我们看到的逻辑公式,结果是文本版本。我们之前查看的内容,如果没有下雨,那么。那意味着哈利拜访了海格,但我们还有额外的。信息可以获取,在这种情况下,我们还知道。
哈利要么拜访海格,要么邓布利多,那么我该怎么编码呢?这意味着在我的知识中,我真的有多个知识。信息在进行,我知道一件事和另一件事以及另一件事,所以我将继续。把我的所有知识用一个“与”包裹起来,并将事情转移到一个新的。
只是多做一点确认,但我知道,多个事情,所以我说知识。是多个不同句子的和。我知道多个不同的句子是真的。有一句我知道是真的。是这个含义,如果没有。下雨那么哈利拜访了海格,另一个我知道的。
真实的是,哈格里德或邓布利多,换句话说,或者,所以海格或邓布利多是真的,因为。 我知道哈利拜访了海格或,邓布利多,但我知道的不止这些。
事实上,之前那个初始句子说哈利拜访了海格或。邓布利多,但不是两者,所以现在我想要一个。句子来编码的想法是哈利没有同时拜访海格和。邓布利多,哈利拜访海格和邓布利多的概念会这样表示,并且是海格和。
邓布利多,如果这不是真的,如果我,想说不是,那么我只需将。整个内容放在一个节点内,所以现在,这三行第八行表示,如果没有下雨,那么哈利拜访了,海格,第九行表示哈利拜访了。海格或邓布利多,第十行表示,海格没有或哈利没有拜访两者。
海格和邓布利多都不是,真的是这样,海格符号和邓布利多符号中只有一个可以是真的,最后我知道的最后一条信息是,哈利拜访了邓布利多,所以现在这些就是我所知道的知识点,一句和恩洛的。
句子还有另一个还有另一个,还有我。
可以打印出我所知道的,只是为了更直观地看到,现在是我计算机内部正在使用这些不同的Python对象表示的信息的逻辑表示。再次查看逻辑π,如果你想了解它到底是如何工作的。
实现这一点,但不必太担心所有的细节,我们在这里说,如果没有下雨,那么哈利拜访了海格,我们说海格或邓布利多是真的,我们说海格和邓布利多不同时为真,也就是说它们不能都是真的。
是真的,我们也知道邓布利多是真的,所以这个长的逻辑句子代表我们的知识库,这是我们知道的事情,现在我们希望做的是使用模型检查来提出查询,问一个问题,比如基于这些信息,我知道现在是否在下雨吗。
我们作为人类能够通过逻辑推理,搞清楚基于这些句子,我们可以得出这个结论,也就是要弄清楚,是的,外面一定在下雨,但现在我们希望计算机也能做到这一点,所以让我们看看正在进行的模型检查算法。
按照我们刚刚在伪代码中绘制的相同模式来进行,因此我在逻辑π中定义了一个函数,你可以看看,叫做模型检查。模型检查需要两个参数,即我已经知道的知识和查询,目的是为了进行模型检查,我需要枚举所有。
对于所有可能的模型,我需要问自己,知识库是否为真,查询是否为真。所以我需要做的第一件事是以某种方式枚举所有可能的模型,这意味着对于所有存在的可能符号,我需要为它们赋予真和假,并查看。
不论它是否仍然成立,因此这是我们将要做到的方式。我们将开始,所以我在内部定义了另一个助手函数,稍后我们会讲到,但这个函数首先通过确定我处理的符号,获取知识和查询中的所有符号。
在这种情况下,我处理的符号是雨、海格和邓布利多,但根据问题的不同,可能还有其他符号。我们很快将看看一些示例,最终我们将需要一些额外的符号来表示问题,然后。
我们将运行这个检查所有功能,它是一个辅助功能,基本上会递归地调用自己,检查每种可能的命题符号配置。因此,我们从查看这个检查所有功能开始,我们该怎么做呢?如果没有符号,意味着如果我们已经完成了。
这是分配我们已经分配的所有符号,每个符号的值到目前为止我们还没有做到,但如果我们将来做到这一点,那么我们会检查在这个模型中知识是否为真。这一行的意思就是,如果我们使用模型的真值分配来评估知识的命题逻辑公式。
知识为真,如果知识为真,那么只有在查询为真的情况下我们才应该返回真,因为如果知识为真,我们希望查询也为真,以便存在蕴含关系。否则我们无法确定,否则不会有一个INT元素,如果有一种情况是。
我们知道我们的知识是真实的,但查询的内容恰好是错误的。因此这里这一行正在检查,在所有知识为真的世界中,查询也必须为真,否则我们可以直接返回真,因为如果知识不真实,那么我们就不在乎。
这与我们之前列举的表格是等价的,在所有知识库不真实的情况下,这七行中,我们并不关心我们的查询是否真实。我们只关心在知识库实际上真实时,查询是否为真。
正是这个绿色高亮的行,因此这个逻辑是通过那个语句编码的,此外,如果我们还没有分配符号,我们还没有看到任何东西,那么我们要做的第一件事就是弹出一个符号,我先复制符号,只是为了保存一个现有的副本。
我从剩余的符号中弹出一个符号,以便随机选择一个符号,并创建一个该符号为真的模型副本,再创建一个该符号为假的模型副本。因此,我现在有两个模型副本,一个符号为真,一个符号为假。
符号为假,我需要确保这个蕴含在这两个模型中都成立,因此我递归地在语句为真的模型上检查所有内容,并在语句为假的模型上检查所有内容。因此,你可以再次查看这个功能,试图了解这个逻辑究竟是如何工作的。
这是在工作,但实际上它正在递归调用这个检查所有的功能。并且在递归的每一层中,我们都在说让我们选择一个我们尚未分配的新符号,将其分配为真并将其分配为假,然后检查确保在两种情况下蕴含关系都成立。
因为最终我需要检查每一个可能的世界,我需要考虑每一种符号组合,并尝试每种真假组合,以便确定蕴含关系是否成立,这个函数是我们为你写的,但为了在复杂的代码中使用该函数。
我将写的是这样的,我想基于知识进行模型检查,然后提供第二个参数,查询是什么,我想问的事情是,在这种情况下我想问的是:外面下雨吗?模型检查同样需要两个参数,第一个参数是。
我知道的这一信息是这条知识,即一开始给我的这条信息,而第二个参数“下雨”则编码了查询的想法,我想问的是基于这个知识,我是否可以确定外面在下雨。
我可以尝试打印出结果,当我运行这个程序时,我看到答案是正确的,基于这些信息我可以。
可以确凿地说外面在下雨,因为使用这个模型检查算法,我们能够检查在每一个知识为真的世界里,都是在下雨。换句话说,没有一个世界是这个知识为真而不下雨的,因此可以得出结论。
这实际上是在下雨,这种逻辑可以应用于许多不同类型的问题,如果面对一个可以使用某种逻辑推理来尝试解决的问题,你可能会考虑需要哪些命题符号来表示这些信息。
在命题逻辑中你可能会使用哪些语句来编码你所知道的信息,这个过程试图将一个问题转化为确定使用哪些命题符号来编码这个想法或如何逻辑地表示它,被称为知识工程。
工程师和人工智能工程师会把一个问题提炼成计算机能够理解的知识,如果我们能将任何一般性的问题,尤其是人类世界中的问题,转化为计算机能够解决的问题,使用任何数量的。
如果有不同的变量,那么我们可以利用能够执行模型检查或其他推理算法的计算机,实际解决这个问题。现在我们将来看两个或三个知识工程的实例,实践如何解决某个问题。
我们需要弄清楚如何应用逻辑符号,并使用逻辑公式来编码这个想法,我们将以在美国和英国非常流行的棋盘游戏Clue开始。在Clue游戏中,有许多不同的因素在发挥作用,但游戏的基本前提是,如果你从未玩过。
在这个游戏中,有许多不同的人,现在我们只用穆斯塔德上校、普拉姆教授和斯卡雷特小姐。游戏中有几个不同的房间,比如一个舞厅、一个厨房和一个图书馆,还有多种不同的武器:一把刀、一把左轮手枪和一个扳手,以及其中的三样,一人、一房间。
一种武器是解决谜题的关键,即凶手和他们所在的房间以及他们使用的武器。游戏开始时,所有这些卡片被随机洗牌,然后将三张卡片(一个人、一个房间和一种武器)放入一个密封的信封中。
我们不知道,并希望通过某种逻辑过程来弄清楚信封里是什么,哪一个人、哪个房间和哪种武器。我们通过查看这些卡片中的一些(而不是全部)来尝试弄清楚正在发生什么,因此这是一个非常。
这是一个流行的游戏,但现在我们尝试将其形式化,看看是否可以训练计算机通过逻辑推理来玩这个游戏。因此,我们将从思考最终需要的命题符号开始,再次记住,命题符号只是一些。
这个变量可以在世界中为真或为假,因此在这种情况下,命题符号实际上只是对应于信封里可能的每个东西。穆斯塔德的符号在这种情况下,如果穆斯塔德上校在信封里,则为真。
如果他是凶手,则信封里的符号为真,否则为假,普拉姆教授和斯卡雷特小姐也是如此,对于每个房间和每种武器,我们都有一个命题符号来表示这些想法。然后,使用这些命题符号,我们可以开始。
创建逻辑句子。创建。
我们对世界的知识,比如我们知道某人是。
凶手是这三个人之一,实际上是凶手,我们该如何编码呢?我们不确定凶手是谁,但我们知道是第一个人、第二个人或第三个人,因此我可以说。像是穆斯塔德或普拉姆或斯卡雷特,这个知识。
编码这三个人之一。
杀人案件,我们不知道是哪一件,但这三件事情中必须有一个是真的。
我们还知道其他什么信息呢?我们知道,比如说其中一个房间一定是罪行发生的信封里的房间,可能是在舞厅、厨房或图书馆。现在我们不知道是哪一个,但这是我们一开始就知道的知识,即其中一个。
这三种必须在信封里,同样我们可以对武器说同样的事情,武器可能是刀、手枪或扳手,其中一种武器一定是选择的武器,因此是信封中的武器。随着游戏的进行,游戏玩法通过。
人们会获得各种不同的卡片,利用这些卡片可以推导出信息。如果有人给你一张卡片,比如我手里有教授普朗姆的卡片,那么我知道教授普朗姆的卡片不能在信封里。我知道教授普朗姆不是罪犯,所以我知道一部分。
比如信息像不是普朗姆,我知道教授普朗姆一定是假的。这个命题符号不是真的,有时候我可能不确定某张特定的卡片是否在中间,但有时某人会做出猜测,我会知道三种可能性中至少有一种不是真的,比如有人。
我会猜测穆斯塔德上校在图书馆里,拿着手枪或类似的东西。如果是那样,可能会揭示出一张我看不到的卡片。但如果这是一张卡片,且它是穆斯塔德上校、手枪或图书馆之一,那么我知道至少有一个不能在中间,所以我知道。
这就像是说它不是穆斯塔德,或者它不是图书馆,或者它不是手枪。现在也许这些不全都是真,但我知道至少有一个穆斯塔德、图书馆和手枪必须是假的。所以这就是对《线索》游戏的命题逻辑表示,一种编码知识的方式。
我们知道,在这个游戏中,使用命题逻辑,计算机算法像是模型检查,我们刚才看到的实际上可以查看和理解。那么现在让我们来看一些代码,看看这个算法在实践中是如何工作的。好的,我将打开。
打开一个叫做 clue.dot.pi 的文件,我已经开始了。在这里,我定义了一些东西,我必须找到一些符号。注意,我有一个穆斯塔德上校的符号,一个教授普朗姆的符号,一个红衣小姐的符号,这些都放在这个列表中。
我有一个舞厅、厨房和图书馆的符号,放在房间列表中,然后我还有刀、手枪和扳手的符号,这些是我的武器。所以所有这些角色、房间和武器加在一起,这就是我的符号。现在我还有这个捷克知识函数。
而捷克知识函数的作用是它获取我的知识,并试图对我知道的事情得出结论,因此例如我们将遍历所有可能的符号,我们将检查我是否知道那个符号是真的,而一个符号可能是像普拉姆教授或者。
如果我知道在图书馆的那张卡片是真的,换句话说,我知道这张卡片必须在信封里,那么我将使用一个名为C print
的函数打印出来,它可以用颜色打印东西,我将打印出“是”,并用绿色打印,以便我们清楚。
如果我们不确定这个符号是真的,也许我可以检查一下我是否确定这个符号不是真的,比如我知道它肯定不是普拉姆教授,我可以通过再次运行模型检查来做到这一点,这次检查我是否知道这个符号不是真的,如果我确定。
如果符号不是真的,而我不确定这个符号不是真的,因为我说“L如果不是模型检查”,这意味着我不确定这个符号是否是假的。那么我会继续在符号旁边打印出来,因为这个符号可能是真的,也可能不是真的,我其实并不知道,所以我的知识是什么。
实际上你有,好吧,让我们尝试现在来表示我的知识。我知道一些事情,所以我把它们放在N里,我知道三个人中必定有一个是罪犯,所以我知道或者是芥末上校、普拉姆、猩红小姐,这就是我编码的方式,表明它要么是芥末上校,要么是。
普拉姆教授或猩红小姐,我知道事情一定发生在一个房间里,所以我知道,或者是舞厅、厨房、图书馆,例如,我知道必定使用过其中一种武器,所以我知道,或者是刀、左丨轮丨枪、扳手。这可能是我最初的知识,我知道一定是一个。
我所知道的人一定是。
在其中一个房间里,我知道必定有一种武器,我可以通过打印出知识来看到这项知识的公式。
点公式,所以我将运行Python中的clue.py
,现在这是我以逻辑格式知道的信息,我知道是芥末上校或普拉姆教授或猩红小姐,我知道是在舞厅、厨房或图书馆,并且我知道是刀、左丨轮丨枪或扳手,但我不知道更多。
除此之外,我不能真的得出任何明确的结论,实际上我们可以看到,如果我尝试并让我继续运行我现在的知识检查函数,让我们检查这个函数,我检查知识,或者说我刚刚写的这个函数,它会检查所有符号并尝试。
看看我能从任何符号中得出什么结论,所以我将去。
继续运行线索Pi,看看我知道的是什么,似乎我没有确切知道任何东西,我有所有三个人,或者可能是所有三个房间,或者可能是所有三种武器,或者我现在还不确切知道任何东西,但现在让我尝试添加一些额外的信息,看看。
额外的信息,额外的知识能否帮助我们在这个过程中逻辑推理。我们将提供信息,Rai将负责推理并找出能够得出什么结论,所以我从一些卡片开始,这些卡片,科伦。
例如,我知道芥末的卡片,芥末符号必须是错误的。换句话说,芥末不是那个,信封里不是罪犯,所以我可以说知识支持某种东西,叫做每一个,而在这个库中支持点ad,这是一种添加知识或额外信息的方式。
将逻辑句子与结束条款连接,所以我可以说知识添加不是芥末红。我恰好知道因为我有芥末卡,科伦·芥末不是嫌疑人,可能还有其他几张卡,也许我还有厨房的卡,所以我知道不是厨房,也许我还有另一张卡。
这表示它不是左轮手枪,所以我有三张卡,科伦·芥末、厨房和左轮手枪,我以这种方式将其编码到我的AI中,说它不是科伦·芥末,不是厨房。
而且这不是左轮手枪,我知道这些是真的,所以现在当我重新运行线索时,我会看到我能够排除一些可能性。之前我不确定是刀还是左轮手枪还是扳手,刀可能是,左轮手枪可能是,扳手也可能是,现在我只剩下刀和。
在这两者之间我不知道是哪一个,它们都是可能的,但我已经能够排除左轮手枪,因为我知道那是错误的,因为我有左轮手枪卡,所以!
在这场游戏中可能会获取额外的信息,我们会通过向我们的知识集合或我们一直在构建的知识库中添加知识来表示这一点,所以如果我们额外得到了信息,某人做了一个猜测,比如密斯·斯卡雷特。
在图书馆里,有扳手,我们知道有一张卡片被揭示,这意味着那三张卡片中的一张,或者是密斯·斯卡雷特,或者是图书馆,或者是扳手,这三者中至少有一张不在信封里,所以我可以添加一些知识,可以说知识添加,我打算添加一个或的条款,因为我不。
我不确定究竟不是哪一个,但我知道其中一个不在信封里。所以,它不是猩红,也不是图书馆,并且可以支持多个论点,说明它也可能不是,至少其中一个需要是假的,但我不知道是哪个,也许有多个。
这只是一个假设,但至少我知道有一个需要成立,所以现在如果我重新运行推理,我实际上没有任何额外信息,暂时还没有什么可以确凿地说。我仍然知道也许是普拉姆教授,也许是猩红小姐。我还没有排除任何选项,但让我们设想一下我能得到更多的信息。
信息是,有人给我展示了普拉姆教授的卡片,例如,我说。
好吧,让我们回过头来,知识点排除普拉姆,所以我知道普拉姆教授的卡片不在中间,我重新运行推理,好的,现在我能得出一些结论。我已经能够排除普拉姆教授,唯一剩下的可能是猩红小姐。
猩红小姐,我知道,变量必须为真,我已经能够推断出这一点,基于我已有的信息。在舞厅、图书馆、刀和扳手之间,对于这两样,我仍然不确定。那么,让我们再添加一条信息,假设我知道这不是舞厅。
有人给我展示了舞厅的卡片,所以我知道这不是舞厅,这意味着在这个时候我应该能够。
我们得出结论,它是图书馆,来看看。
我会说知识点,排除舞厅,我们将继续运行。
结果是,经过这一切,我不仅能得出结论,知道这是图书馆,我还知道武器是刀,这可能是一个推断,稍微复杂一点,我不会立刻意识到,但通过这个模型检查算法,AI能够做到。
得出这样的结论,我们可以确定猩红小姐在图书馆里,手里拿着刀,我们是怎么知道的呢?我们从这里的这个条件得知,它不是猩红,也不是。
这不是图书馆,也不是扳手,鉴于我们知道是猩红小姐,
我们知道这是图书馆,那么武器唯一剩下的选项是,这不是扳手,这意味着它必须是刀,所以我们作为人类现在可以回过头来推理,尽管这可能并不。
一目了然,这就是使用AI或某种算法来做这件事的优势之一,因为计算机可以穷尽所有可能性,试图弄清楚解决方案到底应该是什么,因此,能够以这种方式表示知识通常是有帮助的。
知识工程的某种情况,我们可以利用计算机来表示知识并根据这些知识得出结论。每当我们可以将某些东西转换为这样的命题逻辑符号时,这种方法可能会很有用,所以你可能对逻辑谜题有所了解。
你必须通过拼图来找出某些事情。这就是经典逻辑谜题的样子,像吉德罗伊、米奈娃、波莫娜和霍拉斯各自属于四个学院中的一个:格兰芬多、赫奇帕奇、拉文克劳和斯莱特林,然后我们有一些。
信息表明,吉德罗伊属于格兰芬多或拉文克劳,波莫娜不属于斯莱特林,而米奈娃属于格兰芬多。我们有一些信息,并且利用这些信息,我们需要能够得出一些关于每个人应该被分配到哪个学院的结论。
我们可以用完全相同的思路来尝试实现这个概念,因此我们需要一些命题符号,在这种情况下,命题符号会变得更加复杂,尽管我们稍后会看到一些让它变得更清晰的方法,但我们需要16个命题符号,每个代表一个。
每个命题符号要么为真,要么为假,因此吉德罗伊是否在格兰芬多要么为真,要么为假。他要么在格兰芬多,要么不在,同样吉德罗伊是否在赫奇帕奇也是如此。每个组合都是真或假。
我们为每一个可能的学院和人都能想出某种命题符号,利用这种类型的知识,我们可以开始思考关于这个谜题的逻辑句子。如果我们在考虑信息之前就知道。
考虑到我们可以思考前提。
每个人都被分配到不同的学院,这告诉我们什么呢?它告诉我们像这样的句子,它告诉我们波莫娜在斯莱特林意味着波莫娜不在赫奇帕奇,如果波莫娜在斯莱特林,那么我们就知道波莫娜不在赫奇帕奇,我们对所有四个人和所有。
无论你选择哪个人,如果他们在一个学院,那么他们就不在另一个学院,因此我可能会有一整套这样的知识陈述。如果我们知道波莫娜在斯莱特林,那么我们就知道波莫娜不在赫奇帕奇。此外,我们还得到了每个人和学院的信息。
如果某人在不同的学院,那么我还有一些知识,类似于这样:米尔娜在拉文克劳,意味着吉尔德罗在拉文克劳。如果他们都在不同的学院,那么如果米尔娜在拉文克劳,那么我们知道吉尔德罗也不在拉文克劳,我还有很多这样的信息。
有类似的句子在表达其他人和其他学院的想法,除了这种形式的句子外,我还知道一些信息,比如吉尔德罗在格兰芬多或拉文克劳,这可以表示为吉尔德罗。
格兰芬多、联盟或拉文克劳,然后使用这些句子,我可以开始对这个世界进行一些推断。让我们看看这个例子的实现,我会继续实际尝试实施这个逻辑难题,看看我们能否找出答案,我会打开难题 pi。
我已经开始实现这种想法,我定义了一份人员列表和一份学院列表,到目前为止,我为每个人和每个学院创建了一个符号,这就是这个双重循环所做的,遍历所有人员,遍历所有学院,为每个创建一个新符号。
然后我添加了一些我知道的,关于每个人都属于一个学院的信息,所以我为每个人添加了信息,比如格兰芬多、赫奇帕奇、拉文克劳或斯莱特林,这四个选项中必有一个成立,每个人都属于一个学院。还有其他什么信息呢?
我知道每个人只属于一个学院,所以没有人属于多个学院。这是如何工作的呢?好吧,这对所有人都是成立的,所以我会遍历每个人,然后我需要遍历所有不同的学院配对。我的想法是,我想编码这个想法:如果米尔娜。
如果某人在格兰芬多,那么米尔娜就不能在拉文克劳,所以我会遍历所有的学院 h1,再遍历所有的学院 h2,只要它们不同 h1 不等于 h2,那么我就会将这条信息添加到我的知识库中。这就是所蕴含的逻辑,如果某人位于 h1,那么我知道。
他们不在学院 h2,所以这些行在编码这个概念:对于每个人,如果他们属于学院 1,那么他们不在学院 2。我们需要编码的另一个逻辑是,每个学院只能有一个人,换句话说,如果波莫娜在赫奇帕奇,那么其他人就不能在了。
也不允许在赫奇帕奇,逻辑是一样的,只是反向的。我遍历所有的学院,并遍历所有不同的人的配对,所以我会遍历一次人员,再遍历一次人员,只有在人员不同的情况下,p1 不等于 p2,我就会添加这个知识。
如果第一个人属于某个学院,则第二个人不属于同一个学院,因此这里我只是编码了代表问题约束的知识。我知道每个人都在不同的学院,我知道任何人只能属于一个学院,我现在可以。
我的知识,并尝试打印出我所知道的信息。所以我会打印出知识。
我们只需看看这个公式的实际应用,我暂时跳过这个,但我们一会儿会回来。
让我们通过运行Python puzzle pie打印出我知道的知识。这是很多信息,我必须滚动,因为有16个不同的变量在进行,但基本想法是如果我们滚动到最顶部,我看到我的初始信息,吉尔德罗伊要么在格兰芬多。
吉尔达·罗伊斯在赫奇帕奇,或者吉尔达·罗伊斯在拉文克劳,或者吉尔达·罗伊斯在斯莱特林,还有更多的信息,所以这相当混乱,比我们想要的要多,很快我们将看到使用逻辑以更好的方式表示这些,但现在我们可以说这些是。
我们正在处理的变量,现在我们想添加一些信息。所以我们要添加的信息是,吉尔德罗伊在格兰芬多还是在,拉文克劳,这些知识是被提供给我们的,所以我会说知识。点添加,我知道吉尔德罗伊要么在格兰芬多,要么在拉文克劳,这两个之一。
一些事情必须是真的,我也知道波莫那不在斯莱特林,所以我可以说知识不包含这个符号,不是波莫那·斯莱特林符号,然后我可以添加米奈娃在格兰芬多的知识,添加符号米奈娃·格兰芬多,所以这些是我知道的知识点,而这个循环。
在底部,循环遍历我的所有符号,检查知识是否推导出该符号,通过再次调用这个模型检查函数,如果我们知道该符号为真,我们就。
打印出符号,现在我可以运行Python puzzle PI,Python将为我解决这个难题,我们能够得出结论吉尔德罗伊属于拉文克劳,波莫那属于赫奇帕奇,米奈娃属于格兰芬多,霍鲁斯属于斯莱特林,仅仅通过将这些知识编码在计算机内。
在这种情况下,这很繁琐,因此我们能够得出结论,你可以想象这适用于许多不同的推理情况,因此不仅仅是这些情况,当我们试图处理哈利·波特角色时。
你是否玩过类似于“猜字游戏”的游戏,试图找出哪个。
不同颜色的顺序进行,尝试对其进行预测。例如,我可以告诉你,让我们来玩一个。
这是一个简化版的猜颜色游戏,其中有四种颜色:红色、蓝色、绿色和黄色,它们以某种顺序排列,但我不会告诉你什么顺序,你只需进行猜测,我会告诉你红色、蓝色、绿色和黄色中,有多少在正确的位置。
这个游戏的简化版本,你可能会猜测红色、蓝色、绿色和黄色。我会告诉你,比如这四个中有两个在正确的位置,而其他两个则。
然后你可以合理地进行猜测,说好吧,让我们试试这个蓝色、红色、绿色和黄色,尝试交换其中两个,这一次也许我会告诉你,知道吗,所有这些都不在正确的位置。然后问题是,好的,这四种颜色的正确顺序是什么。
人类可以开始推理这一点,如果这些都不正确但其中两个是正确的,那么一定是因为我交换了红色和蓝色,这意味着这里的红色和蓝色必须是正确的,而绿色和黄色可能不正确。你可以开始这样做。
这种演绎推理,我们也可以等效地尝试将其编码到我们的计算机中,这将与我们刚才做的逻辑难题非常相似,所以我不会在这段代码上花太多时间,因为它相当相似,但我们还有很多。
一堆颜色和四个不同的位置,这些颜色可以放置在其中。然后我们还有一些额外的知识,我将所有这些编码。
知识,你可以查看这段代码,并在自己的时间里研究,但我只是想展示,当我们。
运行这段代码,运行Python mastermind PI,看看我们得到了什么。我们最终能够计算出红色在z行位置为0,蓝色在1。
把黄色放在两个位置,把绿色放在三个位置,作为这些符号的排序。最终,你可能注意到这个过程花费了相当长的时间,实际上模型检查并不是一个特别高效的算法。为了进行模型。
检查是将我所有可能的不同变量列举出来,看看它们可能是什么。如果我有n个变量,我需要查看2的N次方个可能世界,以执行这个模型检查算法,这可能在特别是当我们。
开始接触越来越大规模的数据集,我们有许多更多的变量在此起作用,而我们只有相对较少的变量,因此这种方法实际上可以有效,但随着变量数量的增加,模型检查就会变得越来越不适合。
尝试解决这些问题,因此虽然对于类似于 mastermind 的结论,可能是可以接受的,确实这是正确的顺序,其中所有四个都是在正确的位置,我们想做的是想出一些更好的方法,以便能够进行推断,而不仅仅是列举所有可能性。
为了做到这一点,我们接下来将过渡到。
哈佛CS50-AI | Python人工智能入门(2020·完整版) - P7:L1- 知识系统知识 3 (推断规则,解析) - ShowMeAI - BV1AQ4y1y7wy
推断规则的想法是一种可以应用的规则,以将已经存在的知识转化为新的知识形式。我们构建推断规则的通用方式是通过在这里有一条水平线,线上的任何东西代表一个前提,即我们知道是真的,然后线下的任何东西就是我们在应用逻辑后能够得出的结论。
所以我们将通过首先用英语展示这些推断规则,然后将其翻译到命题世界来做一些这些推断规则。
逻辑,所以你可以看到这些推断规则实际上是什么样的。因此,比如说,假设我有两条信息,我知道,比如说如果下雨,那么哈里在里面,假设我也知道现在在下雨,那么我们中的大多数人可以合理地得出。
这条信息得出结论,哈里必须在里面。这条推断规则被称为前件肯定,在逻辑中更正式的表述是:如果我们知道alpha蕴含beta,换句话说,如果alpha为真,那么beta也为真,我们也知道alpha是真的,那么我们应该能够得出beta的结论。
也是真的,我们可以应用这个推断规则来获取这两条信息并生成这个新的信息。注意,这与模型检查的方法完全不同,后者的方法是查看所有可能的世界,看看在这些世界中什么是真实的。
在这里,我们并不处理任何特定的世界,而是处理我们知道的知识,以及基于这些知识我们能够得出的结论。我知道a蕴含b,而我知道a,结论是b,这似乎是一个相对明显的规则,但。
如果alpha为真,那么beta也为真,如果我们知道alpha,那么我们应该能够得出结论,beta也是真的,这对许多甚至所有的推断规则都适用,你应该能够查看它们并说,当然,这将是正确的,但把这些全部放在一起。
在一起弄清楚可以应用的推断规则的正确组合,这最终将使我们能够在我们的人工智能中生成有趣的知识,所以这是前件肯定,即如果我们知道alpha,我们知道alpha蕴含beta,那么我们可以。
让我们来看另一个例子,比较直接,比如哈里是罗恩和赫敏的朋友,根据这些信息,我们可以合理地得出哈里也是赫敏的朋友,这必须也是真的,这条推断规则被称为与消除。
如果我们有一个情况,其中 α 和 β 都为真。我有信息 α 和 β,那么仅 α 为真,或者仅 β 为真。但如果我知道两部分都为真,那么其中一部分也必须为真,这从人类的角度来看显而易见。
直觉,但计算机需要被告知这种信息才能应用推理规则。我们需要告诉计算机,这是一个可以应用的推理世界,以便计算机能够访问它,并能够使用它将信息从一种形式转换为另一种形式。
除此之外,让我们看一个推理规则的另一个例子。像“哈利没有通过测试”这句话有点棘手。我们再读一遍,“哈利没有通过测试”是错误的,或者说是假的。如果哈利没有通过测试的说法是假的。
那么唯一合理的结论是哈利通过了测试,因此这并不是一种消去,而是我们称之为双重否定消去。但如果我们在前提中有两个否定,那么我们可以将它们一起去掉,它们相互抵消,一个把真变为假,另一个。
一个把假变成真的,正式一点说,如果前提不是不是 α,那么我们可以得出的结论就是 α,我们可以说 α 为真。接下来我们再看几个这样的例子。
如果下雨,那么哈利在里面。我该如何重新表述这个呢?这个有点棘手,但如果我知道如果下雨,那么哈利在里面,那么我得出两个必须为真的结论,要么没有下雨,要么哈利在里面。
如果下雨,那么哈利在里面,这意味着如果我知道下雨,那么哈利必须在里面。那么还有什么其他可能的情况呢?如果哈利不在里面,那么我知道,肯定没有下雨,因此这两种情况必须成立:要么没有下雨,要么下雨,这样哈利就会在里面。
所以我能得出的结论是,要么没有下雨,要么下雨,因此哈利在里面。这是一种将如果-那么语句转换为或语句的方法,这被称为蕴含消去。这类似于我们一开始所做的。
关于哈利、海格和邓布利多的那几句话,更正式地说,这表示如果我有一个蕴含关系,α 蕴含 β,那么我可以得出结论,要么不是 α,要么是 β,因为只有两种可能:要么 α 为真,要么 α 不为真。
在这些可能性中,α不成立,但如果α成立,那么我们可以得出结论。所以,要么α不成立,要么α成立,此时β也成立。这是一种将蕴含转化为关于“或”的陈述的方式。
好吧,让我们举一个英语例子,比如“如果且仅如果哈利在里面,那么下雨”。这种“如果且仅如果”听起来像我们在命题逻辑中看到的双条件符号,这实际上意味着如果我们要翻译这个。
这意味着如果下雨,那么哈利在里面;如果哈利在里面,那么下雨。这种含义是双向的,这就是我们所说的双条件消去,我可以将双条件“A当且仅当B”翻译成类似“A蕴含B,且B蕴含A”。
许多推理规则是将使用某些符号的逻辑转化为不同的符号,将一个蕴含转化为“或”,或者将一个双条件转化为蕴含。另一个例子是:如果说“哈利和罗恩都未通过测试”。
如果哈利和罗恩都通过了测试,合理的结论是至少有一个人通过了测试,因此结论是:要么哈利没有通过,要么罗恩没有通过,或两者都没有通过。
这并不是排他性“或”,但如果确实不成立哈利和罗恩都通过了测试,那么要么哈利没有通过,要么罗恩没有通过,这种类型的法则就是德摩根法则,在逻辑中非常著名,意在说明我们可以将“且”转换为“或”。
这将“哈利和罗恩都通过了测试”转化为“或”,通过重新排列关系。如果不成立哈利和罗恩都通过测试,那么要么哈利没有通过,要么罗恩没有通过。我们更正式地框定这个逻辑是说,如果不成立。
对于α和β来说,如果α不成立,那么要么非α要么非β。我的思考方式是,如果你在一个“且”表达前有否定,那么你就向内移动否定,翻转“且”为“或”。
否定向内移动,最后翻转为“或”,所以我从“非A且B”变为“非A或非B”,而实际上,存在德摩根定律的反向情况,例如,如果我说“哈利或罗恩未通过测试”,这意味着两者都未通过。
他们通过了测试,那么我可以得出的结论是,哈利没有通过测试,而罗恩也没有通过测试。因此,在这种情况下,我们不是将与转变为或,而是将或转变为与,但想法是相同的,这再次是德摩根定律的另一个例子。
其工作原理是,如果我有非 A 或 B,那么这次相同的逻辑将适用。我将把否定移入文字,并且这次我将把或转换为与。因此,如果不是 A 或 B,这意味着 A 或 B 或 alpha 或 beta 不是正确的,那么我可以说非 alpha 和非 beta,将否定移入文字以使其成立。
这些就是德摩根定律,还有一些其他值得关注的推理规则,其中一个是以这种方式工作的分配律。所以如果我有 alpha 和 beta 或 gamma,那么与数学中一样,你可以使用分配律来分配操作数。
像加法和乘法一样,我可以在这里做类似的事情,我可以说,如果 alpha 和 beta 或 gamma,那么我可以说像 alpha 和 beta 或者 alpha 和 gamma 这样的内容,我已经能够在这个表达式中分配和分配这个符号。这是分配律的一个例子。
将分配律应用于逻辑,就像你会将乘法分配到某个东西的加法上一样。例如,这种方式也可以反过来使用,所以例如,如果我有 alpha 或 beta 和 gamma,我可以将或分配到整个表达式中,我可以说 alpha 或 beta 和。
alpha 或 gamma,因此分配律也以这种方式起作用,如果我想将或移入表达式中,这将是有帮助的。我们将很快看到一个示例,说明为什么我们可能真的关心这样做。好吧,所以现在我们已经看到了很多不同的推理规则。
现在的问题是,我们如何使用这些推理规则来尝试得出一些结论,实际上尝试证明关于蕴含的某些内容。在给定一些初始知识库的情况下,我们希望找到某种方法来证明一个查询是真实的。那么一种思考方式是回顾。
我们上次讨论的内容是关于搜索问题的回顾。搜索问题有某种初始状态,它们有可以从一个状态转换到另一个状态的动作,这些动作由转移模型定义,该模型告诉你如何从一个状态转移到另一个状态,我们也讨论了测试。
查看你是否达到目标,然后用某种路径成本函数来查看你知道你需要走多少步,或者你找到的解决方案有多昂贵。现在我们有这些推理规则,这些规则将一些命题逻辑中的句子集变换为新的命题逻辑句子集。
我们实际上可以将这些句子或这些句子的集合视为搜索问题中的状态,因此如果我们想证明某个查询是真的,证明某个逻辑定理是真的,我们可以将定理证明视为一种搜索问题。我可以说我们从某个初始状态开始,那个初始状态是。
我开始的知识库是我知道的所有句子的集合。可用的行动是什么呢?可用的行动是我在任何时候可以应用的推理规则。过渡模型告诉我,在我应用推理规则之后,这里是我所有的新知识集合。
这将是旧知识集合加上一些我能够得出的附加推理,这与我们上次看到的应用推理规则得出某种结论的方式是一样的。这个结论被添加到我们的知识库中,成为我们的过渡。
模型将编码目标测试。我们的目标测试就是检查我们是否已经证明了我们想要证明的陈述,如果我们试图证明的事情在我们的知识库中,路径成本函数我们试图最小化的可能是步骤数量。
我们需要使用的推理规则,所谓的步骤数量,也就是在我们的证明中。因此在这里,我们能够应用与上次看到的搜索问题相同的思路,去证明一些关于知识的事情,通过将我们的知识以我们能够。
我们可以将其理解为一个搜索问题,具有初始状态、行动和过渡模型。因此这展示了几个事情,其中之一是搜索问题的多功能性,它们可以是我们用来解决迷宫或弄清楚如何从A点到B点的相同类型的算法。
驾驶指示例如也可以用作一种定理证明方法,从某种起始知识库出发,尝试证明一些关于该知识的事情。因此,这再次是除了模型检查之外的第二种方法,来证明某些陈述为真,但事实证明还有更多。
另一种我们可以尝试应用推理的方法,我们现在将讨论这一点,这不是唯一的方法,但肯定是最常见的之一,称为归结。归结基于另一种我们将要看的推理规则,这是一个相当强大的推理规则,能够让我们证明任何事情。
这可以证明关于知识的事情。
基于这个基本思想,假设我知道要么罗恩在大餐厅,要么赫敏在图书馆,假设我还知道罗恩不在大餐厅。基于这两条信息,我能得出什么结论呢?我可以相当合理地得出赫敏必须。
在图书馆,我怎么知道的呢?因为这两个陈述,这两个我们称之为互补的文字,互补的文字彼此相反,似乎相互冲突。这句话告诉我们,要么罗恩在图书馆,所以如果我们知道罗恩不在。
与这个大礼堂相冲突,这意味着赫敏必须在图书馆,而我们可以将其框架为一个更一般的规则,称为单元分辨率规则,该规则表示如果我们有P或Q并且我们还知道非P,那么我们可以合理地得出结论Q,但如果P或Q为真,且我们。
知道P不是真的,唯一的可能性就是Q为真,而这被证明是一个相当强大的推理规则,因为它的作用部分在于我们可以迅速开始概括这个规则,这个Q在这里不需要仅仅是一个单一的命题符号,它可以是。
多个相互链接在一个单一的子句中,我们称之为子句,如果我有类似P或q1或q2或q3,依此类推,一直到QN。所以我有n个不同的其他变量,并且我有非P,那么当这两个互补时,会发生什么呢?是这两个子句解决了。
产生一个新子句,仅仅是q1或q2,一直到QN,或者参数的顺序实际上并不重要,P不需要是第一项,它可以在中间,但这里的想法是,如果我在一个子句中有P,在另一个子句中有非P,那么我知道。
剩下的这些东西之一必须为真,我已经解决了它们以产生一个新的子句,但事实证明我们甚至可以进一步概括这个想法,实际上,并展示我们在这个分辨率上可以拥有的更强大的能力。
规则,所以我们再举一个例子,假设我知道同样的信息,要么罗恩在大礼堂,要么赫敏在图书馆。而我知道的第二条信息是,罗恩不在大礼堂,或者哈利在睡觉,所以这不仅仅是一条信息,我。
有两个不同的情况,我们将会,稍后讨论,我在这里知道什么呢?同样对于任何命题符号,比如罗恩在大礼堂,只有两种可能性:要么罗恩在大礼堂,在这种情况下根据分辨率,我们知道哈利必须在睡觉,要么罗恩不在大礼堂。
根据同样的规则,我们知道赫敏必须在图书馆,基于这两者的结合,我可以说基于这两个前提,我可以得出结论,赫敏要么在图书馆,要么哈利在睡觉。所以再次因为这两者相互冲突,我知道这两者中有一个。
每个这些内容必须为真,你可以仔细查看并尝试推理,确保你相信这个结论。更一般地说,我们可以通过声明,如果我们知道 P 或 Q 为真,同时我们也知道非 P 或 R 为真,我们可以解决这些问题。
将两个子句组合在一起以得到一个新的子句,Q 或 R,其中 Q 或 R 必须为真。再说一次,在最后的情况下,Q 和 R 不需要仅仅是单个命题符号,它们可以是多个符号,所以如果我有一个规则,其中有 P 或 q1 或 q2 或 q3 依此类推,一直到 Q n,其中 n 只是某个数字。
同样地,我有非 P 或 R1 或 R2 等,直到 R M,其中 m 也是某个数字。我可以将这两个子句组合在一起,以便得到其中一个必须为真的结果 q1 或 q2 直到 QN 或 r1 或 r2 直到 RM,这只是我们之前看到的同一规则的概括。
我们将称之为子句,子句被正式定义为文字的析取,其中析取意味着多个事物通过“或”连接在一起,析取意味着通过“或”连接的事物,而结合则是通过“和”连接的,文字则是命题符号。
符号或命题符号的反面,例如 P 或 Q 或非 P 或非 Q,这些都是命题符号或命题符号的反面,我们称之为文字。所以一个子句就像这样 P 或 Q 或 R,例如,同时这赋予了我们一种能力。
这样做可以使我们能够将任何逻辑句子转换为称为合取范式的东西,合取范式句子是一个逻辑句子,它是多个子句的结合。再提醒一下,结合意味着事物通过“和”连接在一起。
子句的组合意味着这是多个独立子句的结合,每个子句中都有“或”。所以像这样 A 或 B 或 C,和 D 或非 E 和 F 或 G,所有在括号中的内容都是一个子句,所有子句通过“和”连接在一起,子句中的所有内容通过“或”分隔。
合取范式只是我们可以将逻辑句子转换为的标准形式,这使得操作和处理都变得简单。结果是,我们可以通过应用一些推理规则和变换将任何逻辑句子转换为合取范式。
看看我们如何实际做到这一点,那么,将逻辑公式转换为合取范式(也称为 CNF)的过程是什么呢?这个过程看起来大概是这样的,我们需要将所有不属于合取范式的符号分开。
条件句和蕴涵等等,将它们转变为更接近合取范式的东西。所以第一步将是消除条件句,那些“如果和仅当”的双箭头,我们知道如何消除条件句,因为我们看到有一个推理规则。
每当我有表达式像“α当且仅当β”时,我可以将其转变为“α蕴涵β”和“β蕴涵α”,根据我们之前看到的推理规则。同样,除了消除条件句,我也可以消除蕴涵和“如果-那么”箭头,使用我们之前看到的相同推理规则。
将“α蕴涵β”转变为“非α或β”,因为这在逻辑上等价于这里的第一个东西,然后我们可以将“非”移入。因为我们不希望结点在表达式的外部,合取范式要求仅有子句。
结点需要紧邻,命题符号,但我们可以移动这些结点。我们可以使用德摩根定律,将像“非A和B”这样的东西转变为“非A或非B”,例如,通过使用德摩根定律来操控。之后我们剩下的就是与和或,容易处理。
我们可以使用分配律来分配或,使得或最终位于表达式内部,且与位于外部。这是我们如何将公式转变为合取范式的一般模式。现在让我们看看一个例子。
这是我们如何做到这一点的例子,接着探讨一下我们为什么要这样做。我们可以这样做,让我们以这个公式为例:“P或Q蕴涵R”,我想将其转换为合取范式,其中都是子句的与。
析取子句是将或连接在一起,所以我需要做的第一件事是什么?这是一个蕴涵,所以让我去掉这个蕴涵,使用蕴涵推理规则,我可以将P或Q转变为P或Q蕴涵R,变为“非P或Q或R”,所以这是第一步,我已经去掉了蕴涵。
接下来,我可以去掉这个表达式外部的结点,我可以将结点移入,使它们更靠近字面量,使用德摩根定律。德摩根定律表示“非P或Q”等价于“非P和非Q”,再次应用我们已经见过的推理规则。
为了翻译这些语句,现在我有两个通过“或”分开的东西,其中内部是一个与。我真的希望将“或”移动到内部,因为合取范式意味着我需要多个子句,所以可以使用。
分配律,如果我有 not P 和 not Q 或 R,我可以将或 R 分配给这两个,以得到 not P 或 R 和 not Q 或 R,使用分配律,而在这里底部就是合取范式,它是合取和析取的子句,只由 ORS 分隔。因此这个过程可以是。
被任何公式使用,以将逻辑句子转换为这种形式。合取范式,我有爪子和爪子和爪子和爪子等等。那么,为什么这有帮助呢?我们为什么要关心将所有这些句子转换为这种形式?这是因为一旦它们进入。
这种形式,其中我们有这些子句,这些子句是我们刚才看到的解析推理规则的输入。如果我有两个子句,其中有一些冲突或互补的东西,我可以解析它们以得到一个新子句,从而得出一个新结论。
我们称这个过程为通过解析推理,使用解析规则来得出某种推理,基于同样的想法,如果我有 P 或 Q 这个子句,并且我有 not P 或 R,那么我可以将这两个子句合并,得到 Q 或 R 作为结果子句,这是一条新的信息。
在此之前有几个关键点值得注意。在我们讨论实际算法之前,有一点是让我们想象我们有 P 或 Q RS,我也有 not P 或 R 或 s。解析规则说,因为这个 P 与这个 not P 冲突,我们将解析以放入。
将所有其他内容结合起来得到 Q 或 s 或 R 或 s,但事实证明,这个 SS 是多余的,或 s 在这里,或 s 在那里,并没有改变句子的含义。因此,在解析时,当我们进行这个解析过程时,通常也会进行一个称为因式分解的过程,其中我们处理任何重复的变量。
显示出来并消除它们,因此 Q 或 s 或 R 或 s 仅变为 Q。R 或 s,s 只需出现一次,不需要多次包含。现在。
值得考虑的最后一个问题是,如果我尝试将 P 和 not P 一起解析,如果我知道 P 是真的,而我知道 not P 是真的。那么解析说我可以将这些子句合并在一起,并查看其他所有内容。在这种情况下,没有其他内容,所以我只剩下我们可能称之为。
空子句只剩下空,空子句始终是假的。空子句等同于只是假的,这很合理,因为不可能同时满足 P 和 not P。P 要么是真的,要么不是真的,这意味着如果 P 为真,那么这必须是。
假如这是假的,那么如果这是对的,这就必须是假的,这两者不可能同时成立。所以如果我尝试解决这两个,它就是一个矛盾,我最终得到这个空子句,而空子句我可以称之为等同于假,这个想法是如果我解决这两个。
矛盾的术语让我得到了空的。
子句,这是我们解析推理算法的基础。我们将如何以非常高的层次进行解析推理呢?我们想证明我们的知识库蕴含某个查询α,基于我们拥有的知识,我们可以确凿地证明α将会。
真实的情况,我们将如何做到这一点呢?为了做到这一点,我们将尝试证明,如果我们知道知识和非α,这将是一个矛盾。这在计算机科学中是一种常见的技术,更多地是证明某些事情的反证法。如果我想证明。
某些东西为真,我可以先假设它为假并展示这将导致矛盾,证明它会导致某种矛盾。如果我想证明的事情在我假设它为假时导致矛盾,那么它必须为真,这就是逻辑的方法或想法。
其背后的证明方法是反证法,这就是我们在这里要做的。我们想证明查询α为真,因此我们将假设它不为真,我们将假设非α,并尝试证明它。如果得出矛盾,那么我们知道我们的知识。
如果我们没有得到矛盾,那么查询α就没有蕴含。这就是反证法的理念,假设你试图证明的事情的反面,如果你能证明这就是矛盾,那么你所证明的必须为真,但更正式地说我们该如何。
实际上该怎么做,我们如何检查知识库和非α不会导致矛盾呢?好吧,这里是。
解析在这里发挥作用,以确定我们的知识库是否蕴含某个查询α。我们将把知识库和非α转换为合取范式,那种所有子句都用AND连接在一起的形式,当我们有这些个别的子句时,我们现在可以继续。
检查我们是否可以使用解析生成新的子句。我们可以任意选择一对子句,检查它们是否互相包含或是互为补充。例如,在一个子句中有P,而在另一个子句中有非P;在一个子句中有R,而在另一个子句中有非R。
那种情况是,一旦我转换为合取范式,我有一大堆子句,我看到两个子句可以解析生成一个新的子句,我就会这样做。这个过程循环进行,我会不断检查是否可以使用解析生成新的子句并持续使用。
生成更多的新子句,然后再继续。可能最终我们会产生空子句,我们之前谈论过的那个子句,如果我将A和非P一起解决,就会产生空子句,而空子句我们知道是错误的,因为我们知道。
不可能同时使P和非P都为真,因此如果我们产生空子句,那么我们就有了矛盾,如果我们有矛盾,这正是我们在通过矛盾的方式所要做的。如果我们有矛盾,那么我们知道我们的知识基础。
必须蕴含这个查询α,我们知道α必须为真,结果证明了这一点。我们在这里不深入证明,但你可以展示,如果你没有生成空子句,那么就没有蕴含。如果我们遇到没有更多新子句可添加的情况,我们已经做完了所有。
我们可以进行解析,但仍然没有产生空子句,在这种情况下就没有蕴含。这就是解析算法,它看起来非常抽象,特别是像“空子句”究竟意味着什么这样的概念,所以我们来看一个例子。
尝试通过这个解析推理过程来证明某种蕴含,因此这是我们的问题,我们有这个知识基础,这就是我们知道的A或B和非B或C和非C,我们想知道这一切是否蕴含A,这就是我们的知识基础,整个逻辑内容和我们的查询。
α只是这个命题符号A,那么我们该怎么做呢?首先,我们想要通过矛盾来证明,因此我们要假设A为假,看看是否会导致某种矛盾。那么我们将从A或B和非B或C和非C开始,这就是我们的知识基础。
假设我们要假设,我们要证明的事情实际上是错误的,因此这现在是合取范式,我有四个不同的子句:我有A或B,我有非B或C,我有非C,我还有非A,现在我可以开始选择两个可以解决并应用的子句。
按照这些四个子句,我看到这两个子句是我可以解决的,因为它们中出现了互补的文字,这里有C,那里有非A。因此,仅查看这两个子句,如果我知道非B或C为真,我就可以将它们解决。
我们知道C为假,那么我可以将这两个子句解决,得出非B必须为真。我可以生成这个新子句作为我现在知道的一个新信息,现在我可以重复这个过程,看看是否可以再次使用解析得到一些新结果。
结论是,我可以使用我刚生成的新子句,和这个子句一起,这些是互补的字面量,这个B与这里的非B互补或冲突,因此如果我知道A或B为真,而B不为真,那么唯一剩下的。
可能性是A必须为真,所以现在我们得到了一个新的子句,我能够生成的,现在我可以再做一次,我在寻找可以解决的两个子句,您可能通过编程方式只需循环遍历所有可能的子句对并检查互补。
每一个字面量在这里,我可以说,好吧,我找到了两个相互冲突的子句,不A和A。当我将这两个子句一起解决时,这与之前解决P和非P时是相同的,当我将这两个子句结合时,我消除了A,剩下了空子句。
而空子句我们知道是假的,这意味着我们有一个矛盾,这意味着我们可以安全地说,这整个知识库确实蕴含A,如果这个句子是真的,那么我们知道A肯定也是真的,所以现在使用推理通过分辨是一种完全不同的方式来尝试证明某个陈述实际上是真的,而不是枚举所有可能的世界。
我们可能身处其中,以便试图找出在何种情况下知识库为真,何种情况下我们的查询为真,而是使用这个分辨算法来说,让我们继续尝试找出我们。
我可以绘制并查看是否达到矛盾,如果我们达到了矛盾,这告诉我们一些关于我们的知识是否确实蕴含查询的事情。实际上,有许多不同的算法可以用于推理。我们刚刚看过的只是其中的几个,实际上。
这一切都仅基于一种特定类型的逻辑,它是基于命题逻辑,在这里我们有这些个体符号,我们通过“和”、“或”、“非”和“蕴含”以及条件连接它们,但命题逻辑并不是唯一存在的逻辑类型,事实上我们看到还有其他逻辑。
命题逻辑中存在的局限性,尤其是在我们在像主脑示例或逻辑难题的例子中看到的,比如我们有来自不同霍格沃茨学院的人,试图找出谁属于哪个学院,这里有很多。
我们需要不同的命题符号来表示一些相当基本的想法,因此现在作为我们在结束今天的课堂之前要看的一最后一个主题,是一种与命题逻辑不同的逻辑类型,称为一阶逻辑,这比命题逻辑要强大一些。
逻辑将使我们更容易表达某些类型的想法。在命题逻辑中,如果我们回想起那个关于人和霍格沃茨学院的难题,我们有一堆符号,每个符号只能为真或假,对吧,我们有一个符号代表米奈娃格兰芬多,这个符号要么为真。
如果米奈娃在格兰芬多则为真,否则为假,对米奈娃赫奇帕奇、米奈娃拉文克劳和米奈娃斯莱特林同样如此,但这开始变得相当冗余,我们希望找到某种方法来表达这些命题符号之间的关系,即米奈娃。
在所有这些中都会出现,而且我希望不必有那么多不同的符号来表示实际上是一个相当简单的问题,所以一阶逻辑将为我们提供一种不同的方式来处理这个想法,给我们两种不同类型的符号。
有一些常量符号将代表像人或房屋这样的对象。然后是谓词符号,可以想象成关系或函数,它们接受输入并评估为真或假,例如告诉我们某些常量或一对常量的某些属性是否存在。
多个常量实际上成立,所以我们稍后会看到一个例子,但目前在这个同样的问题中,我们的常量符号可能是对象,比如人或房屋,像米奈娃、波莫纳、霍拉斯、吉德罗伊,这些都是常量符号,我的四个学院格兰芬多、赫奇帕奇、拉文克劳也是。
和斯莱特林谓词,同时这些谓词符号将是可能对这些单个常量成立或不成立的属性,因此“人”可能对米奈娃成立,但对格兰芬多则为假,因为格兰芬多不是一个人,而“学院”则会对拉文克劳成立。
但这对霍拉斯不成立,因为霍拉斯是一个人,而他属于的同时将会有某种关系,将人与他们的学院联系起来,只告诉我某人是否属于一个学院,所以让我们看看一些在。
一阶逻辑可能看起来像一个句子,可能看起来像这样:“人 米奈娃”,其中米奈娃在括号中,“人”是一个谓词符号,米奈娃是一个常量符号,这个一阶逻辑中的句子实际上意味着米奈娃是一个人,或者“人”属性适用。
对于米奈娃对象,所以如果我想说米奈娃作为一个人,这里是我如何使用一阶逻辑表达这个想法,同时我可以说像格兰芬多的学院 - 同样表达了格兰芬多是一个学院的想法,我可以这样做,使用我们在命题逻辑中看到的所有相同的逻辑联接词。
逻辑将在这里起作用——所以,和或蕴涵通过条件否定。在事实上,我可以使用否定来说一些,比如,不是房子米涅瓦,而这个句子和。第一阶逻辑意味着一些,比如,米涅瓦不是一所房子,它不是真的。房子属性适用于米涅瓦,同时还包括一些。
这些谓词符号只接受一个,单一的参数,我们的一些谓词符号将表达二元,关系,两个参数之间的关系。因此我可以说一些,比如,属于,然后输入米涅瓦和格里芬多,以表达米涅瓦属于格里芬多的想法。
现在这里是关键的区别,但是或,两个之间的关键区别之一。这和命题逻辑中,在命题逻辑中,我需要一个符号表示米涅瓦。格里芬多和一个符号表示米涅瓦,赫奇帕奇,以及所有。其他人的格里芬多和赫奇帕奇,在这种情况下我只需要一个。
我每个人的符号和每个房子的一个符号,然后我。可以表达为一个谓词,比如属于,并说属于。米涅瓦格里芬多,以表达米涅瓦属于格里芬多的想法。因此我们可以看到,第一阶逻辑在能够。
使用现有的常量符号和。谓词来表达这些句子,同时,最小化我需要创建的新符号数量。我只需要八个符号,四个人,四个房子。而不是每个可能组合的十六个符号,但,第一阶逻辑。
给我们提供一些额外的功能,可以用来表达更复杂的。想法,这些额外的功能通常被称为量词,第一阶逻辑有两个主要的量词。
第一个是全称量化,全称量化。让我表达一个想法,比如某件事,对于变量的所有值都将为真。比如,对于所有 X 的值,某个陈述将为真,那么一个全称的句子,量化看起来像什么呢,好吧,我们。
我要用这个倒置的 A 表示对于,所有,所以倒置的 A X 意味着对于所有。X 的值,其中 X 是任何对象,这将成立,它属于 X。格里芬多意味着不属于 X,赫奇帕奇,所以我们来试着解析一下。这意味着对于所有 X 的值,如果这成立,如果 X 属于。
格里芬多,那么这不成立,X 不属于赫奇帕奇。所以翻译成英语,这个句子是说,对于所有对象 X,如果 X 属于格里芬多,那么 X 不属于赫奇帕奇,例如,或者更简单的说法,任何在格里芬多的人不在。
赫奇帕奇更简化地说同样的事情。因此,这种普遍量化让我们表达一个想法,比如某些东西将在特定变量的所有值上成立。此外,普遍量化中我们还有存在量化。
量化表示某些东西在一个变量的所有值上都将成立,存在量化则表示某个表达在至少一个变量的某个值上将成立。让我们看看使用存在量化的示例句子。
句子看起来像这样:存在一个X,这个倒过来的e代表存在。这里我们说存在一个X,使得X是一个房子,且米涅尔瓦属于X。换句话说,存在某个对象X,X是一个房子,而米涅尔瓦属于X,或者更简洁地用英语表达,我只是。
说米涅尔瓦属于一个房子,这里有一个物体是一个房子。米涅尔瓦属于一个房子,通过结合这个普遍和存在的量化,我们可以创造出比仅使用命题逻辑更复杂的逻辑语句。
像这样,对所有X,若X是一个人,则存在一个Y,使得Y是一个房子,且属于X。好吧,这里发生了很多事情,有很多符号。让我们试着解析一下,理解它在说什么。我们说对于所有X的值,如果X是一个人,那么这是正确的。
换句话说,我说对于所有人,我们称那个人为X,这个陈述将会是正确的。那么对所有人来说,什么陈述是正确的呢?那就是存在一个Y,是一个房子。也就是说,存在某个房子,且X属于Y。换句话说,我在说,对于所有人,存在某个房子,使得X。
这个人属于Y这个房子,因此更简洁地说,我在说每个人都属于一个房子,对于所有X,如果X是一个人,那么存在一个房子,X属于它。因此我们现在可以使用这个想法来表达更多强大的概念,现在第一阶逻辑也证明了有许多其他。
这里有各种逻辑,第二阶逻辑和更高阶逻辑,每种逻辑都允许我们表达越来越复杂的想法,但在这种情况下,所有的追求其实都是同一个目标,那就是知识的表示。我们希望我们的AI代理能够了解信息,表示。
这些信息,无论是使用命题逻辑、第一阶逻辑,还是其他逻辑,然后能够基于此推理,得出结论,进行推断,弄清楚是否存在某种蕴含关系,借助某种推理算法。
通过解析或模型检查等推理,或任何其他算法,我们能够使用已知的信息并将其转化为结论。所有这些都帮助我们创造出能够表示关于其所知和所不知道的信息的AI,下次我们将…
看一下我们如何通过不仅仅编码我们确定为真实和不真实的信息,还要关注不确定性,使我们的AI变得更加强大,看看如果AI认为某件事情可能是可能的或可能不是很可能,或者介于这两者之间会发生什么。
在追求构建我们的智能系统的过程中,涵盖了这两个极端。
哈佛CS50-AI | Python人工智能入门(2020·完整版) - P8:L2- 不确定性 1 (概率模型,条件概率,随机变量,贝叶斯规则) - ShowMeAI - BV1AQ4y1y7wy
[音乐]。
好的,欢迎大家回到Python人工智能入门课程。上次我们讨论了AI如何在计算机中表示知识,我们以逻辑句子的形式表示了这些知识,并使用多种不同的逻辑语言。
我们希望我们的人工智能能够表示知识或信息,并以某种方式利用这些信息,推导出新信息,能够根据已知的信息推断出一些额外的结论。
当然,当我们思考计算机和人工智能时,我们的机器很少能够确定某些事情。通常情况下,我们的AI或计算机所处理的信息中会有一些不确定性。
我们很快会讨论一些概率,了解概率的含义,但并不是完全确定的,我们希望利用它所拥有的一些知识,即使它并没有完美的知识,也能进行推理并得出结论,你可能会想象。
例如在一个机器人探索某些环境的上下文中,它可能并不知情。
确切地说,它所在的位置或周围的情况,但它确实可以访问一些数据,这使它能够以某种概率进行推理,某件事是真的或另一件事是可能的,或者你可以想象在更具随机性和不确定性的背景下。
比如预测天气,你可能无法百分之百确定明天的天气,但你可以根据今天和昨天的天气以及其他数据,推断出明天的天气。
同样,我们可以将其简化为可能发生的事件及这些事件的可能性。这在游戏中经常出现,例如,其中存在机会因素。
掷骰子时你不确定骰子的结果,但你知道结果会是1到6中的某个可能性。因此,我们在这里引入了概率论的概念,今天我们将开始研究数学基础。
概率论的关键概念是理解护士的角色,以及如何利用概率。接下来,我们将深入探讨如何在数学上应用概率,以便提出一些可以在计算机中编程的模型,从而编写能够执行任务的人工智能。
我们利用概率信息进行推断,对世界做出一些判断,评估其真实的概率或可能性。因此,概率最终归结为这样一个观点:我们用这个小希腊字母Omega来表示可能的世界。
可能的世界是指,当我掷骰子时,有六个可能的结果,我可以掷出1、2、3、4、5或6,每个都是一个可能的世界,并且每个可能的世界都有一定的发生概率。
我们用大写字母P来表示概率,后面括号中放入我们想要的事件。
这里的概率是某个可能事件的概率。
用小写字母Omega表示的世界有几个基本的概率公理在考虑如何处理概率时非常重要。首先,每个概率值必须在0到1之间,包含这两个值。
概率的另一端是0,这代表不可能发生的事件。例如,掷骰子结果为7是不可能的,如果骰子只有1到6的面,掷出7的事件概率就是0。
其数值范围到正数1,意味着某事件肯定会发生。例如,掷骰子的结果小于10是一个事件,如果我的骰子只有1到6这几个面,这个事件就一定会发生,而其值一般会有较高的范围。
概率的高值意味着事件更有可能发生,而低值则意味着事件较不可能发生。概率的另一个关键规则类似于这个Sigma符号,这个符号如果你之前没有见过,表示求和的概念。
我们将要累加一系列的值,这个Sigma符号今天会出现几次,因为在处理概率时,我们通常需要将多个个体值或个体概率相加以得到其他值,所以会看到这个符号几次。
但这个符号的意思是,如果我对所有可能的世界进行求和,Ω表示所有可能世界的集合,这意味着我对所有可能世界的概率进行求和,最终得到的就是数字1。
将所有可能世界的概率相加,最后应该得到数字1,这意味着所有概率加起来都应该等于1。例如,如果我想象我有一个公正的骰子,数字为1到6,每次掷骰子都有相等的机会。
发生的概率是1/6,因此每个概率介于0和1之间,0意味着不可能,1意味着必然。如果你将所有可能世界的概率相加,会得到数字1,我们可以表示其中任何一个。
像这样的概率,例如我们掷出数字2的概率,每6次掷骰子中大约会有一次是2,这个概率不确定,但稍微大于零,因此这都是相当简单的。
对于单个骰子来说是简单的,但随着我们的世界模型变得复杂,情况变得更加有趣。现在假设我们不仅仅处理一个骰子,而是有两个骰子,例如我这里有一个红色骰子和一个蓝色骰子,我关心的不仅是结果。
每次掷骰子我关心的是两次掷骰子的和,在这种情况下,两次掷骰子的和是3。
我该如何开始推理,例如,如果我现在有两个骰子而不是一个骰子,概率会是什么样的呢?我们可以首先考虑所有可能的世界,在这种情况下,所有可能的世界就是红色和蓝色骰子的每种组合。
我可能会得到红色骰子的结果,可能是1、2、3、4、5或6,对于每一个可能性,蓝色骰子也可以是1、2、3、4、5或6。恰巧在这种情况下,每个可能的组合是等可能的。
各种不同可能世界的可能性并不总是相同。如果你想象更复杂的模型,我们可能会尝试建立并在现实世界中表示的东西,可能并不是每个可能的世界都是等可能的。
在公平的骰子情况下,每次掷骰子,每个数字出现的机会都是一样的,我们可以认为所有这些可能的世界都是同样可能的,但即便所有可能的世界都是同样可能的,这并不一定意味着它们的和也是。
所以如果我们考虑所有这些两个数的和,比如1加1是2,1加2是3,考虑每一对可能的数字,它们的和究竟是什么,我们可以注意到这里有一些模式,并不是每个数字都是。
如果你考虑7,那么当我掷两个骰子时它们的和是7的概率是多少?
有几种方式可以实现这一点,你知道六个可能的世界,其中和是7,可能是1和6,或者是2和5,或者是3和4,或者是4和3,依此类推。但如果你考虑一下,知道我掷两个骰子,两个骰子的和是12的概率,比如说。
从这个图来看,只有一个可能的世界可以实现这一点,那就是红色骰子和蓝色骰子都是6,给我们和为12,所以仅仅通过观察这个图,我们可以看到其中一些概率可能是不同的。
和为7的概率必须大于和为12的概率,我们甚至可以更正式地表示,即和为12的概率是1/36,在这36种同样可能的情况下,6的平方,因为我们有6种红色骰子的选择。
蓝色骰子有6种选择,在那36种选择中,只有一种和为12;而另一方面,如果我们掷两个骰子,它们的和是7,在那36种可能的世界中,有6个世界的和为7,所以我们得到6/36。
简化为分数为1/6,现在我们能够表示这些不同的概率观念,代表一些可能更可能发生的事件,以及其他一些可能性较小的事件,这些判断让我们在抽象中找出。
这种事情发生的概率通常被称为无条件概率,是我们对某个命题的信念程度,对于某个世界的事实,在没有任何其他证据的情况下,如果我掷一个骰子,出现某个数字的机会是多少。
如果我掷两个骰子,那么这两个骰子的和是七的机会是多少?但通常当我们思考概率时,特别是当我们考虑训练AI能够智能地了解世界并基于此做出预测时。
信息,这不是无条件,概率,而是条件概率,概率,而不是没有。
原始知识,我们对世界有一些初步,知识以及世界实际上如何运作,因此条件,概率是给定已经揭示给我们的某些证据的命题的信念程度。那么这看起来是什么样子呢?在符号方面我们将要。
将条件概率表示为,a的概率,然后是这个垂直的。条形,然后是B,读取这个的方式是,垂直条左边的东西是我们想要的,概率在这里现在我想要的。概率是a为真有一个,真实的世界,但这是事件。
实际上发生,然后在垂直条的右侧是我们的。证据,我们已经关于世界的某些已知信息。例如,B为真,因此读取整个表达式的方式是,给定B的情况下,a的概率是多少,已知a为真。
我们已经知道B为真,这种类型的判断条件概率。给定其他事实的一个事物的概率在我们思考类型时出现得很频繁,我们,可能希望我们的AI能够执行的计算。例如,我们可能关心的是,今天下雨的概率,给定我们。
我们知道昨天下雨,我们可以,考虑今天下雨的概率。抽象上今天下雨的机会是什么,但通常。我们有一些额外的证据,我知道,确实是昨天下雨,因此我想计算,今天下雨的概率。
我知道昨天下雨,或者你可能想知道,我想知道。到达目的地的最佳路线概率是否会根据当前。交通条件变化,所以无论交通条件是否变化,这可能。会改变这条路线的概率,实际上是最佳路线,或者你。
可以想象在医疗背景下,我想知道一名患者。患有特定疾病的概率,并且,给定对该患者进行的一些测试的结果,而我有一些证据,这些测试的结果,我想知道,患者患有某种疾病的概率。
特定疾病,因此条件概率的概念无处不在。随着我们开始思考,我们想要推理的内容,但。通过考虑我们已经拥有的证据,能够更智能地推理。我们更能准确地得到结果,关于什么。
是某人患有这种疾病的可能性,如果我们知道这些证据,测试的结果,反之如果我们只是计算无条件。
因此,我们可以开始使用这个来进行数学计算,假设他们得病的概率是什么,没有任何证据来支持我们的结果,无论如何。所以现在我们已经有了条件概率的概念,下一个问题是,好的,我们如何计算条件概率。
我们如何数学上计算,如果我有这样的表达式,我该如何从中得出一个数字,条件概率到底是什么意思。条件概率的公式看起来大致是这样的:给定 B 的情况下 a 的概率,a 为真的概率给定。
我们知道 B 为真的概率等于,a 和 B 都为真的概率与 B 为真的概率的比值。直观地考虑这个问题,如果我想知道在 B 为真的情况下,a 为真的概率,我需要考虑所有的。
它们都为真的方式是,我关心的唯一的世界是 B 已经为真的世界,我可以在某种程度上忽略 B 不为真的所有情况,因为那些与我最终的计算无关,它们与我想要获得的信息无关,所以让我们来。
看一个例子,让我们回到掷两个骰子的例子,以及这两个骰子的和可能为 12 的想法。我们之前讨论过,掷两个骰子和为 12 的无条件概率是 36 分之一,因为在 36 种可能的结果中,只有一种情况。
这两个骰子的和为十二,只有在红色为六,蓝色也为六时才成立。但现在假设我有一些额外的信息,我想知道这两个骰子的和为十二的概率,前提是我知道红色骰子是六,所以我。
已经有了一些证据,我已经知道红色骰子是六,我不知道蓝色骰子的结果,这个信息在这个表达式中并没有给出。但考虑到我知道红色骰子是六,求这两个和为十二的概率。
从前面取出一个表达式,这里是所有的可能性,红色骰子的结果为一到六,蓝色骰子的结果也为一到六。首先,我可能会考虑,我的证据 B 变量的概率是什么,我想知道的是。
红色骰子为六的概率是六,只有一六分之一的选项,实际上这些才是我关心的唯一的世界,其他的都与我的计算无关,因为我已经有了红色骰子为六的证据,所以我不需要关心所有其他的可能性。
这可能导致现在除了红色骰子掷出 6 的事实外,我还需要知道的另一个信息是,我的两个变量 a 和 B 同时为真的概率,即红色骰子为 6 的概率。
它们的总和为 12,那么这两个事件同时发生的概率是什么呢?它只在 36 种情况中的一种发生,即红色和蓝色骰子都为 6。这个信息是我们已经知道的信息,因此这个概率是。
等于 1/36,因此为了得到给定红色骰子为 6 的情况下和为 12 的条件概率,我只需将这两个值相除,1/36 除以 1/6 给我们一个条件概率为 1/6,前提是我知道红色骰子的值为。
两个骰子和为 12 的概率也是 1/6,这对您来说可能也很直观,因为如果红色骰子是 6,唯一能让总和达到 12 的方式是蓝色骰子也掷出 6,而我们知道蓝色骰子掷出 6 的概率是 1/6,因此在这个情况下。
条件概率的计算似乎相当简单,但通过观察这两个事件同时发生的概率来计算条件概率的这个想法是一个重要的概念。
这个概念将会反复出现,这是条件概率的定义,我们将利用这个定义来更一般地思考概率,以便能够对世界得出结论。这个。
再次是这个公式,给定 B 的 a 的概率等于 a 和 B 同时发生的概率除以 B 的概率,您会看到这个。
这个公式有时可以用几种不同的方式书写,您可以想象通过将这个方程的两边都乘以 B 的概率来消去分数,您将得到一个表达式,即 a 和 B 的概率,这是这里的这个表达式,它只是 B 的概率和。
给定 B 的概率乘以某个值,您也可以以等效的方式表示这一点。因为在这个表达式中 a 和 B 是可互换的,a 和 B 与 B 和 a 是一样的。您也可以想象将 a 和 B 的概率表示为给定 a 时 B 的概率乘以 a 的概率,仅需交换所有的。
A 和 B,这三种都是试图表示联合概率的等效方式,因此您有时会看到这些方程式,这可能在您开始推理概率以及思考现实世界中可能发生的值时对您有所帮助。
有时在处理概率时,我们不仅仅关心一个布尔事件,比如某事是否发生。有时,我们可能希望能够表示多个不同可能值的变量。在概率论中,我们称这样的变量为随机变量。
随机变量是概率理论中的一个变量,它有一定的取值范围。我所指的意思是,我可能有一个名为“投掷”的随机变量,它有六个可能的取值。
投掷结果是我的变量,可能的值范围是1、2、3、4、5和6。我可能想要知道每个值的概率,在这种情况下,它们恰好都是相同的,但对于其他随机变量可能就不是这样。例如,我可能有一个随机变量来表示。
以天气为例,其可能的取值范围包括:晴天、阴天、雨天、刮风天或雪天,每种情况可能有不同的概率。我关心的是,天气为晴天的概率,或者天气为阴天的概率,例如,我可能想要知道。
基于这些信息进行一些数学计算,其他随机变量可能是像交通那样的,比如没有交通、轻微交通或严重交通的概率。在这种情况下,交通是我的随机变量,而该随机变量可以取的值在这里。
它的可能性是没有、轻微或严重的降水,而我作为进行这些计算的人,需要决定这些可能的值究竟是什么。例如,针对航班,我可能关心的是是否能如期抵达。
我航班按时到达有几个可能的值:我的航班可能按时,可能延误,可能取消。在这个例子中,“航班”是我的随机变量,而这些是它可能的取值。我常常想要了解某些关于概率的信息。
我的随机变量取值为这些可能的值,这就是我们所称的概率分布。概率分布将一个随机变量与其取值的概率对应起来,所以在这个航班的例子中,我的概率分布可能看起来像这样。
我的概率分布表示随机变量“航班”按时到达的概率为0.6,换句话说,以更通俗的方式表达,我的航班按时的可能性是60%。在这种情况下,我的概率。
航班延误的概率是30%,我的航班被取消的概率是10%或0.1。如果你将所有这些可能的值相加,总和将是1。对所有可能的世界进行加和,我在这里为随机变量航班的三个可能世界,把它们加在一起,结果需要。
是我们之前提到的概率理论的第一公理,所以这现在是一种表示随机变量航班的概率,分布的方法,有时你会看到更简洁的表示,实际上这对于仅仅表达三个可能的值来说太冗长了,因此通常你会看到。
相同的符号使用向量表示,向量就是一系列的值,而不是单一的值,我可能有多个值,因此我可以扩展而不是表示这个,概念这种方式,粗体P,所以较大的P,通常意味着这个变量航班的概率分布是。
等于这个向量表示的,尖括号中的概率,分布是0.6 0.3和1,我只需要知道这个,概率分布是按时、延误或取消来解释这个向量,意味着向量中的第一个值是,我的航班按时的概率。
向量中的第二个值是我航班延误的概率,向量中的第三个值是我航班被取消的概率。因此,这只是表示这个概念的一种替代方式,有时会更冗长,但通常我们只会讨论一个概率。
随机变量的分布,每当我们谈论这个时,我们实际上是在试图弄清楚每一个可能值的概率,这个随机变量可以取的,但这种符号只是一种。
更简洁一点,尽管有时可能会有点困惑,取决于你看到它的上下文,因此我们将开始查看,使用这种符号描述概率的示例,以及描述可能发生的事件,还有几个重要的概念需要了解。
关于概率理论,一个是独立性的概念,独立性是指一个事件的知识不会影响。
另一个事件的概率,例如在我投掷两个骰子的上下文中,我有红色骰子和蓝色骰子,投掷红色骰子和蓝色骰子的概率,这两个事件,红色骰子和蓝色骰子是独立的。知道红色骰子的结果不会改变蓝色骰子的概率。
蓝色骰子并没有给我任何关于蓝色最终值的额外信息,但这并不总是如此,你可能想象在云和雨这样的情况下,它们可能并不是独立的,如果有云,这可能会增加。
后面一天会下雨的概率,所以一些信息会影响其他事件或其他随机变量,因此独立性指的是一个事件不影响另一个事件,如果它们不是独立的,那么可能存在某种关系,因此在数学上正式。
独立性实际上意味着什么呢?回忆一下之前的公式,a 和 B 的概率是 a 的概率乘以给定 a 的 B 的概率,更直观的理解是,要知道 a 和 B 发生的可能性,首先要计算。
发生了 a,然后在知道 a 发生的情况下,让我们计算 B 发生的可能性,并将这两者相乘,但如果 a 和 B 是独立的,意味着知道 a 并不会改变 B 成真的可能性,那么给定 a 的 B 的概率实际上就是。
给定我知道 a 为真,B 成真的概率。那么我知道 a 为真实际上不应该造成太大差异,如果这两个事件是独立的,但 a 不应该影响 B,因此,给定 a 的 B 的概率实际上只是 B 的概率,如果它确实为真。
a 和 B 是独立的,所以这里是一个关于 a 和 B 独立性的定义示例,a 和 B 的概率就是 a 的概率乘以 B 的概率,每当你发现两个事件 a 和 B 之间有这种关系时,你就可以说。
a 和 B 是独立的,所以一个例子可能是我们之前查看的骰子,从红色骰子是六的概率和蓝色骰子是六的概率来看,这只是红色是六的概率乘以蓝色是六的概率,两者都等于 1/36,所以我可以说。
这两个事件是独立的,而不独立的例子,例如,这个概率是 1/36,我们之前谈过,但不独立的情况可能是这样,红色骰子掷出六点和红色骰子掷出四点的概率,如果你只是天真地认为红色。
红色骰子六,如果我只掷一次骰子,你可以想象这个天真的方法是相同的,每个骰子的概率都是 1/6,所以将它们相乘,概率是 1/36,但当然如果你只掷一次红色骰子,根本不可能得到。
红色骰子有两个不同的值,我不能同时得到六和四,所以概率应该是零。但如果你将红色六的概率乘以红色四的概率,那将等于1/36,但当然这不是事实,因为我们知道不可能有概率为零的情况。
当我们掷一次红色骰子时,我们得到六和四,因为只有一个结果。实际上可以是,因此我们可以说红色掷出六和红色掷出四这两个事件并不独立。如果我知道红色掷出的是六,我就知道红色掷出不可能是四,因此这些事情是不独立的。
如果我想计算概率,我需要使用这个条件概率,作为两个事件发生的常规概率定义。现在红色掷出的概率是,这个乘法我们得到的。
得到六的概率是1/6,但已知掷出的是六的情况下,掷出四的概率是多少呢?这实际上是零,因为在我们已经知道红色掷出的是六的情况下,红色掷出不可能是四,因此,如果我们计算这个值。
零这个数字。因此,这个条件概率的概念会反复出现,尤其是当我们开始推理多个可能相互作用的随机变量时,这引导我们进入概率论中一个非常重要的规则。
贝叶斯定理结果表明,只要使用我们已经学到的概率信息,并稍微应用一点代数,我们实际上可以自己推导出贝叶斯定理。
但在推理时,这是一个非常重要的规则,尤其是在考虑概率的上下文时。
计算机能做的,或者数学家可以通过访问关于概率的信息来做到的,因此让我们回到这些方程,能够自己推导出贝叶斯定理。我们知道A和B的概率,A和B发生的可能性是B的可能性,然后是已知B为真的情况下A的可能性。
同样,已知A和B的情况下A的概率是A的概率乘以已知A为真的情况下B的概率。这是一种对称关系,A和B的顺序并不重要,B和A意味着相同的事,因此在这些中。
方程中我们可以直接将a和B互换,以表示完全相同的。概念,所以我们知道这两个方程已经成立,我们已经看到了。现在让我们对这些内容进行一些代数运算,右侧的两个表达式都等于a和B的概率。
所以我可以将这两个右侧的表达式设为相等,如果它们都等于a和B的概率,那么它们也必然相等。因此,a的概率乘以给定a的B的概率等于B的概率。
乘以给定B的a的概率,现在我们要做的只是进行一些简单的除法,我将两边都除以a的概率,现在我得到的是。贝叶斯规则,给定a时B的概率,等于B的概率乘以给定B的a的概率除以,a的概率,有时在。
贝叶斯规则中你会看到这两个参数的顺序交换,所以不再是B。乘以给定B的a,而是给定B的a乘以B,最终这并不重要,因为在乘法中你可以交换两个数的顺序,而不会改变结果,但目前这是。
贝叶斯规则的最常见公式是,给定a时B的概率等于。给定B的a的概率乘以,B的概率除以。
a的概率,这个规则在推断世界事物时非常重要,因为它意味着你可以表达一个。条件概率,即给定a时B的条件概率,使用关于给定B的a的概率的知识,使用反向的关系。
条件概率,所以我们先做一个小示例。看看我们可能如何使用它,然后,更一般地探讨这意味着什么,所以我们将构造一个我有一些信息的情况,涉及两个我关心的事件,即早上有云的想法。
早上以及下午下雨的想法,这两者是。
可能发生的不同事件,比如早上阴天或,下午下雨。对我来说,关心的是早上有云的情况下,下午下雨的概率,这是一个我可能在早上会问的问题,我向外看,或者用相机观察外面,看到早上的云。
我们想得出结论,想弄清楚下午下雨的概率,当然在抽象上我们没有。获取这类信息的渠道,但我们可以利用数据来开始尝试。弄清楚这一点,所以现在假设我有一些信息。
我可以获取的信息是,80%的雨天下午是以多云的早晨开始的。你可能想象我可以通过查看一段时间的数据来收集这些信息,但我知道80%的情况下,当下午下雨时,早晨是多云的。
我还知道40%的日子是多云的。
早晨我也知道10%的日子会有雨天,现在利用这些信息,我想知道早晨有云的情况下,下午下雨的概率。我想知道在早晨有云的情况下,下午下雨的概率,我可以特别使用这个方法。
这个事实的概率。如果我知道80%的雨天下午是以多云的早晨开始的,那么我知道在雨天下午情况下,多云早晨的概率。因此使用贝叶斯规则来表达,这看起来是这样的:给定云的情况下下雨的概率是。
早晨有云的概率乘以下雨的概率,再除以云的概率。在这里,我只是将这个方程中A和B的值代入。
之前的贝叶斯规则,然后我可以进行计算。我知道如果下雨,那么早晨有云的概率是80%。这里降雨的概率是0.1,因为10%的日子有雨,40%的日子是多云。我算出答案是0.2。
所以在早晨多云的情况下,下午下雨的概率是0.2,这就是贝叶斯规则的应用,利用一个条件概率我们可以获得反向的条件概率,这在有一个条件概率时往往是有用的。
条件概率可能更容易让我们知道,或者更容易有数据,而利用这些信息,我们可以计算其他条件概率。那么这看起来是什么样子呢?这意味着知道在雨天下午情况下多云早晨的概率,我们可以。
计算在多云的早晨情况下,下午下雨的概率,或者更一般地说,如果我们知道某些可见效果的概率,也就是我们能看到和观察到的效果,假设有一些我们不确定的未知原因,那么我们可以计算出这个未知原因的概率。
因为可见的效果,所以是什么原因呢?在医学的背景下,我可能知道某些医疗测试结果的概率,假设有一种疾病,我知道如果某人有这种疾病,那么X%的情况下,医疗测试结果将会显示为这种情况。
有了这些信息,我就可以计算,好的,给定我知道医学测试结果,某人患有疾病的可能性,这是一条通常更容易知道的信息,更容易立即获取数据的信息,而这正是我。
我可能想计算,或者想知道,例如,如果我知道什么。某些伪钞的概率,它们在边缘有模糊的文字,因为伪造打印机,打印文字的精度远不如。
例如,X百分比的伪钞具有模糊的文字,利用这些信息,我可以计算一些我可能想知道的信息,例如,给定我知道某张钞票上有模糊的文字。那张钞票是伪钞的概率是多少,所以给定一个条件。
我可以计算其他条件概率,因此。现在我们已经看了一些不同类型的概率,我们已经。看过无条件概率,看看发生此事件的概率是多少,给定没有额外证据的情况下。
可能会访问,并且我们还看过条件概率。在这种情况下,我有某种证据,我希望能够利用这些证据。
哈佛CS50-AI | Python人工智能入门(2020·完整版) - P9:L2- 不确定性 2 (联合概率,贝叶斯网络) - ShowMeAI - BV1AQ4y1y7wy
还计算其他的概率,另一种对我们思考很重要的概率是联合概率,这涉及到同时考虑多个不同事件的可能性。那么我们所说的是什么呢?例如,我可能有。
概率分布看起来像这样,哦,我想知道早上的云的概率分布,而这个分布看起来是这样的,40%的时间我这个随机变量等于阴天,60%的时间则不是阴天。
这里只是一个简单的概率分布,有效地告诉我们。
我知道40%的时间是阴天,我也可能有一个概率分布来表示下午下雨的情况。
在10%的时间里,或者概率为0.1时,下午下雨,而概率为0.9时,下午不下雨,利用这两条信息,我实际上对这两个变量之间的关系并没有太多的信息,但我。
如果我能访问他们的联合概率,意味着每一种组合的情况,包括早上阴天和下午下雨、早上阴天和下午不下雨、早上不阴天和下午下雨、以及早上不阴天和下午不下雨。
如果我能访问这四个值的情况,我会获得更多的信息,因此可以将这些信息整理成一个像这样的表格,而这个表格不仅仅是概率分布,而是联合概率分布,它告诉我每种可能的概率分布。
这些随机变量可以取值的组合,所以如果我想知道在任何给定的日子里同时阴天和下雨的概率,我会说,好吧,查看阴天和下雨的情况,以及这两者的交集,那一行。
那一列是0.08,这就是同时阴天和下雨的概率,利用这条信息和这个条件概率表,我可以开始推导关于条件概率等其他信息,所以我可能会问一个问题,比如概率是多少。
已知下雨情况下的云分布,意味着我确定下雨。告诉我已知的情况下,云层是否存在的概率分布。这里我用C来表示这个随机变量,我在寻找一个。
分布的意思是,答案不是一个单一的值,而是两个值,一个由两个组成的向量,第一个值是云的概率,第二个值是非云的概率,但这两个值的和必然是1,因为当你加起来的时候。
所有可能世界的概率以及你得到的结果必须是数字1。那么我们知道如何计算条件概率的知识是什么呢?我们知道,给定B的概率是A与B的联合概率除以B的概率。那么这意味着什么呢?
这意味着我可以计算在下雨的情况下云的概率。
下雨的概率是云的概率和下雨的联合概率除以下雨的概率,而这里的逗号是为了云的概率分布。
雨,这里的逗号代表“和”,你会发现逻辑运算符“和”和逗号是可以互换使用的。这意味着云的概率分布和知道下雨的事实除以下雨的概率,而值得注意的是。
为了简化我们的数学运算,我们通常会这样做:除以下雨的概率。这里的下雨概率仅仅是一个数值常量。除以下雨的概率就只是除以某个常量,换句话说,就是乘以该常量的逆。
很多时候我们可以不担心这个值的准确性,只需知道它实际上是一个常量,我们稍后会看到原因。因此,我们有时会将其表示为α乘以。
这里的分子是C这个变量的概率分布,而我们知道它正在下雨。例如,我们在这里所做的只是将这个值1除以下雨的概率,这实际上只是我们将要除以的一个常量,或者等效地,在最后乘以它的逆。
我们暂时称它为α,稍后再处理。但这里的关键思想,现在这个想法将再次出现,即在给定下雨的情况下,C的条件分布是成比例的,意味着只是某个因子乘以C与下雨同时成立的联合概率。
那么我们怎么解决这个问题呢?这将是已知下雨时的云的概率,值为0.08;以及已知下雨时的非云的概率,值为0.02,因此我们得到的α乘以这里的概率分布,0.08是云和雨,0.02是非云。
云和雨,但当然0.08和0.02的和并不等于1,而我们知道在。
概率分布如果你考虑所有可能的值,它们的总和必须为 1,因此我们知道,只需要找出一个常数来归一化这些值,可以说是用某个数乘或除,以便使所有的概率总和为 1。
如果我们将这两个数字都乘以 10,那么结果就可以得到 0.8,比例依然是等效的,但现在 0.8 加 0.2 的总和为 1。因此,请查看这个,看看你能否一步一步理解我们如何从一个点到另一个点,但这里的关键思想。
通过使用联合概率,这些概率是同时阴天和下雨,以及不阴天和下雨的概率。我可以利用这些信息,计算在下雨的情况下,阴天的概率与不阴天的概率。
乘以某个归一化常数,可以说计算机可以开始做到这一点,与这些不同类型的概率互动,结果是有许多其他的概率规则将对我们有用,当我们开始。
探索我们如何实际利用这些信息进行更复杂的概率和分布以及随机变量分析,以便与之互动,所以这里有几个重要的概率规则,其中最简单的规则之一就是。
这个否定规则是什么,事件 a 不发生的概率是怎样的?事件有一定的概率,我想知道 a 不发生的概率是什么。
结果是 1 减去 a 的概率。
这很有意义,因为这两种情况要么 a 发生,要么 a 不发生,那么加起来就会。
在这两种情况下,你必须得到 1,这意味着 a 不发生的概率必须是 1 减去 a 的概率,因为 a 的概率和不发生 a 的概率必须加起来等于 1。它们必须包括所有可能的情况,我们已经看到了计算 a 和 B 概率的表达式,我们可能还希望合理地计算。
概率 A 或 B 的问题是,某件事情发生或另一件事情发生的概率是什么?例如,我可能想计算如果我掷两个骰子,一个红色,一个蓝色,a 为六的概率或 B 为六的概率,像是其中一个发生。
错误的方法是,仅仅说好吧,A出现为六,红色骰子出现为六的概率是六分之一,蓝色骰子也是六分之一,把它们加在一起就得到六分之二,也就是三分之一,但这存在一个问题。
双重计数了红色骰子和蓝色骰子都出现为六的情况,我已经计数了这个实例两次。因此,为了解决这个问题,计算A或B的实际表达式使用了我们称之为包含-排除公式的东西。我们将A的概率加上。
B的概率和之前一样,但我需要排除我已经双重计数的情况,所以我从中减去。
A和B的概率,这给我结果是A或B,我考虑A为真的所有情况和B为真的所有情况。如果你想象这是一个A为真和B为真的情况的维恩图,我只需减去中间部分,以去掉我多余的情况。
通过在这两个个别表达式中双重计数而得到的。另一个非常有用的规则是一个叫做边际化的规则。某种边际化是在回答这样一个问题:我如何使用一些我可能接触到的其他变量来计算A的概率。
即使我对B没有额外的信息,我也知道B某个事件可以有两个可能的状态,要么B发生,要么B不发生。假设它是布尔值,真或假。
这意味着,对于我来说,要计算A的概率,只有两种情况,要么A发生且B发生,要么A发生且B不发生,这两种情况是互斥的,意味着它们不能同时发生。要么B发生,要么B不发生,它们是互斥的或分开的。
所以我可以通过将这两个情况相加来计算A的概率,A为真的概率是A和B为真的概率加上A为真的概率和。
B不为真,所以通过边际化,我看到了可能发生的两种情况,要么B发生,要么B不发生,在这两种情况下,我查看A发生的概率,如果我把这些加在一起,那么我就得到了这个概率。
事件整体发生,所以看看这个规则,B是什么并不重要。
或者它与A的关系如何,只要我知道这些联合分布,我就能计算出A的整体概率。如果我有像A和B的联合分布这样的联合分布,这可能是一个有用的方法,只需计算一些无条件概率,比如。
A的概率,我们很快也会看到这些示例。有时,这些变量不仅仅是随机变量,可能不是像B那样的事件,它们发生了或没有发生,可能存在一些更广泛的概率分布,其中有多个。
所有可能值,因此在这里,为了使用这个边际化规则,我需要对所有其他随机变量可能取的值进行求和,而不仅仅是B。因此在这里我们将看到一个关于随机变量的规则版本,它将包括求和符号,以表示我正在进行求和。
对一大堆个体值进行求和,因此这个规则看起来更复杂,但实际上是。
这完全是相同的规则,我在这里所说的是,如果我有两个随机变量,一个叫X,一个叫Y,那么X等于某个值X子I的概率,这只是该变量所取的某个值。我该如何计算呢?我将对J进行求和,J的范围将是某个值X子I。
针对Y可以取的所有可能值,让我们看看X等于X子I和Y等于Y子J的概率。因此,这正是相同的规则。
唯一的不同在于,现在我正在对Y可以取的所有可能值进行求和,也就是说,让我们将所有这些可能的情况加起来,看看这个联合分布在给定所有可能的Y值的情况下,X取我关心的值的概率。
如果我将所有这些加起来,那么我就可以得到X等于某个值的无条件概率,无论X是否等于。
让我们来看一下这个规则,因为它看起来有点复杂,让我们试着给它一个具体的例子。这是之前提到的同一个联合分布,我有“多云”、“不多云”、“下雨”、“不下雨”。也许我想访问某个变量,我想知道“多云”的概率是多少。
边际化说,如果我有这个联合分布,我想知道的概率是。
如果是多云的话,我需要考虑另一个变量,也就是这里没有的变量,想法是它在下雨,我考虑了这两种情况:要么下雨,要么不下雨,我只需将每种可能的值相加。换句话说,云的概率等于。
求和“多云且下雨”的概率,以及“多云且不下雨”的概率,因此这些现在是我可以访问的值,这些值就位于这个联合概率表中。那么,既多云又下雨的概率是多少?这只是。
这两者的交集是,点零八,概率是多云而不下雨,好的,这里是多云,这里是不下雨,是0.32。因此是点零八加0.32,等于零点四,这就是无条件概率,实际上是多云的,因此边际化为我们提供了一种方式来。
从这些联合分布转到,我可能关心的一些个体概率,你会稍后看到为什么我们关心这一点,为什么这对我们在进行一些计算时实际上有用。最后的规则,我们将在过渡到某些内容之前看看。
有点不同的是,条件化规则与边际化规则非常相似,但它说,如果我有两个事件a和B,但不是访问它们的联合概率,而是访问它们的条件概率,它们之间的关系如何。如果我。
想知道a发生的概率,并且我知道有一些。
另一个变量B要么发生,要么不发生,因此我可以说,a的概率是给定B时a的概率乘以B的概率。意思是B发生了,给定我知道,B发生了,a发生的可能性是什么。然后我考虑其他。
B没有发生的情况,因此这是B没有发生的概率。
这是a发生的概率,给定我知道B没有发生。
这实际上是等价规则,只是使用条件概率,而不是联合概率,我在说让我们看看这两种情况,并以B为条件,看看B发生的情况,看看B不发生的情况。
我得到的概率结果,正如在边际化的情况下,针对可能在值域中采取多个可能值的随机变量有一个等价规则一样,条件化也有同样的等价规则,再次出现求和意味着我正在对所有进行求和。
随机变量Y可能采取的一些可能值,但如果我想知道X采取这个值的概率,那么我将对Y可能采取的所有值J进行求和,并说,好的,Y采取的机会是什么。
在那个值YJ上并将其乘以X采取的条件概率。
这个值给定Y采取了那个,值YJ,因此等价规则只是使用。
条件概率而不是联合概率,利用我们对联合概率的理解可以在这两者之间转换。所以好吧,我们已经看到很多数学,我们只是奠定了数学的基础,如果你对概率还没有看到太多细节,也不需要担心。
到目前为止,这些是我们开始探索如何将这些概率思想应用于计算机内部、我们正在设计的AI代理的基础。
能够表示信息、概率和各种事件之间的可能性,因此我们可以生成许多不同的概率模型,但我们要讨论的第一个模型是被称为贝叶斯网络的水。
一些随机变量的网络。
连接的随机变量将表示这些随机变量之间的依赖关系,实际上这个世界上的大多数随机变量并不是彼此独立的,它们之间存在某种我们关心的事物之间的关系。如果今天下雨,你知道这可能会。
增加我的航班或火车延误的可能性,例如,这些随机变量之间存在一些依赖关系,而贝叶斯网络能够捕捉这些依赖性。那么,什么是贝叶斯网络,它的实际结构是什么,以及它是如何工作的呢?贝叶斯网络将是一个。
有向图,我们之前见过有向图,它们是带有箭头或边的单独节点,连接一个节点到另一个节点,指向特定方向,因此这个有向图将有节点,其中每个节点将在这个有向图中代表一个。
随机变量类似于天气或我的火车是否准时到达,我们将有一条箭头从节点X指向节点Y,表示X是Y的父节点,这将是我们的符号,如果有一条箭头从X到Y,X将被视为Y的父节点,这一点很重要。
因为每个节点将有一个概率分布,我们将与之存储的分布是给定一些证据和X的父节点的X的分布。所以更直观地思考这种方式,父节点可以看作是某些我们想要研究的效果的原因。
观察,因此让我们看一个贝叶斯网络的实际例子,思考推理该网络可能涉及的逻辑。假设我有一个城外的约会,我需要乘火车去。
约会,我可能关心的事情是什么呢?我关心的是能否准时到达约会,要么我能准时到达并参加,要么我错过了这个约会,而你可以想象这受到了火车的影响,火车要么准时,要么延误。
例如,火车本身的运行也受到影响,火车是否准时可能取决于天气,比如说雨,知道吗?如果没有雨,可能是小雨,或者是大雨,此外,还可能受到其他变量的影响。
比如说,如果铁轨上有维护工作,那可能会增加我的火车延误的可能性,因此我们可以用一个贝叶斯网络来表示所有这些想法,它看起来是这样的,这里我有四个节点代表四个。
我想跟踪的随机变量有一个叫雨的,它可以在其领域中取三个可能的值:没有雨、小雨或大雨。还有一个变量叫维护,用于表示铁轨上是否有维护工作。
这有两个可能的值,要么是有维护,要么是没有维护,接着我有一个随机变量表示火车是否准时,这个随机变量在其领域内有两个可能的值,火车。
火车要么准时,要么延误,最后我有一个随机变量用于表示我是否能到达约会,我的约会随机变量有两个可能的值:参加和错过,这里有我四个可能的值。
节点,每个节点代表一个随机变量,每个节点都有可能的值域,箭头表示边。
从一个节点指向另一个节点的箭头编码了这个图中的某种依赖关系,我是否能准时到达约会取决于火车是否准时,而火车是否准时则取决于由指向这个节点的两个箭头给出的两个因素。
这些节点之间的关系可能会有所不同,但这个概念是依赖于铁轨上是否有维护工作,同时也依赖于是否下雨,或者现在是否在下雨。为了让事情变得复杂一点,假设铁轨维护的情况也可能受到影响。
比如说,雨,如果下大雨,那可能意味着当天进行铁轨维护的可能性会减少,因为他们更倾向于在不下雨的日子进行维护。
我们可以基于父节点为这些节点构建概率分布,因此让我们逐个查看这个概率分布可能实际是什么样子,我们将从这个根节点开始,即雨的节点,这个节点在顶部,没有下雨。
指向它的箭头意味着它的概率分布将不是条件分布,它不是基于任何东西,我只是对可能的雨水随机值有一些概率分布。
变量的分布可能看起来像这样:没有雨、小雨和大雨每种情况都有一个概率分布。
这里可能的值是我说没有雨的概率是0.7,轻微降雨的概率是0。
雨的概率是0.2,大雨的概率是0.1,例如,这里是这个贝叶斯网络中根节点的概率分布。现在我们考虑网络中的下一个节点:轨道维护是有还是没有,通常这个分布要编码的想法是,雨越大,轨道维护的可能性就越小,因为进行轨道维护的人可能想等到不那么潮湿的日子再进行维护。
进行轨道维护的前提是下雨,例如,这个概率分布可能是什么样的,这将是一个条件概率分布,这里是雨水随机变量的三个可能值,我将在这里简略表示。
要么没有雨,要么小雨,要么大雨,对于每一个可能的值,要么有轨道维护,要么没有轨道维护,并且这些都有与之相关的概率,我看到如果不下雨,则轨道维护的概率是0.4,而没有轨道维护的概率是0.6。
如果下大雨,那么轨道维护的概率是0.1,而没有轨道维护的概率是0.9。这些行的概率总和为1,因为这些代表了是否下雨的不同值。
这个随机变量可以取值,每个值都有自己的概率分布,最终所有的概率加起来将等于1,因此这就是我们对于这个随机变量的分布,称为关于是否有火车轨道维护的分布。
现在让我们考虑下一个变量,这里我们有一个叫做火车的节点,它在我们的贝叶斯网络中,具有两个可能的值:准时和延误。这个节点将依赖于指向它的两个节点,火车是否准时或延误取决于这两个节点。
是否有轨道维护,这取决于是否有小雨。较大的降雨概率可能意味着我的火车更有可能延误,如果有轨道维护,这也可能意味着我的火车更有可能延误,因此你可以构建一个更大的概率模型。
一个条件概率分布,而不是像这里仅依赖一个变量,现在依赖于两个变量,分别是代表小雨的R和代表维护的“是”。再一次,每一行都有两个值并且相加得到总和。
一个表示火车准时,另一个表示火车延误。这里我可以说,比如如果我知道有小雨和轨道维护,那么R表示小雨,M表示是。这样的话,我的火车准时的概率为0.6,而延误的概率为零。
如果火车延误,你可以想象通过查看现实世界的数据来收集这些数据,比如,如果我知道有小雨和轨道维护,火车是否延误的情况有多频繁。你可以开始构建这个模型,但有趣的是,
关键是智能地尝试找出如何对这些事情进行排序,哪些因素可能影响贝叶斯网络中的其他节点。我关心的最后一件事是我是否能如期参加预约,因此我是否参加或错过了预约,最终。
不论我是否参加或错过了预约,这都受到轨道维护的影响,因为这间接表明,如果有轨道维护,我的火车可能更有可能延误,如果我的火车更有可能延误,那么我更可能错过我的预约。
预约,但我们在这个贝叶斯网络中编码的只是我们可能认为更直接的关系,因此火车对预约有直接影响。考虑到我知道火车是否准时或延误,知道是否有轨道维护不会给我任何额外的信息。
如果我知道火车的这些其他节点不会真正影响结果,因此我们可能使用另一个条件概率分布来表示,可能看起来像这样,火车可以有两个可能的值,要么我的火车。
列车准时到达或延误,对于这两种可能的值,我有一个分布,表示我能够参加会议的概率,以及我错过会议的概率,显然,如果我的列车准时到达,我更有可能参加会议,而不是列车延误。
如果延误,我更可能错过会议,因此所有这些节点结合在一起表示这个贝叶斯网络,这是一个随机变量网络,我最终关心它们的值,以及它们之间的某种关系,某种依赖性。
节点之间的依赖关系使我能够计算某个节点的概率,前提是知道其父节点的值。
既然我们已经能够描述这个贝叶斯网络的结构,以及这些节点之间的关系,通过将每个节点与一个概率分布关联。
对于这个根节点,如降雨的情况,以及所有其他节点的条件概率分布,其概率依赖于父节点的值,我们可以开始使用表格中的信息进行一些计算。
让我们设想一个简单的计算,比如轻雨的概率,我将如何获取轻雨的概率?轻雨在这里是根节点,如果我想计算该概率,我只需查看。
无论这是一个无条件的概率分布,还是关于降雨的概率分布,并从中提取出轻雨的概率,这只是一个我已经可以获取的单一值,但我们也可以想象想要计算更复杂的联合概率,比如同时有轻雨和没有轨道维护的概率,这就是一个联合概率。
对于两个值,轻雨和没有轨道维护,我可能会首先这样做,先获取轻雨的概率,但现在我也想要没有轨道维护的概率,当然这个节点依赖于降雨的值,所以我真正想要的是。
在知道有轻雨的情况下,没有轨道维护的概率,因此计算这个概率的表达式是,轻雨和没有轨道维护的概率实际上就是轻雨的概率和没有轨道维护的概率。
给定我知道已经有轻雨的情况下,我会取轻雨的无条件概率,乘以在知道有轻雨的情况下没有轨道维护的条件概率,你可以对每个你想要的变量重复这个过程。
将其添加到这个联合概率中,我可能想要计算如果我想知道小雨、没有轨道维护和延迟列车的概率。那么这将是小雨的概率乘以,在小雨的情况下没有轨道维护的概率乘以。
给定小雨和没有轨道维护的情况下,延迟火车的概率。因为火车是否准时或延迟取决于另外两个变量,所以我有两个证据片段,这些证据用于计算条件概率,而这三个值仅仅是一个值。
我可以通过查看这些,单个概率分布之一来查找。它被编码到我的贝叶斯网络中,如果我想要一个关于四个变量的联合概率,比如,小雨、没有轨道维护和延迟火车的概率,以及我错过我的约会,那么这将。
乘以四个不同的值,每个值来自这些单独的节点。它将是小雨的概率,然后是,在小雨的情况下没有轨道维护的概率,然后是,给定小雨和没有轨道维护的情况下延迟火车的概率,最后是,关于我是否能按时到达这个节点。
我是否能按时到达约会并不取决于这两个变量,前提是我知道火车是否准时。我只需要关注条件概率,考虑我错过了火车或错过了我的约会。前提是火车恰好很准时,因此在这里表示。
通过四个概率,每个概率位于其中一个。这些节点的概率分布都相乘,直到。我可以像这样取一个变量,并且,算出联合概率。是通过乘一堆这些,单独的概率来实现的。
贝叶斯网络,但当然就像上次一样,我真正想做的是能够获得新信息。这就是我们在知识的背景下,想要用我们的贝叶斯网络做的事情。我们讨论了推理问题,给定我知道的事情。
只要我能得出结论,就能对其他我也知道是真的事实进行推断。而我们现在要做的,就是把同样的想法应用于概率,使用我所拥有的一些信息,无论是证据还是一些,我能算出其他的概率。
对某些变量是确定的,但我能否算出其他变量以特定值出现的概率。因此,在这里我们引入了推理问题。在一个概率背景下,变量不一定真实,而可能是取不同值的随机变量。
以某种概率,那么我们该如何。
正式定义这个推理问题究竟是什么好吧。
推理问题有几个部分,我们有一些查询变量X,我们想要计算分布,或者我想知道我错过火车的概率,或者我想知道是否有卡车维护,这些都是我想要获取信息的内容,然后我有一些证据变量。
也许这只是一个证据,也许是多个证据,但我已经观察到了某些变量。
比如说,我可能观察到下雨,这是我拥有的证据,我知道。
有小雨,或者我知道那里有大雨,这就是我使用的证据,我想知道我的火车延误的概率,比如说,这是我可能想根据这些证据询问的问题。
我在贝叶斯网络中观察到的变量,当然,这也留下了一些隐藏变量,这些变量既不是证据变量也不是查询变量,所以你可以想象在我知道是否下雨的情况下,我想知道我的。
火车是否会延误,隐藏变量是我无法接触到的,例如,轨道上是否有维护,或者我能否准时赴约,这些是我无法接触到的变量,它们是隐藏的,因为它们不是我观察到的。
它们并不是我所询问的查询内容,所以最终我们想要计算的是,我想知道在观察到事件E后,X的概率分布,即我观察到的事件。
火车随机变量的可能值是准时还是延误,它发生的可能性是什么,结果是我们可以使用我们已经看到的概率规则来进行这个计算,最终我们将看看质量。
从高层次的抽象角度来看,最终我们可以让计算机和现有的编程库开始为我们做一些数学运算,但了解推理过程发生时的实际情况是很有帮助的,想象一下某种事件。
我想计算给定某些条件的预约随机变量的概率分布。
证据是我知道有小雨且没有轨道维护的情况下。因此,这就是我的证据。
我观察到的变量值,我观察到降雨的值,我知道今天有雨,并且我知道没有轨道维护发生,而我关心的是这个随机变量约会。我想知道这个随机变量约会的分布是什么。
我能参加我的约会的机会有多大?在这证据和隐藏变量下,我错过约会的机会有多大,而我无法获取的信息是这个变量列车,这些信息并不是我所看到的证据的一部分。
这不是我观察到的东西,但它是;这也不是我询问的查询。那么,这个推断过程可能是什么样的呢?如果你回忆一下,来自于定义条件的记忆。
概率和使用条件概率进行数学计算。我们知道条件概率与联合概率成正比,我们通过回忆条件B的概率仅仅是某个常数因子α乘以A和B的概率来记住这一点。
因子α的结果像是除以B的概率,但重要的是它只是某个常数乘以联合分布,即所有这些单独事件发生的概率,所以在这种情况下,我可以取约会随机变量的概率给定。
轻微降雨和没有轨道维护,假设这只是会成比例于某个常数α乘以联合概率,即约会随机变量的特定值和轻微降雨以及没有轨道维护。那么,我该如何计算这个呢?
约会和轻微降雨以及没有轨道维护的概率,而我真正关心的是知道我需要这四个值,才能够计算整个的联合分布,因为在特定的约会中,取决于列车的值。为了做到这一点,我在这里。
我可以开始使用那个边际化的技巧,只有两种方式我可以获得任何配置的约会,轻微降雨和没有轨道维护。要么这个特定变量设置发生,列车准时,要么这个特定变量设置发生,而列车就会延误。
延迟,这是两个我想考虑的可能情况,如果我将这两个情况相加,那么我就可以通过将所有隐藏变量或变量的可能性加在一起,得到结果,但由于这里只有一个变量列车,我所需要做的就是遍历所有可能的值。
那个隐藏变量的列车并添加起来。
它们的概率,因此这个概率,表达式在这里变成了关于约会的概率分布,情况是光线良好、没有雨以及火车准时。对于约会的概率分布,情况是下雨、没有轨道维护,并且火车延误。例如,我将考虑火车的两个可能值。
并将它们相加,这些只是我们之前看到的联合概率,如何通过计算这些父节点的概率并将它们相乘来得出。然后你需要在最后进行归一化,从高层次讲,以确保所有内容。
加起来就是第一步,因此这个公式,关于如何做到这一点的过程被称为枚举推理,看起来有点复杂,但最终它。看起来是这样的,现在让我们尝试提炼所有这些。符号实际代表的意思,我们从这里开始,我关心的是。
我查询变量X的概率,给定某种证据,我对条件概率知道些什么。条件概率与联合概率成比例,因此在某种α,即某个归一化常数。乘以这个联合概率,好的,为此我要进行边缘化。
关于所有隐藏变量,即我没有直接观察到的所有变量。对于这些值,我基本上将对所有可能发生的情况进行迭代,并将它们全部相加,因此我可以将这转化为一个对所有Y的求和,范围涵盖所有可能的隐藏变量,以及。
它们可以取的值,并将所有可能的个体。概率相加,这将允许我进行这个枚举推理的过程。现在,最终如果我们人类必须为自己完成所有这些数学计算,那确实是很麻烦的,但事实是这就是计算机和人工智能的优势所在。
计算机理解贝叶斯网络对于理解这些推理过程特别有帮助,并能够进行这些计算,利用你在这里看到的信息。你可以自己从零开始实现一个贝叶斯网络,但这就。
有许多库,尤其是专门为Python编写的库,允许我们更轻松地进行这种概率推理,能够处理贝叶斯网络并进行这些计算,所以,虽然不需要理解所有底层的数学,但对如何。
它是有效的,但你只需要能够描述网络的结构,并提出查询,以能够生成结果。因此现在让我们看一个例子,结果发现Python中存在许多。
对于进行这种推断,使用哪个具体库并不太重要,它们的行为都非常相似,但我将使用的库是石榴库,在模型pi内部,我定义了一个贝叶斯网络,使用了这个结构和语法。
石榴库期望的内容是,我实际上是在Python中创建节点来表示您刚才看到的贝叶斯网络的每个节点,所以在导入石榴库后的第4行,我定义了一个名为'rain'的变量,它将代表一个节点。
在我的贝叶斯网络中,它将成为一个节点,这种分布有三个可能的值:没有降雨、轻降雨和大降雨,分别对应的概率是0.7(无降雨)、0.2(轻降雨)和0.1(大降雨)。
下一个变量,即轨道维护的变量,例如,它依赖于降雨变量,而这不是一个无条件分布,而是一个条件分布,如条件概率表所示,目的是我遵循这个分布以满足降雨的条件。
所以如果没有降雨,则有0.4的机会进行轨道维护;如果没有降雨,则没有轨道维护的机会为0.6。同样,对于轻降雨,我也有一个重降雨的分布。
刚才以图形方式表示,但我告诉这个Python程序,维护节点遵循这个特定的条件概率分布,我们对其他随机变量也做同样的事情,训练是我分布中的一个节点,它是一个有两个条件的概率表。
父节点不仅依赖于降雨,还依赖于轨道维护,所以我在这里说,假设没有降雨且有轨道维护,我的火车准时的概率为0.8,延误的概率为0.2,同样我可以对火车节点的所有其他可能的父节点值做同样的事情。
对于火车节点的所有这些可能值,这里是火车节点应该遵循的分布,并且我对约会做同样的事情,基于火车变量的分布,然后最后我实际做的是。
通过描述网络的状态和在依赖节点之间添加边缘来构建这个网络,因此我创建了一个新的贝叶斯网络,向其中添加状态:一个用于降雨,一个用于维护,一个用于火车,一个用于约会,然后我添加连接相关部分的边缘。
一支箭指向维护,因为降雨影响轨道维护,降雨也影响火车维护,影响火车和火车。影响我能否按时参加我的预约,而烘焙只是最终确定模型并进行一些额外的计算,因此具体的语法是。
这实际上不是重要的部分,石榴恰好是几种不同库之一,所有这些库都可以用于类似目的。你可以为自己描述并定义一个库,实现类似的功能,但这里的关键概念是,有人可以为一个。
一般的贝叶斯网络有基于其父节点的节点,然后程序员所需做的就是使用其中一个库来定义这些节点和这些概率分布,我们可以基于此开始进行一些有趣的逻辑,所以让我们尝试这样做,例如条件或。
我们之前手动计算的联合概率计算,通过进入似然性嗨,我们在这里导入我刚刚定义的模型,我想计算模型的概率,它计算给定观察的概率,我想计算。
没有降雨的概率没有轨道。
维护我的火车准时,我能参加会议,所以算是最优场景,没有降雨和轨道维护,我的会议。所有这些实际上发生的概率是多少,我可以利用库进行计算,并且打印并运行,iPhone的似然PI,我看到。
好的,概率大约是零点三四,所以大约三分之一的时间。一切对我来说都顺利,在这种情况下没有降雨,没有轨道维护,火车准时,我能够参加会议,但我可以试验,尝试计算其他概率。
一切都顺利进行直到火车,但我仍然错过了会议,所以没有降雨。
没有轨道维护火车准时,但我错过了预约,让我们计算这个概率,写下来,概率大约是0.04,所以。
大约4%的时间火车会准时,没有降雨,没有轨道维护,但我仍然会错过会议,因此这实际上只是我们之前联合概率计算的实现。这个库很可能首先计算没有。
降雨的概率,然后计算在没有降雨的情况下没有轨道维护的概率。然后是给定这两个值,我的火车准时的概率,以及在我知道火车准时的情况下我错过预约的概率,所以这再次是联合概率的计算。
结果是我们也可以让我们的计算机解决推理问题,开始根据我们所看到的信息证据进行推断,其他变量也有多大可能性是真的。所以让我们深入推理,假设我们在这里,再次导入那个确切的。
使用这一信息,我希望能够从之前的模型中导入所有节点和所有边,以及那里的概率分布,现在有一个函数来进行某种预测,我将其传入这个模型中。
我观察到的证据,所以在这个Python程序中,我编码了我观察到的证据,我观察到火车延误,这是一种四个随机变量之一的值。
获取灵感,我们弄清楚了关于我的贝叶斯网络中其他随机变量值的推断,我希望能够对其他所有内容进行预测,因此所有实际的计算逻辑只在这三行中发生。
我在下面做出这个预测,我只是迭代所有的状态和所有的预测,并将它们打印出来,以便我们可以直观地看到结果。但让我们找出,考虑到火车延误,我可以对其他随机变量的值进行什么预测。让我们运行Python推理并停止PI。
运行这个,结果是我得到了,考虑到火车延误这一事实。
我观察到的证据,考虑到有45%的概率或46%的概率没有下雨,有31%的概率有小雨,有23%的概率有大雨,我可以看到一个概率分布,涉及维护以及我是否能够。
参加或错过我的约会,现在我们知道,无论我是否参加或错过约会,这仅仅取决于火车是否延误。它不应该依赖于其他任何因素,因此,让我们假设我知道有大雨,这不应该影响。
约会,实际上如果我上来这里。
这个分布,并添加一些证据,假设我知道降雨量很大,那是证据,我现在有两条证据,我知道雨很大,我知道我的火车延误。我可以通过再次运行这个推理过程来计算概率,并查看结果。
我知道雨很大,我知道我的火车延误,轨道维护的概率分布因此改变,考虑到现在有大雨,轨道维护的可能性更小,88%而不是之前的64%。那么,我提前到达的概率是多少呢?
仍然会参加预约,错过的概率是0.6。预约的概率是0.4,因为这仅仅取决于我的火车是否准时或者延误,这里实施了推理算法的想法,以便基于现有的证据进行推理。
我们可以推断出其他变量的值,枚举推理就是执行这种推理过程的一种方式,只需遍历隐藏变量可能取的所有值,并计算出当前的概率。
事实证明,这并不是特别高效,确实可以通过避免多次计算相同的概率进行优化,有一些方法可以优化程序,以避免重复计算相同的概率,但即便如此,随着变量数量的增加,
可能的变量取值数量增大,我们将不得不进行大量计算,以便能够进行这种推理,到了那时,这种推理所需的时间可能开始变得不合理。
正因如此,通常在涉及概率和不确定的事情时,我们并不总是关心进行精确推理并准确知道概率,但如果我们能近似推理过程,做某种近似。
推理可能会很有效,即使我不知道确切的概率,但我对概率有一个大致的了解,随着时间的推移,我的准确度会越来越高,这可能是相当不错的。
特别是如果我能更快地实现这一点,那么我如何在内部进行近似推理呢?
哈佛CS50-CS | 计算机科学导论(2020·完整版) - P1:L0- 计算机科学基础知识 - ShowMeAI - BV1Hh411W7Up
好的,这就是CS50,哈佛大学的计算机科学与编程艺术导论。
在美丽的桑德斯剧院共同度过这个校园时光。
当然,今年有一些不同之处,不止一个原因,但我们在这里。
现在在哈佛大学的洛布戏剧中心进行,而这要感谢我们的朋友们与美国剧院公司的合作。我们有了这个新空间,甚至包括。
在这段时间里,我们与一支极具才华的团队合作。
本学期的CS50课程,因此我敢说我们将会有一些新的改进演示。
在这个过程中,我们感谢我们的东道主,美国剧院公司。现在我们希望至少唤起一些回忆,或对校园的某些想象,特别是对于那些本学期无法亲自到场的你们,因此我们进入了哈佛档案馆。在他们的收藏中有这幅由哈佛学生创作的水彩画。
一位200多年前的研究生,乔纳森·费舍,他坐在现在的哈佛广场,观察哈佛校园的一些早期建筑。多亏了科技,我们将这幅相对较小的水彩画复原,这幅画是这位研究生在200多年前创作的。
现在,舞台上装饰着洛布戏剧中心的布景,如果左边是霍利斯大厅,右边是哈佛大厅,这是校园内的课堂建筑之一,第一年,哈佛校长在这里生活和工作。所以欢迎来到CS50,我可以说,嗯,不久前。
大约20年前我上过同样的课程,但正如你所知,我在学习CS50,学习计算机领域时曾感到一些紧张。我在大学时选择了一条舒适的道路,早期学习了政府,坐在这个CS50的课堂上。
我意识到,家庭作业其实可以是有趣的,我发现编程本身也很吸引人,尽管很多人认为它在高中是怎样的,实际上它是关于解决问题的,因此它非常适用于计算机科学和工程领域。
但实际上,艺术、人文学科、社会科学等等,如果你对参加像CS50这样的课程感到有点不安,要知道几乎每年,参加CS50的学生中有近三分之二从未上过计算机科学课程。现在,很多今天和你一起上课的同学也是如此。
你处于非常相似的境地,确实有很好的同伴,最终重要的是。在CS50中,我们强调,如大纲所述,课程中最终重要的不是你相对于自己在开始时所处的位置,实际上考虑到你目前的背景,可能完全没有基础,并考虑到几个月。
最终它应该是衡量你自己成功的标准,因此朝着这个目标前进。
我们将从编程开始,这里有一幅来自往年的超级马里奥兄弟的图像,我们将重现这个游戏的一部分,尽管使用的是文本,也就是ASCII艺术,但我们将在课程的第二周左右完成,这将是你写的第一个程序之一。
快进到几个问题集或编程作业之后,或者几周后,你将构建我们所称的CS50金融。这是你自己开发的一个网络应用,运行在互联网上,并与雅虎财经或类似的API接口。
从一个第三方服务开始,允许你的用户登录和注册,买卖股票,用虚拟货币来说。几个月后,你将真正从构建自己的网络应用开始,接着是课程的顶点体验,这将是你自己的最终项目,但究竟是什么呢?
计算机科学,我们在这一周零的第一周所想要做的,就是准确考虑这意味着什么,这就是计算机科学,这是解决问题。你有一些输入,这就是你关心的问题,你想要解决它。你关心的是这个问题的解决方案,也就是所谓的输出。
在这个输入和输出之间,是一个黑盒子,里面发生着某种魔法,这个魔法你最终能够驾驭,并促使计算机为你解决问题。最终,这个黑盒子里就是我们需要共同认可的代码。
我们将如何表示这些输入和输出,我们都必须说一种共同的语言,所以我们需要达成一致,如何表示这些输入。那么我们通常如何表示信息呢?也许做的最简单的事情是用手指,无论我们是在网上还是面对面。
房间里的人数,所以你可能会用手指来代表每个人。你可以用手上的简单数字来计算,当然你不能仅仅用这只手数得太高,但实际上这里还有一个系统,那就是一元记数法,uno意味着。
一只手指向上或向下,所以你可以数到五。当然,如果我再伸出一只手,我可以数到十,然后事情会变得有点困难,但这是一种表示信息的系统,而且它相当普遍,当然,当我们所有人都在一起时。
年轻的你和我,只是手上的数字,还有其他类型的数字,即我们所知道的十进制数字。那些数字在技术上更正式地称为基数10,这只是一个 fancy 的说法,描述了人类的事实。人类确实倾向于通常使用这些数字,当然是0到9,并使用这几位数字。
我们可以组成数字,比如0到9,但也包括10、11和12,甚至更高,仍然使用多个数字。但是计算机并不真正使用与我们相同的语言,在某种意义上,它们比我们人类简单得多。尽管它们看起来如此复杂,但归根结底,这些都是人类制造的设备。
它们在核心上相对简单,实际上即使你不完全知道自己在说什么,但至少你听说过这种情况。你认为计算机会说什么语言?计算机说什么语言?如果不是我们使用的从0到9的系统,或者十进制。
布莱恩,我们能看看谁会回答这个问题,计算机使用什么系统?就像你听说过的,无论你之前是否上过计算机科学课,基思,我们可以继续吗?是的,呃,计算机使用二进制,二进制,没错,呃,计算机使用二进制。你能详细解释一下你所说的二进制吗?
呃,它是零和一,所以像我们使用零到九作为基数十,它使用零到一作为基数二,没错,所以计算机使用所谓的二进制系统,意味着二,并且确实只使用这两个数字,也就是零和一。这个系统实际上相当简单。
如果我们只使用这两个数字,数字,我们要如何表示这个数字呢?两个、三个、四个,或者任何更大的数字,这似乎几乎是向后退了一步,但实际上并不是,结果是,这个所谓的系统或二进制。之所以称为二进制,是因为它只使用零和一。
这里还有其他的命名法,我们通常称之为两个字的可能性。数字仅仅是在屏幕上的符号,因此二进制数字也称为位,电脑使用这些称为位的东西进行二进制计算。但这意味着什么,为什么会这样?像为什么几十年前没有发明使用零的电脑,完全是一个新系统。
对我们来说,这个问题都难以思考,更别提讨论了,毕竟,电脑使用的输入实际上只是我们每天或每隔几天都在做的事情。手机要么确保它仍然插着,要么插上以便充电,这些设备如今。输入的形式是电力,我们不必深入探讨其细微差别。
电力只是一种,但我认为这与电子流入设备以进行充电有关。我们只需知道这些设备,也就是我们使用的电脑和手机。但就这样,如果我们利用这些电力,也许我们可以开始用信息来表示。旧的、现在关闭的剧院中的鬼灯,它有能力。
开启时我们只需要插入它或按下开关,令人信服的是,使用灯光的比喻是,现在这个灯泡是关闭的,但当我允许电流流动时,开关。现在它当然是开的,如果我拔掉它,或者再次按下开关。
它是关的,或者如果我把它插回去它就是开的。
这个非常简单的想法的含义是,我们可以拿一个物理设备,比如一个灯泡,通过插入或拔出它,来表示。我表示灯泡的开与关,但我们可以简单地称其为开和关。我们也可以称之为零和一,因此这确实是。
这个给我们带来电脑的想法的萌芽,以及它们使用的二进制系统,毕竟电力,嗯,就让我们利用它来掌握和跟踪信息吧,让我们存储一点电力。当我们想表示一个时,让我们释放掉这电力。
当我们想表示零时,因此因为电脑的输入是如此简单。这给了我们这些零和一,而我们似乎给自己创造了一个问题,开关,如果它关了可能是零,如果它开了可能是一个。但我怎么能数高于一呢?这个问题仍然根本存在。
嗯,我当然可以用更多的灯光,使用三个灯泡我们能从零数到多少?两个可能性,但有三个灯泡我们能数到多高呢?让我来问一下这个屏幕上的问题。稍后你们会在屏幕上看到这个特定的问题。
你可以在你的设备上响应,因此我给你三个灯泡,而不是一个,每一个都可以开或关,我们或许可以数得多高。
所以你会在屏幕上看到,答案正在出来,我们有很多人认为是60%以上的,认为八是你能数到的最高的,很多人认为是七,还有一些人认为可能是三或二,所以这实际上是一个有趣的答案范围。让我们看看实际情况如何。
最简单地说,我认为如果我们打开这些灯泡,如果它们目前代表零。显然,我可以打开一个,再打开第二个,称之为二,打开第三个。现在三个都亮了,我们可以说,现在我们代表的是三,但我们还不够聪明,只是在计算。
三个因为我只是将它们打开,这个故事从左到右。但如果我们聪明一点,或许可以从右到左打开它们,或者我们以不同的方向进行排列,也就是我们采取的方向,或者空中有多少根手指,而不是所创造的模式。
那么让我们统计一下,所以让我有条不紊地打开一些灯泡。尽管是虚拟的,这里可能是一个,这里可能是两个,这里可能是三个,但然后我们大概就结束这个故事了。那么我们如何能做得更好呢?让我们从零开始,二。称这个为4,称这个为5,这个为6,这个为7。如果你没有也没关系。
相信我,那是一个独特的模式,开始时是关闭的,最后是开启的,但在此过程中确实有八个。那么我能数到多高呢,这实际上取决于你从哪个数字开始计数,正如我们到目前为止所做的,计算机科学家们总是这样做。
实习计算机程序通常从零开始计数,所有东西都是关闭的。你可以称之为零,所以如果我们从零开始计数,而我们刚才看到的有八种可能的模式。那么这将允许我们数到七,所以从零到。
七,所以七是我们可以用三个灯泡数到的最高数字。所以提出七是答案的36%的人确实是正确的。如果你假设我们从一开始计数,那也没问题,但至少在计算机世界中,现在我们通常按照惯例说,有八种这样的可能性。
好吧,这一切都很好,用灯泡的模式来表示事物,但我们如何才能真正得到计算机实际使用的零和一呢?因为最终计算机内部有什么呢,开关。数以百万计的小开关,可以是打开或一,或者关闭或零,这些开关正好发生在。
被称为晶体管,这些天计算机确实有数以百万计的东西可以以不同的模式开或关。所以如果你有能力打开和关闭所有这些开关。那么我们在使用这些开关时,可以就如何表示信息达成一致,嗯,非常好。
我们其实不需要思考得太复杂,孩子们,如果我们考虑一下不仅是零和一,而是整个十进制系统,从零到九,今天你我都是从这个系统开始我们的一天。这个系统是如何工作的呢?好吧,屏幕上显示的是一、二、三,所以你可能在想那是123,但并不完全,屏幕上显示的只是。
符号的模式是一、二、三或三个数字,可能我们所有人本能地都在说,显然是123。但是自从你考虑为什么是123以来,可能已经过了很多年。好吧,让我们考虑一下这些数字或符号各自代表什么,如果你像我一样,成长过程中学习了位值。
中间的是十位,左边的是百位。那么我们如何从这三个符号或数字一、二、三中得到呢?我们每个人都是通过1加上10乘以2,加上1乘以3,这当然就是100加20加3,或我们都知道的数学值是123。所以,这是一个有点循环的论证,但只是提醒我们我们是如何得到这个结果的。
结果发现,在计算机的世界里,他们使用的系统在根本上是一样的,唯一的区别是计算机只能接触到0和1,而不是从0到9。因此,如果我们现在在抽象层面考虑这里表示的三个可能的数字,让我们考虑一下1。
10和100等等,那么为什么会这样呢?其实是有一个模式,涉及到指数或幂,因此如果我们深入探讨,最右边的列实际上是10的零次方,如果你还记得,这只是意味着1。10的第一次方就是10,而10的平方或10的第二次方。
是100,但有趣的是,10的参与使得从0到9总共有10个数字,因此这些列使用的是以10为基数。你也许可以先于我考虑,如果在二进制系统中计算机只使用两个数字,0和1,那么从123到123的过程又是怎样的呢?
变化是这些列的含义,现在我们有了个位。仍然是因为2的0次方是1,但然后我们有2的1次方,2的2次方等等,当然如果我们在计算机使用的二进制世界中进行计算,我们有个位、二位、四位等等,现在我们可以顺利进行,即使我们。
现在我们必须在一个不同的基数系统中思考,我们可以开始计数,从灯泡的隐喻出发,考虑如果所有的灯泡都关着,我们又会把这些东西视作零,因此那将是一个符号或数字的模式,二进制中的0,0,0,但在我们人类的世界里。
你今天可能会瞬间进行的心算是,显然这是4乘0,加上2乘0,加上1乘0,当然在十进制中就是0。但是计算机是如何表示数字1的呢?它只是将最右边的位从0变为1或更多。
并点亮最右边的灯泡,就像我之前做的,1 0。在二进制中,我如何表示3,这是我们即将不同的地方。现在我打开两个开关,因为我需要在二的位和一的位上给我数学上的三,接下来如果我们选择。
嗯,数到四,那就是一零零,如果我想数到五,那就是一零一,六就是一一零,最后七就是一一一。
所以似乎使用三个比特,每个比特可以是零或一。是的,你可以将它们排列组合成八个,首先,第二次乘以二,第三次乘以二,总共给我们八个,但根据这个数学和从零开始计数的直觉,我们最多只能数到七,总之,我们继续前进,我们没有。
就说吧,我们假设有一个灯泡或者三个灯泡,我们实际上很幸运地拥有像灯泡、一个罐子和一个门在舞台上,你知道我们能否继续在屏幕上放一个随机数,好的,所以如果你。
关闭 打开 打开,所以关闭 打开。
关闭 关闭 打开 打开,所以让我们继续问一个问题,只有一个灯泡、两个还是三个,但在这种情况下至少是六个灯泡,我们实际得到什么价值呢?好吧,让我把问题放在屏幕上,这个问题将在你们的屏幕上弹出。
一一零零一,零代表哦,是的,意识到我必须澄清一下,读出来。实际上这里什么十进制数字呢?
二进制数 1 1,零零一零从左到右表示。不幸的是,我是从我的左边读到右边,但你们的正好相反。所以这里我们有一个压倒性的反应,50确实是正确答案,为什么呢?如果我转到物理灯泡这里,我们只考虑。
一刻钟,模式实际上是什么,位置,4 8 16 32,我们可以继续,但没有。
这将无关紧要,因为它们都是关闭的,所以我们有 32 加 16 加 2,这确实给了我们。
我们在十进制中知道的数字,作为 50。想象一下我们可以用其他灯泡数到多高,好的,所以我们开始了电的故事,然后转向数字,以十进制或二进制表示东西,但我们有点把自己逼入了一个角落。
因为如果我们只有开关或隐喻的灯泡,可以看作是零和一,那么计算机能做的事情似乎仅仅是计算,也就是充当计算器,实际上早期计算机的确是为了便利人类的数学计算而设计的。
当然,我们现在所使用的,每天在手机、笔记本和台式电脑上使用的技术要复杂得多。!
想想计算机如何表示字母,不仅仅是数字,布莱恩,如果你想举手,我们可以请教一下,有人能告诉我们,计算机如何表示像英语这样的字母,如果我们只有这些,你认为呢?让我们看看,索非亚你怎么看?
哦,我们可以将字母表中获得的数字进行分配,是的,我们可以将特定的数字用二进制分配给字母,这似乎是我们唯一的选择,如果我们只能改变这些开关或灯泡,或者位,那我们必须达成共识。
以相同的方式表示字母,现在也许最简单的方法是,大家都同意大写字母A是数字一,所以你,二进制数字一,那对于B我们可以用数字二,数字三,D可以是四,依此类推,我们只需达成一致。
人类确实做到了这一点,但方式稍有不同,多年前他们决定出于一些我们现在不讨论的原因,大写字母a实际上会被表示为我们知道的十进制数字65。现在以位形式看,这将是这样的位模式。
计算机用来表示我们现在知道的十进制数字65,而计算机将做的就是注意你使用的程序类型,所以,如果你在使用计算器,或者使用类似Excel的软件来处理数字,那么在这种上下文中运行像计算器或分析软件。
程序将在计算机内部看到的,表示十进制数字65,因为它是在计算器的上下文中,或者在屏幕上,字面上是十进制数字65,但如果你我使用的是短信或数字,但在字母上,比如英语字母,在这种情况下,你的计算机会非常智能。
在短信或电子邮件等上下文中表示65,实际上表示的是大写字母a,因此模式是相同的,表示方式相同,但上下文是不同的,人类多年前提出的将65映射到66为b,67为c的系统被称为ascii,美国信息标准代码。
交换意味着有一个明确定义的映射,这是一群人在几十年前决定的,用来将字母(在这里是英语字母)映射到从65开始的数字和标点符号的小写字母。所以,假设你确实收到了。
一条包含模式的文本消息,实际上只是一串十进制数字。假设你收到了一个文本消息,包含这些数字模式72 73 33,你可能刚收到什么消息?让我先调出简略图表,看看你到底收到了什么消息。
72 73 33,萨姆纳,我们能否把这个同样的三字母单词放在灯上?如果你想以位的形式看到这个相同的模式72 73 33,现在也可以。那么,这个模式代表什么呢,呃,兰汉姆,我们可以请教你吗?呃,那就是高音和感叹号,没错。
确实是高音加感叹号,回过头来,现在可能很容易推测,72和73分别是h和i。不过,兰汉姆也提到感叹号,点点之间有一个明确的映射。
我们可能在意的是高音,33可能比其他更明显。我们需要一个更大的图表,所以如果你现在上电脑查看ascii图表,你会看到类似这样的东西,当然你也可以直接搜索ascii来获取相同图表的副本。
老实说,位通常不是一个非常有用的计量单位,因为。
你会看到h确实是72,i确实是73,但如果我们看左边,33显然是感叹号,你只有查过或者记住了才能知道这一点。但你我使用的计算机和手机,实际上就是这样被编程的。
但事实证明,我们还应该考虑我们现在使用了多少个零和一来表示72、73和33。因此,让我们最后一次看看灯泡。
这些是位模式,当你从朋友那里收到说hi的文本消息时,hi加感叹号全大写,你实际上在技术上收到了一个位模式。如果是无线的话,那代表着这个位模式,通常现在的计算机使用八位来表示每个字符。
当ascii刚出来时,他们通常使用七位,是出于效率考虑,因为那时空间昂贵。但现在我们使用八位,这已成为表示字符的标准,因此我们这里有八位,八位,八个高音消息,总计。
它们如此之小,仅仅是0或1,实际上你可以称其为一个词汇,这个术语在某种上下文中是这样的。但一般来说,在兆字节或甚至千兆字节的上下文中,当你谈论以字节为单位时,无论是百万字节还是十亿字节,每个字节很简单地是一个八位模式。
0和1,因此如果我们有多达64个灯泡可以使用,64除以8就是8个字符,所以似乎我们可以在这个舞台上拼写一个八个字母的单词。如果我们可以随意展示一个随机的八个字母单词,那就太好了。你能从左到右拼写一个八个字母的单词吗?
使用被称为ASCII的系统的单词,但当然我们在这里有点偏见,因为ASCII是信息交换的美国标准代码,而在典型的美国英语键盘上,字符肯定比大写字母(如A到H和I)要多,还有一些标点符号和数字,但也有很多缺失的内容。
可能会发现使用这样的键盘,尤其是消除一些限制或挫败感。为什么会这样,ASCII似乎缺少了什么?让我们问这个问题,如果我们给自己八个比特或一个字节,我们可能会有多少不同的字符呢?
实际上显示出来,实际上在你的屏幕上,你应该看到这个问题。现在,你可以用八个比特表示多少符号,这实际上归结于一天结束时,ASCII或计算机能够支持多少字母,加上标点符号,以及大小写字母,似乎大约是72%。
你可能认为答案是256,确实是这样。你可以通过数学计算出来,如果是0或1,这意味着第一个比特有两个可能性,第二个比特也有两个可能性,依此类推。这就是2的8次方或256,这没问题。
这并不是显而易见的,但如果你有八个比特,每个比特可以是两种值之一,你就可以得出256种可能性。那些说答案是255的人,在这种情况下是错的,因为现在我们讨论的是总的模式数量,确实是256,八个比特或八个灯泡似乎确实如此。
255的情况是因为我们可以排列出所有不同的模式。但让我向观众提出问题,为什么美国英语键盘会特别有限,反过来,为什么ASCII在表示人类语言时实际上并不合适,即使这正是计算机多年前开始使用的。
ASCII缺失了什么,为什么256个总可能性可能不足?确实,我是说,缺少了很多不是其他语言的重音字符,但如果你只是考虑像亚洲语言,字符的数量远超过256个,正是如此。因此,我们不仅缺少必要的。
在一些语言中,你可能缺少的字符,尤其是在亚洲语言、阿拉伯语等语言中,我们使用的符号远超过256个。英语我们可以通过适应这个键盘来应付,但一旦涉及字符。
更不用说其他符号了,事实证明,还有其他一些我们人类喜欢说的东西,这些东西最近变得流行,也就是说,你很可能在今天的某个时刻发送或接收了这些东西,通常被称为表情符号。尽管这些表情符号看起来像。
图片看起来像是图像,实际上,它们在计算机中的实现方式是以零和一的模式存在。实际上,这些仅仅是字母表中的字符,所谓的表情符号字母表,也就是说,每一个这些面孔和许多其他表情符号都有一些零和一的模式来表示。
这是因为,从ASCII开始,它只使用了七个甚至在某种意义上是八个比特来表示所有可能的字符。
现在有一种叫做Unicode的系统,支持的不仅是英语,还有所有人类语言,这是书面和口头表达的理想目标。书面形式,无论是打印还是电子形式,都是目标,此外。
这就是说我们可以像这样表示事物,这是所谓的“带泪水的快乐脸”。而这种带泪水的快乐脸截至去年是最受欢迎的表情符号,通过短信、电子邮件、社交媒体等发送,但归根结底你收到的就像是键盘上的一个键,所以实际上你看上去并不会知道它,但实际上。
表示这个带泪水的快乐脸的十进制数字恰好是128514,因此为了凯文的观点,表示不仅是某些人类需求,需要超过256个字符,所以我们不仅可以使用8比特,还可以使用16、24或32,这样的可能性巨大。如果你收到或者发送那个带泪水的快乐脸。
从技术上讲,你只是在发送一个看起来像这样的比特模式,这就是全部。所有这些事情,好吧,我们再次从电开始,然后表示数字。现在我们能够以表情符号的形式表示字母,甚至情感,还有什么呢?我们看过的表情符号,都是图像性质的,因此。
这就引出了一个问题,计算机如何表示颜色之类的东西,比如这个流泪的笑脸。
它有很多黄色,那么黄色或任何颜色在计算机中是如何表示的呢?让我再问一下观众,如果你手头只有位,零和一,我们作为人类需要达成一致,如何表示颜色,可能性是什么?这不一定是答案,但你自己的可能是什么?
如果你自己第一次设计这个,计算机如何表示颜色呢?你可能会为不同的颜色和色调分配数字,并使用相同的系统,没错。完美的直觉,你会将数字分配给不同的颜色。
我们都必须达成一致,这个映射到底是什么。结果证明有不同的方法来实现这一点,如果你们当中有艺术家并使用Photoshop等工具,像RGB这样的缩写,红、绿、蓝,但还有其他缩写和方法来实现亚斯敏的想法,我们只是以某种方式映射。
零和一与实际颜色的对应关系,实际上RGB表示红、绿、蓝,这是人类多年前想出的一个系统,说明我们实际上可以获得每种颜色的红光、绿光和蓝光。这就引出了一个问题,我们如何表示红色的量。
我们如何表示绿色的量?我们如何表示蓝色的量?正如亚斯敏所说,我们有位可以做到这一点。假设我们收到一个位模式,那是72、73、33,但这次它不是在电子邮件中,也不是在短信中,而是在我在Photoshop中打开的文件的上下文中。所以就像我打开了一个。
这是某人给我发的照片,我想进行一些编辑,转换位。那么在这个消息中,它表示什么?它的值仍然很高,但在图像上下文中,它实际上将表示一些红色、一些绿色和一些蓝色。正如我们之前发现的,您可以得到的总可能性是。
用八位表示的值恰好是256,最高值可以表示为255,如果从零开始计数,这意味着每一个这三个位数都是0到255之间的数字。因此,72感觉像是中等量的红色。73像是中等量的绿色,33像是少量的蓝色。
如果你将这三种颜色的数量结合起来,八位加八位加八位,总共使用24位,前八位表示红色,中间八位表示绿色,最后八位表示蓝色,结果会形成一个看起来像这样的点,一个黄色的点。因此,实际上这个表情符号在使用时。
在屏幕上显示的,是计算机解析128514的值,知道哦,那是带有喜悦泪水的表情符号,但在显示时,计算机将使用不同的比特模式来控制颜色,你我在计算机上看到的点。
屏幕或甚至现在的电视,被称为像素,它们是代表某种颜色的小方块,比如这里的这个黄色,你实际上可以在某些情况下看到它们。如果我继续拉出,带有喜悦泪水的同一张脸并放大。
稍微放大一点,真的再放大一点,现在你可以实际看到我们称之为像素化的东西,几乎可以肯定你在Facebook、Instagram或你可能正在调整或编辑的照片中见过这一点,这些照片的分辨率不够。图像的分辨率,就是水平上有多少个像素或点。
如果你真的放大图像,你最终会看到,即使在这个放大的快乐面孔中,有大量的黄色点,以及一大堆,组成了这个非常多彩的图像,因此你可以在这种情况下看到它们。每一个点现在,像素据我所说大约使用24位或。
三个字节,现在你可以想象,图像中可能有数百甚至数千个点。如果我们缩小并再次查看所有点。因此,如果每一个点或像素是三个字节,这就是为什么你我从互联网下载的照片,通常不是用字节来测量,而是用。
或者是兆字节(百万字节),或者更大,十亿字节或千兆字节,但这就是在幕后发生的事情,我们只是以这种方式表示信息。好吧,让我现在问一个后续问题,如果我们现在多亏了Yasmin,代表了所有的图像是一个像素网格,你获取。
你所提出的相同原则是,你代表每个点的颜色,并且有一大堆点给我们图像。你会如何建议计算机表示视频文件。即使你不知道答案,计算机现在可能如何使用仅有的比特来表示视频文件?谁想回答这个问题,计算机可能如何表示。
嗯,我可能只是在快速改变,嗯,我可能只是在快速改变,字节。只是快速改变字节,我听到你能否稍微详细说明一下。你说的快速改变字节是什么意思,嗯,就像快速改变单个像素的RGB。正是如此,以匹配视频的那个瞬间或视频的部分。
完美,所以如果你考虑一下你的手机或笔记本电脑或桌面显示器的矩形屏幕,如果你每秒或每秒多次改变这些点的颜色,我们会产生屏幕上实际存在运动的错觉,因此是视频,所以实际上是视频。
从某种意义上说,这只是一大堆图像,根据雅斯敏的定义,快速地飞过屏幕。因此你甚至可以看到这种老派风格,比如让我在我的屏幕上打开一个短视频,代表一个翻页书,你可能在小时候做过一个,或者你的老师做过,或者你至少见过。
在某个地方,如果你拿一大堆纸,把它们用订书机或夹子固定在一起,画出很多相似但略有不同的图片,你就可以创造出动画,或者说真的,视频,而这在纯电子世界中,甚至是纸质的。
嗯,在计算机世界中,实际上只是一个图像序列以某种速度飞过屏幕,这就是我们今天所知的视频文件,而我们还可以深入探讨更多的细节,例如,你可能如何表示音乐。
嗯,音乐可以用不同的方式表示,比如如果你弹钢琴,你可能知道有像A到G这样的音符,但你知道,也许我们只需要一个数字来代表每一个可能的音符,就像图像使用多个数字来表示点一样,我们可以用一个数字来表示音符。
在一首歌中,还有另一个数字,用于表示该节点的持续时间。你应该听那音符多少秒、毫秒或拍子,因此你可以想出其他的表达方式,但音乐确实可以在计算机的世界中量化成小块信息,只要你和我。
一致同意如何表示这就是这些东西的工作原理。如果你曾经想过,为什么会有JPEG、PNG和GIF,这些不同的计算机文件扩展名或格式只是代表一群人一致同意如何在文件中存储零和一的模式,以便当这些零和一。
加载到计算机中进行显示或解释时,它知道这些模式代表什么。图像的表示方式略有不同,声音和视频的表示方式也略有不同,但归根结底,这一切都是零和一。因此,这一切意味着,只要我们在世界各地达成一致,理想地,如何表示信息,现在我们可以表示问题的输入。
希望能够解决问题,得到输出,因此在解决问题,或者说更广义的计算机科学中,剩下的就是查看这个黑匣子内部,考虑字母、图像、视频、声音,并将它们转换为实际解决方案,因此在这个黑匣子里面,我们通常会描述为指令。
为了解决问题,它们甚至不必涉及计算机。我们人类可以仅通过遵循别人的指令来执行算法。如果你曾经从食谱书中准备过东西,按照食谱,你就是在逐步执行算法。但与许多食谱或我们人类相互给出的许多指令不同,执行过程中没有模糊的空间。
计算机,计算机算法在机器实现时,确实不仅要正确,以便你得到正确的输出,必须非常精确,因为与我们人类不同,我们可以大概理解你的意思,计算机会逐步将算法转换为某些内容。
计算机能理解的语言,责任在于你,确保计算机不会误解你想要的内容。那么我们来考虑这样一个算法,在我们的手机上,无论是iOS还是Android之类的,你都有一个联系人应用程序,而这个联系人应用程序可能。
存储你所有的朋友、家人和同事,可能是按字母顺序排列的,也许是按名字,也许是按姓氏,或者你以其他方式组织这个设备。老派的版本是纸质的,看起来大致是这样:一本电话簿,在一本旧式电话簿里面,确实没什么特别的。
印刷得多,但它是一样的,典型电话簿里有一堆名字和号码,Android手机或iOS手机也是如此。所以假设我们想解决一个问题,输入电话簿,但也包括要查找某人的名字。我的名字,例如,如果我想查找。
我的电话号码或者你可能打开这本书,开始寻找。例如,假设按照名字排序,我在第一页没有看到大卫,所以我继续翻到第二页。我在那里也没有看到自己,所以我继续翻到第三页,我在那里也没有看到自己。
翻到第四页,寻找我的名字,以及我的号码。如果正确性很重要,算法。逐步翻页寻找大卫,正确的,你认为在Zoom中你应该看到一些图标,在参与者窗口下标记为“是”和“否”,如果你想虚拟投票,选择“是”或“否”。
这是黄页,所以我在电话簿中肯定不会出现。但确实,我们假设它包含人类。好吧,看起来这个算法确实是正确的,但它非常非常慢,而我们,计算机科学的关键不仅在于算法的正确性,还有它的效率,以及设计得有多好。
这个算法是正确的,它虽然慢,但我会找到我自己,不过当然我们可以做得更好,而不是一页一页地寻找我自己,四、六、八、十,听起来更快,通过电话簿寻找我自己,这个算法正确吗?让我去问问观众中的某个人,这个搜索算法正确吗?
有人一次看两页,正确吗?因为我声称这样更有效,我声称这样设计得更好,因为我将解决问题的速度提高一倍。不是的,因为你可能会在页面上跳过你的名字,是的,我可能会在页面上跳过我的名字。让我问个跟进问题,我可以修正这个问题吗?
我是否必须抛弃整个算法,或者我们至少能解决这个问题吗?你觉得呢?我认为无论你翻到哪一页,都有助于选择哪个名字在那儿,也许看看你的名字是在前面还是后面,太好了,这正是正确的直觉,我不认为我们必须完全放弃“快两倍”的想法,我能到达。
e区是晚了一页,我应该至少回头查一页,因为我可能会运气不好,也许大卫正夹在两页之间。在那种情况下,我可能会飞过,来到电话簿的末尾说,没有大卫,而我只是运气不好,大约有50%的概率,但正如你所提到的,我至少可以恢复。
我会有条件地问自己,等一下,可能我刚刚错过了,回头再查一遍,这样我可以获得整体的速度提升,但至少可以修正那种错误或bug,bug是编程中的专业术语,指的是程序中的错误或算法中的错误,当我们实际上去搜索电话时。
它们通常不是从顶部开始,然后到达底部,而是计算机可能会更直观地,从中间开始,也许会稍微偏向左边,如果你知道字母表的开头是d,但我就是打开中间,随意浏览,而我在m区。
那么,当我在m区时,我知道什么?再问一个人,我在m区。作为人类,你会如何处理这个输入以解决这个问题?我知道在电话簿中我名字的位置,我能做出什么决定?我能做出什么决定,凯尔,你会怎么做?
嗯,从m区开始,你知道你的名字肯定不会在那里,所以我的名字不会在m区,但多亏了电话簿的字母排序,我至少知道,我可以大口咬下去,这既是比喻上的也是字面上的。
在电话簿的情况下,我可以真的扔掉一半的问题。所以,如果我从一千页的电话簿开始,或者我手机中有一千个联系人。通过大致找到中间,看看左边和右边,我可以决定,正如你所注意到的,那并不在我这一页上。
我在寻找,但我可以决定往左边还是右边。我知道D在M之前,所以现在我可以往左走。而有趣的是,我可以用完全相同的方法,用不同的方式,应用同样的逻辑打开这本电话簿的中间部分,现在我在G部分看到了他,可以撕掉。
把一半的问题扔掉,现在我已经从大约一千页减少到500页,再到250页。如果我再这样做,可能会发现自己,哦,我到了C部分,现在可以再把问题一分为二,扔掉左边的一半。现在我只剩下125页了,这还是很多,但天哪,我已经从一千页减少到了。
从500页到250页再到125页,这比从一千页到998页再到996页到994页要快得多。这两种算法所需的时间都要长得多,所以当你实际上查看949-468-2750时,949-468-2750。
随意发短信问候或打电话打个招呼,但我发现自己用那个第三个算法更快。那么,结论是什么呢?我们可以把这实际上看作一个可视化的东西,让我继续,而不是一次又一次地将整个电话簿一分为二,我们有这个由布莱恩制作的可视化图。
这真是奇妙,描绘出一本一千二十四页的电话簿。每次翻一页,现在我们剩下996页,995页。老实说,这并不是特别启发人,按照这样的速度,用那个算法在电话簿中找到大卫或任何名字将需要很长时间。
但如果我稍微聪明一点,更加直觉一点,我可以再从一千二十四页开始,这次采用“分而治之”的方法,每次一半。将电话簿一分为二。
我只剩下一页,如果我们实际上做一下数学计算,如果从大约一千页开始,这个电话簿,468-2750。所以,468-2750。也就是说,第三个算法不仅是正确的,就像第一个算法肯定是的,第二个算法在修复bug后也可以是的,但它也更加。
设计得更好,更高效,所以我们可以从图形上也看到这一点。让我提议一个,不是数字分析或其他东西,而是一些稍微可视化的东西,比如这样,所以如果我在这里有一个X轴,代表水平方向上问题的大小,即数量。
电话簿中的页数和y轴上解决问题所需的时间,这些算法看起来如何,如果我们简单地将它们绘制成图。第一种算法在这里以红色描绘,正是一条直线,斜率为1,因为页面数量和所需时间之间存在一对一的关系。
每新增一页电话簿,意味着我可能需要多走一步,除非我走运,能找到在电话簿前面的位置,转变。第二种算法实际上更好,依然是一条直线。因此,它仍然是线性关系,但每两页在电话簿中。
书中我多了一步,两页一次转动,两页一次转动。因此,它严格优于第一种算法,为什么呢?假设为了讨论,电话簿有这么多页,用这条虚线描绘。那么第二种算法所需的时间是多少?
在电话簿中找到某个人将需要这段时间,对吧?在这两条线交点处,如果你使用第一种算法,实际上将需要同样多的时间。因此,假设我们根据需要折回,如果我走得太远超过一个名字,两者都是正确的,但这两者在本质上是相同的。
它们形状相同,坦白说,它们在说和执行第三种算法时感觉都很慢。如果我们将其绘制成图,它在问题的规模和解决问题所需时间之间有根本不同的关系,线条如预期那样上升。因为页面越多,解决问题所需的时间就会越长,但注意它上升的速度有多慢。
这东西几乎没有开始上升,随着问题规模的增大而变得更加缓慢。直观地说,假设理由翻倍了,也许剑桥合并成一个大的电话簿,那么现在有大约2000页,那么接下来需要多少更多的步骤。书中,仅仅增加一步,所以如果你在这条绿色线上看得更远。
当电话簿的规模翻倍时,线本身只会略微上升,因为没有大问题。使用第三种算法,你将问题分解成更大的字节。因此,这两者编程的本质最终就像是利用你带到课堂上的想法,以及你可能在日常生活中使用的想法。
但你不一定会考虑如何利用这些算法来表示问题,以及如何将它们翻译成,开始思考。算法不仅关乎它们的正确性,例如在这里,我故意标记了这三条线为n、n/2,计算机科学家倾向于使用n作为变量。
就像数学家可能会说x或y或z,代表数字。因此,第一条红线是运行时间,所需步骤的数量,情况n。如果电话簿中有n页,也许我在寻找某个人,正好在电话簿的末尾,这将花费我所有的n步才能找到他们。
第二个算法将花费一半的步骤,因此我们将其表示为n除以2。因为如果我们一次处理两页,我们将更快地到达电话簿的末尾,假设名字以z开头;但第三个算法,如果你对数学有点生疏,可以表示为以2为底的对数,绿色线。
描述了解决问题所需的时间,当每次传递时,每一步你都在将问题分成两半,其他两个算法则是从问题中取出一或两个字节,第三个算法是一次处理整个问题的一半,这使得它更强大。因此,当涉及编程时,现在我们。
算法,代码或者在这个例子中我们称之为伪代码,稍后我们将专注于一种实际的编程语言,尽管是图形化的,但现在我们先考虑一些构造或基本理念,这些将对本课程有用,所以让我提出。
我所做的口头表达可以翻译成伪代码。这就像用英语或任何你所说或写的语言实现的算法,但关键是,它必须是正确的。理想情况下,它最好是精确的,以免产生歧义。
第一步确实是我拿起电话簿,第二步,翻到电话簿的中间,第三步。查看页面,我确实做到了,现在事情变得有趣,第四步,页面。我想做什么呢?我应该打电话给那个人,问题解决了,我得到了输出,人的号码,但还有另一种可能性。
如果那个人在书的前面,结果是m。但我在找大卫,他在左边,我应该做什么呢?翻到书的左半部分的中间,这正是我所做的,我有点不必要地将问题一分为二。但从算法上讲,我只是查看了书的左半部分,接下来我该做什么呢?
实际上这就是我提议的,算法现在可以重复进行。我们可以回到第三行,为什么呢?因为从第三行开始,我有一个查找某人在书中的算法,现在减半了,但还有另一种情况,如果那个人在后面,名字以d开头。
但如果那个人在字母表的末尾,那么如果这个人稍后出现在书中,同样的思路,打开中间的部分,回到第三步。最后还有第四种可能性,左边的内容根本不存在,这最后一点虽然有些微妙,但却非常重要。
我曾经有过非常沮丧的经历,比如你的手机、电脑卡住,你看到愚蠢的旋转沙滩球或沙漏,东西冻结或重启,出现问题是难以解释的,你可能会认为是你的错,但其实通常是你使用的软件的程序员的错。
你的计算机或设备,为什么程序员常常未能预见到可能的场景呢?在这种情况下有四种场景,但你可以想象到,可能会忘记,哦,或许大卫根本不在这个电话簿中,但你最好处理这个场景。
计算机冻结、挂起、重启或出现其他问题,这通常是因为人类没有为某些可能的场景编码。那么,我们在课堂上看到的基本概念是什么呢?现在在黄色突出显示的其实是一些动词或我们与这个电话簿一起练习的动作,这些内容通常是。
编程称为函数,函数是一个动作或动词,是让计算机执行某个操作的语句。接下来突出显示的是我们所称的条件或分支。这些就像是道路上的典型分叉,你可以选择这个、那个或其他事情,你可以做一个、两个、三个或四个条件的决定,条件的数量可以很多。
但是你如何决定选择哪条分叉,无论是做这个、那个还是另一个,布尔表达式就是一个问题,其答案是是或否,真或假,或者简单地说是1或0。所有这些对于我们之前提到的提问人都是等价的。
在书中稍后的提问人,还有第三个问题,所以如果你回答一个零的答案,那就是给我们这些称为布尔表达式的东西。最后在这里的黄色部分是这些内容,回到第三行。这将引发我们所称的循环或周期,这只是一个编程构造。
算法的原则使你能够反复执行某个操作,这样你就不需要编写一百行算法,而是可以编写十三行算法并重用其中的部分。我们将开始cs50,看看一个实际使用的语言,它在年轻时被称为编程语言。
对于你们中的一些人来说,它其实代表了很多,这为我们在短短一周内过渡到一种更传统、更老派的语言C奠定了基础。但在CS50的所有语言中,我们将看到称为函数、条件、布尔表达式和循环的这些东西,今天稍后我们也会。
看一些我们描述为变量的其他特性,类似于数学中的x、y和z,允许计算机同时做多件事情的线程,以及其他一些特性,因此在这里我们从伪代码过渡到实际代码。你在屏幕上看到的就是一种名为的语言示例。
C语言是我们这个学期会花大量时间学习的,这就是我之前提到的旧语言,但这种语言有点晦涩,确实在第一眼看去,你可能会想,为什么会有井号、尖括号、括号、大括号、分号和引号,我的天,这里有这么多。
与现在屏幕上的语法相对应,你可能可以猜到这个程序的功能。让我快速询问一下观众,这个程序可能做什么?即使你从未编程过,让我们快速听取任何人的答案。让我们快速听取任何人的答案,这个程序做什么。
嗯,它只是打印出“你好,世界”,嗯,它只是打印出“你好,世界”。没错,它只打印“你好,世界”,我的天,看看我们需要输入的所有语法和按键,仅仅是为了命令计算机去做这些事情,而今天我们将进行对比。
是scratch,我们会给自己一天的时间来看看更友好、更图形化的东西,这将使我们能够探索这些想法,并为更复杂、更深奥的内容奠定基础,但在一个我们不需要担心括号、分号的上下文中,所以让我介绍一下。
你接下来看到的scratch是由麻省理工学院媒体实验室的一些人开发的,你可以在家里跟着玩,如果你愿意,可以访问scratch.mit.edu,它是基于网页的,但也有离线版本,如果你不习惯使用。
互联网的最佳部分,但用户界面通常看起来像这样,简单的游览。因此,在scratch.mit.edu,当你通过界面上的按钮创建一个项目时,你首先会看到scratch,这个程序的名字来源于一只生活在这个小矩形世界中的猫,你可以上下左右移动。
但这只猫可以变成任何称为精灵的可视化表示。在左边,现在是所有与scratch一起提供的构建模块,所有的编程构造,你会注意到它们是根据颜色和描述进行分类的,还有一大堆“做”的部分,今天的目标不是深入了解。
这些各式各样的拼图的杂草,但要突出一些基本的想法。这些想法是可能的,我们将在屏幕中间探索这些想法。我们将能够在片刻之后,开始将这些拼图拖放到这个更大的屏幕上,并将它们相互连接,如果这样做是合逻辑的。
因此,最终对于最复杂的程序,我们实际上可以创建更多的角色或精灵,并在屏幕上进行很多交互。但让我们先从一个例子入手,去scratch.mit.edu,你可以在家一起玩。
同样,我将点击创建,以便准确进入那个帐户。从一开始,除非你愿意,否则让我开始创建。第一个程序,曾由Lore编写的,简单地就是Iris所提议的“你好,世界”,这是一个在屏幕上打印“你好,世界”的程序。
那我们怎么做到呢?我可能会很快做到,因为我之前使用过这个界面。但对于你来说,如果你从未做过问题集或编程作业,目标就是让你动手探索,探究,找出你正在寻找的想法。
你最终会发现弹出,而我将尝试的第一个是这个拼图块,颜色叫做“当绿色旗帜被点击时”。这很有趣,因为如果我去Scratch的舞台,这个旗帜停止标志将意味着停止。所以如果我想在点击那个绿色旗帜时发生某事,我就会。
先从这个拼图块开始,我现在将进入“外观”类别,在外观类别中,有一大堆方块,但我们将保持简单,我将说出Iris所提到的经典语句:“你好,世界”。我将缩回去,移动到Scratch这里。
现在我将点击绿色旗帜,瞧,“你好,世界”,这就是我,也许很快也是你的第一个程序。虽然这并不是特别有趣,但对于第一次可能会让人满意,但我们可以使这个程序更具互动性,并开始叠加这些构建块。
一种更像是搜索的算法,就像查找有多个步骤的电话簿。让我先停止这个程序,让我稍微探索一下。这次让我进入感应这个蓝色类别,你会看到这个块,“询问你叫什么名字并等待”,但请注意,“你叫什么名字”是在这个白色区域。
椭圆形意味着如果我愿意,我可以改变问题,但我暂时对这个问题没意见。让我先去掉这些方块,这次从“询问你叫什么名字”开始,并等待。但注意这有点像一个块。这一“询问”拼图块将字面上问正在玩这个游戏的人。
一个问题,它将把那个问题的答案存储在一个变量中。这里描绘的是一个蓝色的椭圆,称为答案,就像数学中的那样。那么,让我再次去看看,去说,hello,但这次你知道吗,让我。继续说,hello,逗号,然后好吧,让我,给自己一秒钟的说模块,但我。
不想再说hello了,所以我,打算删除它,但我要回到传感器部分。我将拖动并放下答案,现在它,靠近时,似乎想要磁性连接。实际上Scratch会自动扩展,填充这个拼图块,所以现在我,有一个程序,似乎。一个用Scratch编写的程序,一个用Scratch编写的软件。
当点击绿色旗帜时,将会询问“你的名字是什么?”并等待。这是我们的函数say hello,表示,回答人类输入的内容。那么,让我去Scratch的世界,点击绿色旗帜。注意猫在问我“你的名字是什么?”我输入大卫。
进入,嗯,我只看到大卫。好吧,也许是我,绿色旗帜,发生了什么,这似乎是一个错误。发生了什么,这似乎是一个错误,因为我很确定我有三个,函数:ask,say和say,但我觉得我错过了,第二个指令,你对我犯的错误有什么想法?呃,娜塔莉,你觉得呢?嗨,嗯,你能听到我吗?你觉得呢?嗨,嗯,你能听到我吗?
是的,是的,我们能听到。那么,您,替换输出为相同的函数。是的,我把输出替换成了相同的,函数,老实说。尽管我们使用的是相当简单的,程序Scratch,我的Mac实际上还挺快,而你的PC或,你的Mac或你的手机也很快。
尽管Scratch在说“你好”,并且在说“回答”,正如娜塔莉所指出的,答案有点。难以表达,因为我没有停顿,所以我可以进去找到一个模块。有一个等待模块可以让我插入一个任意的。暂停,但我真的希望这是一次性完成的,那我该如何做到呢?让我去看看。
在操作下,结果发现这里有一堆与数学相关的,东西,还有一些。下面,连接苹果和香蕉,这与苹果和香蕉没有任何关系,那只是占位符,但这里有这个,合并,你知道吗,让我继续。替换第一个,输入为说,然后让我连接hello,逗号。
然后不是香蕉,但让我拖动答案,注意它会放置。到位,让我把这个其他模块扔掉,删除东西时你可以直接拖。到左边然后放开,现在注意我有一个程序,询问“你的名字是什么?”然后我将说连接hello和答案的结果,让我去试试。
现在在停止旧的那个后。
“你叫什么名字”,我输入“david”,按回车,瞧,正如娜塔莉所指出的,现在它不再自我纠缠,破坏之前的内容,现在我可以一口气说完。程序变得更加有趣,但范式与之前没有不同。事实上,让我提出我们刚刚做的所有事情。
完美地契合这个解决问题的整体心理模型,以及计算机科学本身的含义。例如,如果这是需要解决的问题,我有输入和输出是我的目标,算法在中间,让我们考虑我的第一个程序的模型。
我们刚刚写的实际上是“hello world”,它在自己的椭圆函数中。在 Scratch 中称为 say,因此算法是逐步指令,函数就是算法。在这种情况下,函数称为 say,hello world,但事情刚刚变得更有趣。就在娜塔莉的评论之后,当我引入类似“问你叫什么名字”的内容时。
然后注意这次模型发生了什么,现在问题的输入是“你叫什么名字”,这是输入的字符串,而询问块的目的是让猫给我这样的答案。现在这个答案很有趣,因为它有一个前缀。因此,这个块很有趣,因为注意到输入的白色椭圆。
说块实际上还有另一块拼图,然后在上面还有两块拼图。这里很酷的是,当编程时,一个函数可以成为另一个函数的输入,因此流程非常简单。现在我有两个输入到这个函数,都是我写的“hello”。
并回答来自询问块的问题,现在所讨论的算法是连接函数。
我刚刚使用的它的输出,希望是“hello, david”。但我不想在屏幕上看到白色椭圆显示“hello, david”。所以让我只专注于输出,使其成为最终函数的输入,即那个 say 块,瞧,现在猫说出我想要它说的内容。
即使你开始嵌套,也就是将这些拼图一块块放在一起。我们所做的仅仅是传入输入并获得输出,进行某些操作。那真的就是编程最终的范式,但我们可以让猫做更有趣的事情。为了好玩,让我深入研究一下。
屏幕上,Scratch 有这些所谓的扩展,事物也是如此,让我去右上角的文本到语音。这是使用基于云的服务,即某种基于互联网的服务,它将发送我输入的文字,互联网的某个服务器将会以口头形式响应我刚输入的内容。让我来试试。
继续尝试这个,让我去掉紫色的保存功能。并用这个说话模块替换它,让我拖入这个拼图块。注意,它将扩展以填充,而我这次不再使用这个。我要停止,然后播放。
再一次,输入我的名字,和你好大卫,好的,这不是一个很自然的猫叫声,但注意我们可以设置不同的声音。注意我可以拖动这个拼图块,你甚至可以把模块放在其他模块内部,注意它可以去你想要的任何地方。我将它放在最顶部,尽管我可以放在几个不同的地方。
squeak,听起来合适,让我们尝试一下,输入我的名字大卫。你好大卫,好的,声音仍然不是很好,如果我把它改成小猫,现在你会听到这个。输入我的名字并按下回车,喵喵,好吧,所以在那时我输入的内容并不重要。但现在这太惊人了,我们已经从仅仅说。
你好,世界变成了你好大卫,这正在动态变化,如果你输入名字就会更改。现在多亏了云,即互联网的服务器,我们自动将文本转换为文件,笔记和持续时间,以及我电脑可以播放的内容。好吧,让我们实际上让这个猫叫声更像一只猫,让我继续。
去掉这些模块,让我从声音类别中给自己加一个,怎么样这个播放声音喵,直到完成。现在这是一个简单的程序,当点击绿色标志时播放声音喵,直到完成,来吧,我将点击播放。好了,如果我想再听到猫叫,我得再做一次。
好的,这很好,我可以通过点击播放来娱乐自己一会儿。但我们当然可以做得更好,你可以想象这会很快变得乏味。那么,我如何让猫一遍又一遍地这样做呢?你知道吗,让我喵,喵喵叫三次,所以现在是两次。
好吧,似乎控制没有起作用。让我给它一个小小的暂停,等一秒。好的,稍微快乐一点的猫,但现在这看起来有点乱。确实,它叫了三次,但让我去观众那里。
现在让我们考虑设计,回忆一下,我们的电话簿,第三个算法设计得更好。因为它更快,更有效,但设计还有另一个元素。那就是尽量不要重复自己,所以那些编程的人可能知道,实际上我们在第三行称之为循环。
原来Scratch支持这些称为循环的东西,实际上这里就有一个。如果我放大左侧,注意到控制模块下的这些橙色模块中有一个重复块,尽管默认是10,我敢打赌我们可以更改它。所以让我把它拖到这里,丢掉很多内容。
多余的复制粘贴,让我把这些拼图块移动到重复块里面,它也会扩大以适应它们,没问题。让我把重复改为三,然后重新连接所有东西。现在程序更加紧凑,使用了更少的拼图块或更少的代码行。
更少的步骤,如果你愿意,来达到同样的结果。现在如果我……它仍在工作,所以你可以想象,这仍在工作,所以你可以想象将其更改为你想要的任何数字,甚至有一个无限块,我们可以一直做下去。如果猫要一直这样做,但现在这个程序更好了,现在设计得更好。
因为如果我想改变猫等待的时间,或者我想改变猫喵的总次数,我可以在一个地方而不是在一两个或三个地方改变那些细节,通过复制粘贴那些相同的拼图块。那么关于那个无限循环呢?如果你确实想做一些事情。
我可能想做些什么呢?好吧,让我们让猫开始移动。让我现在进入运动类别,去指向鼠标指针。让我放大这个,每次猫指向这个步骤时,我将抓取移动的一些步骤,并将10改为1,现在我将点击播放,接下来我们……
有点响应我的最大光标,我可以移动它,而且我可以让它,实际上。它指向鼠标光标,然后再移动一步。现在我可以让它移动得更快,让我。不是一次一步,而是一次两步,我们会看到现在猫移动得更快,不算超级快,让我们做20步。
一次调整一步,看看会发生什么,这正是动画的本质。你越是调整步骤的数量,或者时间上发生变化的次数,屏幕上就会呈现出越多的视觉效果。那么我们还能做些什么呢?就只需跟随,嗯,你知道的,猫,跟着我,让我试试别的。
总之,让我继续,打开另一个扩展,进入画笔工具,这将让我现在像用铅笔一样绘画,仍然让猫跟着我。我想,其实知道吗,我们来改变一下。我们就让它去我所在的地方,所以还有一个块说“去到”。
随机位置我不想要那样,所以我要通过这个小三角菜单更改为去鼠标指针。现在猫的指针永远不会滑动或者慢慢地、快速地去,它只会去到光标所在的地方。让我现在去下面这个新的笔类别,我该怎么做呢?你知道的,我对我……
当我上下左右移动光标时,在屏幕上用墨水绘图,但我只想在笔放下时绘制。注意左侧的两个拼图块,笔放下和笔抬起,但这里缺少一条逻辑。让我问问观众。我们怎么能增强这个程序,不仅让猫跟随我的光标。
但我也在屏幕上绘制,尼古拉斯,你会有什么解决方案?呃,所以你可以做的是,呃,采取一个if语句,这样你可以控制,当笔抬起或按下,取决于你有的某个条件。我知道有很多东西,比如你用。
如果鼠标点击,如果鼠标在上,你可以说笔是按下的,也可以是抬起的。然后只要它一直跟随,它就会一直在开或关,我真的不知道,不,你真的知道。这是完美的,因为你采用了有永远块的原则,不仅去鼠标指针,还提议。
通过条件提出问题,所以让我实际去控制下,注意类似我们的电话簿,如果else,嗯,这里只有两个问题。我认为当你提出时,笔是,呃,鼠标按钮是按下还是抬起。所以我认为我们只需一个if else,所以让我去拖动这个。
在鼠标指针下方,注意到这里中间这个小的梯形形状。让我去感知这里,注意到,如果我向下滚动,是的,它就在左侧,注意这个。鼠标按下问号,这些是我们的布尔表达式,让我把那个布尔表达式拖到这里。适配它,然后我想做什么,如果笔按下,或者如果鼠标像这样。
嗯,让我全屏,这样我们可以看得更清楚,点击播放。现在猫跟着我,正如承诺的那样,但现在它是一个绘画的猫。如果我点击鼠标按钮,我可以说像很糟糕的草写,像“你好”,自从我做草写已经很久了,所以我们现在有猫。
实际上绘制一些东西,说实话,它画的是一只猫,有点荒谬。但你知道吗,Scratch有这些服装,我们可以在左上角改变它,即使Scratch可以改成笔,或者标记,或者任何我们想要的东西,精灵实际上只是屏幕上的一个角色。
那可以采取任何我们想要的形式。那么我们如何进一步推进呢?我喜欢引入条件和循环,但这里还有其他原则可以介绍。让我去开始一个全新的程序,看看我们能否开始计数,并开始记录信息。
让我们在点击绿旗时这样做,这次我们去变量下,给自己一个新的变量,这个是一个变量,我将要的。
叫这个计数器,仅仅是一个从一开始的计数器。现在这给了我一些自定义拼图块,叫做计数器,还有默认的我的变量,已经在那里了,我将去做这个。我将计数器最初设置为某个永远,让我抓取一个永远块。
永远,当前计数,所以我不想让它说你好两秒,我想让它说。某些东西一秒,比如说,我要回到变量。我要抓住这个我创建的新圆形计数器。然后把它拖到那里,这样你就可以读取,变成一,然后永远说计数器一秒,但。
如果我们不想让猫重复说同一个数字,那么。这将隐式地使数字加一,变为二,变为三,理想情况下一直数到无限,实际上使用一个变量,一个变量,信息。在这种情况下,一个数字不断被重绘,一遍又一遍,现在让我继续。
并且开始打开我提前写的几个程序,这样我们就可以参观其中一些。我有一个叫做反弹的程序,这也是编程的一部分,不仅仅是写自己的代码,还有阅读自己的代码。让我放大一下这个,我已经创建好的。
而考虑一下它所说的,首先设置旋转样式左右,这只是为了修复一个本来会是错误的地方,猫意外地变成了倒立。但让我让我的手挥动。让猫移动10步,如果它碰到边缘,那么转180度,所以现在我们可以重新引入。
动画并不是由我这个人用光标驱动的。我现在可以制作一个游戏,嗯,一个互动的艺术作品或任何东西,现在猫是自我驱动的,因为当我点击播放时,注意它在前后移动,边缘,而那个布尔问题的答案实际上是。
嗯,是的或真的或一,然后它将转180度,但这看起来有点傻,诚然。你知道,猫是从屏幕反弹的,这也许有点不现实,但它并没有真正走路,它是在滑动,但这就是动画的特点。正如我们之前提到的,视频在一天结束时,屏幕。
你知道,我敢打赌我们可以创建我们的真实视频,不仅仅是一个穿着服装的猫,而是猫的脚是这样的,如果我们给自己一个第二个。
服装几乎是一样的,但它的脚稍微不同,翻页书。我们之前看过的,你知道,我敢打赌如果我在这两个服装之间切换。不断改变猫的状态,我敢打赌我们可以创造出。真正运动的错觉,这就是我们在这个其他反弹示例中所拥有的。
在这个其他反弹示例中,我们有猫现在不仅是前后移动,还注意这个紫色拼图块,在它反弹到边缘后,或者考虑反弹到边缘时。它不断更换服装,变成下一个,再到下一个,所以现在。它不是完全完美,像我们所称的非常低的帧率,这就是。
就像在网上观看一个非常糟糕的动画GIF,它只有两个不同的帧,但看起来更像是他在走,而不是在滑来滑去。而且这个也,Scratch支持听起来不错,比如说,这里是我们之前听到的喵声,我可以录制自己的声音,如果我点击。
这里的小加号图标,点击录制并允许Scratch访问我的麦克风。点击确认几次,好吧,让我录制我自己的声音。哎呀,这就是“哎呀”这个词。
我可以修剪掉开头,让我保存,命名为“哎呀”,现在让我回到我的代码。在声音块下,你知道吗,让我先说。如果我触碰到边缘,我不仅想转180度。现在我可以让这个变得更有趣,哎呀,哎呀,哎呀。
好吧,还是不太像猫,但我们只是在不断叠加。这里的关键点是,随着这些程序变得越来越复杂,目标绝不应是Scratch或C,或者最终的Python,在这门课或其他课程中,仅仅是开始并尝试实现你的全部构想。注意这些程序中的每一个。
我打算从小处着手,添加一两块或三块拼图。从简单的东西逐步构建到更复杂的东西,你知道吗,我敢打赌如果我们合成东西。这里还有另一个例子,可能涉及到抚摸猫,让我继续看看这个程序,这个相对简单,但目前还没有做任何事情。
我已经点击了绿色旗帜,让我放大代码,也许你现在可以阅读我提前写的代码。猫总是在问一个问题,如果触碰到鼠标指针,则播放声音喵,直到完成。好吧,似乎即使程序在运行,它也没有做任何事情,但它在等待某件事情发生,所以让我把光标移到猫上。
所以似乎是这样,如果我保持它开启,它就会不停地喵喵叫,这很像一只猫。你可以想象在循环中拥有条件,正是你想让某样东西做的事,更强大的是,即使在像Scratch这样的语言中,我们能做到这一点吗?让我打开这个海狮。
有一个非常独特的吠叫,但他现在展示的是一个有多个脚本的程序。因此,在这个Scratch项目中,我们不仅仅是一个程序,而是两个。注意它们都以点击绿色旗帜开始,然后你把它们放到屏幕上,看起来更长,但这只是互相适配。
注意每秒钟左右海狮在吠叫,坦率地说,这很快就会让人烦躁,但我该如何停止呢?让我去左边看看,当它还在吠叫时,注意海狮总是在问一个问题,如果静音等于假,那么开始声音C。
说“嗨嗨嗨”两秒钟,所以什么是“静音”?它的形状代表了一个变量,比如x、y或z,这只是保留信息的一种方式。这就像说“静音变量”的值是“假”。如果不静音,继续播放海狮的声音,但我的天。
在右侧这里注意到,还有另一个程序,当点击绿色旗帜时,条被按下。如果“静音”是真的,则将“静音”设置为假,否则将“静音”设置为真,所以程序在“静音”与“非静音”之间切换。我的天,这个程序仍在运行。
非常花哨,很快让我继续创建另一个程序。我会创建一个只有两个块的程序,让我再次进入扩展。视频传感,这次注意启动程序的不同类型。并不是每个程序都必须在点击绿色旗帜时启动,这里有一个类似的形状,但这是。
绿色的那个说,当视频运动时,屏幕正在移动,让我将其提高到50%,让我继续进行这个拼图,两个,50,出去,你会注意到我实际上在屏幕上,让我移出舞台,现在什么也没发生,尽管让我去抚摸一下猫。[音乐],让我再做一次。
检测运动然后执行,这些简单的构建块可以让我们获得越来越多有趣的事情,你知道吗,我们甚至可以有多个精灵。让我打开一个你可能在游泳池里玩过的老式游戏,一个人喊出“Marco”,其他人则。
假设要喊出“Polo”,注意这里有一个包含两个精灵的程序,所以有两个木偶,一个橙色木偶和一个蓝色木偶,而且在底部这是第一次,我们有两个不同的精灵可以编写程序,现在选中了橙色木偶,这意味着左上方的程序。
这里属于橙色木偶,永远地说,如果按下键盘的空格键,则说“Marco”两秒钟,然后这是新的功能,在编程中有一种方法让一个程序与另一个程序对话,或者在这种情况下,让一个精灵与另一个精灵对话,传递你在屏幕上看不到的秘密消息。
但一个程序可以从另一个程序那里听到,这称为广播事件。这就是橙色木偶的图标,他不会做很多事情,但在绿色旗帜被点击时,他不会做任何事情,当摄像头看到运动时,他将会在收到事件时。
说“polo”两秒钟,所以在这种情况下,如果我现在按下播放,什么也不会发生,但当我按下空格键时,橙色说“marco”,蓝色说“polo”,但它们是独立写的,我为蓝色编程,它们以某种方式互相通信。说到通信,现在有更多的事情可以做,多亏了互联网和。
进入云端,让我快速打开另一个新画布。给自己设定一个“当绿色旗帜被点击时”,让我再问一遍同样的问题。但现在让我进入这些扩展,找到翻译扩展。它将使用云端,将我输入的内容发送到互联网。
获取响应,然后在这里的屏幕上显示,所以屏幕。像是说“hello”,但我不想说类别,我想翻译你知道我喜欢这个块将某些内容翻译成另一种语言。但让我再拿一个“join”块,继续连接。
这个“hello”这个词,以及人输入的名字,所以为了得到这一点,我再次需要“answer”块,之前我说过。将“hello”和“answer”的连接结果,虽然上次是带有逗号的,但现在让我们这样做,让我将“join”的输出作为“translate”的输入,让我。
在这里将“say”翻译成阿拉伯语,让我将其拖放到“say”块中,所以现在我们有两个输入进入“join”,而“join”的输出进入“translate”的输入,翻译的输出进入“say”,但最终结果是我会输入我的名字David并按下回车,您好David,现在用阿拉伯语表示,都是得益于。
这些功能条件、循环的原则,现在甚至还有添加。在互联网的背景下,但让我们考虑一下游戏,总之,有一种方法可以实现,事实上我们已经做到。让我回到我们上次谈到的喵喵声,在我们的一些喵喵示例中,代码看起来像这样,我重复了三次,请再次回想。
一次又一次,我当时争辩说,这样的设计更好,为什么呢?因为我不是一遍又一遍地拖放同样的拼图块。我使用了一个重复块,去掉了所有的冗余,或许我保持了简单,我在使用一些更复杂的想法,但代码更简单,现在拼图块也更少。
但事实证明这里有一个错过的机会。
机会,可以应用计算机科学的另一个原则,这通常是我们所描述的。
抽象,这是一种很棒的问题解决技术,实际上只是说,让我们把一个非常复杂的想法,或者它可以以复杂的方式实现,但让我们现在简单地说明。让我们现在简单地说明,我们要把它考虑到Scratch中。
有趣的是,我并没有预见到会有一个“喵”块,就像有一个“说”块,还有一个“想”块,但没有“喵”块,这对一个内置有帽子的程序似乎很合适,因此我们可以这样做,就像你可以创建自己的变量一样,注意左下角,你可以用这个粉色类别创建自己的块。
如果我去这里,我要创建一个“喵”,我会很简单地点击确认。现在注意我得到了这个新的拼图块,上面写着“定义喵”,它准备好与其他块连接了,我该如何定义“喵”,我只是把这个拖过来。
右侧因为我刚刚创建了这个自定义块或拼图块。我现在有一个叫“喵”的粉色块,就好像它是Scratch自带的。现在吸引人的是,我可以把这个看作是“看不见、想不见”。谁在乎“喵”是如何实现的,我们只需规定,我们可以理所当然地认为它存在。
如果我现在放大这个新程序,现在从某种意义上说更易读了,稍短一些,有更少的拼图块,但也更具自描述性,我可以阅读我的代码,看着这段代码说,好吧,很明显,它会重复三次一个“喵”块,但让我们播放一下,我打赌我们可以再简化一步。
但我打赌我们可以简化这一点,灵活点,让我右键点击或者控制点击我们称之为“n”的“喵”自定义块,添加一个标签,内容是。
我来点击确认,注意到我的拼图块现在看起来不同了,更像是麻省理工学院的一些输入块,带有这些小白椭圆,事实上现在注意我能做什么,我可以改变“喵”的定义,正如Scratch已经为我做的那样,现在我可以在里面做更多的事情。
让我先断开所有这些,移动“重复”块到“喵”的定义本身,让我来播放声音并等待在那个重复块里面,但注意这个小圆圈在末尾,让我只是重复任意次数,现在我不需要担心。
硬编码3、10或其他任何东西,现在不再需要担心了,让我们把之前写的缩减成仅仅两个拼图块,当绿色旗帜被点击时“喵”确定三次,我不需要再知道或关心“喵”是如何实现的。
我只需要知道有人为我做了这件事,无论是麻省理工学院,重玩,[音乐],二和三。因此,现在我们有了一个抽象实现,将一个相对复杂的想法,比如让猫叫,不用担心所谓的实现细节,仅仅定义一个拼图块或函数。
叫做“喵”,现在让我们把所有这些结合起来,看看一些你们前辈的创作,算是一种故事,几年前你们的同学制作的。涉及到一个姜饼故事,让我去玩,你现在会看到我们已经有多个精灵,每个都有不同的服装,而我被问到一个问题,你会。
像是一个苹果,yes 或 no,所以我不再被问我的名字,而是被问,我是否想要一个苹果。我输入 yes,然后按下回车,注意,这,嗯,不幸的是,那是错误的决定。好的,不幸的是,那是错误的决定,所以。没关系,我们重新开始,红色,停止标志,绿色旗帜,您好,亲爱的,您想要一个吗?
苹果不,我们从这个教训中学习,这次,再次注意动作,所以有一些动画,碰到了另一个精灵,这也很不幸。让我们最后再试一次这个艺术作品,现在我们有了一个苹果,学到了教训,杯子蛋糕学到了教训,好的,现在让我们看看会发生什么。
好吧,惊喜结局,但这一切都是为了,好的,惊喜结局,但这一切都是为了。说通过使用这些循环、条件和函数的构建块,你能开始制作一些更互动的东西。事实上,几年前我自己也做过一些,最开始我自己写的是。
Scratch 实际上是我在研究生院时,交叉注册了一门 MIT 的课程。教授是 Scratch 的作者和创始人,他提出了我如何思考解决现在一个相当大的问题的方法。回到那个时候,把尽可能多掉落的垃圾拖入垃圾桶。
现在发生了什么,一块垃圾正在屏幕上掉落。你会看到它从上面移动,就像这样,但看这个,我敢打赌,使用一个条件和一个永远循环,我们可以做到这一点,让它可以被拾起,注意现在垃圾跟随我的光标,就像猫之前那样,并注意,如果碰到这个其他垃圾桶精灵。
也许我们甚至可以让奥斯卡从桶里跳出来,他开始计算我的得分,从而使用一个变量,确实,随着更多精灵或更多垃圾掉落,我可以继续以这种方式玩游戏,但在这里,即使事情开始更快速地发生,屏幕上也有更多内容,背景中正在播放音乐。
这一切都简化为基本构建块,写下了你刚刚看到的。其实我做的第一件事是,我在谷歌上查找,找到了“芝麻街”的路灯,我把它放在屏幕上,那算是版本一,它什么也不做,但看起来像我想要的样子。
然后我添加了垃圾桶,然后我想,我编程了一块垃圾或一个。精灵掉落,所以我把猫换成了一块垃圾,然后让它从上到下动画。然后在第四或第五个版本中,我添加了一个永远循环和一个条件。检查鼠标按钮是否按下,如果是的话,我让它跟随鼠标指针,所以我拿了一个大。
问题被逐步拆解成更小的步骤,这也是 CS50 的安德鲁·贝瑞多年前采取的相同方法,他是我们的一位教学助理,我想给大家留个印象的是他的第一个 Scratch 项目,这是一个他称之为。tune 的程序,观看时,今天我们的最后一个 Scratch 程序,安德鲁是如何做到的。
现在你看到的编程,安德鲁进入了现实世界,并没有追求计算机科学,他现在实际上是克里夫兰布朗队的总经理,这是一个美国足球队,这也说明了你可以形成什么样的基础,无论你的。
预期专业是什么,考虑到毕竟我们在这门课程中要关注的很多想法最终都是关于。问题解决,编程只是这个行业的一个工具,实际上,即使在当今世界,对于算法、分析、视频模拟等都有很多。
安德鲁的世界和你的世界,必然会在你开始构建自己的工具箱和理解时碰撞,因此。总结一下,我们来看看安德鲁的项目,期间这是 CS50,现在,[音乐]。[音乐],嗨嗨,我们是你的天气女孩,嗨嗨,我们是你的天气女孩,听着。
为你的孤独女孩准备好,把那些雨伞留在家里,好吧,我们得赶紧。我们得赶紧。
[掌声]。
哈佛CS50-CS | 计算机科学导论(2020·完整版) - P10:L5- 数据结构 1(数组、链表、树、哈希表、字典树、堆、栈、队列) - ShowMeAI - BV1Hh411W7Up
一切都好。
这是CS50,这是第五周,回想一下上周在第四周我们介绍了一些新的构建模块,即指针,并详细讲述了如何现在操作计算机的内存,并开始以更低的层次进行处理,今天我们将使用这些基本构建模块来开始。
创建称为数据结构的东西,在计算机内存中,结果是,一旦你拥有了引用计算机中不同位置的能力,你可以创建自己的自定义形状,称为自定义数据结构,确实我们将通过回顾一下第一次看到数据结构的地方开始。
在第二周,所以我们称之为第二周,那是我们玩C语言的第二周。我们向你介绍了数组的概念,数组只是内存的一个连续序列,你可以在其中存储一堆整数,逐个存储,或者可能是一个后面接着另一个,这些数组可能已经。
如此图示表示,所以这将是一个大小为3的数组。
它,开始遇到问题,但也解决了问题,今天假设你想向这个数组添加另一个数字,但你只考虑创建一个大小为三的数组,数组在C语言中的麻烦是,它们并不容易调整大小,你们都知道,你必须提前决定数组的大小。
所以如果你后来改变主意,或者你的程序运行得够久,你就会发现自己陷入困境,比如,第四个,理想情况下你会将其放入这个数组,然后继续你的事情,但数组的麻烦在于,那块内存并不是独立存在的,回想一下如果我们。
放大一点,看看你计算机的所有内存。
这一个字节以及其他许多字节可能很可能被其他变量或你讨论的其他方面使用,假设相关程序有一个大小为三的数组,包含整数一二三,然后假设你同一个程序,在代码的某处有一个字符串,你已经,世界。
偶然间,可能是一个h-e-l-l-o,逗号 空格 w-o-r-l-d 反斜杠零。并且可能有“自由内存”,可以使用。那些被垃圾值填充的内存,垃圾并不是坏事,它只是意味着你,值是。或者曾经在那里,因此有“自由空间”,每一个oscar都代表。
有效的自由空间,带着一些垃圾值的残余,可能是某些。过去执行的遗留问题,但这里的问题是,你可能想放置它的位置。那么如果我们有一个大小为三的数组,包含三个整数一、二、三,但它有点被逼到墙角,H-E-L-L。
等等,已经立刻在那里,没有牺牲h,这确实不觉得像是解决方案,问题是我们是不是完全没希望?在这种情况下,你能否向数组添加数字,或者有没有什么解决方案即使你从未编程过,屏幕上有那种布局。
我会说,嗯,也许你可以创建一个新数组,但大小大一个或多一个元素,然后添加那个新元素。是的,这真的是个不错的直觉,毕竟屏幕上有所有这些垃圾值。
空间,我可以把一、二、三、四放在这里,或者放在这里。所以我们有一些灵活性,但圣地亚哥说得对。直观上,我们只需要专注于新数组中的四个可用位置。这个大小为四的数组最初有这四个垃圾值,但那是。
好的,因为圣地亚哥也提到我们将一、二、三放入新数组中。也许我们现在甚至可以释放原始数组的内存。就像如果我们使用malloc
一样,这当然会留给我们一个大小为四的数组,带有第四个垃圾值,但现在。
我们确实有空间放置数字四,所以看起来这个问题是有解决方案的,不违反数组的定义。数组的唯一定义实际上是内存必须是连续的。你不能随便在计算机的内存中放置四。
成为现有内存之后,如果整个结构确实还是数组,但我担心这可能会花费我们一些时间,实际上让我,继续打开屏幕上的一个问题,欢迎你们参与,插入的运行时间会是什么。
现在揭示投票问题,随时可以去常用的URL,布莱恩,如果你不介意按习惯复制粘贴,你认为插入的运行时间是多少。
插入数组,回想过去我们讨论过数组时的时间,我们实际上在谈论插入它们。如果我们取一个,像80%的人觉得是线性时间O(n),插入到数组中可能需要多达n步,5%的人提议是n log n,7%的人则。
n 的平方,然后是两和五个百分点,这是一种有趣的混合,过去。我们谈到了搜索回忆,通常我们能达到,呃,O(log n),这真的很好。不幸的是,如果我们必须执行圣地亚哥所提议的,实际上把所有元素复制。 从旧数组到新数组,这确实会花费我们多达 n 步。
因为我们必须将每个原始元素一二三。复制到新的数组中,新的数组大小为 n,加一,因此总的步骤是 n 步。所以插入到数组的运行时间,至少在上限方面,将确实是 O(n),因为你必须潜在地复制,所有那些元素。
但如果我们考虑插入的运行时间下限,我们可能会有不同的看法。数组的插入下限可能是什么?在这里我们可以使用 Ω 符号,最好的情况可能需要多少步。插入一个值到数组中,我们不会对此进行投票。
布莱恩,为什么我们不去做一下电话呢。
有人能说说插入运行时间的下限吗,瑞安,你觉得呢?好的,最好的情况是数组中只有一个元素,因此你只需。放入数组,没错,所以如果你有一个数组,让我强调一下,它已经。空着,可以容纳新的元素,那么确实,Ω(1) 常数时间就是你所需的。
在数组中,无论数组多大,也许它的大小是四,但你只放入了一个数字。
这没关系,因为你可以立即将新数字放入位置。
请记住,数组支持随机访问,可以直接跳到任何位置,所谓的常数。时间只需一步,如果不满,那么是的,插入到数组中的下限将是,常数时间 Ω(1),但正如我们在圣地亚哥的情况中看到的那样,你会有一个已经填满的数组。
元素,如果你想再添加一个,在这种情况下,上限确实是 O(n),因为你必须从一个转移到另一个。现在你们中的一些,Java。可能对“向量”这个短语很熟悉,向量有点像可以调整大小的数组,可以增大或缩小,这在 C 中并不是数组的性质。
在 C 中,数组只是连续的内存块,值一个接一个。 但是一旦你决定了它们的大小,那就是它了,你必须自己调整大小。它们不会自动为你增长,因此数组是。我们看到的第一个,也是最简单的数据结构。
但这并没有我们现在可以做的强大,因为我们可以访问计算机的内存。今天我们将开始利用这些称为指针的东西,即我们可以引用内存位置的地址,我们将开始拼凑一些更复杂的数据结构,首先是单维的。
在某种意义上,这是二维的,在某种意义上,通过使用一些非常基本的构建块,回想一下这三种语法。
从过去几周的结构回忆来看,这个机制是C语言中的这个关键字,我们可以在内存中定义自己的结构。我们看到一个包含姓名和电话号码的例子,就像电话簿一样。你已经见过点运算符,点运算符是我们如何进入这样的结构并获取点名称或点数字,即结构内部的特定变量。
然后在上周的回忆中,我们看到了星号运算符,这是解引用运算符,口语上意思是去这个特定地址。因此,仅仅使用这三种成分,我们现在能够构建自己的自定义数据数组。这最终能帮助我们更有效地解决问题,实际上解决了一些数组的问题,而这些问题可以说是什么呢?
这在C语言编程中是如此常见的技巧,星号的使用,今天几乎是构建块的过去。但我们将以新的方式使用这些构建块,以开始不同地解决问题,首先通过一种称为列表的数据结构。
好吧,如果向数组中插入需要大O的N步,坦率地说,这有点烦人。这种开销相当大,因为随着时间的推移,如果有大量数据,像谷歌或推特这样的公司,如果你的数组是为了高效搜索而设计,那是没问题的。
回想一下,如果我们使用类似二分搜索的方式并保持一切排序,则是大O的对数N。但如果每次你想在数组中添加另一个推文或其他网页时,这将是非常痛苦的,根据你解决的问题,你可能需要如Santiago所提到的那样进行复制。
列表将把你原始小数组的所有内容放入一个新的更大的数组中,只是为了添加更多的推文或网页等。因此,链表将是一个更动态的数据结构,你可以在不触及所有原始数据的情况下,增减数据结构,而不必从旧位置移动到新位置。
那么这可能看起来像什么呢?好吧,让我们再考虑一下计算机的内存,假设我想再次存储那些相同的值。比如数字1,为了讨论方便,假设它在我计算机的内存中,地址为0x123,0x表示这是一个十六进制数字。
一二三是完全任意的,我的讨论,因此让我规定这就是数字一在计算机内存中的位置,在这个新解决方案中存储大量数据,假设我想存储数字二,也许它在地址ox456。那么假设我想存储数字三,假设它在地址ox789,所以故意注意。
这些数字分散在计算机的内存中,因为毕竟数组。你可能有hello world或程序,这有点碍事,因此如果我提议,然后二,然后三,那很好,随便放在你想要的地方,你不必担心已经存在的值在哪里,相反,你可以把这些值放在有空余的地方。
问题是如果你只是开始随便放值,比如一二三,内存值。对,你可能知道一在哪里,但仅仅向右看一位置已不足以找到下一个值或加二找到下一个值,在数组中,一切都是连续的。
但如果我们开始将计算机的内存视为一个画布,我们想要。只要我们能够以某种方式第二到第三,无论其他杂乱的东西如何,这都是可以的。因此,事实上让我建议我们通过可能从计算机中偷取更多空间来做到这一点,而不是仅使用足够的内存来。
存储一二和三,让我存储两倍的信息,除了每个数字的数据,让我存储一点元数据,可以说是我不根本关心但将帮助我跟踪实际数据的值。让我建议在这个框中,我实际存储这个值。
ox456再次以十六进制书写,但这只是数字,它是内存中其他地方的地址,在这个框中让我提出我存储ox789。在这个框中让我随意说ox0,我这样做,即使你从未见过这种结构,这正在演变成所谓的链表,为什么我刚刚做的就是这样。
除了分别存储一二和三外,我现在还在额外的内存块中存储ox456和ox789,但为什么。这样我们就知道第一个元素是如何与第二个元素相关的,或者它们是如何连接在一起的,确切地说在第一个和第二个之间,所以现在我实际上使用了两倍的空间来存储。
分别存储指向下一个元素的指针,现在我会将其视为一个列表。这是ox456,因为数字二在ox456上,我关心的是ox789,所以这只是一个有用的方式,现在我可以留给自己线索,以便我可以随意放置一、二、三。
想要在计算机内存中,随处都有可用空间,并且仍然想办法从一个到另一个,然后再到另一个,我们实际上之前见过一些这样的语法,位,这只是技术术语,称为 null(空),我们上周介绍过,这是一个特殊符号。
表示某些地方出现了问题。
与内存有关,或者你没有空间,这种情况类似于地址缺失的情况,保证如果你使用的是任何有用的地址,但你知道,像上周一样,这种情况有点复杂,我并不太关心 0x,*****。所以让我们先抽象掉这一点,开始真正思考这个问题。
一组数字以某种方式。
在底层,链接是通过地址或指针实现的。这些低级数字,如 0x123456789,但从图示上看,我们只需开始将链接列表视为节点的集合。
可以说,节点通过指针连接,因此节点只是一个通用的计算机科学术语,指代某种东西,我在这里关心的是一个数字,这就是一个链接列表,每个矩形代表一个节点,最终,结构。但让我在此暂停,看看是否有任何问题,关于我们所构建的结构。
关于这个叫做链接列表的东西之前有没有问题,我们在一些代码中看到它,布莱恩,是的,聊天中有个问题问,难道这不是一种浪费内存吗?对,确实是个很好的观察,这不就是在浪费内存吗,我们存储了所有这些地址。
除了我们关心的数字一、二、三,没错,实际上这正是我们付出的代价,这将是本周、上周以及之后每周的主题,每当我们在计算机科学和编程中解决问题时,总会有某种代价权衡,因此如果刚才是不可接受的。
在数组中插入的时间复杂度是 O(n),因为,哇,这将需要很多步骤,将数组插入到新数组中,如果这是原因,或者,任何你正在处理的问题。
解决得很好,那就不错,你可以解决那个问题,现在你的数字可以在内存中的任何地方,无需将现有数字移动到其他地方,从而节省了时间,但你付出的代价确实是更多的空间,因此在这一点上,这取决于什么对你更重要。
计算机的时间、你的人类时间,或者,也许是空间或空间的成本。你可能真的需要为那台计算机购买更多的内存,所以这将会是。时间在编程中无处不在,嗯,让我们考虑一下如何。回想一下我们上次在C语言中看到结构体时,我们做了这样的事情来定义一个人。
有两个与之关联的事物,一个名字和一个数字,所以。今天我们不关心人、名字和数字,我们关心的是这些,节点。所以让我去d并倒带,抹去那个,让我们说每个节点在这个结构中重命名,person也作为节点,一个整数。
在我们这里,我留出了一个其他值的空间,因为我们最终需要能够。存储第二个数据,第二个数据将是一个指针,它将。表达得很明显,但我们上周奠定了指针的基础。我该如何描述这个结构有指向,另一个这样的结构的指针,有什么想法。
口头上使用的语法,或者即使你不确定确切的,咒语到底。我们应该使用什么符号来表示地址,指向另一个这样的节点,布莱恩。呃,有人建议我们使用节点指针作为节点,节点指针,好的,我我确实记得上周的,表示如果你有int指针。
这是一个整数的地址,如果你是字符,所以如果所有这些箭头实际上只是。代表节点的地址,那语法大概会类似于节点指针,现在我可以把这个指针。称作任何我想要的名字,按照惯例我称之为,life,结构,这将会是额外的。
被称为number的整数,我提议描述那个单独数据结构的顶部,但。这里有一个微妙的问题,在C语言中,记住C是一种相对简单的语言。尽管它常常看起来复杂,但它不理解任何。它之前没有见过的东西,所以现在注意到第一次。
我提到的节点直到现在是,代码,问题在于typedef的性质。实际上在编译器读取那最后一行代码和分号之前,节点并不存在。换句话说,在这个结构内使用或引用所谓的节点是错误的,因为,节点在执行之前并不存在。
值得庆幸的是,有一个解决方法。在C中你可以实际添加一个,额外的词。紧跟在关键字结构后面,我们保持简单,使用必要的。现在我要改变结构内部来表达这一点。所以感觉有点冗长。
这感觉像是有点复制粘贴,但这是提示中的做法。与我们为函数谈论的原型精神相似,这给编译器提供了一个线索,表明好吧,将会存在一个叫做结构节点的东西。然后你可以在那个数据结构内部使用它,并称之为结构节点指针,这更像是一个。
多个单词像这样,但它类似于字符、星号或实例。就像上周一样,我将任意调用,接下来在这里也会发生相同的事情。与过去的情况一样,通过在最后调用这个节点,编译器。你知道的,你不需要到处称它为结构节点。
可以把这个东西叫做节点,所以在这种情况下,它有点冗长,但所有这些只是为我在计算机中创建节点的定义,正如我们用那个矩形描绘的那样。那么我们现在如何将这转化为更有用的代码,而不仅仅是,我们如何开始构建链表呢,让我来。
提出一个链表实际上只需一个指针,事实上,感谢剧院的道具商店,我们这里有一个空指针。如果你愿意,我将这个变量称为list,而list当前指向没有任何东西,这个箭头只是指向地面,这意味着它是空的,实际上并没有指向任何有用的东西。
假设我现在想开始分配一个链表,其中包含三个数字一、二和三。
我该如何做到这一点呢?目前在叫做list的地方,唯一存在的东西。故事中没有数组,最后在第二周,今天全是关于链表的。那么我如何获得一个代表一的木块,一个代表二的木块,以及一个代表三的木块呢?
我们需要使用上周的新朋友malloc,回想一下malloc。允许你分配内存,尽可能多的内存,只要你告诉它那个东西的大小,所以坦率地说,我认为我们今天最终可以使用malloc。
但现在我开始实例化,也就是创建这个列表。动态分配一个结构体,把数字一放进去,另一个结构体把数字二放上去,再一个结构体把数字三放在这里。实际上将它们串联在一起,让一个指向另一个,所幸的是道具商店为我们创建了一大堆这些,让我去malloc一个非常重的节点。
它有两个值的空间,你会看到它有一个数字和一个下一个指针。所以我要在这里首先安装的数字,将是数字一,比如说,我将留下一个空位,表示这是一个空指针。
现在我要做一些类似的事情,说好吧,我的变量叫做list,生活的目的在于跟踪这个列表在内存中的位置。我将通过让这个变量指向这个节点来连接一个到另一个。当时要分配另一个节点时,我想插入到这个。
在数组的世界里,内存,复制这个值到新的值中。我不必这样做,在链表的世界里,我只需第二次调用malloc。并说给我另一块足够容纳一个节点的内存。感谢从道具商店里获得的,里面什么也没有,只有占位符,所以它是垃圾。
直到我实际说数字将是数字二,然后我转到我的链表,其变量名为list,我想插入这个东西,所以我跟随箭头。我然后将这个节点的下一个字段指向这个第三个节点,因此现在我有一个大小为二的链表。图片中有三个东西,但这只是一个简单的变量,这只是一个指针。
这指向实际的节点,而该节点又指向另一个实际的节点。现在假设我想把数字三插入这个链表,记住malloc的强大之处在于它可以从计算机中可用的地方分配内存,所谓的堆,这意味着从定义上讲,它可能不是连续的。
下一个数字实际上可能不是内存,它可能在那边,所以这可能是malloc现在。并分配一个第三个节点,它可能在计算机的内存中无处可用,除了像是在这里,这样没问题,它不必像数字三那样在它的位置是连续的,但如果我想保持一个指向那个节点的指针,以便所有这些。
事情被串联在一起,我再次从头开始,我跟随箭头,我跟随箭头。其中之一是,我必须连接这些东西,因此,现在这个指针需要指向。这里的这个块,这个视觉效果是非常有意图的,这些节点可以在计算机的内存中随处可见,它们不一定是连续的。
这样做的缺点是你不能依赖二分搜索,我们的朋友从第0周开始,它的时间复杂度是O(log n),你可以比O(n)快得多地找到东西,这就是在第一周中电话簿示例的整个要点,但这种方法的优点是你不必实际不断分配和复制。
更多的内存和所有的值,任何时候你想要调整这个大小,我有点尴尬地承认,不知道为什么我气喘吁吁,只是在这里malloc节点,但重点是像使用malloc和。
一些价格,嗯,这确实令人疲惫,但这也会在。内存中分散东西,但你有这种动态性,老实说,如果你是世界上的推特,谷歌,我们有,很多很多的数据,需要复制。你所有的数据从一个位置到另一个,santiago最初提出。
作为数组的解决方案,使用这些动态,数据结构,如链表,在哪里可用。并且你以某种方式记住它,通过将东西结合在一起,就像这些物理指针。这真的是技术的前沿,你可以创造这些更动态的。结构,如果这对你来说更重要,问题,在我们现在翻译这些物理,块之前。
有什么问题或困惑,brian,像往常一样,如果大家在聊天中更舒服,随时可以说出来。聊天中有一个问题,在整个链表的过程中,我们实际上使用了malloc,malloc的用途是什么。真的很好问题,那么我们在哪里。
每次我离开舞台并拿到其中一个大块时,使用malloc。
那是我模拟分配一个节点的过程,所以当你调用。malloc时,它返回给你上周提到的第一字节的地址。一个内存块,如果你调用,malloc一个字节,它会给你一个字节的地址,如果你调用malloc 100,百字节,它们是连续的,所以每一个。
节点然后代表,调用,malloc,实际上也许brian,回答这个问题的最佳方式是将其翻译。现在将其翻译为一些实际代码,我们来做吧,然后再回头看。所以这里是,例如,一行代码,代表我们开始的。
故事中我们只有一个变量,叫做list,它最初被初始化为。什么都没有,这个箭头并没有,指向任何东西,实际上如果它只是指向上。下左或右,它会被认为是一个垃圾值,对吧?这就是它内部的情况。在我实际放入实际值之前,如果我不赋值给它,谁。
知道它指向什么,但让我去啊d并更改代码。这个列表变量默认有一些,类似于,null,所以我在这里将其表示。比喻地说,仅仅指向地面,现在表示null。这将是相应的代码行,仅为你创建。
一个空的链表是故事的开端,现在回想一下,我接下来的事情是。拿回这些大盒子中的一个,这段代码可能看起来像这个节点星。尽管我可以将变量称为任何我想要的,malloc,节点的大小,所以我们可能会看到它只是一个,字节,任何数据类型都是如此,我可以做到。
数学并弄清楚在我的Mac或PC或CS50 IDE中,这些节点应该占用多少字节,sizeof刚好回答了这个问题。因此,malloc再次接受一个参数,即你想动态分配的字节数,它返回这些字节第一个的地址。可以将其视为我之前黄色幻灯片中的一个。
它返回的像是这块内存左上角字节的地址,我要将其赋值给一个变量,称为n,以表示一个节点,它的节点星号地址,以及上周看到的星号语法。所以这给我一个块。
最初只是垃圾值,所以没有数字在位,谁知道箭头指向哪里。如果我现在在屏幕上画出来,它看起来有点像这样。列表初始化为null,它现在不指向任何东西。但我刚刚声明的变量n指向一个节点,但在那个节点内部,谁知道。
垃圾值的数字和下一个只是垃圾值,因为那是默认情况下的残余。但现在,让我建议我们这样做代码,只要n不为null,这是你要养成的习惯。每当在C中调用返回指针的函数时,你几乎总是应该检查。
是null还是不null,因为如果它确实是null,你不想去触碰它,因为这意味着这里没有有效的地址。这是使用指针时的惯例,但如果n不等于null,那是件好事,这意味着它是计算机内存中的有效地址。
让我继续,转到那个地址,语法是星号n,就像上周一样。然后点运算符意味着进入结构内部,并进入其中的变量,在这种情况下称为number。因此,当我继续并做这样的事情时。
变量此时指向唯一的节点,并存储数字1。当我继续时,它将包含数字1,一旦我执行这行代码,星号n意味着从这里的地址开始,进入,然后在这种情况下把数字1放进去,我想,嗯,我想继续并替换。
垃圾值表示该结构中的下一个指针,并用null替换它。null只是意味着这是列表的结束,这里没有任何东西。我不想要垃圾值,因为垃圾值是一个任意的数字,可能指向这个方向、那个方向,象征性地说,我想真正地改变它。
这要设为null,因此我可以使用相同的语法,但这里有一个聪明的做法,我不需要像之前提到的那样使用星号和点,因为它们带有一些语法糖。你可以用箭头表示法来替换我们刚才看到的星号、括号和点,这意味着跟随箭头,然后将这个设置为null。
将箭头字面上指向地面以保持清晰,星号,括号。与这完全相同,大多数人之所以更喜欢使用箭头表示法,是因为它确实捕捉到了这种物理性,你从地址开始,你到达那里,然后查看字段数字或下一个,但它是等价的。
与我们刚才看到的星号和点的语法一样,所以在这两个步骤之后,我们是否已经将这个节点初始化为包含数字一和内部的null,但接下来会发生什么呢?在这个故事的这个时刻,在这段代码中,我还有其他变量,没有展示出来,因为我们。
现在从木工的世界过渡到实际代码,所以有另一个变量n,我可以代表自己,如果我是这个值。直到我实际执行这一行代码list = n
,我才记得这个节点在计算机的内存中在哪里,因此到目前为止,n实际上只是。
这是一个临时变量,它是我用来实际跟踪这个内存中的东西的变量,但是,如果我想最终添加这个节点,那么就是null。请记住,这个指针指向地面,代表null。但是当我现在想记住这个链表中有一个节点时。
我需要实际执行这样一行code
,list = null
,好吧,我们接下来做了什么,让我们再进一步一步,所以在这个故事的这个时刻。如果我表示n,我也指向同一个块,n是临时的,因此最终可以消失,但在这个故事的这个时刻,我们有一个大小为一的链表。
让我们继续深入,假设现在我执行这些code
行,我们会稍微快一点,首先这一行代码与之前相同,嘿,malloc
给我一个足够大的内存块,能容纳一个节点,再次让我们使用这个临时变量n来指向它,假设这意味着。
如果我代表这个临时变量,我指向这里的新内存块。然后我检查n是否不等于null,那么只有在这种情况下,我才去插入数字二,就像我之前实际做的那样,并且我是否最初将指针初始化为不指向某个尚不存在的其他节点,即。
这样代表null,这就是它,现在已经分配了第二个节点,但请注意。实际上,这种断开只是因为我分配了一个新节点,并放入我关心的数字,并将其下一个指针初始化为null,这并不意味着它是数据结构的一部分,链表,新,所以我们需要执行另一行。
现在编写代码,以便我们最终能够从这幅图中获取到最后的一个,并且在这里我们可以使用与之前相同的语法,箭头,根据那段代码,然后我更新下一个字段。在那段代码的最后,我现在是否有一个大小为二的链表?因为我不仅分配了节点,还初始化了它的两个变量。
数字和下一个分别,我还将它与链表上的现有节点连接在一起。让我再稍微做一点,以便我们可以一次性看到所有内容。现在我们有了这幅图,让我们执行相同类型的代码。我将数字初始化为三,那么这段代码为我做了什么?那一块代码已经malloc了。
第三点也是最后一点,我将顶部初始化为数字三,我将底部初始化为null,我将用箭头指向地面来表示。然后有一个最后的步骤,如果我想去插入那个第三个节点,而不仅仅是我自己指向的变量n,我现在需要这样做。
这种语法你不会经常使用,我们稍后会在实际程序中看到,但它只是说明了我们正在操作的基本构建块。如果我想继续列出,我可以跟随箭头,一旦我能再次跟随箭头到n,因为n是最近分配的节点的当前地址,所以尽管语法有些复杂。
我使用的两个箭头,字面上就像代码。
只是遵循指针的表现,跟随指针,boom,将一个值赋给下一个。那么在故事的这个时刻,当我……我仍然在图片中,但如果我……临时变量,voila,我们一步一步地构建了一个全新的链表。
这似乎是很多工作,但它允许我们动态地增长这个东西。但让我在这里暂停,有关于链表的任何问题或困惑吗?如果我们试图创建一个更长的列表,比如超过三个元素,难道不会显得乏味吗?
是的,真是个好观察,所以我说你通常不会这样写,我们将暂时在一个完整的程序中做到这一点,只是为了演示,但一般来说,你可能会使用类似循环的东西,然后你会在这里迭代一次,再迭代一次。
让我暂时规定,如果你以正确的方式使用循环,你可以通过不断更新变量,只写出一个箭头。因此,有一种方法可以避免这种情况,你可以做得更动态。让我来提一个我自己的问题,如果……。
你想对这个问题进行回答,搜索链表的运行时间是什么?在大 O 表示法中,搜索链表的运行时间是什么?那么,最坏情况的上限是什么?
搜索像这样的链表,不论它有三个元素还是更多。看起来像以前的情况,大约是八十,它的复杂度是大 O 的 n,这实际上是正确的。考虑最坏情况,假设你在寻找数字三,你必须查看所有三个数字,如果你在找数字十。
你将从这里开始,在开始的地方继续寻找,寻找,寻找,直到你到达末尾,意识到数字十甚至不在这里,此时你已经查看了 n 个元素,但链表。
在常数时间内,链表的操作可以很简单。
一点算术,你可以跳到中间元素或第一个元素,所有这些都是使用常数时间完成的。
不幸的是,链表最终只由一个地址表示,这个地址指向第一个节点。因此,即使你们所有人都能在镜头前看到这个节点和那个节点,作为人类我们能同时看到,但计算机只能跟随这些列表,在这种情况下,它的复杂度将是大 O 的 n,但让我问一个后续问题。
这里的一个后续问题是,插入到链表中的运行时间是什么?插入到链表中的运行时间是什么?所以你有一个新的数字,负五。
无论是什么,都将涉及 malloc,但这只是常数时间,它只是一个函数调用,但你必须在某个地方插入它。在这里,看来你们 68% 的人提议是大 O 的 1,这很有趣,常数时间的 n,是否有人愿意在聊天中或口头表达意见?
至于为什么你觉得这是其中之一,它确实是那些答案之一。你对你思考的背后有什么想法吗?如果结果是其他的也没关系,聊天中有什么想法吗?嗯,我想这可能是 O 的 n,因为创建一个新节点的事实,我认为,计算机正在做的所有事情。
当你分配它时,它将像使用那些箭头一样,从一个小时到下一个小时,再到下一个,依此类推。我认为这将是全部,正如你所描述的,它将是一个事件,但你们两个在做一个假设,就像其他大约 80 或 25 个人正在做的那样,你们似乎在。
假设如果新数字,假设是数字四,必须放在最后,或者如果是数字五,它必须放在最后,而我故意设置的情况。是那样的,我恰好保持了它们,按顺序排列一二三。从左到右,但到目前为止我,尚未做出条件,让它。
链表必须是排序的,尽管我们到目前为止看到的例子都是故意这样做的。但你知道吗,如果你想让它,变得花哨一点,更高效。你想要分配数字四,坦白说你并不在乎。保持链表的排序顺序,嘿,只需将这个拿出来放入你的新节点。
在这里插入,另外一个也插入在这里,只需在列表开头插入。新元素,之后的每个数字。像之前一样malloc,但只需在这里继续插入,现在它不会花费一步。因为就我所说的,有像malloc的步骤,我必须拔掉这个。
必须插回去,所以这总共是三到四步,但四步也是。常量,这是O(1),因为这是固定步数。所以如果你能够牺牲排序顺序,当谈到这个列表时,你可以。
在常量时间内插入插入插入,列表将会越来越长。但是从开始而不是,从结束,所以这也是一个权衡。如果你不在乎排序顺序,并且你没有任何算法或代码。要求它是排序的,那么你可以去继续走这条捷径,达到常量时间插入。
无论是twitter、google,还是类似的,或许这实际上是一个净节省,还是一件好事。但再说一次,你牺牲了排序顺序,接下来让我们把这个翻译成一些实际的代码。让我去继续,在uh cs50 ide,然后让我们去继续,现在。实际对数字做一些操作,并开始处理事情。
在内存中,所以我准备去这里,创建一个程序,叫做list.dot c,我的第一个版本是,并且,返回空,然后在这里让我继续。就像我们开始的那样,给自己一个大小为三的整数列表,所以这是,三。这个大小为三的数组,我准备去继续硬编码一些新。
值放进去,所以在第一个位置,我放上数字一。在第二个位置,我会放上,放,数字三,然后只是为了,演示这个。这是按我预想的方式工作的,我将要,做一个快速的for循环,所以对于int i从零开始,i小于三,i加一,然后在这个循环里面,我准备去继续打印出来。
百分之i,然后我将打印,出i的值,所以现在我已经打印出所有的。这些值在我的循环中让我去继续,做出,列表。让我去继续,然后做./list。并按下回车,确实我得到了哎呀,不,不是我想要的,所以这是个好,可教的时刻。
坦率地说,我做错了什么,布莱恩,有人注意到如果我。我的目标是打印出列表,但不知怎么的我打印出了0 1。
2,确实不是这个列表中的数字,呃,所以你打印了i,你应该打印出列表的i,是的,我应该打印出数组的内容,也就是列表。方括号i,所以这是我一个新手的错误,让我修正,列表。点斜杠列表,瞧,我现在打印了列表,所以这就是第周,数组。
在第二周,但现在让我去啊d,转到更动态的东西。那里我不必提前承诺,创建一个呃数组,我可以这样做。通过动态分配的一块内存,所以让我删除我在main中做的所有事情。让我去啊d,给自己这个,让我去啊**d,声明。
一系列值,其中列表现在将,操作,我将去啊**d和malloc。让我们看看,我想要三个整数的空间,所以最简单的做法是。如果我只是想保持简单,我实际上可以这样做,三倍sizeofint,所以这个版本的我的程序不会使用,数组,而是使用malloc,但它会。
动态分配那个数组,给我,我们将看看这是什么语法,一如既往,现在每次你使用malloc,我应该检查,列表是否等于空,如果是,你知道。我只是返回一个,记住你可以返回零。或者一个或其他值,让main有效地退出你的程序。
我将去啊**d,返回一些东西,非常糟糕的出错了,像是我完全没有内存了,但现在我有了这个内存块,ant。这实际上是malloc给自己一个数组的方式,直到现在每次我们为自己创建一个。数组,我们都使用方括号表示法,而你们都放了一个。
方括号中的数字,给自己一个那个大小的数组。但坦率地说,如果我们有malloc和,内存,如果我想存储三个整数。为什么不让我请求malloc三倍。
整数的大小和,malloc的工作方式,实际上是,它会返回给我一个连续的。
这样大小的内存块,所以,那个只是,就像我们呃,这是一个我们将在分配实际节点时使用的技术,所以在这个故事的这个时刻,只要列表不等于空,我现在有一个足够大的内存块,能够容纳三个整数,和之前一样,我可以去啊**d,初始化这些,第一元素将是一个,第二个。
元素将是二,第三个元素,将是三,并注意这种等价关系。现在在使用数组和使用指针之间,C在这方面有点灵活。如果你有一块内存由malloc返回给你。你可以像上周一样使用方括号表示法,你可以使用方括号表示法。
将那块内存视为数组,因为毕竟数组是什么?它是一块连续的内存块,而这正是 malloc 返回的。如果你想更复杂一些,你实际上可以说去那个地址,把数字一放在那里,你可以说去那个地址加上。
一个数字放在这里,接下来的数字你可以说去那个地址加上,二。再把第三个数字放在那里,但,老实说这变得非常快。至少对大多数人来说,这就变成了不可读的,这就是所谓的指针,指针。这相当于使用我们已经用了一段时间的语法。
这只是使用方括号,方括号的好处在于计算机会为你确定每个整数之间的间隔,因为它知道大小,事情变得有趣也变得烦人,而 santiago 记得是帮助解决这个问题的,早些时候在第五行分配了三个整数。
但现在在第 13 行,我心想,哦,天哪,列表,我显然可以重做所有代码。但是假设这个故事的一部分,这里需要更多内存,那么我该如何做到这一点呢?让我继续临时分配另一块内存,我将称之为 temp。根据惯例,这次我将继续分配四个,作为故事的需要。
我搞砸了,我想为原始分配足够的空间。我并不是完全搞砸,我现在决定要为这个数组添加第四个数字。像往常一样,我应该检查 temp 是否等于空,你知道我将继续释放我已经分配的内存,然后我就要离开这里,返回 1。
出了点问题,没什么可以演示的,所以我将完全退出主函数。但如果 malloc 没有返回空,并且一切正常,我将要做什么?好吧,让我们首先开始这个。这个对话,for int i gets 0,i less than 3 i plus plus,让我们继续,复制到这个新的临时内存块,原始块中有什么,复制,新的数组。
这里是我们如何在代码中做到这一点,简单使用一个循环。像第二周那样,然后让我继续,再添加一个值。temp 中的 bracket three,这是第四个位置,如果从零开始。我将继续把数字四放在那里,现在在这个时候。
我将继续记住这个事实,temp 是我的新列表,所以我将继续释放原始列表,并将我的旧列表更新为。
指向新列表,最后我将继续使用另一个循环,仅仅为了演示我认为这次正确地迭代到四,而不是三,我将继续使用百分号 i 打印列表中 i 的内容,所以让我们快速回顾一下,我们开始这个故事是通过分配一个。
这次动态演示malloc只是返回一块内存,如果,作为一个数组。你绝对可以把这些东西放在这里,如果,列表等于空就是错误,出了问题。这里有趣的代码从这里恢复,我把数字1、2和3放在内存块的0、1和2位置,这块内存我又把它当成数组来处理。
但是现在在故事的这个阶段,我规定等等。我想去啊**d再添加一个第四个值,我该如何做到这一点,让我们规定我想返回去,更改现有程序,因为。假设为了讨论,这段代码是在谷歌运行的。
随着时间推移推特,只有在收到另一条推文后,他们的代码才意识到,哦,我们需要更多的,15,这次我分配了足够的空间来存储四个整数。我再次进行一些错误检查,如果。
不会发生,让我们完全退出,但如果没有发生什么坏事,让我们接受圣地亚哥的建议,把他的英文建议翻译成C,让我们使用一个从0到3的for循环和内存。
原始内存块的内容,所以temp i获得列表i。然后在这里,temp括号三的点是,从零开始。但是在这个故事的这个阶段,正如我之前的幻灯片一样,我在大小为三的数组中有1、2、3,并且我有1、2、4,让我去啊**d,释放原始内存。
列表并把那块原始内存交还给计算机。让我记住使用我更好命名的变量,这个新内存块的地址。
内存是,然后只是为了炫耀,让我去啊**d,再用一个for循环。这次计数四次,而不是三次,让我现在打印出所有这些值。这是我交叉手指的地方,编译我的新程序,它没有编译成功,好的。因为看起来我有一个括号多了,所以让我们用make重新编译这个程序。
列表另一个错误,所以让我向上滚动一下,哦,有趣。所以这是一个常见的错误,隐式声明库函数。malloc某某,所以每当你遇到隐式声明错误,几率是你只是做了简单的事情,比如你忘记了。
必需的头文件,上周提到的,malloc在标准库中,free也是。因此现在让我们创建列表,双手合十,希望这次能成功,点斜杠列表,瞧瞧。一二三四,所以这现在是一个。
完全逐字翻译,程序,开始时使用了一个大小为三的数组,动态分配内存,然后通过创建一个新大小为旧大小的数组来调整大小,然后像以前一样进行处理。我故意在这里两次使用malloc,如下所示,如果你在C中创建一个数组。
使用方括号表示法,你把自己困在了一个角落。
你可以看到,并且调整你用方括号声明的数组大小。
更技术性地说,当你使用方括号时,你是在栈上静态分配数组,这是计算机内存的一部分,属于该计算机和函数的栈帧。上周的图示中,如果你使用 malloc,我们的新工具,要求从堆中获取内存块。
回馈,并且获取更多,来回的,实际上还有一种更简单的方式来进行相对的重新分配。
通过调整内存块的大小,你不必像之前那样做所有这些,你不需要使用 malloc 两次,你可以在开始时使用一次 malloc,然后使用一个其实在这种情况下非常有用的新函数叫做 reallock,你实际上可以在大小为四倍的内存块中做到这一点。
尺寸结束,但具体来说。
重新分配的东西叫做列表,因此 reallock 与 malloc 非常相似,但它接受两个参数。
一个是你想要的内存大小,无论是更大还是更小,但它接受第二个参数,第一个参数是你已经分配的内存块的地址,正如 malloc 的顶端所示。
在第16行,我现在传递那个地址。
回到 reallock,说等一下,这里是你给我的相同地址,现在请将其调整为大小四,并且它会将现在足够大小的地址返回给你。
reallock 实际上会将旧内存复制到新的内存中,因此再次回到今天一开始的 santiago 故事,reallock 不仅会在你请求时给予你更大的内存块,还会归还你之前请求的内存地址,并且会给你新的内存块地址。
这个空间足以容纳所有新值,而且它也很智能。
如果在现有内存块的最后有足够的空间。
在早先的幻灯片中,你实际上会得到相同的地址,但计算机的操作系统,无论是 Windows、macOS 还是 Linux,都将记住“好的,我知道最初给你分配了三个字节,恰好在那块内存的末尾有空间,所以现在我将记住整型数据”。
或者传入的任何数字,再次,自己可以让计算机实际为你执行重新分配,有没有任何关于malloc、realloc或free的问题?
注意,这还不是一个列表,这仍然是一个数组,因此我们仍然需要处理这个程序。
进一步一步,实际上从使用数组的这块内存过渡到这些实际节点。但在我们这样做之前,有没有问题或困惑,布莱恩,有什么我们需要在这里讨论的吗?是的,有人问,为什么在程序结束时不需要释放临时变量,因为我是个傻瓜,忽略了这个关键点。
在这种情况下,要释放的不是临时变量,而是列表,因此临时变量我已经释放,嗯,此时它是等效的。
这一行27,名称更好。
我把它等于临时变量,这样我就可以将其视为一个更大的列表。不过你说得对,这是我疏忽了,Valgrind肯定不喜欢这种做法。在这个程序的最后,我应该绝对释放列表。然而,我并不需要释放临时变量本身的名称。
关于那个任务的好问题和好发现,其他问题或评论。有人问,为什么链表只使用数组和realloc、malloc来处理这些内容?嗯,真的很好的问题,那么我们如何改善这种情况,如果我们可以用这种方式使用数组。回想一下,在这种回归中,我刚刚做的是。
回归到我们开始的故事,刚刚写完。
我为这个数组重新分配了更多空间,这意味着我。
手动使用那个for循环,或者用它自己的for循环来复制所有旧值到新的数组中。
所以我们在到目前为止写的这个程序的三个版本中采取的方法是动态的。
当涉及到插入时,它们的时间复杂度都是O(n)。
他们没有给我们复制的动态性。
而且我们还没有能力直接在结构的开头进行插入,时间复杂度为O(1),所以再次这是我们开始时的代码方法。因此,最终目标将是改变这个代码,并给我们一个适当的整数,但我们已经过了一个小时,让我们先休息五分钟。
休息一下,我们再回来。