diff --git a/chapter_array_and_linkedlist/array.md b/chapter_array_and_linkedlist/array.md index b8b9aeade..aa7d27fa3 100755 --- a/chapter_array_and_linkedlist/array.md +++ b/chapter_array_and_linkedlist/array.md @@ -101,7 +101,9 @@ comments: true === "Dart" ```dart title="array.dart" - + /* 初始化数组 */ + List arr = List.filled(5, 0); // [0, 0, 0, 0, 0] + List nums = [1, 3, 2, 5, 4]; ``` ## 4.1.1.   数组优点 @@ -258,9 +260,9 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "Dart" ```dart title="array.dart" - /* 随机返回一个 数组元素 */ + /* 随机返回一个数组元素 */ int randomAccess(List nums) { - // 在区间[0,size) 中随机抽取一个数字 + // 在区间 [0, nums.length) 中随机抽取一个数字 int randomIndex = Random().nextInt(nums.length); // 获取并返回随机元素 int randomNum = nums[randomIndex]; @@ -444,9 +446,8 @@ elementAddr = firtstElementAddr + elementLength * elementIndex ```dart title="array.dart" /* 扩展数组长度 */ List extend(List nums, int enlarge) { - // 初始化一个扩展长度后的数组,元素初始值为0 + // 初始化一个扩展长度后的数组 List res = List.filled(nums.length + enlarge, 0); - // 将原数组中的所有元素复制到新数组 for (var i = 0; i < nums.length; i++) { res[i] = nums[i]; @@ -606,7 +607,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex ```dart title="array.dart" /* 在数组的索引 index 处插入元素 num */ void insert(List nums, int num, int index) { - // 把索引index以及之后的所有元素向后移动一位 + // 把索引 index 以及之后的所有元素向后移动一位 for (var i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } @@ -747,6 +748,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex ```dart title="array.dart" /* 删除索引 index 处元素 */ void remove(List nums, int index) { + // 把索引 index 之后的所有元素向前移动一位 for (var i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } @@ -942,7 +944,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex for (var num in nums) { count++; } - // 通过forEach方法遍历数组 + // 通过 forEach 方法遍历数组 nums.forEach((element) { count++; }); diff --git a/chapter_array_and_linkedlist/linked_list.md b/chapter_array_and_linkedlist/linked_list.md index 3f9d51768..392c5f79a 100755 --- a/chapter_array_and_linkedlist/linked_list.md +++ b/chapter_array_and_linkedlist/linked_list.md @@ -162,7 +162,12 @@ comments: true === "Dart" ```dart title="" - + /* 链表节点类 */ + class ListNode { + int val; // 节点值 + ListNode? next; // 指向下一节点的指针(引用) + ListNode(this.val, [this.next]); // 构造函数 + } ``` !!! question "尾节点指向什么?" @@ -348,7 +353,18 @@ comments: true === "Dart" ```dart title="linked_list.dart" - + /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */\ + // 初始化各个节点 + ListNode n0 = ListNode(1); + ListNode n1 = ListNode(3); + ListNode n2 = ListNode(2); + ListNode n3 = ListNode(5); + ListNode n4 = ListNode(4); + // 构建引用指向 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; ``` ## 4.2.1.   链表优点 @@ -637,6 +653,7 @@ comments: true /* 删除链表的节点 n0 之后的首个节点 */ void remove(ListNode n0) { if (n0.next == null) return; + // n0 -> P -> n1 ListNode P = n0.next!; ListNode? n1 = P.next; n0.next = n1; @@ -1159,7 +1176,13 @@ comments: true === "Dart" ```dart title="" - + /* 双向链表节点类 */ + class ListNode { + int val; // 节点值 + ListNode next; // 指向后继节点的指针(引用) + ListNode prev; // 指向前驱节点的指针(引用) + ListNode(this.val, [this.next, this.prev]); // 构造函数 + } ``` ![常见链表种类](linked_list.assets/linkedlist_common_types.png) diff --git a/chapter_array_and_linkedlist/list.md b/chapter_array_and_linkedlist/list.md index ff7d099c8..122717014 100755 --- a/chapter_array_and_linkedlist/list.md +++ b/chapter_array_and_linkedlist/list.md @@ -113,7 +113,11 @@ comments: true === "Dart" ```dart title="list.dart" - + /* 初始化列表 */ + // 无初始值 + List list1 = []; + // 有初始值 + List list = [1, 3, 2, 5, 4]; ``` **访问与更新元素**。由于列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问和更新元素,效率很高。 @@ -217,7 +221,11 @@ comments: true === "Dart" ```dart title="list.dart" + /* 访问元素 */ + int num = list[1]; // 访问索引 1 处的元素 + /* 更新元素 */ + list[1] = 0; // 将索引 1 处的元素更新为 0 ``` **在列表中添加、插入、删除元素**。相较于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但插入和删除元素的效率仍与数组相同,时间复杂度为 $O(N)$ 。 @@ -411,7 +419,21 @@ comments: true === "Dart" ```dart title="list.dart" + /* 清空列表 */ + list.clear(); + /* 尾部添加元素 */ + list.add(1); + list.add(3); + list.add(2); + list.add(5); + list.add(4); + + /* 中间插入元素 */ + list.insert(3, 6); // 在索引 3 处插入数字 6 + + /* 删除元素 */ + list.removeAt(3); // 删除索引 3 处的元素 ``` **遍历列表**。与数组一样,列表可以根据索引遍历,也可以直接遍历各元素。 @@ -570,7 +592,17 @@ comments: true === "Dart" ```dart title="list.dart" + /* 通过索引遍历列表 */ + int count = 0; + for (int i = 0; i < list.length; i++) { + count++; + } + /* 直接遍历列表元素 */ + count = 0; + for (int n in list) { + count++; + } ``` **拼接两个列表**。给定一个新列表 `list1` ,我们可以将该列表拼接到原列表的尾部。 @@ -659,7 +691,9 @@ comments: true === "Dart" ```dart title="list.dart" - + /* 拼接两个列表 */ + List list1 = [6, 8, 7, 10, 9]; + list.addAll(list1); // 将列表 list1 拼接到 list 之后 ``` **排序列表**。排序也是常用的方法之一。完成列表排序后,我们便可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法。 @@ -736,7 +770,8 @@ comments: true === "Dart" ```dart title="list.dart" - + /* 排序列表 */ + list.sort(); // 排序后,列表元素从小到大排列 ``` ## 4.3.2.   列表实现 * diff --git a/chapter_backtracking/backtracking_algorithm.md b/chapter_backtracking/backtracking_algorithm.md index 92604a44a..0135e388b 100644 --- a/chapter_backtracking/backtracking_algorithm.md +++ b/chapter_backtracking/backtracking_algorithm.md @@ -869,7 +869,26 @@ comments: true === "Dart" ```dart title="" - + /* 回溯算法框架 */ + void backtrack(State state, List, List res) { + // 判断是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + return; + } + // 遍历所有选择 + for (Choice choice in choices) { + // 剪枝:判断选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } ``` 下面,我们尝试基于此框架来解决例题三。在例题三中,状态 `state` 是节点遍历路径,选择 `choices` 是当前节点的左子节点和右子节点,结果 `res` 是路径列表,实现代码如下所示。 diff --git a/chapter_computational_complexity/space_complexity.md b/chapter_computational_complexity/space_complexity.md index b35bfe5ef..0d4fff06f 100755 --- a/chapter_computational_complexity/space_complexity.md +++ b/chapter_computational_complexity/space_complexity.md @@ -267,7 +267,26 @@ comments: true === "Dart" ```dart title="" + /* 类 */ + class Node { + int val; + Node next; + Node(this.val, [this.next]); + } + /* 函数 */ + int function() { + // do something... + return 0; + } + + int algorithm(int n) { // 输入数据 + const int a = 0; // 暂存数据(常量) + int b = 0; // 暂存数据(变量) + Node node = Node(0); // 暂存数据(对象) + int c = function(); // 栈帧空间(调用函数) + return a + b + c; // 输出数据 + } ``` ## 2.3.2.   推算方法 @@ -395,7 +414,13 @@ comments: true === "Dart" ```dart title="" - + void algorithm(int n) { + int a = 0; // O(1) + List b = List.filled(10000, 0); // O(1) + if (n > 10) { + List nums = List.filled(n, 0); // O(n) + } + } ``` **在递归函数中,需要注意统计栈帧空间**。例如,函数 `loop()` 在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。而递归函数 `recur()` 在运行过程中会同时存在 $n$ 个未返回的 `recur()` ,从而占用 $O(n)$ 的栈帧空间。 @@ -600,7 +625,21 @@ comments: true === "Dart" ```dart title="" - + int function() { + // do something + return 0; + } + /* 循环 O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + function(); + } + } + /* 递归 O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } ``` ## 2.3.3.   常见类型 diff --git a/chapter_computational_complexity/time_complexity.md b/chapter_computational_complexity/time_complexity.md index ec08ad774..42464793e 100755 --- a/chapter_computational_complexity/time_complexity.md +++ b/chapter_computational_complexity/time_complexity.md @@ -162,7 +162,16 @@ $$ === "Dart" ```dart title="" - + // 在某运行平台下 + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 循环 n 次 + for (int i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++ + print(0); // 5 ns + } + } ``` 然而实际上,**统计算法的运行时间既不合理也不现实**。首先,我们不希望预估时间和运行平台绑定,因为算法需要在各种不同的平台上运行。其次,我们很难获知每种操作的运行时间,这给预估过程带来了极大的难度。 @@ -378,7 +387,22 @@ $$ === "Dart" ```dart title="" - + // 算法 A 时间复杂度:常数阶 + void algorithmA(int n) { + print(0); + } + // 算法 B 时间复杂度:线性阶 + void algorithmB(int n) { + for (int i = 0; i < n; i++) { + print(0); + } + } + // 算法 C 时间复杂度:常数阶 + void algorithmC(int n) { + for (int i = 0; i < 1000000; i++) { + print(0); + } + } ``` ![算法 A, B, C 的时间增长趋势](time_complexity.assets/time_complexity_simple_example.png) @@ -536,7 +560,15 @@ $$ === "Dart" ```dart title="" - + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 循环 n 次 + for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) + print(0); // +1 + } + } ``` $T(n)$ 是一次函数,说明时间增长趋势是线性的,因此可以得出时间复杂度是线性阶。 @@ -768,7 +800,20 @@ $$ === "Dart" ```dart title="" - + void algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + print(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + print(0); + } + } + } ``` ### 2) 判断渐近上界 @@ -1684,7 +1729,7 @@ $$ int count = 0; // 计数器 // 外循环:未排序区间为 [0, i] for (var i = nums.length - 1; i > 0; i--) { - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (var j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] diff --git a/chapter_data_structure/basic_data_types.md b/chapter_data_structure/basic_data_types.md index bb40b70d4..06e36288f 100644 --- a/chapter_data_structure/basic_data_types.md +++ b/chapter_data_structure/basic_data_types.md @@ -137,5 +137,9 @@ comments: true === "Dart" ```dart title="" - + /* 使用多种「基本数据类型」来初始化「数组」 */ + List numbers = List.filled(5, 0); + List decimals = List.filled(5, 0.0); + List characters = List.filled(5, 'a'); + List booleans = List.filled(5, false); ``` diff --git a/chapter_hashing/hash_map.md b/chapter_hashing/hash_map.md index c48a9ff5b..af61b2d70 100755 --- a/chapter_hashing/hash_map.md +++ b/chapter_hashing/hash_map.md @@ -232,7 +232,24 @@ comments: true === "Dart" ```dart title="hash_map.dart" + /* 初始化哈希表 */ + Map map = {}; + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map[12836] = "小哈"; + map[15937] = "小啰"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鸭"; + + /* 查询操作 */ + // 向哈希表输入键 key ,得到值 value + String name = map[15937]; + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(10583); ``` 遍历哈希表有三种方式,即 **遍历键值对、遍历键、遍历值**。 @@ -396,7 +413,21 @@ comments: true === "Dart" ```dart title="hash_map.dart" + /* 遍历哈希表 */ + // 遍历键值对 Key->Value + map.forEach((key, value) { + print('$key -> $value'); + }); + // 单独遍历键 Key + map.keys.forEach((key) { + print(key); + }); + + // 单独遍历值 Value + map.values.forEach((value) { + print(value); + }); ``` ## 6.1.2.   哈希函数 diff --git a/chapter_heap/heap.md b/chapter_heap/heap.md index 416eefef2..3537f6588 100644 --- a/chapter_heap/heap.md +++ b/chapter_heap/heap.md @@ -310,7 +310,7 @@ comments: true === "Dart" ```dart title="heap.dart" - + // Dart 未提供内置 Heap 类 ``` ## 8.1.2.   堆的实现 diff --git a/chapter_preface/suggestions.md b/chapter_preface/suggestions.md index 473c06af1..87d0934af 100644 --- a/chapter_preface/suggestions.md +++ b/chapter_preface/suggestions.md @@ -151,12 +151,14 @@ comments: true === "Dart" ```dart title="" - // 标题注释,用于标注函数、类、测试样例等 + /* 标题注释,用于标注函数、类、测试样例等 */ // 内容注释,用于详解代码 - // 多行 - // 注释 + /** + * 多行 + * 注释 + */ ``` ## 0.2.2.   在动画图解中高效学习 diff --git a/chapter_stack_and_queue/deque.md b/chapter_stack_and_queue/deque.md index 61ef4d8f2..c65de91bc 100644 --- a/chapter_stack_and_queue/deque.md +++ b/chapter_stack_and_queue/deque.md @@ -292,7 +292,30 @@ comments: true === "Dart" ```dart title="deque.dart" + /* 初始化双向队列 */ + // 在 Dart 中,Queue 被定义为双向队列 + Queue deque = Queue(); + /* 元素入队 */ + deque.addLast(2); // 添加至队尾 + deque.addLast(5); + deque.addLast(4); + deque.addFirst(3); // 添加至队首 + deque.addFirst(1); + + /* 访问元素 */ + int peekFirst = deque.first; // 队首元素 + int peekLast = deque.last; // 队尾元素 + + /* 元素出队 */ + int popFirst = deque.removeFirst(); // 队首元素出队 + int popLast = deque.removeLast(); // 队尾元素出队 + + /* 获取双向队列的长度 */ + int size = deque.length; + + /* 判断双向队列是否为空 */ + bool isEmpty = deque.isEmpty;W ``` ## 5.3.2.   双向队列实现 * diff --git a/chapter_stack_and_queue/queue.md b/chapter_stack_and_queue/queue.md index 91fb5a85c..fa0f142aa 100755 --- a/chapter_stack_and_queue/queue.md +++ b/chapter_stack_and_queue/queue.md @@ -261,7 +261,28 @@ comments: true === "Dart" ```dart title="queue.dart" + /* 初始化队列 */ + // 在 Dart 中,队列类 Qeque 是双向队列,也可作为队列使用 + Queue queue = Queue(); + /* 元素入队 */ + queue.add(1); + queue.add(3); + queue.add(2); + queue.add(5); + queue.add(4); + + /* 访问队首元素 */ + int peek = queue.first; + + /* 元素出队 */ + int pop = queue.removeFirst(); + + /* 获取队列的长度 */ + int size = queue.length; + + /* 判断队列是否为空 */ + bool isEmpty = queue.isEmpty; ``` ## 5.2.2.   队列实现 diff --git a/chapter_stack_and_queue/stack.md b/chapter_stack_and_queue/stack.md index 28d1ea5ee..0cb41c792 100755 --- a/chapter_stack_and_queue/stack.md +++ b/chapter_stack_and_queue/stack.md @@ -259,7 +259,28 @@ comments: true === "Dart" ```dart title="stack.dart" + /* 初始化栈 */ + // Dart 没有内置的栈类,可以把 List 当作栈来使用 + List stack = []; + /* 元素入栈 */ + stack.add(1); + stack.add(3); + stack.add(2); + stack.add(5); + stack.add(4); + + /* 访问栈顶元素 */ + int peek = stack.last; + + /* 元素出栈 */ + int pop = stack.removeLast(); + + /* 获取栈的长度 */ + int size = stack.length; + + /* 判断是否为空 */ + bool isEmpty = stack.isEmpty; ``` ## 5.1.2.   栈的实现 diff --git a/chapter_tree/array_representation_of_tree.md b/chapter_tree/array_representation_of_tree.md index 56817fd48..50f417679 100644 --- a/chapter_tree/array_representation_of_tree.md +++ b/chapter_tree/array_representation_of_tree.md @@ -111,7 +111,9 @@ comments: true === "Dart" ```dart title="" - + /* 二叉树的数组表示 */ + // 使用 int? 可空类型 ,就可以使用 null 来标记空位 + List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` ![任意类型二叉树的数组表示](binary_tree.assets/array_representation_with_empty.png) diff --git a/chapter_tree/avl_tree.md b/chapter_tree/avl_tree.md index 58043fb4d..709c90c46 100644 --- a/chapter_tree/avl_tree.md +++ b/chapter_tree/avl_tree.md @@ -180,7 +180,14 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "Dart" ```dart title="" - + /* AVL 树节点类 */ + class TreeNode { + int val; // 节点值 + int height; // 节点高度 + TreeNode? left; // 左子节点 + TreeNode? right; // 右子节点 + TreeNode(this.val, [this.height = 0, this.left, this.right]); + } ``` 「节点高度」是指从该节点到最远叶节点的距离,即所经过的“边”的数量。需要特别注意的是,叶节点的高度为 0 ,而空节点的高度为 -1 。我们将创建两个工具函数,分别用于获取和更新节点的高度。 diff --git a/chapter_tree/binary_tree.md b/chapter_tree/binary_tree.md index 45ace000c..ed7788936 100644 --- a/chapter_tree/binary_tree.md +++ b/chapter_tree/binary_tree.md @@ -150,7 +150,13 @@ comments: true === "Dart" ```dart title="" - + /* 二叉树节点类 */ + class TreeNode { + int val; // 节点值 + TreeNode? left; // 左子节点指针 + TreeNode? right; // 右子节点指针 + TreeNode(this.val, [this.left, this.right]); + } ``` 节点的两个指针分别指向「左子节点」和「右子节点」,同时该节点被称为这两个子节点的「父节点」。当给定一个二叉树的节点时,我们将该节点的左子节点及其以下节点形成的树称为该节点的「左子树」,同理可得「右子树」。 @@ -346,7 +352,18 @@ comments: true === "Dart" ```dart title="binary_tree.dart" - + /* 初始化二叉树 */ + // 初始化节点 + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // 构建引用指向(即指针) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; ``` **插入与删除节点**。与链表类似,通过修改指针来实现插入与删除节点。 @@ -470,7 +487,13 @@ comments: true === "Dart" ```dart title="binary_tree.dart" - + /* 插入与删除节点 */ + TreeNode P = new TreeNode(0); + // 在 n1 -> n2 中间插入节点 P + n1.left = P; + P.left = n2; + // 删除节点 P + n1.left = n2; ``` !!! note