Add Swift language blocks to the docs.

This commit is contained in:
Yudong Jin 2023-01-08 19:41:05 +08:00
parent 3ba37dba3a
commit 73e3452838
22 changed files with 414 additions and 70 deletions

View file

@ -15,12 +15,12 @@ int *randomNumbers(int n) {
nums[i] = i + 1; nums[i] = i + 1;
} }
// 随机打乱数组元素 // 随机打乱数组元素
for (int i = n - 1; i > 0; i--) { for (int i = n - 1; i > 0; i--) {
int j = rand() % (i + 1); int j = rand() % (i + 1);
int temp = nums[i]; int temp = nums[i];
nums[i] = nums[j]; nums[i] = nums[j];
nums[j] = temp; nums[j] = temp;
} }
return nums; return nums;
} }

View file

@ -11,8 +11,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="coverlet.collector" Version="3.1.0" /> <PackageReference Include="coverlet.collector" Version="3.1.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -4,7 +4,7 @@
* Author: nuomi1 (nuomi1@qq.com) * Author: nuomi1 (nuomi1@qq.com)
*/ */
// /* */
func randomAccess(nums: [Int]) -> Int { func randomAccess(nums: [Int]) -> Int {
// [0, nums.count) // [0, nums.count)
let randomIndex = nums.indices.randomElement()! let randomIndex = nums.indices.randomElement()!
@ -13,7 +13,7 @@ func randomAccess(nums: [Int]) -> Int {
return randomNum return randomNum
} }
// /* */
func extend(nums: [Int], enlarge: Int) -> [Int] { func extend(nums: [Int], enlarge: Int) -> [Int] {
// //
var res = Array(repeating: 0, count: nums.count + enlarge) var res = Array(repeating: 0, count: nums.count + enlarge)
@ -25,7 +25,7 @@ func extend(nums: [Int], enlarge: Int) -> [Int] {
return res return res
} }
// index num /* index num */
func insert(nums: inout [Int], num: Int, index: Int) { func insert(nums: inout [Int], num: Int, index: Int) {
// index // index
for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) { for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) {
@ -35,7 +35,7 @@ func insert(nums: inout [Int], num: Int, index: Int) {
nums[index] = num nums[index] = num
} }
// index /* index */
func remove(nums: inout [Int], index: Int) { func remove(nums: inout [Int], index: Int) {
let count = nums.count let count = nums.count
// index // index
@ -44,7 +44,7 @@ func remove(nums: inout [Int], index: Int) {
} }
} }
// /* */
func traverse(nums: [Int]) { func traverse(nums: [Int]) {
var count = 0 var count = 0
// //
@ -57,7 +57,7 @@ func traverse(nums: [Int]) {
} }
} }
// /* */
func find(nums: [Int], target: Int) -> Int { func find(nums: [Int], target: Int) -> Int {
for i in nums.indices { for i in nums.indices {
if nums[i] == target { if nums[i] == target {

View file

@ -112,6 +112,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title=""
```
**尾结点指向什么?** 我们一般将链表的最后一个结点称为「尾结点」,其指向的是「空」,在 Java / C++ / Python 中分别记为 `null` / `nullptr` / `None` 。在不引起歧义下,本书都使用 `null` 来表示空。 **尾结点指向什么?** 我们一般将链表的最后一个结点称为「尾结点」,其指向的是「空」,在 Java / C++ / Python 中分别记为 `null` / `nullptr` / `None` 。在不引起歧义下,本书都使用 `null` 来表示空。
**链表初始化方法**。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。 **链表初始化方法**。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。
@ -122,7 +128,7 @@ comments: true
=== "Java" === "Java"
```java title="" ```java title="linked_list.java"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个结点 // 初始化各个结点
ListNode n0 = new ListNode(1); ListNode n0 = new ListNode(1);
@ -139,7 +145,7 @@ comments: true
=== "C++" === "C++"
```cpp title="" ```cpp title="linked_list.cpp"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个结点 // 初始化各个结点
ListNode* n0 = new ListNode(1); ListNode* n0 = new ListNode(1);
@ -156,7 +162,7 @@ comments: true
=== "Python" === "Python"
```python title="" ```python title="linked_list.py"
""" 初始化链表 1 -> 3 -> 2 -> 5 -> 4 """ """ 初始化链表 1 -> 3 -> 2 -> 5 -> 4 """
# 初始化各个结点 # 初始化各个结点
n0 = ListNode(1) n0 = ListNode(1)
@ -173,7 +179,7 @@ comments: true
=== "Go" === "Go"
```go title="" ```go title="linked_list.go"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个结点 // 初始化各个结点
n0 := NewListNode(1) n0 := NewListNode(1)
@ -191,7 +197,7 @@ comments: true
=== "JavaScript" === "JavaScript"
```js title="" ```js title="linked_list.js"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个结点 // 初始化各个结点
const n0 = new ListNode(1); const n0 = new ListNode(1);
@ -208,7 +214,7 @@ comments: true
=== "TypeScript" === "TypeScript"
```typescript title="" ```typescript title="linked_list.ts"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个结点 // 初始化各个结点
const n0 = new ListNode(1); const n0 = new ListNode(1);
@ -225,13 +231,13 @@ comments: true
=== "C" === "C"
```c title="" ```c title="linked_list.c"
``` ```
=== "C#" === "C#"
```csharp title="" ```csharp title="linked_list.cs"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个结点 // 初始化各个结点
ListNode n0 = new ListNode(1); ListNode n0 = new ListNode(1);
@ -246,6 +252,12 @@ comments: true
n3.next = n4; n3.next = n4;
``` ```
=== "Swift"
```swift title="linked_list.swift"
```
## 链表优点 ## 链表优点
**在链表中,插入与删除结点的操作效率高**。例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。 **在链表中,插入与删除结点的操作效率高**。例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。
@ -256,7 +268,7 @@ comments: true
=== "Java" === "Java"
```java title="" ```java title="linked_list.java"
/* 在链表的结点 n0 之后插入结点 P */ /* 在链表的结点 n0 之后插入结点 P */
void insert(ListNode n0, ListNode P) { void insert(ListNode n0, ListNode P) {
ListNode n1 = n0.next; ListNode n1 = n0.next;
@ -277,7 +289,7 @@ comments: true
=== "C++" === "C++"
```cpp title="" ```cpp title="linked_list.cpp"
/* 在链表的结点 n0 之后插入结点 P */ /* 在链表的结点 n0 之后插入结点 P */
void insert(ListNode* n0, ListNode* P) { void insert(ListNode* n0, ListNode* P) {
ListNode* n1 = n0->next; ListNode* n1 = n0->next;
@ -300,7 +312,7 @@ comments: true
=== "Python" === "Python"
```python title="" ```python title="linked_list.py"
""" 在链表的结点 n0 之后插入结点 P """ """ 在链表的结点 n0 之后插入结点 P """
def insert(n0, P): def insert(n0, P):
n1 = n0.next n1 = n0.next
@ -319,7 +331,7 @@ comments: true
=== "Go" === "Go"
```go title="" ```go title="linked_list.go"
/* 在链表的结点 n0 之后插入结点 P */ /* 在链表的结点 n0 之后插入结点 P */
func insert(n0 *ListNode, P *ListNode) { func insert(n0 *ListNode, P *ListNode) {
n1 := n0.Next n1 := n0.Next
@ -341,7 +353,7 @@ comments: true
=== "JavaScript" === "JavaScript"
```js title="" ```js title="linked_list.js"
/* 在链表的结点 n0 之后插入结点 P */ /* 在链表的结点 n0 之后插入结点 P */
function insert(n0, P) { function insert(n0, P) {
let n1 = n0.next; let n1 = n0.next;
@ -362,7 +374,7 @@ comments: true
=== "TypeScript" === "TypeScript"
```typescript title="" ```typescript title="linked_list.ts"
/* 在链表的结点 n0 之后插入结点 P */ /* 在链表的结点 n0 之后插入结点 P */
function insert(n0: ListNode, P: ListNode): void { function insert(n0: ListNode, P: ListNode): void {
const n1 = n0.next; const n1 = n0.next;
@ -383,13 +395,13 @@ comments: true
=== "C" === "C"
```c title="" ```c title="linked_list.c"
``` ```
=== "C#" === "C#"
```csharp title="" ```csharp title="linked_list.cs"
// 在链表的结点 n0 之后插入结点 P // 在链表的结点 n0 之后插入结点 P
void Insert(ListNode n0, ListNode P) void Insert(ListNode n0, ListNode P)
{ {
@ -410,13 +422,19 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="linked_list.swift"
```
## 链表缺点 ## 链表缺点
**链表访问结点效率低**。上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。 **链表访问结点效率低**。上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。
=== "Java" === "Java"
```java title="" ```java title="linked_list.java"
/* 访问链表中索引为 index 的结点 */ /* 访问链表中索引为 index 的结点 */
ListNode access(ListNode head, int index) { ListNode access(ListNode head, int index) {
for (int i = 0; i < index; i++) { for (int i = 0; i < index; i++) {
@ -430,7 +448,7 @@ comments: true
=== "C++" === "C++"
```cpp title="" ```cpp title="linked_list.cpp"
/* 访问链表中索引为 index 的结点 */ /* 访问链表中索引为 index 的结点 */
ListNode* access(ListNode* head, int index) { ListNode* access(ListNode* head, int index) {
for (int i = 0; i < index; i++) { for (int i = 0; i < index; i++) {
@ -444,7 +462,7 @@ comments: true
=== "Python" === "Python"
```python title="" ```python title="linked_list.py"
""" 访问链表中索引为 index 的结点 """ """ 访问链表中索引为 index 的结点 """
def access(head, index): def access(head, index):
for _ in range(index): for _ in range(index):
@ -456,7 +474,7 @@ comments: true
=== "Go" === "Go"
```go title="" ```go title="linked_list.go"
/* 访问链表中索引为 index 的结点 */ /* 访问链表中索引为 index 的结点 */
func access(head *ListNode, index int) *ListNode { func access(head *ListNode, index int) *ListNode {
for i := 0; i < index; i++ { for i := 0; i < index; i++ {
@ -471,7 +489,7 @@ comments: true
=== "JavaScript" === "JavaScript"
```js title="" ```js title="linked_list.js"
/* 访问链表中索引为 index 的结点 */ /* 访问链表中索引为 index 的结点 */
function access(head, index) { function access(head, index) {
for (let i = 0; i < index; i++) { for (let i = 0; i < index; i++) {
@ -485,7 +503,7 @@ comments: true
=== "TypeScript" === "TypeScript"
```typescript title="" ```typescript title="linked_list.ts"
/* 访问链表中索引为 index 的结点 */ /* 访问链表中索引为 index 的结点 */
function access(head: ListNode | null, index: number): ListNode | null { function access(head: ListNode | null, index: number): ListNode | null {
for (let i = 0; i < index; i++) { for (let i = 0; i < index; i++) {
@ -500,13 +518,13 @@ comments: true
=== "C" === "C"
```c title="" ```c title="linked_list.c"
``` ```
=== "C#" === "C#"
```csharp title="" ```csharp title="linked_list.cs"
// 访问链表中索引为 index 的结点 // 访问链表中索引为 index 的结点
ListNode Access(ListNode head, int index) ListNode Access(ListNode head, int index)
{ {
@ -520,6 +538,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="linked_list.swift"
```
**链表的内存占用多**。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。 **链表的内存占用多**。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。
## 链表常用操作 ## 链表常用操作
@ -528,7 +552,7 @@ comments: true
=== "Java" === "Java"
```java title="" ```java title="linked_list.java"
/* 在链表中查找值为 target 的首个结点 */ /* 在链表中查找值为 target 的首个结点 */
int find(ListNode head, int target) { int find(ListNode head, int target) {
int index = 0; int index = 0;
@ -544,7 +568,7 @@ comments: true
=== "C++" === "C++"
```cpp title="" ```cpp title="linked_list.cpp"
/* 在链表中查找值为 target 的首个结点 */ /* 在链表中查找值为 target 的首个结点 */
int find(ListNode* head, int target) { int find(ListNode* head, int target) {
int index = 0; int index = 0;
@ -560,7 +584,7 @@ comments: true
=== "Python" === "Python"
```python title="" ```python title="linked_list.py"
""" 在链表中查找值为 target 的首个结点 """ """ 在链表中查找值为 target 的首个结点 """
def find(head, target): def find(head, target):
index = 0 index = 0
@ -574,7 +598,7 @@ comments: true
=== "Go" === "Go"
```go title="" ```go title="linked_list.go"
/* 在链表中查找值为 target 的首个结点 */ /* 在链表中查找值为 target 的首个结点 */
func find(head *ListNode, target int) int { func find(head *ListNode, target int) int {
index := 0 index := 0
@ -591,7 +615,7 @@ comments: true
=== "JavaScript" === "JavaScript"
```js title="" ```js title="linked_list.js"
/* 在链表中查找值为 target 的首个结点 */ /* 在链表中查找值为 target 的首个结点 */
function find(head, target) { function find(head, target) {
let index = 0; let index = 0;
@ -608,7 +632,7 @@ comments: true
=== "TypeScript" === "TypeScript"
```typescript title="" ```typescript title="linked_list.ts"
/* 在链表中查找值为 target 的首个结点 */ /* 在链表中查找值为 target 的首个结点 */
function find(head: ListNode | null, target: number): number { function find(head: ListNode | null, target: number): number {
let index = 0; let index = 0;
@ -625,13 +649,13 @@ comments: true
=== "C" === "C"
```c title="" ```c title="linked_list.c"
``` ```
=== "C#" === "C#"
```csharp title="" ```csharp title="linked_list.cs"
// 在链表中查找值为 target 的首个结点 // 在链表中查找值为 target 的首个结点
int Find(ListNode head, int target) int Find(ListNode head, int target)
{ {
@ -647,6 +671,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="linked_list.swift"
```
## 常见链表类型 ## 常见链表类型
**单向链表**。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` **单向链表**。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null`
@ -760,6 +790,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title=""
```
![linkedlist_common_types](linked_list.assets/linkedlist_common_types.png) ![linkedlist_common_types](linked_list.assets/linkedlist_common_types.png)
<p align="center"> Fig. 常见链表类型 </p> <p align="center"> Fig. 常见链表类型 </p>

View file

@ -91,6 +91,12 @@ comments: true
List<int> list = numbers.ToList(); List<int> list = numbers.ToList();
``` ```
=== "Swift"
```swift title="list.swift"
```
**访问与更新元素**。列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。 **访问与更新元素**。列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。
=== "Java" === "Java"
@ -169,6 +175,12 @@ comments: true
list[1] = 0; // 将索引 1 处的元素更新为 0 list[1] = 0; // 将索引 1 处的元素更新为 0
``` ```
=== "Swift"
```swift title="list.swift"
```
**在列表中添加、插入、删除元素**。相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。 **在列表中添加、插入、删除元素**。相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。
=== "Java" === "Java"
@ -317,6 +329,12 @@ comments: true
list.RemoveAt(3); list.RemoveAt(3);
``` ```
=== "Swift"
```swift title="list.swift"
```
**遍历列表**。与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。 **遍历列表**。与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。
=== "Java" === "Java"
@ -437,6 +455,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="list.swift"
```
**拼接两个列表**。再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。 **拼接两个列表**。再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。
=== "Java" === "Java"
@ -502,6 +526,12 @@ comments: true
list.AddRange(list1); // 将列表 list1 拼接到 list 之后 list.AddRange(list1); // 将列表 list1 拼接到 list 之后
``` ```
=== "Swift"
```swift title="list.swift"
```
**排序列表**。排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。 **排序列表**。排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。
=== "Java" === "Java"
@ -559,6 +589,12 @@ comments: true
list.Sort(); // 排序后,列表元素从小到大排列 list.Sort(); // 排序后,列表元素从小到大排列
``` ```
=== "Swift"
```swift title="list.swift"
```
## 列表简易实现 * ## 列表简易实现 *
为了帮助加深对列表的理解,我们在此提供一个列表的简易版本的实现。需要关注三个核心点: 为了帮助加深对列表的理解,我们在此提供一个列表的简易版本的实现。需要关注三个核心点:
@ -1220,3 +1256,10 @@ comments: true
} }
} }
``` ```
=== "Swift"
```swift title="my_list.swift"
```

View file

@ -421,12 +421,12 @@ $$
```go title="" ```go title=""
func algorithm(n int) { func algorithm(n int) {
a := 1 // +1 a := 1 // +1
a = a + 1 // +1 a = a + 1 // +1
a = a * 2 // +1 a = a * 2 // +1
// 循环 n 次 // 循环 n 次
for i := 0; i < n; i++ { // +1 for i := 0; i < n; i++ { // +1
fmt.Println(a) // +1 fmt.Println(a) // +1
} }
} }
``` ```
@ -2657,7 +2657,7 @@ $$
=== "Swift" === "Swift"
```swift title="" ```swift title="worst_best_time_complexity.swift"
// 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 // 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱
func randomNumbers(n: Int) -> [Int] { func randomNumbers(n: Int) -> [Int] {
// 生成数组 nums = { 1, 2, 3, ..., n } // 生成数组 nums = { 1, 2, 3, ..., n }

View file

@ -108,25 +108,25 @@ comments: true
=== "Go" === "Go"
```go title="hash_map_test.go" ```go title="hash_map.go"
/* 初始化哈希表 */ /* 初始化哈希表 */
mapp := make(map[int]string) mapp := make(map[int]string)
/* 添加操作 */ /* 添加操作 */
// 在哈希表中添加键值对 (key, value) // 在哈希表中添加键值对 (key, value)
mapp[12836] = "小哈" mapp[12836] = "小哈"
mapp[15937] = "小啰" mapp[15937] = "小啰"
mapp[16750] = "小算" mapp[16750] = "小算"
mapp[13276] = "小法" mapp[13276] = "小法"
mapp[10583] = "小鸭" mapp[10583] = "小鸭"
/* 查询操作 */ /* 查询操作 */
// 向哈希表输入键 key ,得到值 value // 向哈希表输入键 key ,得到值 value
name := mapp[15937] name := mapp[15937]
/* 删除操作 */ /* 删除操作 */
// 在哈希表中删除键值对 (key, value) // 在哈希表中删除键值对 (key, value)
delete(mapp, 10583) delete(mapp, 10583)
``` ```
=== "JavaScript" === "JavaScript"
@ -207,6 +207,12 @@ comments: true
map.Remove(10583); map.Remove(10583);
``` ```
=== "Swift"
```swift title="hash_map.swift"
```
遍历哈希表有三种方式,即 **遍历键值对、遍历键、遍历值** 遍历哈希表有三种方式,即 **遍历键值对、遍历键、遍历值**
=== "Java" === "Java"
@ -339,6 +345,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="hash_map.swift"
```
## 哈希函数 ## 哈希函数
哈希表中存储元素的数据结构被称为「桶 Bucket」底层实现可能是数组、链表、二叉树红黑树或是它们的组合。 哈希表中存储元素的数据结构被称为「桶 Bucket」底层实现可能是数组、链表、二叉树红黑树或是它们的组合。
@ -756,6 +768,12 @@ $$
} }
``` ```
=== "Swift"
```swift title="array_hash_map.swift"
```
## 哈希冲突 ## 哈希冲突
细心的同学可能会发现,**哈希函数 $f(x) = x \% 100$ 会在某些情况下失效**。具体地,当输入的 key 后两位相同时,哈希函数的计算结果也相同,指向同一个 value 。例如,分别查询两个学号 12836 和 20336 ,则有 细心的同学可能会发现,**哈希函数 $f(x) = x \% 100$ 会在某些情况下失效**。具体地,当输入的 key 后两位相同时,哈希函数的计算结果也相同,指向同一个 value 。例如,分别查询两个学号 12836 和 20336 ,则有

View file

@ -192,6 +192,19 @@ comments: true
*/ */
``` ```
=== "Swift"
```swift title=""
/* 标题注释,用于标注函数、类、测试样例等 */
// 内容注释,用于详解代码
/**
* 多行
* 注释
*/
```
""" """
在 Java, C, C++, C#, Go, JS, TS 的代码注释中,`/* ... */` 用于注释函数、类、测试样例等标题, `// ...` 用于解释代码内容;类似地,在 Python 中,`""" ... """` 用于注释标题, `# ...` 用于解释代码。 在 Java, C, C++, C#, Go, JS, TS 的代码注释中,`/* ... */` 用于注释函数、类、测试样例等标题, `// ...` 用于解释代码内容;类似地,在 Python 中,`""" ... """` 用于注释标题, `# ...` 用于解释代码。

View file

@ -210,6 +210,12 @@ $$
} }
``` ```
=== "Swift"
```swift title="binary_search.swift"
```
### “左闭右开”实现 ### “左闭右开”实现
当然,我们也可以使用“左闭右开”的表示方法,写出相同功能的二分查找代码。 当然,我们也可以使用“左闭右开”的表示方法,写出相同功能的二分查找代码。
@ -374,6 +380,12 @@ $$
} }
``` ```
=== "Swift"
```swift title="binary_search.swift"
```
### 两种表示对比 ### 两种表示对比
对比下来,两种表示的代码写法有以下不同点: 对比下来,两种表示的代码写法有以下不同点:
@ -460,6 +472,12 @@ $$
int m = i + (j - i) / 2; int m = i + (j - i) / 2;
``` ```
=== "Swift"
```swift title=""
```
## 复杂度分析 ## 复杂度分析
**时间复杂度 $O(\log n)$ ** 其中 $n$ 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。 **时间复杂度 $O(\log n)$ ** 其中 $n$ 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。

View file

@ -95,6 +95,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="hashing_search.swift"
```
再比如,如果我们想要给定一个目标结点值 `target` ,获取对应的链表结点对象,那么也可以使用哈希查找实现。 再比如,如果我们想要给定一个目标结点值 `target` ,获取对应的链表结点对象,那么也可以使用哈希查找实现。
![hash_search_listnode](hashing_search.assets/hash_search_listnode.png) ![hash_search_listnode](hashing_search.assets/hash_search_listnode.png)
@ -179,6 +185,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="hashing_search.swift"
```
## 复杂度分析 ## 复杂度分析
**时间复杂度:** $O(1)$ ,哈希表的查找操作使用 $O(1)$ 时间。 **时间复杂度:** $O(1)$ ,哈希表的查找操作使用 $O(1)$ 时间。

View file

@ -122,6 +122,12 @@ comments: true
``` ```
=== "Swift"
```swift title="linear_search.swift"
```
再比如,我们想要在给定一个目标结点值 `target` ,返回此结点对象,也可以在链表中进行线性查找。 再比如,我们想要在给定一个目标结点值 `target` ,返回此结点对象,也可以在链表中进行线性查找。
=== "Java" === "Java"
@ -238,6 +244,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="linear_search.swift"
```
## 复杂度分析 ## 复杂度分析
**时间复杂度 $O(n)$ ** 其中 $n$ 为数组或链表长度。 **时间复杂度 $O(n)$ ** 其中 $n$ 为数组或链表长度。

View file

@ -212,6 +212,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="bubble_sort.swift"
```
## 算法特性 ## 算法特性
**时间复杂度 $O(n^2)$ ** 各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。 **时间复杂度 $O(n^2)$ ** 各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。
@ -414,3 +420,9 @@ comments: true
} }
} }
``` ```
=== "Swift"
```swift title="bubble_sort.swift"
```

View file

@ -175,6 +175,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="insertion_sort.swift"
```
## 算法特性 ## 算法特性
**时间复杂度 $O(n^2)$ ** 最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。 **时间复杂度 $O(n^2)$ ** 最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。

View file

@ -388,6 +388,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="merge_sort.swift"
```
下面重点解释一下合并方法 `merge()` 的流程: 下面重点解释一下合并方法 `merge()` 的流程:
1. 初始化一个辅助数组 `tmp` 暂存待合并区间 `[left, right]` 内的元素,后续通过覆盖原数组 `nums` 的元素来实现合并; 1. 初始化一个辅助数组 `tmp` 暂存待合并区间 `[left, right]` 内的元素,后续通过覆盖原数组 `nums` 的元素来实现合并;

View file

@ -223,6 +223,12 @@ comments: true
``` ```
=== "Swift"
```swift title="quick_sort.swift"
```
!!! note "快速排序的分治思想" !!! note "快速排序的分治思想"
哨兵划分的实质是将 **一个长数组的排序问题** 简化为 **两个短数组的排序问题** 哨兵划分的实质是将 **一个长数组的排序问题** 简化为 **两个短数组的排序问题**
@ -359,6 +365,12 @@ comments: true
``` ```
=== "Swift"
```swift title="quick_sort.swift"
```
## 算法特性 ## 算法特性
**平均时间复杂度 $O(n \log n)$ ** 平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。 **平均时间复杂度 $O(n \log n)$ ** 平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。
@ -574,6 +586,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="quick_sort.swift"
```
## 尾递归优化 ## 尾递归优化
**普通快速排序在某些输入下的空间效率变差**。仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。 **普通快速排序在某些输入下的空间效率变差**。仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。
@ -652,10 +670,10 @@ comments: true
// 对两个子数组中较短的那个执行快排 // 对两个子数组中较短的那个执行快排
if pivot-left < right-pivot { if pivot-left < right-pivot {
quickSort(nums, left, pivot-1) // 递归排序左子数组 quickSort(nums, left, pivot-1) // 递归排序左子数组
left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right] left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right]
} else { } else {
quickSort(nums, pivot+1, right) // 递归排序右子数组 quickSort(nums, pivot+1, right) // 递归排序右子数组
right = pivot - 1 // 剩余待排序区间为 [left, pivot - 1] right = pivot - 1 // 剩余待排序区间为 [left, pivot - 1]
} }
} }
} }
@ -734,3 +752,9 @@ comments: true
} }
} }
``` ```
=== "Swift"
```swift title="quick_sort.swift"
```

View file

@ -192,3 +192,9 @@ comments: true
/* 判断双向队列是否为空 */ /* 判断双向队列是否为空 */
bool isEmpty = deque.Count == 0; bool isEmpty = deque.Count == 0;
``` ```
=== "Swift"
```swift title="deque.swift"
```

View file

@ -228,6 +228,12 @@ comments: true
bool isEmpty = queue.Count() == 0; bool isEmpty = queue.Count() == 0;
``` ```
=== "Swift"
```swift title="queue.swift"
```
## 队列实现 ## 队列实现
队列需要一种可以在一端添加,并在另一端删除的数据结构,也可以使用链表或数组来实现。 队列需要一种可以在一端添加,并在另一端删除的数据结构,也可以使用链表或数组来实现。
@ -612,6 +618,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="linkedlist_queue.swift"
```
### 基于数组的实现 ### 基于数组的实现
数组的删除首元素的时间复杂度为 $O(n)$ ,因此不适合直接用来实现队列。然而,我们可以借助两个指针 `front` , `rear` 来分别记录队首和队尾的索引位置,在入队 / 出队时分别将 `front` / `rear` 向后移动一位即可,这样每次仅需操作一个元素,时间复杂度降至 $O(1)$ 。 数组的删除首元素的时间复杂度为 $O(n)$ ,因此不适合直接用来实现队列。然而,我们可以借助两个指针 `front` , `rear` 来分别记录队首和队尾的索引位置,在入队 / 出队时分别将 `front` / `rear` 向后移动一位即可,这样每次仅需操作一个元素,时间复杂度降至 $O(1)$ 。
@ -1015,6 +1027,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="array_queue.swift"
```
## 队列典型应用 ## 队列典型应用
- **淘宝订单**。购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。 - **淘宝订单**。购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。

View file

@ -228,6 +228,12 @@ comments: true
bool isEmpty = stack.Count()==0; bool isEmpty = stack.Count()==0;
``` ```
=== "Swift"
```swift title="stack.swift"
```
## 栈的实现 ## 栈的实现
为了更加清晰地了解栈的运行机制,接下来我们来自己动手实现一个栈类。 为了更加清晰地了解栈的运行机制,接下来我们来自己动手实现一个栈类。
@ -591,6 +597,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="linkedlist_stack.swift"
```
### 基于数组的实现 ### 基于数组的实现
使用「数组」实现栈时,将数组的尾部当作栈顶,这样可以保证入栈与出栈操作的时间复杂度都为 $O(1)$ 。准确地说,由于入栈的元素可能是源源不断的,我们需要使用可以动态扩容的「列表」。 使用「数组」实现栈时,将数组的尾部当作栈顶,这样可以保证入栈与出栈操作的时间复杂度都为 $O(1)$ 。准确地说,由于入栈的元素可能是源源不断的,我们需要使用可以动态扩容的「列表」。
@ -870,6 +882,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="array_stack.swift"
```
!!! tip !!! tip
某些语言并未专门提供栈类,但我们可以直接把该语言的「数组」或「链表」看作栈来使用,并通过“脑补”来屏蔽无关操作,而无需像上述代码去特意包装一层。 某些语言并未专门提供栈类,但我们可以直接把该语言的「数组」或「链表」看作栈来使用,并通过“脑补”来屏蔽无关操作,而无需像上述代码去特意包装一层。

View file

@ -94,6 +94,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
} }
``` ```
=== "Swift"
```swift title="avl_tree.swift"
```
「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1** 。我们封装两个工具函数,分别用于获取与更新结点的高度。 「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1** 。我们封装两个工具函数,分别用于获取与更新结点的高度。
=== "Java" === "Java"
@ -176,6 +182,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
} }
``` ```
=== "Swift"
```swift title="avl_tree.swift"
```
### 结点平衡因子 ### 结点平衡因子
结点的「平衡因子 Balance Factor」是 **结点的左子树高度减去右子树高度**,并定义空结点的平衡因子为 0 。同样地,我们将获取结点平衡因子封装成函数,以便后续使用。 结点的「平衡因子 Balance Factor」是 **结点的左子树高度减去右子树高度**,并定义空结点的平衡因子为 0 。同样地,我们将获取结点平衡因子封装成函数,以便后续使用。
@ -247,6 +259,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
} }
``` ```
=== "Swift"
```swift title="avl_tree.swift"
```
!!! note !!! note
设平衡因子为 $f$ ,则一棵 AVL 树的任意结点的平衡因子皆满足 $-1 \le f \le 1$ 。 设平衡因子为 $f$ ,则一棵 AVL 树的任意结点的平衡因子皆满足 $-1 \le f \le 1$ 。
@ -361,6 +379,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作其可 **在不影
``` ```
=== "Swift"
```swift title="avl_tree.swift"
```
### Case 2 - 左旋 ### Case 2 - 左旋
类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。 类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。
@ -457,6 +481,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作其可 **在不影
} }
``` ```
=== "Swift"
```swift title="avl_tree.swift"
```
### Case 3 - 先左后右 ### Case 3 - 先左后右
对于下图的失衡结点 3 **单一使用左旋或右旋都无法使子树恢复平衡**,此时需要「先左旋后右旋」,即先对 `child` 执行「左旋」,再对 `node` 执行「右旋」。 对于下图的失衡结点 3 **单一使用左旋或右旋都无法使子树恢复平衡**,此时需要「先左旋后右旋」,即先对 `child` 执行「左旋」,再对 `node` 执行「右旋」。
@ -626,6 +656,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作其可 **在不影
} }
``` ```
=== "Swift"
```swift title="avl_tree.swift"
```
## AVL 树常用操作 ## AVL 树常用操作
### 插入结点 ### 插入结点
@ -744,6 +780,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作其可 **在不影
} }
``` ```
=== "Swift"
```swift title="avl_tree.swift"
```
### 删除结点 ### 删除结点
「AVL 树」删除结点操作与「二叉搜索树」删除结点操作总体相同。类似地,**在删除结点后,也需要从底至顶地执行旋转操作,使所有失衡结点恢复平衡**。 「AVL 树」删除结点操作与「二叉搜索树」删除结点操作总体相同。类似地,**在删除结点后,也需要从底至顶地执行旋转操作,使所有失衡结点恢复平衡**。
@ -902,6 +944,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作其可 **在不影
} }
``` ```
=== "Swift"
```swift title="avl_tree.swift"
```
### 查找结点 ### 查找结点
「AVL 树」的结点查找操作与「二叉搜索树」一致,在此不再赘述。 「AVL 树」的结点查找操作与「二叉搜索树」一致,在此不再赘述。

View file

@ -192,6 +192,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="binary_search_tree.swift"
```
### 插入结点 ### 插入结点
给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根结点 < 右子树的性质插入操作分为两步 给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根结点 < 右子树的性质插入操作分为两步
@ -422,6 +428,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="binary_search_tree.swift"
```
为了插入结点,需要借助 **辅助结点 `prev`** 保存上一轮循环的结点,这样在遍历到 $\text{null}$ 时,我们也可以获取到其父结点,从而完成结点插入操作。 为了插入结点,需要借助 **辅助结点 `prev`** 保存上一轮循环的结点,这样在遍历到 $\text{null}$ 时,我们也可以获取到其父结点,从而完成结点插入操作。
与查找结点相同,插入结点使用 $O(\log n)$ 时间。 与查找结点相同,插入结点使用 $O(\log n)$ 时间。
@ -808,6 +820,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="binary_search_tree.swift"
```
## 二叉搜索树的优势 ## 二叉搜索树的优势
假设给定 $n$ 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为: 假设给定 $n$ 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为:

View file

@ -106,6 +106,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title=""
```
结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点将左子结点以下的树称为该结点的「左子树 Left Subtree」右子树同理。 结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点将左子结点以下的树称为该结点的「左子树 Left Subtree」右子树同理。
除了叶结点外,每个结点都有子结点和子树。例如,若将上图的「结点 2」看作父结点那么其左子结点和右子结点分别为「结点 4」和「结点 5」左子树和右子树分别为「结点 4 以下的树」和「结点 5 以下的树」。 除了叶结点外,每个结点都有子结点和子树。例如,若将上图的「结点 2」看作父结点那么其左子结点和右子结点分别为「结点 4」和「结点 5」左子树和右子树分别为「结点 4 以下的树」和「结点 5 以下的树」。
@ -263,6 +269,12 @@ comments: true
n2.right = n5; n2.right = n5;
``` ```
=== "Swift"
```swift title="binary_tree.swift"
```
**插入与删除结点**。与链表类似,插入与删除结点都可以通过修改指针实现。 **插入与删除结点**。与链表类似,插入与删除结点都可以通过修改指针实现。
![binary_tree_add_remove](binary_tree.assets/binary_tree_add_remove.png) ![binary_tree_add_remove](binary_tree.assets/binary_tree_add_remove.png)
@ -358,6 +370,12 @@ comments: true
n1.left = n2; n1.left = n2;
``` ```
=== "Swift"
```swift title="binary_tree.swift"
```
!!! note !!! note
插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。 插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。
@ -495,6 +513,12 @@ comments: true
int?[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; int?[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };
``` ```
=== "Swift"
```swift title=""
```
![array_representation_with_empty](binary_tree.assets/array_representation_with_empty.png) ![array_representation_with_empty](binary_tree.assets/array_representation_with_empty.png)
回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。 回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。

View file

@ -185,6 +185,12 @@ comments: true
``` ```
=== "Swift"
```swift title="binary_tree_bfs.swift"
```
## 前序、中序、后序遍历 ## 前序、中序、后序遍历
相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」其体现着一种“先走到尽头再回头继续”的回溯遍历方式。 相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」其体现着一种“先走到尽头再回头继续”的回溯遍历方式。
@ -443,6 +449,12 @@ comments: true
} }
``` ```
=== "Swift"
```swift title="binary_tree_dfs.swift"
```
!!! note !!! note
使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。 使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。