mirror of
https://github.com/krahets/hello-algo.git
synced 2026-04-04 03:00:06 +08:00
Merge branch 'krahets:master' into master
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
# Editor
|
||||
.vscode/
|
||||
.idea/
|
||||
hello-algo.iml
|
||||
|
||||
# mkdocs files
|
||||
site/
|
||||
@@ -13,6 +14,3 @@ docs/overrides/
|
||||
|
||||
# python files
|
||||
__pycache__
|
||||
|
||||
# iml
|
||||
hello-algo.iml
|
||||
|
||||
@@ -15,12 +15,12 @@ int *randomNumbers(int n) {
|
||||
nums[i] = i + 1;
|
||||
}
|
||||
// 随机打乱数组元素
|
||||
for (int i = n - 1; i > 0; i--) {
|
||||
int j = rand() % (i + 1);
|
||||
int temp = nums[i];
|
||||
nums[i] = nums[j];
|
||||
nums[j] = temp;
|
||||
}
|
||||
for (int i = n - 1; i > 0; i--) {
|
||||
int j = rand() % (i + 1);
|
||||
int temp = nums[i];
|
||||
nums[i] = nums[j];
|
||||
nums[j] = temp;
|
||||
}
|
||||
return nums;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
/**
|
||||
* File: avl_tree.cpp
|
||||
* Created Time: 2022-12-2
|
||||
* Author: mgisr (maguagua0706@gmail.com)
|
||||
*/
|
||||
|
||||
#include "../include/include.hpp"
|
||||
|
||||
class AvlTree {
|
||||
private:
|
||||
TreeNode *root{};
|
||||
static bool isBalance(const TreeNode *p);
|
||||
static int getBalanceFactor(const TreeNode *p);
|
||||
static void updateHeight(TreeNode *p);
|
||||
void fixBalance(TreeNode *p);
|
||||
static bool isLeftChild(const TreeNode *p);
|
||||
static TreeNode *&fromParentTo(TreeNode *node);
|
||||
public:
|
||||
AvlTree() = default;
|
||||
AvlTree(const AvlTree &p) = default;
|
||||
const TreeNode *search(int val);
|
||||
bool insert(int val);
|
||||
bool remove(int val);
|
||||
void printTree();
|
||||
};
|
||||
|
||||
// 判断该结点是否平衡
|
||||
bool AvlTree::isBalance(const TreeNode *p) {
|
||||
int balance_factor = getBalanceFactor(p);
|
||||
if (-1 <= balance_factor && balance_factor <= 1) { return true; }
|
||||
else { return false; }
|
||||
}
|
||||
|
||||
// 获取当前结点的平衡因子
|
||||
int AvlTree::getBalanceFactor(const TreeNode *p) {
|
||||
if (p->left == nullptr && p->right == nullptr) { return 0; }
|
||||
else if (p->left == nullptr) { return (-1 - p->right->height); }
|
||||
else if (p->right == nullptr) { return p->left->height + 1; }
|
||||
else { return p->left->height - p->right->height; }
|
||||
}
|
||||
|
||||
// 更新结点高度
|
||||
void AvlTree::updateHeight(TreeNode *p) {
|
||||
if (p->left == nullptr && p->right == nullptr) { p->height = 0; }
|
||||
else if (p->left == nullptr) { p->height = p->right->height + 1; }
|
||||
else if (p->right == nullptr) { p->height = p->left->height + 1; }
|
||||
else { p->height = std::max(p->left->height, p->right->height) + 1; }
|
||||
}
|
||||
|
||||
void AvlTree::fixBalance(TreeNode *p) {
|
||||
// 左旋操作
|
||||
auto rotate_left = [&](TreeNode *node) -> TreeNode * {
|
||||
TreeNode *temp = node->right;
|
||||
temp->parent = p->parent;
|
||||
node->right = temp->left;
|
||||
if (temp->left != nullptr) {
|
||||
temp->left->parent = node;
|
||||
}
|
||||
temp->left = node;
|
||||
node->parent = temp;
|
||||
updateHeight(node);
|
||||
updateHeight(temp);
|
||||
return temp;
|
||||
};
|
||||
// 右旋操作
|
||||
auto rotate_right = [&](TreeNode *node) -> TreeNode * {
|
||||
TreeNode *temp = node->left;
|
||||
temp->parent = p->parent;
|
||||
node->left = temp->right;
|
||||
if (temp->right != nullptr) {
|
||||
temp->right->parent = node;
|
||||
}
|
||||
temp->right = node;
|
||||
node->parent = temp;
|
||||
updateHeight(node);
|
||||
updateHeight(temp);
|
||||
return temp;
|
||||
};
|
||||
// 根据规则选取旋转方式
|
||||
if (getBalanceFactor(p) > 1) {
|
||||
if (getBalanceFactor(p->left) > 0) {
|
||||
if (p->parent == nullptr) { root = rotate_right(p); }
|
||||
else { fromParentTo(p) = rotate_right(p); }
|
||||
} else {
|
||||
p->left = rotate_left(p->left);
|
||||
if (p->parent == nullptr) { root = rotate_right(p); }
|
||||
else { fromParentTo(p) = rotate_right(p); }
|
||||
}
|
||||
} else {
|
||||
if (getBalanceFactor(p->right) < 0) {
|
||||
if (p->parent == nullptr) { root = rotate_left(p); }
|
||||
else { fromParentTo(p) = rotate_left(p); }
|
||||
} else {
|
||||
p->right = rotate_right(p->right);
|
||||
if (p->parent == nullptr) { root = rotate_left(p); }
|
||||
else { fromParentTo(p) = rotate_left(p); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断当前结点是否为其父节点的左孩子
|
||||
bool AvlTree::isLeftChild(const TreeNode *p) {
|
||||
if (p->parent == nullptr) { return false; }
|
||||
return (p->parent->left == p);
|
||||
}
|
||||
|
||||
// 返回父节点指向当前结点指针的引用
|
||||
TreeNode *&AvlTree::fromParentTo(TreeNode *node) {
|
||||
if (isLeftChild(node)) { return node->parent->left; }
|
||||
else { return node->parent->right; }
|
||||
}
|
||||
|
||||
const TreeNode *AvlTree::search(int val) {
|
||||
TreeNode *p = root;
|
||||
while (p != nullptr) {
|
||||
if (p->val == val) { return p; }
|
||||
else if (p->val > val) { p = p->left; }
|
||||
else { p = p->right; }
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool AvlTree::insert(int val) {
|
||||
TreeNode *p = root;
|
||||
if (p == nullptr) {
|
||||
root = new TreeNode(val);
|
||||
return true;
|
||||
}
|
||||
for (;;) {
|
||||
if (p->val == val) { return false; }
|
||||
else if (p->val > val) {
|
||||
if (p->left == nullptr) {
|
||||
p->left = new TreeNode(val, p);
|
||||
break;
|
||||
} else {
|
||||
p = p->left;
|
||||
}
|
||||
} else {
|
||||
if (p->right == nullptr) {
|
||||
p->right = new TreeNode(val, p);
|
||||
break;
|
||||
} else {
|
||||
p = p->right;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; p != nullptr; p = p->parent) {
|
||||
if (!isBalance(p)) {
|
||||
fixBalance(p);
|
||||
break;
|
||||
} else { updateHeight(p); }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AvlTree::remove(int val) {
|
||||
TreeNode *p = root;
|
||||
if (p == nullptr) { return false; }
|
||||
while (p != nullptr) {
|
||||
if (p->val == val) {
|
||||
TreeNode *real_delete_node = p;
|
||||
TreeNode *next_node;
|
||||
if (p->left == nullptr) {
|
||||
next_node = p->right;
|
||||
if (p->parent == nullptr) { root = next_node; }
|
||||
else { fromParentTo(p) = next_node; }
|
||||
} else if (p->right == nullptr) {
|
||||
next_node = p->left;
|
||||
if (p->parent == nullptr) { root = next_node; }
|
||||
else { fromParentTo(p) = next_node; }
|
||||
} else {
|
||||
while (real_delete_node->left != nullptr) {
|
||||
real_delete_node = real_delete_node->left;
|
||||
}
|
||||
std::swap(p->val, real_delete_node->val);
|
||||
next_node = real_delete_node->right;
|
||||
if (real_delete_node->parent == p) { p->right = next_node; }
|
||||
else { real_delete_node->parent->left = next_node; }
|
||||
}
|
||||
if (next_node != nullptr) {
|
||||
next_node->parent = real_delete_node->parent;
|
||||
}
|
||||
for (p = real_delete_node; p != nullptr; p = p->parent) {
|
||||
if (!isBalance(p)) { fixBalance(p); }
|
||||
updateHeight(p);
|
||||
}
|
||||
delete real_delete_node;
|
||||
return true;
|
||||
} else if (p->val > val) {
|
||||
p = p->left;
|
||||
} else {
|
||||
p = p->right;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void inOrder(const TreeNode *root) {
|
||||
if (root == nullptr) return;
|
||||
inOrder(root->left);
|
||||
cout << root->val << ' ';
|
||||
inOrder(root->right);
|
||||
}
|
||||
|
||||
void AvlTree::printTree() {
|
||||
inOrder(root);
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
int main() {
|
||||
AvlTree tree = AvlTree();
|
||||
// tree.insert(13);
|
||||
// tree.insert(24);
|
||||
// tree.insert(37);
|
||||
// tree.insert(90);
|
||||
// tree.insert(53);
|
||||
|
||||
tree.insert(53);
|
||||
tree.insert(90);
|
||||
tree.insert(37);
|
||||
tree.insert(24);
|
||||
tree.insert(13);
|
||||
tree.remove(90);
|
||||
tree.printTree();
|
||||
const TreeNode *p = tree.search(37);
|
||||
cout << p->val;
|
||||
return 0;
|
||||
}
|
||||
@@ -30,8 +30,7 @@ vector<int> hierOrder(TreeNode* root) {
|
||||
int main() {
|
||||
/* 初始化二叉树 */
|
||||
// 这里借助了一个从数组直接生成二叉树的函数
|
||||
TreeNode* root = vecToTree(vector<int>
|
||||
{ 1, 2, 3, 4, 5, 6, 7, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX });
|
||||
TreeNode* root = vecToTree(vector<int> { 1, 2, 3, 4, 5, 6, 7 });
|
||||
cout << endl << "初始化二叉树\n" << endl;
|
||||
PrintUtil::printTree(root);
|
||||
|
||||
|
||||
@@ -41,8 +41,7 @@ void postOrder(TreeNode* root) {
|
||||
int main() {
|
||||
/* 初始化二叉树 */
|
||||
// 这里借助了一个从数组直接生成二叉树的函数
|
||||
TreeNode* root = vecToTree(vector<int>
|
||||
{ 1, 2, 3, 4, 5, 6, 7, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX});
|
||||
TreeNode* root = vecToTree(vector<int> { 1, 2, 3, 4, 5, 6, 7 });
|
||||
cout << endl << "初始化二叉树\n" << endl;
|
||||
PrintUtil::printTree(root);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <climits>
|
||||
#include "ListNode.hpp"
|
||||
#include "TreeNode.hpp"
|
||||
|
||||
|
||||
@@ -27,23 +27,24 @@ struct TreeNode {
|
||||
* @return TreeNode*
|
||||
*/
|
||||
TreeNode *vecToTree(vector<int> list) {
|
||||
if (list.empty()) {
|
||||
if (list.empty())
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto *root = new TreeNode(list[0]);
|
||||
queue<TreeNode *> que;
|
||||
size_t n = list.size(), index = 1;
|
||||
while (index < n) {
|
||||
que.emplace(root);
|
||||
size_t n = list.size(), index = 0;
|
||||
while (!que.empty()) {
|
||||
auto node = que.front();
|
||||
que.pop();
|
||||
|
||||
if (++index >= n) break;
|
||||
if (index < n) {
|
||||
node->left = new TreeNode(list[index++]);
|
||||
node->left = new TreeNode(list[index]);
|
||||
que.emplace(node->left);
|
||||
}
|
||||
if (++index >= n) break;
|
||||
if (index < n) {
|
||||
node->right = new TreeNode(list[index++]);
|
||||
node->right = new TreeNode(list[index]);
|
||||
que.emplace(node->right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@ namespace hello_algo.chapter_tree
|
||||
{
|
||||
/* 初始化二叉树 */
|
||||
// 这里借助了一个从数组直接生成二叉树的函数
|
||||
TreeNode? root = TreeNode.ArrToTree(new int?[] {
|
||||
1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null});
|
||||
TreeNode? root = TreeNode.ArrToTree(new int?[] { 1, 2, 3, 4, 5, 6, 7 });
|
||||
Console.WriteLine("\n初始化二叉树\n");
|
||||
PrintUtil.PrintTree(root);
|
||||
|
||||
|
||||
@@ -57,8 +57,7 @@ namespace hello_algo.chapter_tree
|
||||
{
|
||||
/* 初始化二叉树 */
|
||||
// 这里借助了一个从数组直接生成二叉树的函数
|
||||
TreeNode? root = TreeNode.ArrToTree(new int?[] {
|
||||
1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null});
|
||||
TreeNode? root = TreeNode.ArrToTree(new int?[] { 1, 2, 3, 4, 5, 6, 7 });
|
||||
Console.WriteLine("\n初始化二叉树\n");
|
||||
PrintUtil.PrintTree(root);
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace hello_algo.include
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a binary tree with an array
|
||||
* Generate a binary tree given an array
|
||||
* @param arr
|
||||
* @return
|
||||
*/
|
||||
@@ -31,22 +31,22 @@ namespace hello_algo.include
|
||||
TreeNode root = new TreeNode((int) arr[0]);
|
||||
Queue<TreeNode> queue = new Queue<TreeNode>();
|
||||
queue.Enqueue(root);
|
||||
int i = 1;
|
||||
while (queue.Count!=0)
|
||||
int i = 0;
|
||||
while (queue.Count != 0)
|
||||
{
|
||||
TreeNode node = queue.Dequeue();
|
||||
if (++i >= arr.Length) break;
|
||||
if (arr[i] != null)
|
||||
{
|
||||
node.left = new TreeNode((int) arr[i]);
|
||||
queue.Enqueue(node.left);
|
||||
}
|
||||
i++;
|
||||
if (++i >= arr.Length) break;
|
||||
if (arr[i] != null)
|
||||
{
|
||||
node.right = new TreeNode((int) arr[i]);
|
||||
queue.Enqueue(node.right);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
package chapter_array_and_linkedlist
|
||||
|
||||
/* 列表类简易实现 */
|
||||
type MyList struct {
|
||||
type myList struct {
|
||||
numsCapacity int
|
||||
nums []int
|
||||
numsSize int
|
||||
@@ -13,8 +13,8 @@ type MyList struct {
|
||||
}
|
||||
|
||||
/* 构造函数 */
|
||||
func newMyList() *MyList {
|
||||
return &MyList{
|
||||
func newMyList() *myList {
|
||||
return &myList{
|
||||
numsCapacity: 10, // 列表容量
|
||||
nums: make([]int, 10), // 数组(存储列表元素)
|
||||
numsSize: 0, // 列表长度(即当前元素数量)
|
||||
@@ -23,17 +23,17 @@ func newMyList() *MyList {
|
||||
}
|
||||
|
||||
/* 获取列表长度(即当前元素数量) */
|
||||
func (l *MyList) size() int {
|
||||
func (l *myList) size() int {
|
||||
return l.numsSize
|
||||
}
|
||||
|
||||
/* 获取列表容量 */
|
||||
func (l *MyList) capacity() int {
|
||||
func (l *myList) capacity() int {
|
||||
return l.numsCapacity
|
||||
}
|
||||
|
||||
/* 访问元素 */
|
||||
func (l *MyList) get(index int) int {
|
||||
func (l *myList) get(index int) int {
|
||||
// 索引如果越界则抛出异常,下同
|
||||
if index >= l.numsSize {
|
||||
panic("索引越界")
|
||||
@@ -42,7 +42,7 @@ func (l *MyList) get(index int) int {
|
||||
}
|
||||
|
||||
/* 更新元素 */
|
||||
func (l *MyList) set(num, index int) {
|
||||
func (l *myList) set(num, index int) {
|
||||
if index >= l.numsSize {
|
||||
panic("索引越界")
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func (l *MyList) set(num, index int) {
|
||||
}
|
||||
|
||||
/* 尾部添加元素 */
|
||||
func (l *MyList) add(num int) {
|
||||
func (l *myList) add(num int) {
|
||||
// 元素数量超出容量时,触发扩容机制
|
||||
if l.numsSize == l.numsCapacity {
|
||||
l.extendCapacity()
|
||||
@@ -61,7 +61,7 @@ func (l *MyList) add(num int) {
|
||||
}
|
||||
|
||||
/* 中间插入元素 */
|
||||
func (l *MyList) insert(num, index int) {
|
||||
func (l *myList) insert(num, index int) {
|
||||
if index >= l.numsSize {
|
||||
panic("索引越界")
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func (l *MyList) insert(num, index int) {
|
||||
}
|
||||
|
||||
/* 删除元素 */
|
||||
func (l *MyList) remove(index int) int {
|
||||
func (l *myList) remove(index int) int {
|
||||
if index >= l.numsSize {
|
||||
panic("索引越界")
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func (l *MyList) remove(index int) int {
|
||||
}
|
||||
|
||||
/* 列表扩容 */
|
||||
func (l *MyList) extendCapacity() {
|
||||
func (l *myList) extendCapacity() {
|
||||
// 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组
|
||||
l.nums = append(l.nums, make([]int, l.numsCapacity*(l.extendRatio-1))...)
|
||||
// 更新列表容量
|
||||
@@ -103,7 +103,7 @@ func (l *MyList) extendCapacity() {
|
||||
}
|
||||
|
||||
/* 返回有效长度的列表 */
|
||||
func (l *MyList) toArray() []int {
|
||||
func (l *myList) toArray() []int {
|
||||
// 仅转换有效长度范围内的列表元素
|
||||
return l.nums[:l.numsSize]
|
||||
}
|
||||
|
||||
@@ -9,31 +9,31 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
/* Node 结构体 */
|
||||
type Node struct {
|
||||
/* 结构体 */
|
||||
type node struct {
|
||||
val int
|
||||
next *Node
|
||||
next *node
|
||||
}
|
||||
|
||||
/* TreeNode 二叉树 */
|
||||
type TreeNode struct {
|
||||
/* treeNode 二叉树 */
|
||||
type treeNode struct {
|
||||
val int
|
||||
left *TreeNode
|
||||
right *TreeNode
|
||||
left *treeNode
|
||||
right *treeNode
|
||||
}
|
||||
|
||||
/* 创建 Node 结构体 */
|
||||
func newNode(val int) *Node {
|
||||
return &Node{val: val}
|
||||
/* 创建 node 结构体 */
|
||||
func newNode(val int) *node {
|
||||
return &node{val: val}
|
||||
}
|
||||
|
||||
/* 创建 TreeNode 结构体 */
|
||||
func newTreeNode(val int) *TreeNode {
|
||||
return &TreeNode{val: val}
|
||||
/* 创建 treeNode 结构体 */
|
||||
func newTreeNode(val int) *treeNode {
|
||||
return &treeNode{val: val}
|
||||
}
|
||||
|
||||
/* 输出二叉树 */
|
||||
func printTree(root *TreeNode) {
|
||||
func printTree(root *treeNode) {
|
||||
if root == nil {
|
||||
return
|
||||
}
|
||||
@@ -72,7 +72,7 @@ func spaceLinear(n int) {
|
||||
// 长度为 n 的数组占用 O(n) 空间
|
||||
_ = make([]int, n)
|
||||
// 长度为 n 的列表占用 O(n) 空间
|
||||
var nodes []*Node
|
||||
var nodes []*node
|
||||
for i := 0; i < n; i++ {
|
||||
nodes = append(nodes, newNode(i))
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func spaceQuadraticRecur(n int) int {
|
||||
}
|
||||
|
||||
/* 指数阶(建立满二叉树) */
|
||||
func buildTree(n int) *TreeNode {
|
||||
func buildTree(n int) *treeNode {
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,30 +7,30 @@ package chapter_hashing
|
||||
import "fmt"
|
||||
|
||||
/* 键值对 int->String */
|
||||
type Entry struct {
|
||||
type entry struct {
|
||||
key int
|
||||
val string
|
||||
}
|
||||
|
||||
/* 基于数组简易实现的哈希表 */
|
||||
type ArrayHashMap struct {
|
||||
bucket []*Entry
|
||||
type arrayHashMap struct {
|
||||
bucket []*entry
|
||||
}
|
||||
|
||||
func newArrayHashMap() *ArrayHashMap {
|
||||
func newArrayHashMap() *arrayHashMap {
|
||||
// 初始化一个长度为 100 的桶(数组)
|
||||
bucket := make([]*Entry, 100)
|
||||
return &ArrayHashMap{bucket: bucket}
|
||||
bucket := make([]*entry, 100)
|
||||
return &arrayHashMap{bucket: bucket}
|
||||
}
|
||||
|
||||
/* 哈希函数 */
|
||||
func (a *ArrayHashMap) hashFunc(key int) int {
|
||||
func (a *arrayHashMap) hashFunc(key int) int {
|
||||
index := key % 100
|
||||
return index
|
||||
}
|
||||
|
||||
/* 查询操作 */
|
||||
func (a *ArrayHashMap) get(key int) string {
|
||||
func (a *arrayHashMap) get(key int) string {
|
||||
index := a.hashFunc(key)
|
||||
pair := a.bucket[index]
|
||||
if pair == nil {
|
||||
@@ -40,22 +40,22 @@ func (a *ArrayHashMap) get(key int) string {
|
||||
}
|
||||
|
||||
/* 添加操作 */
|
||||
func (a *ArrayHashMap) put(key int, val string) {
|
||||
pair := &Entry{key: key, val: val}
|
||||
func (a *arrayHashMap) put(key int, val string) {
|
||||
pair := &entry{key: key, val: val}
|
||||
index := a.hashFunc(key)
|
||||
a.bucket[index] = pair
|
||||
}
|
||||
|
||||
/* 删除操作 */
|
||||
func (a *ArrayHashMap) remove(key int) {
|
||||
func (a *arrayHashMap) remove(key int) {
|
||||
index := a.hashFunc(key)
|
||||
// 置为 nil ,代表删除
|
||||
a.bucket[index] = nil
|
||||
}
|
||||
|
||||
/* 获取所有键对 */
|
||||
func (a *ArrayHashMap) entrySet() []*Entry {
|
||||
var pairs []*Entry
|
||||
func (a *arrayHashMap) entrySet() []*entry {
|
||||
var pairs []*entry
|
||||
for _, pair := range a.bucket {
|
||||
if pair != nil {
|
||||
pairs = append(pairs, pair)
|
||||
@@ -65,7 +65,7 @@ func (a *ArrayHashMap) entrySet() []*Entry {
|
||||
}
|
||||
|
||||
/* 获取所有键 */
|
||||
func (a *ArrayHashMap) keySet() []int {
|
||||
func (a *arrayHashMap) keySet() []int {
|
||||
var keys []int
|
||||
for _, pair := range a.bucket {
|
||||
if pair != nil {
|
||||
@@ -76,7 +76,7 @@ func (a *ArrayHashMap) keySet() []int {
|
||||
}
|
||||
|
||||
/* 获取所有值 */
|
||||
func (a *ArrayHashMap) valueSet() []string {
|
||||
func (a *arrayHashMap) valueSet() []string {
|
||||
var values []string
|
||||
for _, pair := range a.bucket {
|
||||
if pair != nil {
|
||||
@@ -87,7 +87,7 @@ func (a *ArrayHashMap) valueSet() []string {
|
||||
}
|
||||
|
||||
/* 打印哈希表 */
|
||||
func (a *ArrayHashMap) print() {
|
||||
func (a *arrayHashMap) print() {
|
||||
for _, pair := range a.bucket {
|
||||
if pair != nil {
|
||||
fmt.Println(pair.key, "->", pair.val)
|
||||
|
||||
@@ -6,8 +6,9 @@ package chapter_searching
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/krahets/hello-algo/pkg"
|
||||
"testing"
|
||||
|
||||
. "github.com/krahets/hello-algo/pkg"
|
||||
)
|
||||
|
||||
func TestHashingSearch(t *testing.T) {
|
||||
|
||||
@@ -8,25 +8,25 @@ package chapter_sorting
|
||||
// 左子数组区间 [left, mid]
|
||||
// 右子数组区间 [mid + 1, right]
|
||||
func merge(nums []int, left, mid, right int) {
|
||||
// 初始化辅助数组 借助 copy模块
|
||||
// 初始化辅助数组 借助 copy 模块
|
||||
tmp := make([]int, right-left+1)
|
||||
for i := left; i <= right; i++ {
|
||||
tmp[i-left] = nums[i]
|
||||
}
|
||||
// 左子数组的起始索引和结束索引
|
||||
left_start, left_end := left-left, mid-left
|
||||
leftStart, leftEnd := left-left, mid-left
|
||||
// 右子数组的起始索引和结束索引
|
||||
right_start, right_end := mid+1-left, right-left
|
||||
rightStart, rightEnd := mid+1-left, right-left
|
||||
// i, j 分别指向左子数组、右子数组的首元素
|
||||
i, j := left_start, right_start
|
||||
i, j := leftStart, rightStart
|
||||
// 通过覆盖原数组 nums 来合并左子数组和右子数组
|
||||
for k := left; k <= right; k++ {
|
||||
// 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
||||
if i > left_end {
|
||||
if i > leftEnd {
|
||||
nums[k] = tmp[j]
|
||||
j++
|
||||
// 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
|
||||
} else if j > right_end || tmp[i] <= tmp[j] {
|
||||
} else if j > rightEnd || tmp[i] <= tmp[j] {
|
||||
nums[k] = tmp[i]
|
||||
i++
|
||||
// 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
package chapter_sorting
|
||||
|
||||
// 快速排序
|
||||
type QuickSort struct{}
|
||||
type quickSort struct{}
|
||||
|
||||
// 快速排序(中位基准数优化)
|
||||
type QuickSortMedian struct{}
|
||||
type quickSortMedian struct{}
|
||||
|
||||
// 快速排序(尾递归优化)
|
||||
type QuickSortTailCall struct{}
|
||||
type quickSortTailCall struct{}
|
||||
|
||||
/* 哨兵划分 */
|
||||
func (q *QuickSort) partition(nums []int, left, right int) int {
|
||||
func (q *quickSort) partition(nums []int, left, right int) int {
|
||||
// 以 nums[left] 作为基准数
|
||||
i, j := left, right
|
||||
for i < j {
|
||||
@@ -33,7 +33,7 @@ func (q *QuickSort) partition(nums []int, left, right int) int {
|
||||
}
|
||||
|
||||
/* 快速排序 */
|
||||
func (q *QuickSort) quickSort(nums []int, left, right int) {
|
||||
func (q *quickSort) quickSort(nums []int, left, right int) {
|
||||
// 子数组长度为 1 时终止递归
|
||||
if left >= right {
|
||||
return
|
||||
@@ -46,7 +46,7 @@ func (q *QuickSort) quickSort(nums []int, left, right int) {
|
||||
}
|
||||
|
||||
/* 选取三个元素的中位数 */
|
||||
func (q *QuickSortMedian) medianThree(nums []int, left, mid, right int) int {
|
||||
func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int {
|
||||
if (nums[left] > nums[mid]) != (nums[left] > nums[right]) {
|
||||
return left
|
||||
} else if (nums[mid] < nums[left]) != (nums[mid] > nums[right]) {
|
||||
@@ -56,7 +56,7 @@ func (q *QuickSortMedian) medianThree(nums []int, left, mid, right int) int {
|
||||
}
|
||||
|
||||
/* 哨兵划分(三数取中值)*/
|
||||
func (q *QuickSortMedian) partition(nums []int, left, right int) int {
|
||||
func (q *quickSortMedian) partition(nums []int, left, right int) int {
|
||||
// 以 nums[left] 作为基准数
|
||||
med := q.medianThree(nums, left, (left+right)/2, right)
|
||||
// 将中位数交换至数组最左端
|
||||
@@ -79,7 +79,7 @@ func (q *QuickSortMedian) partition(nums []int, left, right int) int {
|
||||
}
|
||||
|
||||
/* 快速排序 */
|
||||
func (q *QuickSortMedian) quickSort(nums []int, left, right int) {
|
||||
func (q *quickSortMedian) quickSort(nums []int, left, right int) {
|
||||
// 子数组长度为 1 时终止递归
|
||||
if left >= right {
|
||||
return
|
||||
@@ -92,7 +92,7 @@ func (q *QuickSortMedian) quickSort(nums []int, left, right int) {
|
||||
}
|
||||
|
||||
/* 哨兵划分 */
|
||||
func (q *QuickSortTailCall) partition(nums []int, left, right int) int {
|
||||
func (q *quickSortTailCall) partition(nums []int, left, right int) int {
|
||||
// 以 nums[left] 作为基准数
|
||||
i, j := left, right
|
||||
for i < j {
|
||||
@@ -111,7 +111,7 @@ func (q *QuickSortTailCall) partition(nums []int, left, right int) int {
|
||||
}
|
||||
|
||||
/* 快速排序(尾递归优化)*/
|
||||
func (q *QuickSortTailCall) quickSort(nums []int, left, right int) {
|
||||
func (q *quickSortTailCall) quickSort(nums []int, left, right int) {
|
||||
// 子数组长度为 1 时终止
|
||||
for left < right {
|
||||
// 哨兵划分操作
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// 快速排序
|
||||
func TestQuickSort(t *testing.T) {
|
||||
q := QuickSort{}
|
||||
q := quickSort{}
|
||||
nums := []int{4, 1, 3, 1, 5, 2}
|
||||
q.quickSort(nums, 0, len(nums)-1)
|
||||
fmt.Println("快速排序完成后 nums = ", nums)
|
||||
@@ -19,7 +19,7 @@ func TestQuickSort(t *testing.T) {
|
||||
|
||||
// 快速排序(中位基准数优化)
|
||||
func TestQuickSortMedian(t *testing.T) {
|
||||
q := QuickSortMedian{}
|
||||
q := quickSortMedian{}
|
||||
nums := []int{4, 1, 3, 1, 5, 2}
|
||||
q.quickSort(nums, 0, len(nums)-1)
|
||||
fmt.Println("快速排序(中位基准数优化)完成后 nums = ", nums)
|
||||
@@ -27,7 +27,7 @@ func TestQuickSortMedian(t *testing.T) {
|
||||
|
||||
// 快速排序(尾递归优化)
|
||||
func TestQuickSortTailCall(t *testing.T) {
|
||||
q := QuickSortTailCall{}
|
||||
q := quickSortTailCall{}
|
||||
nums := []int{4, 1, 3, 1, 5, 2}
|
||||
q.quickSort(nums, 0, len(nums)-1)
|
||||
fmt.Println("快速排序(尾递归优化)完成后 nums = ", nums)
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
package chapter_stack_and_queue
|
||||
|
||||
/* 基于环形数组实现的队列 */
|
||||
type ArrayQueue struct {
|
||||
type arrayQueue struct {
|
||||
data []int // 用于存储队列元素的数组
|
||||
capacity int // 队列容量(即最多容量的元素个数)
|
||||
front int // 头指针,指向队首
|
||||
rear int // 尾指针,指向队尾 + 1
|
||||
}
|
||||
|
||||
// NewArrayQueue 基于环形数组实现的队列
|
||||
func NewArrayQueue(capacity int) *ArrayQueue {
|
||||
return &ArrayQueue{
|
||||
// newArrayQueue 基于环形数组实现的队列
|
||||
func newArrayQueue(capacity int) *arrayQueue {
|
||||
return &arrayQueue{
|
||||
data: make([]int, capacity),
|
||||
capacity: capacity,
|
||||
front: 0,
|
||||
@@ -22,21 +22,21 @@ func NewArrayQueue(capacity int) *ArrayQueue {
|
||||
}
|
||||
}
|
||||
|
||||
// Size 获取队列的长度
|
||||
func (q *ArrayQueue) Size() int {
|
||||
// size 获取队列的长度
|
||||
func (q *arrayQueue) size() int {
|
||||
size := (q.capacity + q.rear - q.front) % q.capacity
|
||||
return size
|
||||
}
|
||||
|
||||
// IsEmpty 判断队列是否为空
|
||||
func (q *ArrayQueue) IsEmpty() bool {
|
||||
// isEmpty 判断队列是否为空
|
||||
func (q *arrayQueue) isEmpty() bool {
|
||||
return q.rear-q.front == 0
|
||||
}
|
||||
|
||||
// Offer 入队
|
||||
func (q *ArrayQueue) Offer(v int) {
|
||||
// offer 入队
|
||||
func (q *arrayQueue) offer(v int) {
|
||||
// 当 rear == capacity 表示队列已满
|
||||
if q.Size() == q.capacity {
|
||||
if q.size() == q.capacity {
|
||||
return
|
||||
}
|
||||
// 尾结点后添加
|
||||
@@ -45,9 +45,9 @@ func (q *ArrayQueue) Offer(v int) {
|
||||
q.rear = (q.rear + 1) % q.capacity
|
||||
}
|
||||
|
||||
// Poll 出队
|
||||
func (q *ArrayQueue) Poll() any {
|
||||
if q.IsEmpty() {
|
||||
// poll 出队
|
||||
func (q *arrayQueue) poll() any {
|
||||
if q.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
v := q.data[q.front]
|
||||
@@ -56,9 +56,9 @@ func (q *ArrayQueue) Poll() any {
|
||||
return v
|
||||
}
|
||||
|
||||
// Peek 访问队首元素
|
||||
func (q *ArrayQueue) Peek() any {
|
||||
if q.IsEmpty() {
|
||||
// peek 访问队首元素
|
||||
func (q *arrayQueue) peek() any {
|
||||
if q.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
v := q.data[q.front]
|
||||
@@ -66,6 +66,6 @@ func (q *ArrayQueue) Peek() any {
|
||||
}
|
||||
|
||||
// 获取 Slice 用于打印
|
||||
func (s *ArrayQueue) toSlice() []int {
|
||||
return s.data[s.front:s.rear]
|
||||
func (q *arrayQueue) toSlice() []int {
|
||||
return q.data[q.front:q.rear]
|
||||
}
|
||||
|
||||
@@ -5,47 +5,47 @@
|
||||
package chapter_stack_and_queue
|
||||
|
||||
/* 基于数组实现的栈 */
|
||||
type ArrayStack struct {
|
||||
type arrayStack struct {
|
||||
data []int // 数据
|
||||
}
|
||||
|
||||
func NewArrayStack() *ArrayStack {
|
||||
return &ArrayStack{
|
||||
func newArrayStack() *arrayStack {
|
||||
return &arrayStack{
|
||||
// 设置栈的长度为 0,容量为 16
|
||||
data: make([]int, 0, 16),
|
||||
}
|
||||
}
|
||||
|
||||
// Size 栈的长度
|
||||
func (s *ArrayStack) Size() int {
|
||||
// size 栈的长度
|
||||
func (s *arrayStack) size() int {
|
||||
return len(s.data)
|
||||
}
|
||||
|
||||
// IsEmpty 栈是否为空
|
||||
func (s *ArrayStack) IsEmpty() bool {
|
||||
return s.Size() == 0
|
||||
// isEmpty 栈是否为空
|
||||
func (s *arrayStack) isEmpty() bool {
|
||||
return s.size() == 0
|
||||
}
|
||||
|
||||
// Push 入栈
|
||||
func (s *ArrayStack) Push(v int) {
|
||||
// push 入栈
|
||||
func (s *arrayStack) push(v int) {
|
||||
// 切片会自动扩容
|
||||
s.data = append(s.data, v)
|
||||
}
|
||||
|
||||
// Pop 出栈
|
||||
func (s *ArrayStack) Pop() any {
|
||||
// pop 出栈
|
||||
func (s *arrayStack) pop() any {
|
||||
// 弹出栈前,先判断是否为空
|
||||
if s.IsEmpty() {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
val := s.Peek()
|
||||
val := s.peek()
|
||||
s.data = s.data[:len(s.data)-1]
|
||||
return val
|
||||
}
|
||||
|
||||
// Peek 获取栈顶元素
|
||||
func (s *ArrayStack) Peek() any {
|
||||
if s.IsEmpty() {
|
||||
// peek 获取栈顶元素
|
||||
func (s *arrayStack) peek() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
val := s.data[len(s.data)-1]
|
||||
@@ -53,6 +53,6 @@ func (s *ArrayStack) Peek() any {
|
||||
}
|
||||
|
||||
// 获取 Slice 用于打印
|
||||
func (s *ArrayStack) toSlice() []int {
|
||||
func (s *arrayStack) toSlice() []int {
|
||||
return s.data
|
||||
}
|
||||
|
||||
@@ -51,48 +51,48 @@ func TestDeque(t *testing.T) {
|
||||
|
||||
func TestLinkedListDeque(t *testing.T) {
|
||||
// 初始化队列
|
||||
deque := NewLinkedListDeque()
|
||||
deque := newLinkedListDeque()
|
||||
|
||||
// 元素入队
|
||||
deque.OfferLast(2)
|
||||
deque.OfferLast(5)
|
||||
deque.OfferLast(4)
|
||||
deque.OfferFirst(3)
|
||||
deque.OfferFirst(1)
|
||||
deque.offerLast(2)
|
||||
deque.offerLast(5)
|
||||
deque.offerLast(4)
|
||||
deque.offerFirst(3)
|
||||
deque.offerFirst(1)
|
||||
fmt.Print("队列 deque = ")
|
||||
PrintList(deque.toList())
|
||||
|
||||
// 访问队首元素
|
||||
front := deque.PeekFirst()
|
||||
front := deque.peekFirst()
|
||||
fmt.Println("队首元素 front =", front)
|
||||
rear := deque.PeekLast()
|
||||
rear := deque.peekLast()
|
||||
fmt.Println("队尾元素 rear =", rear)
|
||||
|
||||
// 元素出队
|
||||
pollFirst := deque.PollFirst()
|
||||
pollFirst := deque.pollFirst()
|
||||
fmt.Print("队首出队元素 pollFirst = ", pollFirst, ",队首出队后 deque = ")
|
||||
PrintList(deque.toList())
|
||||
pollLast := deque.PollLast()
|
||||
pollLast := deque.pollLast()
|
||||
fmt.Print("队尾出队元素 pollLast = ", pollLast, ",队尾出队后 deque = ")
|
||||
PrintList(deque.toList())
|
||||
|
||||
// 获取队的长度
|
||||
size := deque.Size()
|
||||
size := deque.size()
|
||||
fmt.Println("队的长度 size =", size)
|
||||
|
||||
// 判断是否为空
|
||||
isEmpty := deque.IsEmpty()
|
||||
isEmpty := deque.isEmpty()
|
||||
fmt.Println("队是否为空 =", isEmpty)
|
||||
}
|
||||
|
||||
// BenchmarkArrayQueue 67.92 ns/op in Mac M1 Pro
|
||||
func BenchmarkLinkedListDeque(b *testing.B) {
|
||||
stack := NewLinkedListDeque()
|
||||
stack := newLinkedListDeque()
|
||||
// use b.N for looping
|
||||
for i := 0; i < b.N; i++ {
|
||||
stack.OfferLast(777)
|
||||
stack.offerLast(777)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
stack.PollFirst()
|
||||
stack.pollFirst()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,31 +8,31 @@ import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
// LinkedListDeque 基于链表实现的双端队列, 使用内置包 list 来实现栈
|
||||
type LinkedListDeque struct {
|
||||
// linkedListDeque 基于链表实现的双端队列, 使用内置包 list 来实现栈
|
||||
type linkedListDeque struct {
|
||||
data *list.List
|
||||
}
|
||||
|
||||
// NewLinkedListDeque 初始化双端队列
|
||||
func NewLinkedListDeque() *LinkedListDeque {
|
||||
return &LinkedListDeque{
|
||||
// newLinkedListDeque 初始化双端队列
|
||||
func newLinkedListDeque() *linkedListDeque {
|
||||
return &linkedListDeque{
|
||||
data: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// OfferFirst 队首元素入队
|
||||
func (s *LinkedListDeque) OfferFirst(value any) {
|
||||
// offerFirst 队首元素入队
|
||||
func (s *linkedListDeque) offerFirst(value any) {
|
||||
s.data.PushFront(value)
|
||||
}
|
||||
|
||||
// OfferLast 队尾元素入队
|
||||
func (s *LinkedListDeque) OfferLast(value any) {
|
||||
// offerLast 队尾元素入队
|
||||
func (s *linkedListDeque) offerLast(value any) {
|
||||
s.data.PushBack(value)
|
||||
}
|
||||
|
||||
// PollFirst 队首元素出队
|
||||
func (s *LinkedListDeque) PollFirst() any {
|
||||
if s.IsEmpty() {
|
||||
// pollFirst 队首元素出队
|
||||
func (s *linkedListDeque) pollFirst() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
e := s.data.Front()
|
||||
@@ -40,9 +40,9 @@ func (s *LinkedListDeque) PollFirst() any {
|
||||
return e.Value
|
||||
}
|
||||
|
||||
// PollLast 队尾元素出队
|
||||
func (s *LinkedListDeque) PollLast() any {
|
||||
if s.IsEmpty() {
|
||||
// pollLast 队尾元素出队
|
||||
func (s *linkedListDeque) pollLast() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
e := s.data.Back()
|
||||
@@ -50,35 +50,35 @@ func (s *LinkedListDeque) PollLast() any {
|
||||
return e.Value
|
||||
}
|
||||
|
||||
// PeekFirst 访问队首元素
|
||||
func (s *LinkedListDeque) PeekFirst() any {
|
||||
if s.IsEmpty() {
|
||||
// peekFirst 访问队首元素
|
||||
func (s *linkedListDeque) peekFirst() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
e := s.data.Front()
|
||||
return e.Value
|
||||
}
|
||||
|
||||
// PeekLast 访问队尾元素
|
||||
func (s *LinkedListDeque) PeekLast() any {
|
||||
if s.IsEmpty() {
|
||||
// peekLast 访问队尾元素
|
||||
func (s *linkedListDeque) peekLast() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
e := s.data.Back()
|
||||
return e.Value
|
||||
}
|
||||
|
||||
// Size 获取队列的长度
|
||||
func (s *LinkedListDeque) Size() int {
|
||||
// size 获取队列的长度
|
||||
func (s *linkedListDeque) size() int {
|
||||
return s.data.Len()
|
||||
}
|
||||
|
||||
// IsEmpty 判断队列是否为空
|
||||
func (s *LinkedListDeque) IsEmpty() bool {
|
||||
// isEmpty 判断队列是否为空
|
||||
func (s *linkedListDeque) isEmpty() bool {
|
||||
return s.data.Len() == 0
|
||||
}
|
||||
|
||||
// 获取 List 用于打印
|
||||
func (s *LinkedListDeque) toList() *list.List {
|
||||
func (s *linkedListDeque) toList() *list.List {
|
||||
return s.data
|
||||
}
|
||||
|
||||
@@ -9,26 +9,26 @@ import (
|
||||
)
|
||||
|
||||
/* 基于链表实现的队列 */
|
||||
type LinkedListQueue struct {
|
||||
type linkedListQueue struct {
|
||||
// 使用内置包 list 来实现队列
|
||||
data *list.List
|
||||
}
|
||||
|
||||
// NewLinkedListQueue 初始化链表
|
||||
func NewLinkedListQueue() *LinkedListQueue {
|
||||
return &LinkedListQueue{
|
||||
// newLinkedListQueue 初始化链表
|
||||
func newLinkedListQueue() *linkedListQueue {
|
||||
return &linkedListQueue{
|
||||
data: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// Offer 入队
|
||||
func (s *LinkedListQueue) Offer(value any) {
|
||||
// offer 入队
|
||||
func (s *linkedListQueue) offer(value any) {
|
||||
s.data.PushBack(value)
|
||||
}
|
||||
|
||||
// Poll 出队
|
||||
func (s *LinkedListQueue) Poll() any {
|
||||
if s.IsEmpty() {
|
||||
// poll 出队
|
||||
func (s *linkedListQueue) poll() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
e := s.data.Front()
|
||||
@@ -36,26 +36,26 @@ func (s *LinkedListQueue) Poll() any {
|
||||
return e.Value
|
||||
}
|
||||
|
||||
// Peek 访问队首元素
|
||||
func (s *LinkedListQueue) Peek() any {
|
||||
if s.IsEmpty() {
|
||||
// peek 访问队首元素
|
||||
func (s *linkedListQueue) peek() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
e := s.data.Front()
|
||||
return e.Value
|
||||
}
|
||||
|
||||
// Size 获取队列的长度
|
||||
func (s *LinkedListQueue) Size() int {
|
||||
// size 获取队列的长度
|
||||
func (s *linkedListQueue) size() int {
|
||||
return s.data.Len()
|
||||
}
|
||||
|
||||
// IsEmpty 判断队列是否为空
|
||||
func (s *LinkedListQueue) IsEmpty() bool {
|
||||
// isEmpty 判断队列是否为空
|
||||
func (s *linkedListQueue) isEmpty() bool {
|
||||
return s.data.Len() == 0
|
||||
}
|
||||
|
||||
// 获取 List 用于打印
|
||||
func (s *LinkedListQueue) toList() *list.List {
|
||||
func (s *linkedListQueue) toList() *list.List {
|
||||
return s.data
|
||||
}
|
||||
|
||||
@@ -9,26 +9,26 @@ import (
|
||||
)
|
||||
|
||||
/* 基于链表实现的栈 */
|
||||
type LinkedListStack struct {
|
||||
type linkedListStack struct {
|
||||
// 使用内置包 list 来实现栈
|
||||
data *list.List
|
||||
}
|
||||
|
||||
// NewLinkedListStack 初始化链表
|
||||
func NewLinkedListStack() *LinkedListStack {
|
||||
return &LinkedListStack{
|
||||
// newLinkedListStack 初始化链表
|
||||
func newLinkedListStack() *linkedListStack {
|
||||
return &linkedListStack{
|
||||
data: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// Push 入栈
|
||||
func (s *LinkedListStack) Push(value int) {
|
||||
// push 入栈
|
||||
func (s *linkedListStack) push(value int) {
|
||||
s.data.PushBack(value)
|
||||
}
|
||||
|
||||
// Pop 出栈
|
||||
func (s *LinkedListStack) Pop() any {
|
||||
if s.IsEmpty() {
|
||||
// pop 出栈
|
||||
func (s *linkedListStack) pop() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
e := s.data.Back()
|
||||
@@ -36,26 +36,26 @@ func (s *LinkedListStack) Pop() any {
|
||||
return e.Value
|
||||
}
|
||||
|
||||
// Peek 访问栈顶元素
|
||||
func (s *LinkedListStack) Peek() any {
|
||||
if s.IsEmpty() {
|
||||
// peek 访问栈顶元素
|
||||
func (s *linkedListStack) peek() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
e := s.data.Back()
|
||||
return e.Value
|
||||
}
|
||||
|
||||
// Size 获取栈的长度
|
||||
func (s *LinkedListStack) Size() int {
|
||||
// size 获取栈的长度
|
||||
func (s *linkedListStack) size() int {
|
||||
return s.data.Len()
|
||||
}
|
||||
|
||||
// IsEmpty 判断栈是否为空
|
||||
func (s *LinkedListStack) IsEmpty() bool {
|
||||
// isEmpty 判断栈是否为空
|
||||
func (s *linkedListStack) isEmpty() bool {
|
||||
return s.data.Len() == 0
|
||||
}
|
||||
|
||||
// 获取 List 用于打印
|
||||
func (s *LinkedListStack) toList() *list.List {
|
||||
func (s *linkedListStack) toList() *list.List {
|
||||
return s.data
|
||||
}
|
||||
|
||||
@@ -48,87 +48,87 @@ func TestQueue(t *testing.T) {
|
||||
func TestArrayQueue(t *testing.T) {
|
||||
// 初始化队列,使用队列的通用接口
|
||||
capacity := 10
|
||||
queue := NewArrayQueue(capacity)
|
||||
queue := newArrayQueue(capacity)
|
||||
|
||||
// 元素入队
|
||||
queue.Offer(1)
|
||||
queue.Offer(3)
|
||||
queue.Offer(2)
|
||||
queue.Offer(5)
|
||||
queue.Offer(4)
|
||||
queue.offer(1)
|
||||
queue.offer(3)
|
||||
queue.offer(2)
|
||||
queue.offer(5)
|
||||
queue.offer(4)
|
||||
fmt.Print("队列 queue = ")
|
||||
PrintSlice(queue.toSlice())
|
||||
|
||||
// 访问队首元素
|
||||
peek := queue.Peek()
|
||||
peek := queue.peek()
|
||||
fmt.Println("队首元素 peek =", peek)
|
||||
|
||||
// 元素出队
|
||||
poll := queue.Poll()
|
||||
poll := queue.poll()
|
||||
fmt.Print("出队元素 poll = ", poll, ", 出队后 queue = ")
|
||||
PrintSlice(queue.toSlice())
|
||||
|
||||
// 获取队的长度
|
||||
size := queue.Size()
|
||||
size := queue.size()
|
||||
fmt.Println("队的长度 size =", size)
|
||||
|
||||
// 判断是否为空
|
||||
isEmpty := queue.IsEmpty()
|
||||
isEmpty := queue.isEmpty()
|
||||
fmt.Println("队是否为空 =", isEmpty)
|
||||
}
|
||||
|
||||
func TestLinkedListQueue(t *testing.T) {
|
||||
// 初始化队
|
||||
queue := NewLinkedListQueue()
|
||||
queue := newLinkedListQueue()
|
||||
|
||||
// 元素入队
|
||||
queue.Offer(1)
|
||||
queue.Offer(3)
|
||||
queue.Offer(2)
|
||||
queue.Offer(5)
|
||||
queue.Offer(4)
|
||||
queue.offer(1)
|
||||
queue.offer(3)
|
||||
queue.offer(2)
|
||||
queue.offer(5)
|
||||
queue.offer(4)
|
||||
fmt.Print("队列 queue = ")
|
||||
PrintList(queue.toList())
|
||||
|
||||
// 访问队首元素
|
||||
peek := queue.Peek()
|
||||
peek := queue.peek()
|
||||
fmt.Println("队首元素 peek =", peek)
|
||||
|
||||
// 元素出队
|
||||
poll := queue.Poll()
|
||||
poll := queue.poll()
|
||||
fmt.Print("出队元素 poll = ", poll, ", 出队后 queue = ")
|
||||
PrintList(queue.toList())
|
||||
|
||||
// 获取队的长度
|
||||
size := queue.Size()
|
||||
size := queue.size()
|
||||
fmt.Println("队的长度 size =", size)
|
||||
|
||||
// 判断是否为空
|
||||
isEmpty := queue.IsEmpty()
|
||||
isEmpty := queue.isEmpty()
|
||||
fmt.Println("队是否为空 =", isEmpty)
|
||||
}
|
||||
|
||||
// BenchmarkArrayQueue 8 ns/op in Mac M1 Pro
|
||||
func BenchmarkArrayQueue(b *testing.B) {
|
||||
capacity := 1000
|
||||
stack := NewArrayQueue(capacity)
|
||||
stack := newArrayQueue(capacity)
|
||||
// use b.N for looping
|
||||
for i := 0; i < b.N; i++ {
|
||||
stack.Offer(777)
|
||||
stack.offer(777)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
stack.Poll()
|
||||
stack.poll()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro
|
||||
func BenchmarkLinkedQueue(b *testing.B) {
|
||||
stack := NewLinkedListQueue()
|
||||
stack := newLinkedListQueue()
|
||||
// use b.N for looping
|
||||
for i := 0; i < b.N; i++ {
|
||||
stack.Offer(777)
|
||||
stack.offer(777)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
stack.Poll()
|
||||
stack.poll()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,85 +46,85 @@ func TestStack(t *testing.T) {
|
||||
|
||||
func TestArrayStack(t *testing.T) {
|
||||
// 初始化栈, 使用接口承接
|
||||
stack := NewArrayStack()
|
||||
stack := newArrayStack()
|
||||
|
||||
// 元素入栈
|
||||
stack.Push(1)
|
||||
stack.Push(3)
|
||||
stack.Push(2)
|
||||
stack.Push(5)
|
||||
stack.Push(4)
|
||||
stack.push(1)
|
||||
stack.push(3)
|
||||
stack.push(2)
|
||||
stack.push(5)
|
||||
stack.push(4)
|
||||
fmt.Print("栈 stack = ")
|
||||
PrintSlice(stack.toSlice())
|
||||
|
||||
// 访问栈顶元素
|
||||
peek := stack.Peek()
|
||||
peek := stack.peek()
|
||||
fmt.Println("栈顶元素 peek =", peek)
|
||||
|
||||
// 元素出栈
|
||||
pop := stack.Pop()
|
||||
pop := stack.pop()
|
||||
fmt.Print("出栈元素 pop = ", pop, ", 出栈后 stack = ")
|
||||
PrintSlice(stack.toSlice())
|
||||
|
||||
// 获取栈的长度
|
||||
size := stack.Size()
|
||||
size := stack.size()
|
||||
fmt.Println("栈的长度 size =", size)
|
||||
|
||||
// 判断是否为空
|
||||
isEmpty := stack.IsEmpty()
|
||||
isEmpty := stack.isEmpty()
|
||||
fmt.Println("栈是否为空 =", isEmpty)
|
||||
}
|
||||
|
||||
func TestLinkedListStack(t *testing.T) {
|
||||
// 初始化栈
|
||||
stack := NewLinkedListStack()
|
||||
stack := newLinkedListStack()
|
||||
// 元素入栈
|
||||
stack.Push(1)
|
||||
stack.Push(3)
|
||||
stack.Push(2)
|
||||
stack.Push(5)
|
||||
stack.Push(4)
|
||||
stack.push(1)
|
||||
stack.push(3)
|
||||
stack.push(2)
|
||||
stack.push(5)
|
||||
stack.push(4)
|
||||
fmt.Print("栈 stack = ")
|
||||
PrintList(stack.toList())
|
||||
|
||||
// 访问栈顶元素
|
||||
peek := stack.Peek()
|
||||
peek := stack.peek()
|
||||
fmt.Println("栈顶元素 peek =", peek)
|
||||
|
||||
// 元素出栈
|
||||
pop := stack.Pop()
|
||||
pop := stack.pop()
|
||||
fmt.Print("出栈元素 pop = ", pop, ", 出栈后 stack = ")
|
||||
PrintList(stack.toList())
|
||||
|
||||
// 获取栈的长度
|
||||
size := stack.Size()
|
||||
size := stack.size()
|
||||
fmt.Println("栈的长度 size =", size)
|
||||
|
||||
// 判断是否为空
|
||||
isEmpty := stack.IsEmpty()
|
||||
isEmpty := stack.isEmpty()
|
||||
fmt.Println("栈是否为空 =", isEmpty)
|
||||
}
|
||||
|
||||
// BenchmarkArrayStack 8 ns/op in Mac M1 Pro
|
||||
func BenchmarkArrayStack(b *testing.B) {
|
||||
stack := NewArrayStack()
|
||||
stack := newArrayStack()
|
||||
// use b.N for looping
|
||||
for i := 0; i < b.N; i++ {
|
||||
stack.Push(777)
|
||||
stack.push(777)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
stack.Pop()
|
||||
stack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLinkedListStack 65.02 ns/op in Mac M1 Pro
|
||||
func BenchmarkLinkedListStack(b *testing.B) {
|
||||
stack := NewLinkedListStack()
|
||||
stack := newLinkedListStack()
|
||||
// use b.N for looping
|
||||
for i := 0; i < b.N; i++ {
|
||||
stack.Push(777)
|
||||
stack.push(777)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
stack.Pop()
|
||||
stack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
211
codes/go/chapter_tree/avl_tree.go
Normal file
211
codes/go/chapter_tree/avl_tree.go
Normal file
@@ -0,0 +1,211 @@
|
||||
// File: avl_tree.go
|
||||
// Created Time: 2023-01-08
|
||||
// Author: Reanon (793584285@qq.com)
|
||||
|
||||
package chapter_tree
|
||||
|
||||
import . "github.com/krahets/hello-algo/pkg"
|
||||
|
||||
/* AVL Tree*/
|
||||
type avlTree struct {
|
||||
// 根节点
|
||||
root *TreeNode
|
||||
}
|
||||
|
||||
func newAVLTree() *avlTree {
|
||||
return &avlTree{root: nil}
|
||||
}
|
||||
|
||||
/* 获取结点高度 */
|
||||
func height(node *TreeNode) int {
|
||||
// 空结点高度为 -1 ,叶结点高度为 0
|
||||
if node != nil {
|
||||
return node.Height
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
/* 更新结点高度 */
|
||||
func updateHeight(node *TreeNode) {
|
||||
lh := height(node.Left)
|
||||
rh := height(node.Right)
|
||||
// 结点高度等于最高子树高度 + 1
|
||||
if lh > rh {
|
||||
node.Height = lh + 1
|
||||
} else {
|
||||
node.Height = rh + 1
|
||||
}
|
||||
}
|
||||
|
||||
/* 获取平衡因子 */
|
||||
func balanceFactor(node *TreeNode) int {
|
||||
// 空结点平衡因子为 0
|
||||
if node == nil {
|
||||
return 0
|
||||
}
|
||||
// 结点平衡因子 = 左子树高度 - 右子树高度
|
||||
return height(node.Left) - height(node.Right)
|
||||
}
|
||||
|
||||
/* 右旋操作 */
|
||||
func rightRotate(node *TreeNode) *TreeNode {
|
||||
child := node.Left
|
||||
grandChild := child.Right
|
||||
// 以 child 为原点,将 node 向右旋转
|
||||
child.Right = node
|
||||
node.Left = grandChild
|
||||
// 更新结点高度
|
||||
updateHeight(node)
|
||||
updateHeight(child)
|
||||
// 返回旋转后子树的根节点
|
||||
return child
|
||||
}
|
||||
|
||||
/* 左旋操作 */
|
||||
func leftRotate(node *TreeNode) *TreeNode {
|
||||
child := node.Right
|
||||
grandChild := child.Left
|
||||
// 以 child 为原点,将 node 向左旋转
|
||||
child.Left = node
|
||||
node.Right = grandChild
|
||||
// 更新结点高度
|
||||
updateHeight(node)
|
||||
updateHeight(child)
|
||||
// 返回旋转后子树的根节点
|
||||
return child
|
||||
}
|
||||
|
||||
/* 执行旋转操作,使该子树重新恢复平衡 */
|
||||
func rotate(node *TreeNode) *TreeNode {
|
||||
// 获取结点 node 的平衡因子
|
||||
// Go 推荐短变量,这里 bf 指代 balanceFactor
|
||||
bf := balanceFactor(node)
|
||||
// 左偏树
|
||||
if bf > 1 {
|
||||
if balanceFactor(node.Left) >= 0 {
|
||||
// 右旋
|
||||
return rightRotate(node)
|
||||
} else {
|
||||
// 先左旋后右旋
|
||||
node.Left = leftRotate(node.Left)
|
||||
return rightRotate(node)
|
||||
}
|
||||
}
|
||||
// 右偏树
|
||||
if bf < -1 {
|
||||
if balanceFactor(node.Right) <= 0 {
|
||||
// 左旋
|
||||
return leftRotate(node)
|
||||
} else {
|
||||
// 先右旋后左旋
|
||||
node.Right = rightRotate(node.Right)
|
||||
return leftRotate(node)
|
||||
}
|
||||
}
|
||||
// 平衡树,无需旋转,直接返回
|
||||
return node
|
||||
}
|
||||
|
||||
/* 插入结点 */
|
||||
func (t *avlTree) insert(val int) *TreeNode {
|
||||
t.root = insertHelper(t.root, val)
|
||||
return t.root
|
||||
}
|
||||
|
||||
/* 递归插入结点(辅助函数) */
|
||||
func insertHelper(node *TreeNode, val int) *TreeNode {
|
||||
if node == nil {
|
||||
return NewTreeNode(val)
|
||||
}
|
||||
/* 1. 查找插入位置,并插入结点 */
|
||||
if val < node.Val {
|
||||
node.Left = insertHelper(node.Left, val)
|
||||
} else if val > node.Val {
|
||||
node.Right = insertHelper(node.Right, val)
|
||||
} else {
|
||||
// 重复结点不插入,直接返回
|
||||
return node
|
||||
}
|
||||
// 更新结点高度
|
||||
updateHeight(node)
|
||||
/* 2. 执行旋转操作,使该子树重新恢复平衡 */
|
||||
node = rotate(node)
|
||||
// 返回子树的根节点
|
||||
return node
|
||||
}
|
||||
|
||||
/* 删除结点 */
|
||||
func (t *avlTree) remove(val int) *TreeNode {
|
||||
root := removeHelper(t.root, val)
|
||||
return root
|
||||
}
|
||||
|
||||
/* 递归删除结点(辅助函数) */
|
||||
func removeHelper(node *TreeNode, val int) *TreeNode {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
/* 1. 查找结点,并删除之 */
|
||||
if val < node.Val {
|
||||
node.Left = removeHelper(node.Left, val)
|
||||
} else if val > node.Val {
|
||||
node.Right = removeHelper(node.Right, val)
|
||||
} else {
|
||||
if node.Left == nil || node.Right == nil {
|
||||
child := node.Left
|
||||
if node.Right != nil {
|
||||
child = node.Right
|
||||
}
|
||||
// 子结点数量 = 0 ,直接删除 node 并返回
|
||||
if child == nil {
|
||||
return nil
|
||||
} else {
|
||||
// 子结点数量 = 1 ,直接删除 node
|
||||
node = child
|
||||
}
|
||||
} else {
|
||||
// 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点
|
||||
temp := getInOrderNext(node.Right)
|
||||
node.Right = removeHelper(node.Right, temp.Val)
|
||||
node.Val = temp.Val
|
||||
}
|
||||
}
|
||||
// 更新结点高度
|
||||
updateHeight(node)
|
||||
/* 2. 执行旋转操作,使该子树重新恢复平衡 */
|
||||
node = rotate(node)
|
||||
// 返回子树的根节点
|
||||
return node
|
||||
}
|
||||
|
||||
/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */
|
||||
func getInOrderNext(node *TreeNode) *TreeNode {
|
||||
if node == nil {
|
||||
return node
|
||||
}
|
||||
// 循环访问左子结点,直到叶结点时为最小结点,跳出
|
||||
for node.Left != nil {
|
||||
node = node.Left
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
/* 查找结点 */
|
||||
func (t *avlTree) search(val int) *TreeNode {
|
||||
cur := t.root
|
||||
// 循环查找,越过叶结点后跳出
|
||||
for cur != nil {
|
||||
// 目标结点在 root 的右子树中
|
||||
if cur.Val < val {
|
||||
cur = cur.Right
|
||||
} else if cur.Val > val {
|
||||
// 目标结点在 root 的左子树中
|
||||
cur = cur.Left
|
||||
} else {
|
||||
// 找到目标结点,跳出循环
|
||||
break
|
||||
}
|
||||
}
|
||||
// 返回目标结点
|
||||
return cur
|
||||
}
|
||||
54
codes/go/chapter_tree/avl_tree_test.go
Normal file
54
codes/go/chapter_tree/avl_tree_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// File: avl_tree_test.go
|
||||
// Created Time: 2023-01-08
|
||||
// Author: Reanon (793584285@qq.com)
|
||||
|
||||
package chapter_tree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "github.com/krahets/hello-algo/pkg"
|
||||
)
|
||||
|
||||
func TestAVLTree(t *testing.T) {
|
||||
/* 初始化空 AVL 树 */
|
||||
tree := newAVLTree()
|
||||
/* 插入结点 */
|
||||
// 请关注插入结点后,AVL 树是如何保持平衡的
|
||||
testInsert(tree, 1)
|
||||
testInsert(tree, 2)
|
||||
testInsert(tree, 3)
|
||||
testInsert(tree, 4)
|
||||
testInsert(tree, 5)
|
||||
testInsert(tree, 8)
|
||||
testInsert(tree, 7)
|
||||
testInsert(tree, 9)
|
||||
testInsert(tree, 10)
|
||||
testInsert(tree, 6)
|
||||
|
||||
/* 插入重复结点 */
|
||||
testInsert(tree, 7)
|
||||
|
||||
/* 删除结点 */
|
||||
// 请关注删除结点后,AVL 树是如何保持平衡的
|
||||
testRemove(tree, 8) // 删除度为 0 的结点
|
||||
testRemove(tree, 5) // 删除度为 1 的结点
|
||||
testRemove(tree, 4) // 删除度为 2 的结点
|
||||
|
||||
/* 查询结点 */
|
||||
node := tree.search(7)
|
||||
fmt.Printf("\n查找到的结点对象为 %#v ,结点值 = %d \n", node, node.Val)
|
||||
}
|
||||
|
||||
func testInsert(tree *avlTree, val int) {
|
||||
tree.insert(val)
|
||||
fmt.Printf("\n插入结点 %d 后,AVL 树为 \n", val)
|
||||
PrintTree(tree.root)
|
||||
}
|
||||
|
||||
func testRemove(tree *avlTree, val int) {
|
||||
tree.remove(val)
|
||||
fmt.Printf("\n删除结点 %d 后,AVL 树为 \n", val)
|
||||
PrintTree(tree.root)
|
||||
}
|
||||
@@ -10,26 +10,26 @@ import (
|
||||
. "github.com/krahets/hello-algo/pkg"
|
||||
)
|
||||
|
||||
type BinarySearchTree struct {
|
||||
type binarySearchTree struct {
|
||||
root *TreeNode
|
||||
}
|
||||
|
||||
func NewBinarySearchTree(nums []int) *BinarySearchTree {
|
||||
func newBinarySearchTree(nums []int) *binarySearchTree {
|
||||
// sorting array
|
||||
sort.Ints(nums)
|
||||
root := buildBinarySearchTree(nums, 0, len(nums)-1)
|
||||
return &BinarySearchTree{
|
||||
return &binarySearchTree{
|
||||
root: root,
|
||||
}
|
||||
}
|
||||
|
||||
/* 获取根结点 */
|
||||
func (bst *BinarySearchTree) GetRoot() *TreeNode {
|
||||
func (bst *binarySearchTree) getRoot() *TreeNode {
|
||||
return bst.root
|
||||
}
|
||||
|
||||
/* 获取中序遍历的下一个结点 */
|
||||
func (bst *BinarySearchTree) GetInOrderNext(node *TreeNode) *TreeNode {
|
||||
func (bst *binarySearchTree) getInOrderNext(node *TreeNode) *TreeNode {
|
||||
if node == nil {
|
||||
return node
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func (bst *BinarySearchTree) GetInOrderNext(node *TreeNode) *TreeNode {
|
||||
}
|
||||
|
||||
/* 查找结点 */
|
||||
func (bst *BinarySearchTree) Search(num int) *TreeNode {
|
||||
func (bst *binarySearchTree) search(num int) *TreeNode {
|
||||
node := bst.root
|
||||
// 循环查找,越过叶结点后跳出
|
||||
for node != nil {
|
||||
@@ -61,7 +61,7 @@ func (bst *BinarySearchTree) Search(num int) *TreeNode {
|
||||
}
|
||||
|
||||
/* 插入结点 */
|
||||
func (bst *BinarySearchTree) Insert(num int) *TreeNode {
|
||||
func (bst *binarySearchTree) insert(num int) *TreeNode {
|
||||
cur := bst.root
|
||||
// 若树为空,直接提前返回
|
||||
if cur == nil {
|
||||
@@ -92,7 +92,7 @@ func (bst *BinarySearchTree) Insert(num int) *TreeNode {
|
||||
}
|
||||
|
||||
/* 删除结点 */
|
||||
func (bst *BinarySearchTree) Remove(num int) *TreeNode {
|
||||
func (bst *binarySearchTree) remove(num int) *TreeNode {
|
||||
cur := bst.root
|
||||
// 若树为空,直接提前返回
|
||||
if cur == nil {
|
||||
@@ -136,10 +136,10 @@ func (bst *BinarySearchTree) Remove(num int) *TreeNode {
|
||||
// 子结点数为 2
|
||||
} else {
|
||||
// 获取中序遍历中待删除结点 cur 的下一个结点
|
||||
next := bst.GetInOrderNext(cur)
|
||||
next := bst.getInOrderNext(cur)
|
||||
temp := next.Val
|
||||
// 递归删除结点 next
|
||||
bst.Remove(next.Val)
|
||||
bst.remove(next.Val)
|
||||
// 将 next 的值复制给 cur
|
||||
cur.Val = temp
|
||||
}
|
||||
@@ -160,7 +160,7 @@ func buildBinarySearchTree(nums []int, left, right int) *TreeNode {
|
||||
return root
|
||||
}
|
||||
|
||||
// Print binary search tree
|
||||
func (bst *BinarySearchTree) Print() {
|
||||
// print binary search tree
|
||||
func (bst *binarySearchTree) print() {
|
||||
PrintTree(bst.root)
|
||||
}
|
||||
|
||||
@@ -11,31 +11,31 @@ import (
|
||||
|
||||
func TestBinarySearchTree(t *testing.T) {
|
||||
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
|
||||
bst := NewBinarySearchTree(nums)
|
||||
bst := newBinarySearchTree(nums)
|
||||
fmt.Println("\n初始化的二叉树为:")
|
||||
bst.Print()
|
||||
bst.print()
|
||||
|
||||
// 获取根结点
|
||||
node := bst.GetRoot()
|
||||
node := bst.getRoot()
|
||||
fmt.Println("\n二叉树的根结点为:", node.Val)
|
||||
|
||||
// 查找结点
|
||||
node = bst.Search(5)
|
||||
node = bst.search(5)
|
||||
fmt.Println("\n查找到的结点对象为", node, ",结点值 =", node.Val)
|
||||
|
||||
// 插入结点
|
||||
node = bst.Insert(16)
|
||||
node = bst.insert(16)
|
||||
fmt.Println("\n插入结点后 16 的二叉树为:")
|
||||
bst.Print()
|
||||
bst.print()
|
||||
|
||||
// 删除结点
|
||||
bst.Remove(1)
|
||||
bst.remove(1)
|
||||
fmt.Println("\n删除结点 1 后的二叉树为:")
|
||||
bst.Print()
|
||||
bst.Remove(2)
|
||||
bst.print()
|
||||
bst.remove(2)
|
||||
fmt.Println("\n删除结点 2 后的二叉树为:")
|
||||
bst.Print()
|
||||
bst.Remove(4)
|
||||
bst.print()
|
||||
bst.remove(4)
|
||||
fmt.Println("\n删除结点 4 后的二叉树为:")
|
||||
bst.Print()
|
||||
bst.print()
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ import (
|
||||
func TestLevelOrder(t *testing.T) {
|
||||
/* 初始化二叉树 */
|
||||
// 这里借助了一个从数组直接生成二叉树的函数
|
||||
root := ArrayToTree([]int{1, 2, 3, 4, 5, 6, 7})
|
||||
fmt.Println("初始化二叉树: ")
|
||||
root := ArrToTree([]any{1, 2, 3, 4, 5, 6, 7})
|
||||
fmt.Println("\n初始化二叉树: ")
|
||||
PrintTree(root)
|
||||
|
||||
// 层序遍历
|
||||
nums := levelOrder(root)
|
||||
fmt.Println("层序遍历的结点打印序列 =", nums)
|
||||
fmt.Println("\n层序遍历的结点打印序列 =", nums)
|
||||
}
|
||||
|
||||
@@ -14,22 +14,22 @@ import (
|
||||
func TestPreInPostOrderTraversal(t *testing.T) {
|
||||
/* 初始化二叉树 */
|
||||
// 这里借助了一个从数组直接生成二叉树的函数
|
||||
root := ArrayToTree([]int{1, 2, 3, 4, 5, 6, 7})
|
||||
fmt.Println("初始化二叉树: ")
|
||||
root := ArrToTree([]any{1, 2, 3, 4, 5, 6, 7})
|
||||
fmt.Println("\n初始化二叉树: ")
|
||||
PrintTree(root)
|
||||
|
||||
// 前序遍历
|
||||
nums = nil
|
||||
preOrder(root)
|
||||
fmt.Println("前序遍历的结点打印序列 =", nums)
|
||||
fmt.Println("\n前序遍历的结点打印序列 =", nums)
|
||||
|
||||
// 中序遍历
|
||||
nums = nil
|
||||
inOrder(root)
|
||||
fmt.Println("中序遍历的结点打印序列 =", nums)
|
||||
fmt.Println("\n中序遍历的结点打印序列 =", nums)
|
||||
|
||||
// 后序遍历
|
||||
nums = nil
|
||||
postOrder(root)
|
||||
fmt.Println("后序遍历的结点打印序列 =", nums)
|
||||
fmt.Println("\n后序遍历的结点打印序列 =", nums)
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func printTreeHelper(root *TreeNode, prev *trunk, isLeft bool) {
|
||||
printTreeHelper(root.Left, trunk, false)
|
||||
}
|
||||
|
||||
// trunk Help to Print tree structure
|
||||
// trunk Help to print tree structure
|
||||
type trunk struct {
|
||||
prev *trunk
|
||||
str string
|
||||
@@ -103,4 +103,4 @@ func PrintMap[K comparable, V any](m map[K]V) {
|
||||
for key, value := range m {
|
||||
fmt.Println(key, "->", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,41 +9,48 @@ import (
|
||||
)
|
||||
|
||||
type TreeNode struct {
|
||||
Val int
|
||||
Left *TreeNode
|
||||
Right *TreeNode
|
||||
Val int // 结点值
|
||||
Height int // 结点高度
|
||||
Left *TreeNode // 左子结点引用
|
||||
Right *TreeNode // 右子结点引用
|
||||
}
|
||||
|
||||
func NewTreeNode(v int) *TreeNode {
|
||||
return &TreeNode{
|
||||
Left: nil,
|
||||
Right: nil,
|
||||
Val: v,
|
||||
Val: v,
|
||||
Height: 0,
|
||||
Left: nil,
|
||||
Right: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// ArrayToTree Generate a binary tree with an array
|
||||
func ArrayToTree(arr []int) *TreeNode {
|
||||
// ArrToTree Generate a binary tree given an array
|
||||
func ArrToTree(arr []any) *TreeNode {
|
||||
if len(arr) <= 0 {
|
||||
return nil
|
||||
}
|
||||
root := NewTreeNode(arr[0])
|
||||
// TreeNode only accept integer value for now.
|
||||
root := NewTreeNode(arr[0].(int))
|
||||
// Let container.list as queue
|
||||
queue := list.New()
|
||||
queue.PushBack(root)
|
||||
i := 1
|
||||
i := 0
|
||||
for queue.Len() > 0 {
|
||||
// poll
|
||||
node := queue.Remove(queue.Front()).(*TreeNode)
|
||||
i++
|
||||
if i < len(arr) {
|
||||
node.Left = NewTreeNode(arr[i])
|
||||
queue.PushBack(node.Left)
|
||||
i++
|
||||
if arr[i] != nil {
|
||||
node.Left = NewTreeNode(arr[i].(int))
|
||||
queue.PushBack(node.Left)
|
||||
}
|
||||
}
|
||||
i++
|
||||
if i < len(arr) {
|
||||
node.Right = NewTreeNode(arr[i])
|
||||
queue.PushBack(node.Right)
|
||||
i++
|
||||
if arr[i] != nil {
|
||||
node.Right = NewTreeNode(arr[i].(int))
|
||||
queue.PushBack(node.Right)
|
||||
}
|
||||
}
|
||||
}
|
||||
return root
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestTreeNode(t *testing.T) {
|
||||
arr := []int{2, 3, 5, 6, 7}
|
||||
node := ArrayToTree(arr)
|
||||
arr := []any{1, 2, 3, nil, 5, 6, nil}
|
||||
node := ArrToTree(arr)
|
||||
|
||||
// print tree
|
||||
PrintTree(node)
|
||||
|
||||
@@ -30,8 +30,7 @@ public class binary_tree_bfs {
|
||||
public static void main(String[] args) {
|
||||
/* 初始化二叉树 */
|
||||
// 这里借助了一个从数组直接生成二叉树的函数
|
||||
TreeNode root = TreeNode.arrToTree(new Integer[] {
|
||||
1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null });
|
||||
TreeNode root = TreeNode.arrToTree(new Integer[] { 1, 2, 3, 4, 5, 6, 7 });
|
||||
System.out.println("\n初始化二叉树\n");
|
||||
PrintUtil.printTree(root);
|
||||
|
||||
|
||||
@@ -43,8 +43,7 @@ public class binary_tree_dfs {
|
||||
public static void main(String[] args) {
|
||||
/* 初始化二叉树 */
|
||||
// 这里借助了一个从数组直接生成二叉树的函数
|
||||
TreeNode root = TreeNode.arrToTree(new Integer[] {
|
||||
1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null});
|
||||
TreeNode root = TreeNode.arrToTree(new Integer[] { 1, 2, 3, 4, 5, 6, 7 });
|
||||
System.out.println("\n初始化二叉树\n");
|
||||
PrintUtil.printTree(root);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ package include;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
|
||||
class Trunk {
|
||||
Trunk prev;
|
||||
String str;
|
||||
@@ -103,4 +104,11 @@ public class PrintUtil {
|
||||
System.out.println(kv.getKey() + " -> " + kv.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public static void printHeap(PriorityQueue<Integer> queue) {
|
||||
Integer[] nums = (Integer[])queue.toArray();
|
||||
TreeNode root = TreeNode.arrToTree(nums);
|
||||
|
||||
printTree(root);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public class TreeNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a binary tree with an array
|
||||
* Generate a binary tree given an array
|
||||
* @param arr
|
||||
* @return
|
||||
*/
|
||||
@@ -32,19 +32,19 @@ public class TreeNode {
|
||||
|
||||
TreeNode root = new TreeNode(arr[0]);
|
||||
Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
|
||||
int i = 1;
|
||||
int i = 0;
|
||||
while(!queue.isEmpty()) {
|
||||
TreeNode node = queue.poll();
|
||||
if (++i >= arr.length) break;
|
||||
if(arr[i] != null) {
|
||||
node.left = new TreeNode(arr[i]);
|
||||
queue.add(node.left);
|
||||
}
|
||||
i++;
|
||||
if (++i >= arr.length) break;
|
||||
if(arr[i] != null) {
|
||||
node.right = new TreeNode(arr[i]);
|
||||
queue.add(node.right);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
@@ -71,20 +71,4 @@ public class TreeNode {
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a tree node with specific value in a binary tree
|
||||
* @param root
|
||||
* @param val
|
||||
* @return
|
||||
*/
|
||||
public static TreeNode getTreeNode(TreeNode root, int val) {
|
||||
if (root == null)
|
||||
return null;
|
||||
if (root.val == val)
|
||||
return root;
|
||||
TreeNode left = getTreeNode(root.left, val);
|
||||
TreeNode right = getTreeNode(root.right, val);
|
||||
return left != null ? left : right;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ function hierOrder(root) {
|
||||
/* Driver Code */
|
||||
/* 初始化二叉树 */
|
||||
// 这里借助了一个从数组直接生成二叉树的函数
|
||||
var root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]);
|
||||
var root = arrToTree([1, 2, 3, 4, 5, 6, 7]);
|
||||
console.log("\n初始化二叉树\n");
|
||||
printTree(root);
|
||||
|
||||
/* 层序遍历 */
|
||||
let list = hierOrder(root);
|
||||
console.log("\n层序遍历的结点打印序列 = " + list);
|
||||
console.log("\n层序遍历的结点打印序列 = " + list);
|
||||
|
||||
@@ -40,7 +40,7 @@ function postOrder(root) {
|
||||
/* Driver Code */
|
||||
/* 初始化二叉树 */
|
||||
// 这里借助了一个从数组直接生成二叉树的函数
|
||||
var root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]);
|
||||
var root = arrToTree([1, 2, 3, 4, 5, 6, 7]);
|
||||
console.log("\n初始化二叉树\n");
|
||||
printTree(root);
|
||||
|
||||
@@ -58,4 +58,3 @@ console.log("\n中序遍历的结点打印序列 = " + list);
|
||||
list.length = 0;
|
||||
postOrder(root);
|
||||
console.log("\n后序遍历的结点打印序列 = " + list);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ function TreeNode(val, left, right) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a binary tree with an array
|
||||
* Generate a binary tree given an array
|
||||
* @param arr
|
||||
* @return
|
||||
*/
|
||||
@@ -24,20 +24,21 @@ function arrToTree(arr) {
|
||||
|
||||
let root = new TreeNode(arr[0]);
|
||||
let queue = [root]
|
||||
let i = 1;
|
||||
while(queue.length) {
|
||||
let i = 0;
|
||||
while (queue.length) {
|
||||
let node = queue.shift();
|
||||
if(arr[i] !== null) {
|
||||
if (++i >= arr.length) break;
|
||||
if (arr[i] !== null) {
|
||||
node.left = new TreeNode(arr[i]);
|
||||
queue.push(node.left);
|
||||
}
|
||||
i++;
|
||||
if(arr[i] !== null) {
|
||||
if (++i >= arr.length) break;
|
||||
if (arr[i] !== null) {
|
||||
node.right = new TreeNode(arr[i]);
|
||||
queue.push(node.right);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,30 +5,28 @@ Author: a16su (lpluls001@gmail.com)
|
||||
"""
|
||||
|
||||
import sys, os.path as osp
|
||||
import typing
|
||||
|
||||
sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__))))
|
||||
from include import *
|
||||
|
||||
|
||||
class AVLTree:
|
||||
def __init__(self, root: typing.Optional[TreeNode] = None):
|
||||
def __init__(self, root: Optional[TreeNode] = None):
|
||||
self.root = root
|
||||
|
||||
""" 获取结点高度 """
|
||||
def height(self, node: typing.Optional[TreeNode]) -> int:
|
||||
def height(self, node: Optional[TreeNode]) -> int:
|
||||
# 空结点高度为 -1 ,叶结点高度为 0
|
||||
if node is not None:
|
||||
return node.height
|
||||
return -1
|
||||
|
||||
""" 更新结点高度 """
|
||||
def __update_height(self, node: TreeNode):
|
||||
def __update_height(self, node: Optional[TreeNode]):
|
||||
# 结点高度等于最高子树高度 + 1
|
||||
node.height = max([self.height(node.left), self.height(node.right)]) + 1
|
||||
|
||||
""" 获取平衡因子 """
|
||||
def balance_factor(self, node: TreeNode) -> int:
|
||||
def balance_factor(self, node: Optional[TreeNode]) -> int:
|
||||
# 空结点平衡因子为 0
|
||||
if node is None:
|
||||
return 0
|
||||
@@ -36,7 +34,7 @@ class AVLTree:
|
||||
return self.height(node.left) - self.height(node.right)
|
||||
|
||||
""" 右旋操作 """
|
||||
def __right_rotate(self, node: TreeNode) -> TreeNode:
|
||||
def __right_rotate(self, node: Optional[TreeNode]) -> TreeNode:
|
||||
child = node.left
|
||||
grand_child = child.right
|
||||
# 以 child 为原点,将 node 向右旋转
|
||||
@@ -49,7 +47,7 @@ class AVLTree:
|
||||
return child
|
||||
|
||||
""" 左旋操作 """
|
||||
def __left_rotate(self, node: TreeNode) -> TreeNode:
|
||||
def __left_rotate(self, node: Optional[TreeNode]) -> TreeNode:
|
||||
child = node.right
|
||||
grand_child = child.left
|
||||
# 以 child 为原点,将 node 向左旋转
|
||||
@@ -62,7 +60,7 @@ class AVLTree:
|
||||
return child
|
||||
|
||||
""" 执行旋转操作,使该子树重新恢复平衡 """
|
||||
def __rotate(self, node: TreeNode) -> TreeNode:
|
||||
def __rotate(self, node: Optional[TreeNode]) -> TreeNode:
|
||||
# 获取结点 node 的平衡因子
|
||||
balance_factor = self.balance_factor(node)
|
||||
# 左偏树
|
||||
@@ -92,7 +90,7 @@ class AVLTree:
|
||||
return self.root
|
||||
|
||||
""" 递归插入结点(辅助函数)"""
|
||||
def __insert_helper(self, node: typing.Optional[TreeNode], val: int) -> TreeNode:
|
||||
def __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode:
|
||||
if node is None:
|
||||
return TreeNode(val)
|
||||
# 1. 查找插入位置,并插入结点
|
||||
@@ -114,7 +112,7 @@ class AVLTree:
|
||||
return root
|
||||
|
||||
""" 递归删除结点(辅助函数) """
|
||||
def __remove_helper(self, node: typing.Optional[TreeNode], val: int) -> typing.Optional[TreeNode]:
|
||||
def __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]:
|
||||
if node is None:
|
||||
return None
|
||||
# 1. 查找结点,并删除之
|
||||
@@ -141,7 +139,7 @@ class AVLTree:
|
||||
return self.__rotate(node)
|
||||
|
||||
""" 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) """
|
||||
def __get_inorder_next(self, node: typing.Optional[TreeNode]) -> typing.Optional[TreeNode]:
|
||||
def __get_inorder_next(self, node: Optional[TreeNode]) -> Optional[TreeNode]:
|
||||
if node is None:
|
||||
return None
|
||||
# 循环访问左子结点,直到叶结点时为最小结点,跳出
|
||||
|
||||
@@ -5,20 +5,18 @@ Author: a16su (lpluls001@gmail.com)
|
||||
"""
|
||||
|
||||
import sys, os.path as osp
|
||||
import typing
|
||||
|
||||
sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__))))
|
||||
from include import *
|
||||
|
||||
|
||||
""" 二叉搜索树 """
|
||||
class BinarySearchTree:
|
||||
def __init__(self, nums: typing.List[int]) -> None:
|
||||
def __init__(self, nums: List[int]) -> None:
|
||||
nums.sort()
|
||||
self.__root = self.build_tree(nums, 0, len(nums) - 1)
|
||||
|
||||
""" 构建二叉搜索树 """
|
||||
def build_tree(self, nums: typing.List[int], start_index: int, end_index: int) -> typing.Optional[TreeNode]:
|
||||
def build_tree(self, nums: List[int], start_index: int, end_index: int) -> Optional[TreeNode]:
|
||||
if start_index > end_index:
|
||||
return None
|
||||
|
||||
@@ -31,11 +29,11 @@ class BinarySearchTree:
|
||||
return root
|
||||
|
||||
@property
|
||||
def root(self) -> typing.Optional[TreeNode]:
|
||||
def root(self) -> Optional[TreeNode]:
|
||||
return self.__root
|
||||
|
||||
""" 查找结点 """
|
||||
def search(self, num: int) -> typing.Optional[TreeNode]:
|
||||
def search(self, num: int) -> Optional[TreeNode]:
|
||||
cur = self.root
|
||||
# 循环查找,越过叶结点后跳出
|
||||
while cur is not None:
|
||||
@@ -51,7 +49,7 @@ class BinarySearchTree:
|
||||
return cur
|
||||
|
||||
""" 插入结点 """
|
||||
def insert(self, num: int) -> typing.Optional[TreeNode]:
|
||||
def insert(self, num: int) -> Optional[TreeNode]:
|
||||
root = self.root
|
||||
# 若树为空,直接提前返回
|
||||
if root is None:
|
||||
@@ -81,7 +79,7 @@ class BinarySearchTree:
|
||||
return node
|
||||
|
||||
""" 删除结点 """
|
||||
def remove(self, num: int) -> typing.Optional[TreeNode]:
|
||||
def remove(self, num: int) -> Optional[TreeNode]:
|
||||
root = self.root
|
||||
# 若树为空,直接提前返回
|
||||
if root is None:
|
||||
@@ -126,7 +124,7 @@ class BinarySearchTree:
|
||||
return cur
|
||||
|
||||
""" 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) """
|
||||
def get_inorder_next(self, root: typing.Optional[TreeNode]) -> typing.Optional[TreeNode]:
|
||||
def get_inorder_next(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
|
||||
if root is None:
|
||||
return root
|
||||
# 循环访问左子结点,直到叶结点时为最小结点,跳出
|
||||
@@ -138,7 +136,7 @@ class BinarySearchTree:
|
||||
""" Driver Code """
|
||||
if __name__ == "__main__":
|
||||
# 初始化二叉搜索树
|
||||
nums = list(range(1, 16))
|
||||
nums = list(range(1, 16)) # [1, 2, ..., 15]
|
||||
bst = BinarySearchTree(nums=nums)
|
||||
print("\n初始化的二叉树为\n")
|
||||
print_tree(bst.root)
|
||||
|
||||
@@ -36,5 +36,5 @@ if __name__ == "__main__":
|
||||
print_tree(n1)
|
||||
# 删除结点
|
||||
n1.left = n2
|
||||
print("\n删除结点 P 后\n");
|
||||
print("\n删除结点 P 后\n")
|
||||
print_tree(n1)
|
||||
|
||||
@@ -5,14 +5,12 @@ Author: a16su (lpluls001@gmail.com)
|
||||
"""
|
||||
|
||||
import sys, os.path as osp
|
||||
import typing
|
||||
|
||||
sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__))))
|
||||
from include import *
|
||||
|
||||
|
||||
""" 层序遍历 """
|
||||
def hier_order(root: TreeNode):
|
||||
def hier_order(root: Optional[TreeNode]):
|
||||
# 初始化队列,加入根结点
|
||||
queue = collections.deque()
|
||||
queue.append(root)
|
||||
@@ -32,7 +30,7 @@ def hier_order(root: TreeNode):
|
||||
if __name__ == "__main__":
|
||||
# 初始化二叉树
|
||||
# 这里借助了一个从数组直接生成二叉树的函数
|
||||
root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7, None, None, None, None, None, None, None, None])
|
||||
root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7])
|
||||
print("\n初始化二叉树\n")
|
||||
print_tree(root)
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ Author: a16su (lpluls001@gmail.com)
|
||||
"""
|
||||
|
||||
import sys, os.path as osp
|
||||
import typing
|
||||
|
||||
sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__))))
|
||||
from include import *
|
||||
|
||||
@@ -14,7 +12,7 @@ from include import *
|
||||
res = []
|
||||
|
||||
""" 前序遍历 """
|
||||
def pre_order(root: typing.Optional[TreeNode]):
|
||||
def pre_order(root: Optional[TreeNode]):
|
||||
if root is None:
|
||||
return
|
||||
# 访问优先级:根结点 -> 左子树 -> 右子树
|
||||
@@ -23,7 +21,7 @@ def pre_order(root: typing.Optional[TreeNode]):
|
||||
pre_order(root=root.right)
|
||||
|
||||
""" 中序遍历 """
|
||||
def in_order(root: typing.Optional[TreeNode]):
|
||||
def in_order(root: Optional[TreeNode]):
|
||||
if root is None:
|
||||
return
|
||||
# 访问优先级:左子树 -> 根结点 -> 右子树
|
||||
@@ -32,7 +30,7 @@ def in_order(root: typing.Optional[TreeNode]):
|
||||
in_order(root=root.right)
|
||||
|
||||
""" 后序遍历 """
|
||||
def post_order(root: typing.Optional[TreeNode]):
|
||||
def post_order(root: Optional[TreeNode]):
|
||||
if root is None:
|
||||
return
|
||||
# 访问优先级:左子树 -> 右子树 -> 根结点
|
||||
@@ -45,7 +43,7 @@ def post_order(root: typing.Optional[TreeNode]):
|
||||
if __name__ == "__main__":
|
||||
# 初始化二叉树
|
||||
# 这里借助了一个从数组直接生成二叉树的函数
|
||||
root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7, None, None, None, None, None, None, None, None])
|
||||
root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7])
|
||||
print("\n初始化二叉树\n")
|
||||
print_tree(root)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import queue
|
||||
import random
|
||||
import functools
|
||||
import collections
|
||||
from typing import List
|
||||
from typing import Optional, List, Dict, DefaultDict, OrderedDict, Set, Deque
|
||||
from .linked_list import ListNode, list_to_linked_list, linked_list_to_list, get_list_node
|
||||
from .binary_tree import TreeNode, list_to_tree, tree_to_list, get_tree_node
|
||||
from .print_util import print_matrix, print_linked_list, print_tree, print_dict
|
||||
@@ -26,39 +26,30 @@ class TreeNode:
|
||||
|
||||
def list_to_tree(arr):
|
||||
"""Generate a binary tree with a list
|
||||
|
||||
Args:
|
||||
arr ([type]): [description]
|
||||
|
||||
Returns:
|
||||
[type]: [description]
|
||||
"""
|
||||
if not arr:
|
||||
return None
|
||||
i = 1
|
||||
root = TreeNode(int(arr[0]))
|
||||
queue = collections.deque()
|
||||
queue.append(root)
|
||||
|
||||
i = 0
|
||||
root = TreeNode(arr[0])
|
||||
queue = collections.deque([root])
|
||||
while queue:
|
||||
node = queue.popleft()
|
||||
i += 1
|
||||
if i >= len(arr): break
|
||||
if arr[i] != None:
|
||||
node.left = TreeNode(int(arr[i]))
|
||||
node.left = TreeNode(arr[i])
|
||||
queue.append(node.left)
|
||||
i += 1
|
||||
if i >= len(arr): break
|
||||
if arr[i] != None:
|
||||
node.right = TreeNode(int(arr[i]))
|
||||
node.right = TreeNode(arr[i])
|
||||
queue.append(node.right)
|
||||
i += 1
|
||||
|
||||
return root
|
||||
|
||||
def tree_to_list(root):
|
||||
"""Serialize a tree into an array
|
||||
|
||||
Args:
|
||||
root ([type]): [description]
|
||||
|
||||
Returns:
|
||||
[type]: [description]
|
||||
"""
|
||||
if not root: return []
|
||||
queue = collections.deque()
|
||||
@@ -75,13 +66,6 @@ def tree_to_list(root):
|
||||
|
||||
def get_tree_node(root, val):
|
||||
"""Get a tree node with specific value in a binary tree
|
||||
|
||||
Args:
|
||||
root ([type]): [description]
|
||||
val ([type]): [description]
|
||||
|
||||
Returns:
|
||||
[type]: [description]
|
||||
"""
|
||||
if not root:
|
||||
return
|
||||
|
||||
@@ -9,6 +9,8 @@ let package = Package(
|
||||
.executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]),
|
||||
.executable(name: "space_complexity", targets: ["space_complexity"]),
|
||||
.executable(name: "leetcode_two_sum", targets: ["leetcode_two_sum"]),
|
||||
.executable(name: "array", targets: ["array"]),
|
||||
.executable(name: "linked_list", targets: ["linked_list"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "utils", path: "utils"),
|
||||
@@ -16,5 +18,7 @@ let package = Package(
|
||||
.executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]),
|
||||
.executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]),
|
||||
.executableTarget(name: "leetcode_two_sum", path: "chapter_computational_complexity", sources: ["leetcode_two_sum.swift"]),
|
||||
.executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]),
|
||||
.executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]),
|
||||
]
|
||||
)
|
||||
|
||||
103
codes/swift/chapter_array_and_linkedlist/array.swift
Normal file
103
codes/swift/chapter_array_and_linkedlist/array.swift
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* File: array.swift
|
||||
* Created Time: 2023-01-05
|
||||
* Author: nuomi1 (nuomi1@qq.com)
|
||||
*/
|
||||
|
||||
/* 随机返回一个数组元素 */
|
||||
func randomAccess(nums: [Int]) -> Int {
|
||||
// 在区间 [0, nums.count) 中随机抽取一个数字
|
||||
let randomIndex = nums.indices.randomElement()!
|
||||
// 获取并返回随机元素
|
||||
let randomNum = nums[randomIndex]
|
||||
return randomNum
|
||||
}
|
||||
|
||||
/* 扩展数组长度 */
|
||||
func extend(nums: [Int], enlarge: Int) -> [Int] {
|
||||
// 初始化一个扩展长度后的数组
|
||||
var res = Array(repeating: 0, count: nums.count + enlarge)
|
||||
// 将原数组中的所有元素复制到新数组
|
||||
for i in nums.indices {
|
||||
res[i] = nums[i]
|
||||
}
|
||||
// 返回扩展后的新数组
|
||||
return res
|
||||
}
|
||||
|
||||
/* 在数组的索引 index 处插入元素 num */
|
||||
func insert(nums: inout [Int], num: Int, index: Int) {
|
||||
// 把索引 index 以及之后的所有元素向后移动一位
|
||||
for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) {
|
||||
nums[i] = nums[i - 1]
|
||||
}
|
||||
// 将 num 赋给 index 处元素
|
||||
nums[index] = num
|
||||
}
|
||||
|
||||
/* 删除索引 index 处元素 */
|
||||
func remove(nums: inout [Int], index: Int) {
|
||||
let count = nums.count
|
||||
// 把索引 index 之后的所有元素向前移动一位
|
||||
for i in sequence(first: index, next: { $0 < count - 1 - 1 ? $0 + 1 : nil }) {
|
||||
nums[i] = nums[i + 1]
|
||||
}
|
||||
}
|
||||
|
||||
/* 遍历数组 */
|
||||
func traverse(nums: [Int]) {
|
||||
var count = 0
|
||||
// 通过索引遍历数组
|
||||
for _ in nums.indices {
|
||||
count += 1
|
||||
}
|
||||
// 直接遍历数组
|
||||
for _ in nums {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
|
||||
/* 在数组中查找指定元素 */
|
||||
func find(nums: [Int], target: Int) -> Int {
|
||||
for i in nums.indices {
|
||||
if nums[i] == target {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
@main
|
||||
enum _Array {
|
||||
/* Driver Code */
|
||||
static func main() {
|
||||
/* 初始化数组 */
|
||||
let arr = Array(repeating: 0, count: 5)
|
||||
print("数组 arr = \(arr)")
|
||||
var nums = [1, 3, 2, 5, 4]
|
||||
print("数组 nums = \(nums)")
|
||||
|
||||
/* 随机访问 */
|
||||
let randomNum = randomAccess(nums: nums)
|
||||
print("在 nums 中获取随机元素 \(randomNum)")
|
||||
|
||||
/* 长度扩展 */
|
||||
nums = extend(nums: nums, enlarge: 3)
|
||||
print("将数组长度扩展至 8 ,得到 nums = \(nums)")
|
||||
|
||||
/* 插入元素 */
|
||||
insert(nums: &nums, num: 6, index: 3)
|
||||
print("在索引 3 处插入数字 6 ,得到 nums = \(nums)")
|
||||
|
||||
/* 删除元素 */
|
||||
remove(nums: &nums, index: 2)
|
||||
print("删除索引 2 处的元素,得到 nums = \(nums)")
|
||||
|
||||
/* 遍历数组 */
|
||||
traverse(nums: nums)
|
||||
|
||||
/* 查找元素 */
|
||||
let index = find(nums: nums, target: 3)
|
||||
print("在 nums 中查找元素 3 ,得到索引 = \(index)")
|
||||
}
|
||||
}
|
||||
91
codes/swift/chapter_array_and_linkedlist/linked_list.swift
Normal file
91
codes/swift/chapter_array_and_linkedlist/linked_list.swift
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* File: linked_list.swift
|
||||
* Created Time: 2023-01-08
|
||||
* Author: nuomi1 (nuomi1@qq.com)
|
||||
*/
|
||||
|
||||
import utils
|
||||
|
||||
/* 在链表的结点 n0 之后插入结点 P */
|
||||
func insert(n0: ListNode, P: ListNode) {
|
||||
let n1 = n0.next
|
||||
n0.next = P
|
||||
P.next = n1
|
||||
}
|
||||
|
||||
/* 删除链表的结点 n0 之后的首个结点 */
|
||||
func remove(n0: ListNode) {
|
||||
if n0.next == nil {
|
||||
return
|
||||
}
|
||||
// n0 -> P -> n1
|
||||
let P = n0.next
|
||||
let n1 = P?.next
|
||||
n0.next = n1
|
||||
P?.next = nil
|
||||
}
|
||||
|
||||
/* 访问链表中索引为 index 的结点 */
|
||||
func access(head: ListNode, index: Int) -> ListNode? {
|
||||
var head: ListNode? = head
|
||||
for _ in 0 ..< index {
|
||||
head = head?.next
|
||||
if head == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return head
|
||||
}
|
||||
|
||||
/* 在链表中查找值为 target 的首个结点 */
|
||||
func find(head: ListNode, target: Int) -> Int {
|
||||
var head: ListNode? = head
|
||||
var index = 0
|
||||
while head != nil {
|
||||
if head?.val == target {
|
||||
return index
|
||||
}
|
||||
head = head?.next
|
||||
index += 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
@main
|
||||
enum LinkedList {
|
||||
/* Driver Code */
|
||||
static func main() {
|
||||
/* 初始化链表 */
|
||||
// 初始化各个结点
|
||||
let n0 = ListNode(x: 1)
|
||||
let n1 = ListNode(x: 3)
|
||||
let n2 = ListNode(x: 2)
|
||||
let n3 = ListNode(x: 5)
|
||||
let n4 = ListNode(x: 4)
|
||||
// 构建引用指向
|
||||
n0.next = n1
|
||||
n1.next = n2
|
||||
n2.next = n3
|
||||
n3.next = n4
|
||||
print("初始化的链表为")
|
||||
PrintUtil.printLinkedList(head: n0)
|
||||
|
||||
/* 插入结点 */
|
||||
insert(n0: n0, P: ListNode(x: 0))
|
||||
print("插入结点后的链表为")
|
||||
PrintUtil.printLinkedList(head: n0)
|
||||
|
||||
/* 删除结点 */
|
||||
remove(n0: n0)
|
||||
print("删除结点后的链表为")
|
||||
PrintUtil.printLinkedList(head: n0)
|
||||
|
||||
/* 访问结点 */
|
||||
let node = access(head: n0, index: 3)
|
||||
print("链表中索引 3 处的结点的值 = \(node!.val)")
|
||||
|
||||
/* 查找结点 */
|
||||
let index = find(head: n0, target: 2)
|
||||
print("链表中值为 2 的结点的索引 = \(index)")
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
import utils
|
||||
|
||||
// 函数
|
||||
/* 函数 */
|
||||
@discardableResult
|
||||
func function() -> Int {
|
||||
// do something
|
||||
return 0
|
||||
}
|
||||
|
||||
// 常数阶
|
||||
/* 常数阶 */
|
||||
func constant(n: Int) {
|
||||
// 常量、变量、对象占用 O(1) 空间
|
||||
let a = 0
|
||||
@@ -30,7 +30,7 @@ func constant(n: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
// 线性阶
|
||||
/* 线性阶 */
|
||||
func linear(n: Int) {
|
||||
// 长度为 n 的数组占用 O(n) 空间
|
||||
let nums = Array(repeating: 0, count: n)
|
||||
@@ -40,7 +40,7 @@ func linear(n: Int) {
|
||||
let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") })
|
||||
}
|
||||
|
||||
// 线性阶(递归实现)
|
||||
/* 线性阶(递归实现) */
|
||||
func linearRecur(n: Int) {
|
||||
print("递归 n = \(n)")
|
||||
if n == 1 {
|
||||
@@ -49,13 +49,13 @@ func linearRecur(n: Int) {
|
||||
linearRecur(n: n - 1)
|
||||
}
|
||||
|
||||
// 平方阶
|
||||
/* 平方阶 */
|
||||
func quadratic(n: Int) {
|
||||
// 二维列表占用 O(n^2) 空间
|
||||
let numList = Array(repeating: Array(repeating: 0, count: n), count: n)
|
||||
}
|
||||
|
||||
// 平方阶(递归实现)
|
||||
/* 平方阶(递归实现) */
|
||||
@discardableResult
|
||||
func quadraticRecur(n: Int) -> Int {
|
||||
if n <= 0 {
|
||||
@@ -67,7 +67,7 @@ func quadraticRecur(n: Int) -> Int {
|
||||
return quadraticRecur(n: n - 1)
|
||||
}
|
||||
|
||||
// 指数阶(建立满二叉树)
|
||||
/* 指数阶(建立满二叉树) */
|
||||
func buildTree(n: Int) -> TreeNode? {
|
||||
if n == 0 {
|
||||
return nil
|
||||
@@ -80,7 +80,7 @@ func buildTree(n: Int) -> TreeNode? {
|
||||
|
||||
@main
|
||||
enum SpaceComplexity {
|
||||
// Driver Code
|
||||
/* Driver Code */
|
||||
static func main() {
|
||||
let n = 5
|
||||
// 常数阶
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Author: nuomi1 (nuomi1@qq.com)
|
||||
*/
|
||||
|
||||
// 常数阶
|
||||
/* 常数阶 */
|
||||
func constant(n: Int) -> Int {
|
||||
var count = 0
|
||||
let size = 100_000
|
||||
@@ -14,7 +14,7 @@ func constant(n: Int) -> Int {
|
||||
return count
|
||||
}
|
||||
|
||||
// 线性阶
|
||||
/* 线性阶 */
|
||||
func linear(n: Int) -> Int {
|
||||
var count = 0
|
||||
for _ in 0 ..< n {
|
||||
@@ -23,7 +23,7 @@ func linear(n: Int) -> Int {
|
||||
return count
|
||||
}
|
||||
|
||||
// 线性阶(遍历数组)
|
||||
/* 线性阶(遍历数组) */
|
||||
func arrayTraversal(nums: [Int]) -> Int {
|
||||
var count = 0
|
||||
// 循环次数与数组长度成正比
|
||||
@@ -33,7 +33,7 @@ func arrayTraversal(nums: [Int]) -> Int {
|
||||
return count
|
||||
}
|
||||
|
||||
// 平方阶
|
||||
/* 平方阶 */
|
||||
func quadratic(n: Int) -> Int {
|
||||
var count = 0
|
||||
// 循环次数与数组长度成平方关系
|
||||
@@ -45,7 +45,7 @@ func quadratic(n: Int) -> Int {
|
||||
return count
|
||||
}
|
||||
|
||||
// 平方阶(冒泡排序)
|
||||
/* 平方阶(冒泡排序) */
|
||||
func bubbleSort(nums: inout [Int]) -> Int {
|
||||
var count = 0 // 计数器
|
||||
// 外循环:待排序元素数量为 n-1, n-2, ..., 1
|
||||
@@ -64,7 +64,7 @@ func bubbleSort(nums: inout [Int]) -> Int {
|
||||
return count
|
||||
}
|
||||
|
||||
// 指数阶(循环实现)
|
||||
/* 指数阶(循环实现) */
|
||||
func exponential(n: Int) -> Int {
|
||||
var count = 0
|
||||
var base = 1
|
||||
@@ -79,7 +79,7 @@ func exponential(n: Int) -> Int {
|
||||
return count
|
||||
}
|
||||
|
||||
// 指数阶(递归实现)
|
||||
/* 指数阶(递归实现) */
|
||||
func expRecur(n: Int) -> Int {
|
||||
if n == 1 {
|
||||
return 1
|
||||
@@ -87,7 +87,7 @@ func expRecur(n: Int) -> Int {
|
||||
return expRecur(n: n - 1) + expRecur(n: n - 1) + 1
|
||||
}
|
||||
|
||||
// 对数阶(循环实现)
|
||||
/* 对数阶(循环实现) */
|
||||
func logarithmic(n: Int) -> Int {
|
||||
var count = 0
|
||||
var n = n
|
||||
@@ -98,7 +98,7 @@ func logarithmic(n: Int) -> Int {
|
||||
return count
|
||||
}
|
||||
|
||||
// 对数阶(递归实现)
|
||||
/* 对数阶(递归实现) */
|
||||
func logRecur(n: Int) -> Int {
|
||||
if n <= 1 {
|
||||
return 0
|
||||
@@ -106,7 +106,7 @@ func logRecur(n: Int) -> Int {
|
||||
return logRecur(n: n / 2) + 1
|
||||
}
|
||||
|
||||
// 线性对数阶
|
||||
/* 线性对数阶 */
|
||||
func linearLogRecur(n: Double) -> Int {
|
||||
if n <= 1 {
|
||||
return 1
|
||||
@@ -118,7 +118,7 @@ func linearLogRecur(n: Double) -> Int {
|
||||
return count
|
||||
}
|
||||
|
||||
// 阶乘阶(递归实现)
|
||||
/* 阶乘阶(递归实现) */
|
||||
func factorialRecur(n: Int) -> Int {
|
||||
if n == 0 {
|
||||
return 1
|
||||
@@ -133,39 +133,40 @@ func factorialRecur(n: Int) -> Int {
|
||||
|
||||
@main
|
||||
enum TimeComplexity {
|
||||
/* Driver Code */
|
||||
static func main() {
|
||||
// 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势
|
||||
let n = 8
|
||||
print("输入数据大小 n =", n)
|
||||
print("输入数据大小 n = \(n)")
|
||||
|
||||
var count = constant(n: n)
|
||||
print("常数阶的计算操作数量 =", count)
|
||||
print("常数阶的计算操作数量 = \(count)")
|
||||
|
||||
count = linear(n: n)
|
||||
print("线性阶的计算操作数量 =", count)
|
||||
print("线性阶的计算操作数量 = \(count)")
|
||||
count = arrayTraversal(nums: Array(repeating: 0, count: n))
|
||||
print("线性阶(遍历数组)的计算操作数量 =", count)
|
||||
print("线性阶(遍历数组)的计算操作数量 = \(count)")
|
||||
|
||||
count = quadratic(n: n)
|
||||
print("平方阶的计算操作数量 =", count)
|
||||
print("平方阶的计算操作数量 = \(count)")
|
||||
var nums = Array(sequence(first: n, next: { $0 > 0 ? $0 - 1 : nil })) // [n,n-1,...,2,1]
|
||||
count = bubbleSort(nums: &nums)
|
||||
print("平方阶(冒泡排序)的计算操作数量 =", count)
|
||||
print("平方阶(冒泡排序)的计算操作数量 = \(count)")
|
||||
|
||||
count = exponential(n: n)
|
||||
print("指数阶(循环实现)的计算操作数量 =", count)
|
||||
print("指数阶(循环实现)的计算操作数量 = \(count)")
|
||||
count = expRecur(n: n)
|
||||
print("指数阶(递归实现)的计算操作数量 =", count)
|
||||
print("指数阶(递归实现)的计算操作数量 = \(count)")
|
||||
|
||||
count = logarithmic(n: n)
|
||||
print("对数阶(循环实现)的计算操作数量 =", count)
|
||||
print("对数阶(循环实现)的计算操作数量 = \(count)")
|
||||
count = logRecur(n: n)
|
||||
print("对数阶(递归实现)的计算操作数量 =", count)
|
||||
print("对数阶(递归实现)的计算操作数量 = \(count)")
|
||||
|
||||
count = linearLogRecur(n: Double(n))
|
||||
print("线性对数阶(递归实现)的计算操作数量 =", count)
|
||||
print("线性对数阶(递归实现)的计算操作数量 = \(count)")
|
||||
|
||||
count = factorialRecur(n: n)
|
||||
print("阶乘阶(递归实现)的计算操作数量 =", count)
|
||||
print("阶乘阶(递归实现)的计算操作数量 = \(count)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Author: nuomi1 (nuomi1@qq.com)
|
||||
*/
|
||||
|
||||
// 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱
|
||||
/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */
|
||||
func randomNumbers(n: Int) -> [Int] {
|
||||
// 生成数组 nums = { 1, 2, 3, ..., n }
|
||||
var nums = Array(1 ... n)
|
||||
@@ -13,7 +13,7 @@ func randomNumbers(n: Int) -> [Int] {
|
||||
return nums
|
||||
}
|
||||
|
||||
// 查找数组 nums 中数字 1 所在索引
|
||||
/* 查找数组 nums 中数字 1 所在索引 */
|
||||
func findOne(nums: [Int]) -> Int {
|
||||
for i in nums.indices {
|
||||
if nums[i] == 1 {
|
||||
@@ -25,14 +25,14 @@ func findOne(nums: [Int]) -> Int {
|
||||
|
||||
@main
|
||||
enum WorstBestTimeComplexity {
|
||||
// Driver Code
|
||||
/* Driver Code */
|
||||
static func main() {
|
||||
for _ in 0 ..< 10 {
|
||||
let n = 100
|
||||
let nums = randomNumbers(n: n)
|
||||
let index = findOne(nums: nums)
|
||||
print("数组 [ 1, 2, ..., n ] 被打乱后 =", nums)
|
||||
print("数字 1 的索引为", index)
|
||||
print("数组 [ 1, 2, ..., n ] 被打乱后 = \(nums)")
|
||||
print("数字 1 的索引为 \(index)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,16 @@ public enum PrintUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static func printLinkedList(head: ListNode) {
|
||||
var head: ListNode? = head
|
||||
var list: [String] = []
|
||||
while head != nil {
|
||||
list.append("\(head!.val)")
|
||||
head = head?.next
|
||||
}
|
||||
print(list.joined(separator: " -> "))
|
||||
}
|
||||
|
||||
public static func printTree(root: TreeNode?) {
|
||||
printTree(root: root, prev: nil, isLeft: false)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ function hierOrder(root: TreeNode | null): number[] {
|
||||
/* Driver Code */
|
||||
/* 初始化二叉树 */
|
||||
// 这里借助了一个从数组直接生成二叉树的函数
|
||||
var root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]);
|
||||
var root = arrToTree([1, 2, 3, 4, 5, 6, 7]);
|
||||
console.log('\n初始化二叉树\n');
|
||||
printTree(root);
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ function postOrder(root: TreeNode | null): void {
|
||||
/* Driver Code */
|
||||
/* 初始化二叉树 */
|
||||
// 这里借助了一个从数组直接生成二叉树的函数
|
||||
const root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]);
|
||||
const root = arrToTree([1, 2, 3, 4, 5, 6, 7]);
|
||||
console.log('\n初始化二叉树\n');
|
||||
printTree(root);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class TreeNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a binary tree with an array
|
||||
* Generate a binary tree given an array
|
||||
* @param arr
|
||||
* @return
|
||||
*/
|
||||
@@ -31,19 +31,19 @@ function arrToTree(arr: (number | null)[]): TreeNode | null {
|
||||
|
||||
const root = new TreeNode(arr[0] as number);
|
||||
const queue = [root];
|
||||
let i = 1;
|
||||
let i = 0;
|
||||
while (queue.length) {
|
||||
let node = queue.shift() as TreeNode;
|
||||
if (++i >= arr.length) break;
|
||||
if (arr[i] !== null) {
|
||||
node.left = new TreeNode(arr[i] as number);
|
||||
queue.push(node.left);
|
||||
}
|
||||
i++;
|
||||
if (++i >= arr.length) break;
|
||||
if (arr[i] !== null) {
|
||||
node.right = new TreeNode(arr[i] as number);
|
||||
queue.push(node.right);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
2
codes/zig/.gitignore
vendored
Normal file
2
codes/zig/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
zig-cache/
|
||||
zig-out/
|
||||
46
codes/zig/build.zig
Normal file
46
codes/zig/build.zig
Normal file
@@ -0,0 +1,46 @@
|
||||
const std = @import("std");
|
||||
|
||||
// zig version 0.10.0
|
||||
pub fn build(b: *std.build.Builder) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const mode = b.standardReleaseOptions();
|
||||
|
||||
// "chapter_computational_complexity/time_complexity.zig"
|
||||
// Run Command: zig build run_time_complexity
|
||||
const exe_time_complexity = b.addExecutable("time_complexity", "chapter_computational_complexity/time_complexity.zig");
|
||||
exe_time_complexity.addPackagePath("include", "include/include.zig");
|
||||
exe_time_complexity.setTarget(target);
|
||||
exe_time_complexity.setBuildMode(mode);
|
||||
exe_time_complexity.install();
|
||||
const run_cmd_time_complexity = exe_time_complexity.run();
|
||||
run_cmd_time_complexity.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| run_cmd_time_complexity.addArgs(args);
|
||||
const run_step_time_complexity = b.step("run_time_complexity", "Run time_complexity");
|
||||
run_step_time_complexity.dependOn(&run_cmd_time_complexity.step);
|
||||
|
||||
// "chapter_computational_complexity/worst_best_time_complexity.zig"
|
||||
// Run Command: zig build run_worst_best_time_complexity
|
||||
const exe_worst_best_time_complexity = b.addExecutable("worst_best_time_complexity", "chapter_computational_complexity/worst_best_time_complexity.zig");
|
||||
exe_worst_best_time_complexity.addPackagePath("include", "include/include.zig");
|
||||
exe_worst_best_time_complexity.setTarget(target);
|
||||
exe_worst_best_time_complexity.setBuildMode(mode);
|
||||
exe_worst_best_time_complexity.install();
|
||||
const run_cmd_worst_best_time_complexity = exe_worst_best_time_complexity.run();
|
||||
run_cmd_worst_best_time_complexity.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| run_cmd_worst_best_time_complexity.addArgs(args);
|
||||
const run_step_worst_best_time_complexity = b.step("run_worst_best_time_complexity", "Run worst_best_time_complexity");
|
||||
run_step_worst_best_time_complexity.dependOn(&run_cmd_worst_best_time_complexity.step);
|
||||
|
||||
// "chapter_computational_complexity/leetcode_two_sum.zig"
|
||||
// Run Command: zig build run_leetcode_two_sum
|
||||
const exe_leetcode_two_sum = b.addExecutable("leetcode_two_sum", "chapter_computational_complexity/leetcode_two_sum.zig");
|
||||
exe_leetcode_two_sum.addPackagePath("include", "include/include.zig");
|
||||
exe_leetcode_two_sum.setTarget(target);
|
||||
exe_leetcode_two_sum.setBuildMode(mode);
|
||||
exe_leetcode_two_sum.install();
|
||||
const run_cmd_leetcode_two_sum = exe_leetcode_two_sum.run();
|
||||
run_cmd_leetcode_two_sum.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| run_cmd_leetcode_two_sum.addArgs(args);
|
||||
const run_step_leetcode_two_sum = b.step("run_leetcode_two_sum", "Run leetcode_two_sum");
|
||||
run_step_leetcode_two_sum.dependOn(&run_cmd_leetcode_two_sum.step);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// File: leetcode_two_sum.zig
|
||||
// Created Time: 2023-01-07
|
||||
// Author: sjinzh (sjinzh@gmail.com)
|
||||
|
||||
const std = @import("std");
|
||||
const inc = @import("include");
|
||||
|
||||
const SolutionBruteForce = struct {
|
||||
pub fn twoSum(self: *SolutionBruteForce, nums: []i32, target: i32) [2]i32 {
|
||||
_ = self;
|
||||
var size: usize = nums.len;
|
||||
var i: usize = 0;
|
||||
// 两层循环,时间复杂度 O(n^2)
|
||||
while (i < size - 1) : (i += 1) {
|
||||
var j = i + 1;
|
||||
while (j < size) : (j += 1) {
|
||||
if (nums[i] + nums[j] == target) {
|
||||
return [_]i32{@intCast(i32, i), @intCast(i32, j)};
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const SolutionHashMap = struct {
|
||||
pub fn twoSum(self: *SolutionHashMap, nums: []i32, target: i32) ![2]i32 {
|
||||
_ = self;
|
||||
var size: usize = nums.len;
|
||||
// 辅助哈希表,空间复杂度 O(n)
|
||||
var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator);
|
||||
defer dic.deinit();
|
||||
var i: usize = 0;
|
||||
// 单层循环,时间复杂度 O(n)
|
||||
while (i < size) : (i += 1) {
|
||||
if (dic.contains(target - nums[i])) {
|
||||
return [_]i32{dic.get(target - nums[i]).?, @intCast(i32, i)};
|
||||
}
|
||||
try dic.put(nums[i], @intCast(i32, i));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// Driver Code
|
||||
pub fn main() !void {
|
||||
// ======= Test Case =======
|
||||
var nums = [_]i32{ 2, 7, 11, 15 };
|
||||
var target: i32 = 9;
|
||||
// 方法一
|
||||
var slt1 = SolutionBruteForce{};
|
||||
var res = slt1.twoSum(&nums, target);
|
||||
std.debug.print("方法一 res = ", .{});
|
||||
inc.PrintUtil.printArray(i32, &res);
|
||||
// 方法二
|
||||
var slt2 = SolutionHashMap{};
|
||||
res = try slt2.twoSum(&nums, target);
|
||||
std.debug.print("方法二 res = ", .{});
|
||||
inc.PrintUtil.printArray(i32, &res);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Author: sjinzh (sjinzh@gmail.com)
|
||||
|
||||
const std = @import("std");
|
||||
const inc = @import("include");
|
||||
|
||||
// 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱
|
||||
pub fn randomNumbers(comptime n: usize) [n]i32 {
|
||||
@@ -26,17 +27,15 @@ pub fn findOne(nums: []i32) i32 {
|
||||
}
|
||||
|
||||
// Driver Code
|
||||
pub fn main() !void {
|
||||
pub fn main() void {
|
||||
var i: i32 = 0;
|
||||
while (i < 10) : (i += 1) {
|
||||
const n: usize = 100;
|
||||
var nums = randomNumbers(n);
|
||||
var index = findOne(&nums);
|
||||
std.debug.print("\n数组 [ 1, 2, ..., n ] 被打乱后 = ", .{});
|
||||
for (nums) |num, j| {
|
||||
std.debug.print("{}{s}", .{num, if (j == nums.len-1) "" else "," });
|
||||
}
|
||||
std.debug.print("\n数字 1 的索引为 {}\n", .{index});
|
||||
inc.PrintUtil.printArray(i32, &nums);
|
||||
std.debug.print("数字 1 的索引为 {}\n", .{index});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
13
codes/zig/include/PrintUtil.zig
Normal file
13
codes/zig/include/PrintUtil.zig
Normal file
@@ -0,0 +1,13 @@
|
||||
// File: TreeNode.zig
|
||||
// Created Time: 2023-01-07
|
||||
// Author: sjinzh (sjinzh@gmail.com)
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
// Print an Array
|
||||
pub fn printArray(comptime T: type, nums: []T) void {
|
||||
std.debug.print("[", .{});
|
||||
for (nums) |num, j| {
|
||||
std.debug.print("{}{s}", .{num, if (j == nums.len-1) "]\n" else ", " });
|
||||
}
|
||||
}
|
||||
5
codes/zig/include/include.zig
Normal file
5
codes/zig/include/include.zig
Normal file
@@ -0,0 +1,5 @@
|
||||
// File: include.zig
|
||||
// Created Time: 2023-01-04
|
||||
// Author: sjinzh (sjinzh@gmail.com)
|
||||
|
||||
pub const PrintUtil = @import("PrintUtil.zig");
|
||||
@@ -14,7 +14,7 @@ comments: true
|
||||
|
||||
观察上图,我们发现 **数组首元素的索引为 $0$** 。你可能会想,这并不符合日常习惯,首个元素的索引为什么不是 $1$ 呢,这不是更加自然吗?我认同你的想法,但请先记住这个设定,后面讲内存地址计算时,我会尝试解答这个问题。
|
||||
|
||||
**数组有多种初始化写法。** 根据实际需要,选代码最短的那一种就好。
|
||||
**数组有多种初始化写法**。根据实际需要,选代码最短的那一种就好。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -81,9 +81,17 @@ comments: true
|
||||
int[] nums = { 1, 3, 2, 5, 4 };
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="array.swift"
|
||||
/* 初始化数组 */
|
||||
let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]
|
||||
let nums = [1, 3, 2, 5, 4]
|
||||
```
|
||||
|
||||
## 数组优点
|
||||
|
||||
**在数组中访问元素非常高效。** 这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。
|
||||
**在数组中访问元素非常高效**。这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。
|
||||
|
||||

|
||||
|
||||
@@ -193,9 +201,22 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="array.swift"
|
||||
/* 随机返回一个数组元素 */
|
||||
func randomAccess(nums: [Int]) -> Int {
|
||||
// 在区间 [0, nums.count) 中随机抽取一个数字
|
||||
let randomIndex = nums.indices.randomElement()!
|
||||
// 获取并返回随机元素
|
||||
let randomNum = nums[randomIndex]
|
||||
return randomNum
|
||||
}
|
||||
```
|
||||
|
||||
## 数组缺点
|
||||
|
||||
**数组在初始化后长度不可变。** 由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。
|
||||
**数组在初始化后长度不可变**。由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -317,7 +338,23 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
|
||||
}
|
||||
```
|
||||
|
||||
**数组中插入或删除元素效率低下。** 假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点:
|
||||
=== "Swift"
|
||||
|
||||
```swift title="array.swift"
|
||||
/* 扩展数组长度 */
|
||||
func extend(nums: [Int], enlarge: Int) -> [Int] {
|
||||
// 初始化一个扩展长度后的数组
|
||||
var res = Array(repeating: 0, count: nums.count + enlarge)
|
||||
// 将原数组中的所有元素复制到新数组
|
||||
for i in nums.indices {
|
||||
res[i] = nums[i]
|
||||
}
|
||||
// 返回扩展后的新数组
|
||||
return res
|
||||
}
|
||||
```
|
||||
|
||||
**数组中插入或删除元素效率低下**。假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点:
|
||||
|
||||
- **时间复杂度高:** 数组的插入和删除的平均时间复杂度均为 $O(N)$ ,其中 $N$ 为数组长度。
|
||||
- **丢失元素:** 由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会被丢失。
|
||||
@@ -486,9 +523,32 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="array.swift"
|
||||
/* 在数组的索引 index 处插入元素 num */
|
||||
func insert(nums: inout [Int], num: Int, index: Int) {
|
||||
// 把索引 index 以及之后的所有元素向后移动一位
|
||||
for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) {
|
||||
nums[i] = nums[i - 1]
|
||||
}
|
||||
// 将 num 赋给 index 处元素
|
||||
nums[index] = num
|
||||
}
|
||||
|
||||
/* 删除索引 index 处元素 */
|
||||
func remove(nums: inout [Int], index: Int) {
|
||||
let count = nums.count
|
||||
// 把索引 index 之后的所有元素向前移动一位
|
||||
for i in sequence(first: index, next: { $0 < count - 1 - 1 ? $0 + 1 : nil }) {
|
||||
nums[i] = nums[i + 1]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 数组常用操作
|
||||
|
||||
**数组遍历。** 以下介绍两种常用的遍历方法。
|
||||
**数组遍历**。以下介绍两种常用的遍历方法。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -611,7 +671,24 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
|
||||
}
|
||||
```
|
||||
|
||||
**数组查找。** 通过遍历数组,查找数组内的指定元素,并输出对应索引。
|
||||
=== "Swift"
|
||||
|
||||
```swift title="array.swift"
|
||||
/* 遍历数组 */
|
||||
func traverse(nums: [Int]) {
|
||||
var count = 0
|
||||
// 通过索引遍历数组
|
||||
for _ in nums.indices {
|
||||
count += 1
|
||||
}
|
||||
// 直接遍历数组
|
||||
for _ in nums {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**数组查找**。通过遍历数组,查找数组内的指定元素,并输出对应索引。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -713,10 +790,24 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="array.swift"
|
||||
/* 在数组中查找指定元素 */
|
||||
func find(nums: [Int], target: Int) -> Int {
|
||||
for i in nums.indices {
|
||||
if nums[i] == target {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
```
|
||||
|
||||
## 数组典型应用
|
||||
|
||||
**随机访问。** 如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。
|
||||
**随机访问**。如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。
|
||||
|
||||
**二分查找。** 例如前文查字典的例子,我们可以将字典中的所有字按照拼音顺序存储在数组中,然后使用与日常查纸质字典相同的“翻开中间,排除一半”的方式,来实现一个查电子字典的算法。
|
||||
**二分查找**。例如前文查字典的例子,我们可以将字典中的所有字按照拼音顺序存储在数组中,然后使用与日常查纸质字典相同的“翻开中间,排除一半”的方式,来实现一个查电子字典的算法。
|
||||
|
||||
**深度学习。** 神经网络中大量使用了向量、矩阵、张量之间的线性代数运算,这些数据都是以数组的形式构建的。数组是神经网络编程中最常使用的数据结构。
|
||||
**深度学习**。神经网络中大量使用了向量、矩阵、张量之间的线性代数运算,这些数据都是以数组的形式构建的。数组是神经网络编程中最常使用的数据结构。
|
||||
|
||||
@@ -112,9 +112,23 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
/* 链表结点类 */
|
||||
class ListNode {
|
||||
var val: Int // 结点值
|
||||
var next: ListNode? // 指向下一结点的指针(引用)
|
||||
|
||||
init(x: Int) { // 构造函数
|
||||
val = x
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**尾结点指向什么?** 我们一般将链表的最后一个结点称为「尾结点」,其指向的是「空」,在 Java / C++ / Python 中分别记为 `null` / `nullptr` / `None` 。在不引起歧义下,本书都使用 `null` 来表示空。
|
||||
|
||||
**链表初始化方法。** 建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。
|
||||
**链表初始化方法**。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。
|
||||
|
||||
!!! tip
|
||||
|
||||
@@ -122,7 +136,7 @@ comments: true
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title=""
|
||||
```java title="linked_list.java"
|
||||
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
|
||||
// 初始化各个结点
|
||||
ListNode n0 = new ListNode(1);
|
||||
@@ -139,7 +153,7 @@ comments: true
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title=""
|
||||
```cpp title="linked_list.cpp"
|
||||
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
|
||||
// 初始化各个结点
|
||||
ListNode* n0 = new ListNode(1);
|
||||
@@ -156,7 +170,7 @@ comments: true
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title=""
|
||||
```python title="linked_list.py"
|
||||
""" 初始化链表 1 -> 3 -> 2 -> 5 -> 4 """
|
||||
# 初始化各个结点
|
||||
n0 = ListNode(1)
|
||||
@@ -173,7 +187,7 @@ comments: true
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title=""
|
||||
```go title="linked_list.go"
|
||||
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
|
||||
// 初始化各个结点
|
||||
n0 := NewListNode(1)
|
||||
@@ -191,7 +205,7 @@ comments: true
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js title=""
|
||||
```js title="linked_list.js"
|
||||
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
|
||||
// 初始化各个结点
|
||||
const n0 = new ListNode(1);
|
||||
@@ -208,7 +222,7 @@ comments: true
|
||||
|
||||
=== "TypeScript"
|
||||
|
||||
```typescript title=""
|
||||
```typescript title="linked_list.ts"
|
||||
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
|
||||
// 初始化各个结点
|
||||
const n0 = new ListNode(1);
|
||||
@@ -225,13 +239,13 @@ comments: true
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title=""
|
||||
```c title="linked_list.c"
|
||||
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
```csharp title="linked_list.cs"
|
||||
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
|
||||
// 初始化各个结点
|
||||
ListNode n0 = new ListNode(1);
|
||||
@@ -246,9 +260,26 @@ comments: true
|
||||
n3.next = n4;
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="linked_list.swift"
|
||||
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
|
||||
// 初始化各个结点
|
||||
let n0 = ListNode(x: 1)
|
||||
let n1 = ListNode(x: 3)
|
||||
let n2 = ListNode(x: 2)
|
||||
let n3 = ListNode(x: 5)
|
||||
let n4 = ListNode(x: 4)
|
||||
// 构建引用指向
|
||||
n0.next = n1
|
||||
n1.next = n2
|
||||
n2.next = n3
|
||||
n3.next = n4
|
||||
```
|
||||
|
||||
## 链表优点
|
||||
|
||||
**在链表中,插入与删除结点的操作效率高。** 例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。
|
||||
**在链表中,插入与删除结点的操作效率高**。例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。
|
||||
|
||||

|
||||
|
||||
@@ -256,7 +287,7 @@ comments: true
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title=""
|
||||
```java title="linked_list.java"
|
||||
/* 在链表的结点 n0 之后插入结点 P */
|
||||
void insert(ListNode n0, ListNode P) {
|
||||
ListNode n1 = n0.next;
|
||||
@@ -277,7 +308,7 @@ comments: true
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title=""
|
||||
```cpp title="linked_list.cpp"
|
||||
/* 在链表的结点 n0 之后插入结点 P */
|
||||
void insert(ListNode* n0, ListNode* P) {
|
||||
ListNode* n1 = n0->next;
|
||||
@@ -300,7 +331,7 @@ comments: true
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title=""
|
||||
```python title="linked_list.py"
|
||||
""" 在链表的结点 n0 之后插入结点 P """
|
||||
def insert(n0, P):
|
||||
n1 = n0.next
|
||||
@@ -319,7 +350,7 @@ comments: true
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title=""
|
||||
```go title="linked_list.go"
|
||||
/* 在链表的结点 n0 之后插入结点 P */
|
||||
func insert(n0 *ListNode, P *ListNode) {
|
||||
n1 := n0.Next
|
||||
@@ -341,7 +372,7 @@ comments: true
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js title=""
|
||||
```js title="linked_list.js"
|
||||
/* 在链表的结点 n0 之后插入结点 P */
|
||||
function insert(n0, P) {
|
||||
let n1 = n0.next;
|
||||
@@ -362,7 +393,7 @@ comments: true
|
||||
|
||||
=== "TypeScript"
|
||||
|
||||
```typescript title=""
|
||||
```typescript title="linked_list.ts"
|
||||
/* 在链表的结点 n0 之后插入结点 P */
|
||||
function insert(n0: ListNode, P: ListNode): void {
|
||||
const n1 = n0.next;
|
||||
@@ -383,13 +414,13 @@ comments: true
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title=""
|
||||
```c title="linked_list.c"
|
||||
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
```csharp title="linked_list.cs"
|
||||
// 在链表的结点 n0 之后插入结点 P
|
||||
void Insert(ListNode n0, ListNode P)
|
||||
{
|
||||
@@ -410,13 +441,36 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="linked_list.swift"
|
||||
/* 在链表的结点 n0 之后插入结点 P */
|
||||
func insert(n0: ListNode, P: ListNode) {
|
||||
let n1 = n0.next
|
||||
n0.next = P
|
||||
P.next = n1
|
||||
}
|
||||
|
||||
/* 删除链表的结点 n0 之后的首个结点 */
|
||||
func remove(n0: ListNode) {
|
||||
if n0.next == nil {
|
||||
return
|
||||
}
|
||||
// n0 -> P -> n1
|
||||
let P = n0.next
|
||||
let n1 = P?.next
|
||||
n0.next = n1
|
||||
P?.next = nil
|
||||
}
|
||||
```
|
||||
|
||||
## 链表缺点
|
||||
|
||||
**链表访问结点效率低。** 上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。
|
||||
**链表访问结点效率低**。上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title=""
|
||||
```java title="linked_list.java"
|
||||
/* 访问链表中索引为 index 的结点 */
|
||||
ListNode access(ListNode head, int index) {
|
||||
for (int i = 0; i < index; i++) {
|
||||
@@ -430,7 +484,7 @@ comments: true
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title=""
|
||||
```cpp title="linked_list.cpp"
|
||||
/* 访问链表中索引为 index 的结点 */
|
||||
ListNode* access(ListNode* head, int index) {
|
||||
for (int i = 0; i < index; i++) {
|
||||
@@ -444,7 +498,7 @@ comments: true
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title=""
|
||||
```python title="linked_list.py"
|
||||
""" 访问链表中索引为 index 的结点 """
|
||||
def access(head, index):
|
||||
for _ in range(index):
|
||||
@@ -456,7 +510,7 @@ comments: true
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title=""
|
||||
```go title="linked_list.go"
|
||||
/* 访问链表中索引为 index 的结点 */
|
||||
func access(head *ListNode, index int) *ListNode {
|
||||
for i := 0; i < index; i++ {
|
||||
@@ -471,7 +525,7 @@ comments: true
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js title=""
|
||||
```js title="linked_list.js"
|
||||
/* 访问链表中索引为 index 的结点 */
|
||||
function access(head, index) {
|
||||
for (let i = 0; i < index; i++) {
|
||||
@@ -485,7 +539,7 @@ comments: true
|
||||
|
||||
=== "TypeScript"
|
||||
|
||||
```typescript title=""
|
||||
```typescript title="linked_list.ts"
|
||||
/* 访问链表中索引为 index 的结点 */
|
||||
function access(head: ListNode | null, index: number): ListNode | null {
|
||||
for (let i = 0; i < index; i++) {
|
||||
@@ -500,13 +554,13 @@ comments: true
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title=""
|
||||
```c title="linked_list.c"
|
||||
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
```csharp title="linked_list.cs"
|
||||
// 访问链表中索引为 index 的结点
|
||||
ListNode Access(ListNode head, int index)
|
||||
{
|
||||
@@ -520,15 +574,31 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
**链表的内存占用多。** 链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。
|
||||
=== "Swift"
|
||||
|
||||
```swift title="linked_list.swift"
|
||||
/* 访问链表中索引为 index 的结点 */
|
||||
func access(head: ListNode, index: Int) -> ListNode? {
|
||||
var head: ListNode? = head
|
||||
for _ in 0 ..< index {
|
||||
head = head?.next
|
||||
if head == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return head
|
||||
}
|
||||
```
|
||||
|
||||
**链表的内存占用多**。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。
|
||||
|
||||
## 链表常用操作
|
||||
|
||||
**遍历链表查找。** 遍历链表,查找链表内值为 `target` 的结点,输出结点在链表中的索引。
|
||||
**遍历链表查找**。遍历链表,查找链表内值为 `target` 的结点,输出结点在链表中的索引。
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title=""
|
||||
```java title="linked_list.java"
|
||||
/* 在链表中查找值为 target 的首个结点 */
|
||||
int find(ListNode head, int target) {
|
||||
int index = 0;
|
||||
@@ -544,7 +614,7 @@ comments: true
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title=""
|
||||
```cpp title="linked_list.cpp"
|
||||
/* 在链表中查找值为 target 的首个结点 */
|
||||
int find(ListNode* head, int target) {
|
||||
int index = 0;
|
||||
@@ -560,7 +630,7 @@ comments: true
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title=""
|
||||
```python title="linked_list.py"
|
||||
""" 在链表中查找值为 target 的首个结点 """
|
||||
def find(head, target):
|
||||
index = 0
|
||||
@@ -574,7 +644,7 @@ comments: true
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title=""
|
||||
```go title="linked_list.go"
|
||||
/* 在链表中查找值为 target 的首个结点 */
|
||||
func find(head *ListNode, target int) int {
|
||||
index := 0
|
||||
@@ -591,7 +661,7 @@ comments: true
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js title=""
|
||||
```js title="linked_list.js"
|
||||
/* 在链表中查找值为 target 的首个结点 */
|
||||
function find(head, target) {
|
||||
let index = 0;
|
||||
@@ -608,7 +678,7 @@ comments: true
|
||||
|
||||
=== "TypeScript"
|
||||
|
||||
```typescript title=""
|
||||
```typescript title="linked_list.ts"
|
||||
/* 在链表中查找值为 target 的首个结点 */
|
||||
function find(head: ListNode | null, target: number): number {
|
||||
let index = 0;
|
||||
@@ -625,13 +695,13 @@ comments: true
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title=""
|
||||
```c title="linked_list.c"
|
||||
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
```csharp title="linked_list.cs"
|
||||
// 在链表中查找值为 target 的首个结点
|
||||
int Find(ListNode head, int target)
|
||||
{
|
||||
@@ -647,13 +717,31 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="linked_list.swift"
|
||||
/* 在链表中查找值为 target 的首个结点 */
|
||||
func find(head: ListNode, target: Int) -> Int {
|
||||
var head: ListNode? = head
|
||||
var index = 0
|
||||
while head != nil {
|
||||
if head?.val == target {
|
||||
return index
|
||||
}
|
||||
head = head?.next
|
||||
index += 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
```
|
||||
|
||||
## 常见链表类型
|
||||
|
||||
**单向链表。** 即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` 。
|
||||
**单向链表**。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` 。
|
||||
|
||||
**环形链表。** 如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,我们可以将任意结点看作是头结点。
|
||||
**环形链表**。如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,我们可以将任意结点看作是头结点。
|
||||
|
||||
**双向链表。** 单向链表仅记录了一个方向的指针(引用),在双向链表的结点定义中,同时有指向下一结点(后继结点)和上一结点(前驱结点)的「指针(引用)」。双向链表相对于单向链表更加灵活,即可以朝两个方向遍历链表,但也需要占用更多的内存空间。
|
||||
**双向链表**。单向链表仅记录了一个方向的指针(引用),在双向链表的结点定义中,同时有指向下一结点(后继结点)和上一结点(前驱结点)的「指针(引用)」。双向链表相对于单向链表更加灵活,即可以朝两个方向遍历链表,但也需要占用更多的内存空间。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -760,6 +848,21 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
/* 双向链表结点类 */
|
||||
class ListNode {
|
||||
var val: Int // 结点值
|
||||
var next: ListNode? // 指向后继结点的指针(引用)
|
||||
var prev: ListNode? // 指向前驱结点的指针(引用)
|
||||
|
||||
init(x: Int) { // 构造函数
|
||||
val = x
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
<p align="center"> Fig. 常见链表类型 </p>
|
||||
|
||||
@@ -4,13 +4,13 @@ comments: true
|
||||
|
||||
# 列表
|
||||
|
||||
**由于长度不可变,数组的实用性大大降低。** 在很多情况下,我们事先并不知道会输入多少数据,这就为数组长度的选择带来了很大困难。长度选小了,需要在添加数据中频繁地扩容数组;长度选大了,又造成内存空间的浪费。
|
||||
**由于长度不可变,数组的实用性大大降低**。在很多情况下,我们事先并不知道会输入多少数据,这就为数组长度的选择带来了很大困难。长度选小了,需要在添加数据中频繁地扩容数组;长度选大了,又造成内存空间的浪费。
|
||||
|
||||
为了解决此问题,诞生了一种被称为「列表 List」的数据结构。列表可以被理解为长度可变的数组,因此也常被称为「动态数组 Dynamic Array」。列表基于数组实现,继承了数组的优点,同时还可以在程序运行中实时扩容。在列表中,我们可以自由地添加元素,而不用担心超过容量限制。
|
||||
|
||||
## 列表常用操作
|
||||
|
||||
**初始化列表。** 我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。
|
||||
**初始化列表**。我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -91,7 +91,13 @@ comments: true
|
||||
List<int> list = numbers.ToList();
|
||||
```
|
||||
|
||||
**访问与更新元素。** 列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。
|
||||
=== "Swift"
|
||||
|
||||
```swift title="list.swift"
|
||||
|
||||
```
|
||||
|
||||
**访问与更新元素**。列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -169,7 +175,13 @@ comments: true
|
||||
list[1] = 0; // 将索引 1 处的元素更新为 0
|
||||
```
|
||||
|
||||
**在列表中添加、插入、删除元素。** 相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。
|
||||
=== "Swift"
|
||||
|
||||
```swift title="list.swift"
|
||||
|
||||
```
|
||||
|
||||
**在列表中添加、插入、删除元素**。相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -317,7 +329,13 @@ comments: true
|
||||
list.RemoveAt(3);
|
||||
```
|
||||
|
||||
**遍历列表。** 与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。
|
||||
=== "Swift"
|
||||
|
||||
```swift title="list.swift"
|
||||
|
||||
```
|
||||
|
||||
**遍历列表**。与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -437,7 +455,13 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
**拼接两个列表。** 再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。
|
||||
=== "Swift"
|
||||
|
||||
```swift title="list.swift"
|
||||
|
||||
```
|
||||
|
||||
**拼接两个列表**。再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -502,7 +526,13 @@ comments: true
|
||||
list.AddRange(list1); // 将列表 list1 拼接到 list 之后
|
||||
```
|
||||
|
||||
**排序列表。** 排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。
|
||||
=== "Swift"
|
||||
|
||||
```swift title="list.swift"
|
||||
|
||||
```
|
||||
|
||||
**排序列表**。排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -559,6 +589,12 @@ comments: true
|
||||
list.Sort(); // 排序后,列表元素从小到大排列
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="list.swift"
|
||||
|
||||
```
|
||||
|
||||
## 列表简易实现 *
|
||||
|
||||
为了帮助加深对列表的理解,我们在此提供一个列表的简易版本的实现。需要关注三个核心点:
|
||||
@@ -828,7 +864,7 @@ comments: true
|
||||
|
||||
```go title="my_list.go"
|
||||
/* 列表类简易实现 */
|
||||
type MyList struct {
|
||||
type myList struct {
|
||||
numsCapacity int
|
||||
nums []int
|
||||
numsSize int
|
||||
@@ -836,8 +872,8 @@ comments: true
|
||||
}
|
||||
|
||||
/* 构造函数 */
|
||||
func newMyList() *MyList {
|
||||
return &MyList{
|
||||
func newMyList() *myList {
|
||||
return &myList{
|
||||
numsCapacity: 10, // 列表容量
|
||||
nums: make([]int, 10), // 数组(存储列表元素)
|
||||
numsSize: 0, // 列表长度(即当前元素数量)
|
||||
@@ -846,17 +882,17 @@ comments: true
|
||||
}
|
||||
|
||||
/* 获取列表长度(即当前元素数量) */
|
||||
func (l *MyList) size() int {
|
||||
func (l *myList) size() int {
|
||||
return l.numsSize
|
||||
}
|
||||
|
||||
/* 获取列表容量 */
|
||||
func (l *MyList) capacity() int {
|
||||
func (l *myList) capacity() int {
|
||||
return l.numsCapacity
|
||||
}
|
||||
|
||||
/* 访问元素 */
|
||||
func (l *MyList) get(index int) int {
|
||||
func (l *myList) get(index int) int {
|
||||
// 索引如果越界则抛出异常,下同
|
||||
if index >= l.numsSize {
|
||||
panic("索引越界")
|
||||
@@ -865,7 +901,7 @@ comments: true
|
||||
}
|
||||
|
||||
/* 更新元素 */
|
||||
func (l *MyList) set(num, index int) {
|
||||
func (l *myList) set(num, index int) {
|
||||
if index >= l.numsSize {
|
||||
panic("索引越界")
|
||||
}
|
||||
@@ -873,7 +909,7 @@ comments: true
|
||||
}
|
||||
|
||||
/* 尾部添加元素 */
|
||||
func (l *MyList) add(num int) {
|
||||
func (l *myList) add(num int) {
|
||||
// 元素数量超出容量时,触发扩容机制
|
||||
if l.numsSize == l.numsCapacity {
|
||||
l.extendCapacity()
|
||||
@@ -884,7 +920,7 @@ comments: true
|
||||
}
|
||||
|
||||
/* 中间插入元素 */
|
||||
func (l *MyList) insert(num, index int) {
|
||||
func (l *myList) insert(num, index int) {
|
||||
if index >= l.numsSize {
|
||||
panic("索引越界")
|
||||
}
|
||||
@@ -902,20 +938,23 @@ comments: true
|
||||
}
|
||||
|
||||
/* 删除元素 */
|
||||
func (l *MyList) Remove(index int) {
|
||||
func (l *myList) remove(index int) int {
|
||||
if index >= l.numsSize {
|
||||
panic("索引越界")
|
||||
}
|
||||
num := l.nums[index]
|
||||
// 索引 i 之后的元素都向前移动一位
|
||||
for j := index; j < l.numsSize-1; j++ {
|
||||
l.nums[j] = l.nums[j+1]
|
||||
}
|
||||
// 更新元素数量
|
||||
l.numsSize--
|
||||
// 返回被删除元素
|
||||
return num
|
||||
}
|
||||
|
||||
/* 列表扩容 */
|
||||
func (l *MyList) extendCapacity() {
|
||||
func (l *myList) extendCapacity() {
|
||||
// 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组
|
||||
l.nums = append(l.nums, make([]int, l.numsCapacity*(l.extendRatio-1))...)
|
||||
// 更新列表容量
|
||||
@@ -1220,3 +1259,10 @@ comments: true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="my_list.swift"
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -8,15 +8,15 @@ comments: true
|
||||
|
||||
在开始学习算法之前,我们首先要想清楚算法的设计目标是什么,或者说,如何来评判算法的好与坏。整体上看,我们设计算法时追求两个层面的目标。
|
||||
|
||||
1. **找到问题解法。** 算法需要能够在规定的输入范围下,可靠地求得问题的正确解。
|
||||
2. **寻求最优解法。** 同一个问题可能存在多种解法,而我们希望算法效率尽可能的高。
|
||||
1. **找到问题解法**。算法需要能够在规定的输入范围下,可靠地求得问题的正确解。
|
||||
2. **寻求最优解法**。同一个问题可能存在多种解法,而我们希望算法效率尽可能的高。
|
||||
|
||||
换言之,在可以解决问题的前提下,算法效率则是主要评价维度,包括:
|
||||
|
||||
- **时间效率** ,即算法的运行速度的快慢。
|
||||
- **空间效率** ,即算法占用的内存空间大小。
|
||||
|
||||
数据结构与算法追求“运行得快、内存占用少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。
|
||||
数据结构与算法追求“运行速度快、占用内存少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。
|
||||
|
||||
## 效率评估方法
|
||||
|
||||
@@ -24,9 +24,9 @@ comments: true
|
||||
|
||||
假设我们现在有算法 A 和 算法 B ,都能够解决同一问题,现在需要对比两个算法之间的效率。我们能够想到的最直接的方式,就是找一台计算机,把两个算法都完整跑一遍,并监控记录运行时间和内存占用情况。这种评估方式能够反映真实情况,但是也存在很大的硬伤。
|
||||
|
||||
**难以排除测试环境的干扰因素。** 硬件配置会影响到算法的性能表现。例如,在某台计算机中,算法 A 比算法 B 运行时间更短;但换到另一台配置不同的计算机中,可能会得到相反的测试结果。这意味着我们需要在各种机器上展开测试,而这是不现实的。
|
||||
**难以排除测试环境的干扰因素**。硬件配置会影响到算法的性能表现。例如,在某台计算机中,算法 A 比算法 B 运行时间更短;但换到另一台配置不同的计算机中,可能会得到相反的测试结果。这意味着我们需要在各种机器上展开测试,而这是不现实的。
|
||||
|
||||
**展开完整测试非常耗费资源。** 随着输入数据量的大小变化,算法会呈现出不同的效率表现。比如,有可能输入数据量较小时,算法 A 运行时间短于算法 B ,而在输入数据量较大时,测试结果截然相反。因此,若想要达到具有说服力的对比结果,那么需要输入各种体量数据,这样的测试需要占用大量计算资源。
|
||||
**展开完整测试非常耗费资源**。随着输入数据量的大小变化,算法会呈现出不同的效率表现。比如,有可能输入数据量较小时,算法 A 运行时间短于算法 B ,而在输入数据量较大时,测试结果截然相反。因此,若想要达到具有说服力的对比结果,那么需要输入各种体量数据,这样的测试需要占用大量计算资源。
|
||||
|
||||
### 理论估算
|
||||
|
||||
@@ -34,7 +34,7 @@ comments: true
|
||||
|
||||
**复杂度分析评估随着输入数据量的增长,算法的运行时间和占用空间的增长趋势** 。根据时间和空间两方面,复杂度可分为「时间复杂度 Time Complexity」和「空间复杂度 Space Complexity」。
|
||||
|
||||
**复杂度分析克服了实际测试方法的弊端。** 一是独立于测试环境,分析结果适用于所有运行平台。二是可以体现不同数据量下的算法效率,尤其是可以反映大数据量下的算法性能。
|
||||
**复杂度分析克服了实际测试方法的弊端**。一是独立于测试环境,分析结果适用于所有运行平台。二是可以体现不同数据量下的算法效率,尤其是可以反映大数据量下的算法性能。
|
||||
|
||||
## 复杂度分析的重要性
|
||||
|
||||
|
||||
@@ -103,14 +103,14 @@ comments: true
|
||||
|
||||
```go title=""
|
||||
/* 结构体 */
|
||||
type Node struct {
|
||||
type node struct {
|
||||
val int
|
||||
next *Node
|
||||
next *node
|
||||
}
|
||||
|
||||
/* 创建 Node 结构体 */
|
||||
func newNode(val int) *Node {
|
||||
return &Node{val: val}
|
||||
|
||||
/* 创建 node 结构体 */
|
||||
func newNode(val int) *node {
|
||||
return &node{val: val}
|
||||
}
|
||||
|
||||
/* 函数 */
|
||||
@@ -177,7 +177,7 @@ comments: true
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
// 类
|
||||
/* 类 */
|
||||
class Node {
|
||||
var val: Int
|
||||
var next: Node?
|
||||
@@ -187,7 +187,7 @@ comments: true
|
||||
}
|
||||
}
|
||||
|
||||
// 函数
|
||||
/* 函数 */
|
||||
func function() -> Int {
|
||||
// do something...
|
||||
return 0
|
||||
@@ -208,8 +208,8 @@ comments: true
|
||||
|
||||
**最差空间复杂度中的“最差”有两层含义**,分别为输入数据的最差分布、算法运行中的最差时间点。
|
||||
|
||||
- **以最差输入数据为准。** 当 $n < 10$ 时,空间复杂度为 $O(1)$ ;但是当 $n > 10$ 时,初始化的数组 `nums` 使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ;
|
||||
- **以算法运行过程中的峰值内存为准。** 程序在执行最后一行之前,使用 $O(1)$ 空间;当初始化数组 `nums` 时,程序使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ;
|
||||
- **以最差输入数据为准**。当 $n < 10$ 时,空间复杂度为 $O(1)$ ;但是当 $n > 10$ 时,初始化的数组 `nums` 使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ;
|
||||
- **以算法运行过程中的峰值内存为准**。程序在执行最后一行之前,使用 $O(1)$ 空间;当初始化数组 `nums` 时,程序使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ;
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -301,7 +301,7 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
**在递归函数中,需要注意统计栈帧空间。** 例如函数 `loop()`,在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。而递归函数 `recur()` 在运行中会同时存在 $n$ 个未返回的 `recur()` ,从而使用 $O(n)$ 的栈帧空间。
|
||||
**在递归函数中,需要注意统计栈帧空间**。例如函数 `loop()`,在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。而递归函数 `recur()` 在运行中会同时存在 $n$ 个未返回的 `recur()` ,从而使用 $O(n)$ 的栈帧空间。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -436,14 +436,14 @@ comments: true
|
||||
return 0
|
||||
}
|
||||
|
||||
// 循环 O(1)
|
||||
/* 循环 O(1) */
|
||||
func loop(n: Int) {
|
||||
for _ in 0 ..< n {
|
||||
function()
|
||||
}
|
||||
}
|
||||
|
||||
// 递归 O(n)
|
||||
/* 递归 O(n) */
|
||||
func recur(n: Int) {
|
||||
if n == 1 {
|
||||
return
|
||||
@@ -604,7 +604,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="space_complexity.swift"
|
||||
// 常数阶
|
||||
/* 常数阶 */
|
||||
func constant(n: Int) {
|
||||
// 常量、变量、对象占用 O(1) 空间
|
||||
let a = 0
|
||||
@@ -687,7 +687,7 @@ $$
|
||||
// 长度为 n 的数组占用 O(n) 空间
|
||||
_ = make([]int, n)
|
||||
// 长度为 n 的列表占用 O(n) 空间
|
||||
var nodes []*Node
|
||||
var nodes []*node
|
||||
for i := 0; i < n; i++ {
|
||||
nodes = append(nodes, newNode(i))
|
||||
}
|
||||
@@ -743,7 +743,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="space_complexity.swift"
|
||||
// 线性阶
|
||||
/* 线性阶 */
|
||||
func linear(n: Int) {
|
||||
// 长度为 n 的数组占用 O(n) 空间
|
||||
let nums = Array(repeating: 0, count: n)
|
||||
@@ -834,7 +834,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="space_complexity.swift"
|
||||
// 线性阶(递归实现)
|
||||
/* 线性阶(递归实现) */
|
||||
func linearRecur(n: Int) {
|
||||
print("递归 n = \(n)")
|
||||
if n == 1 {
|
||||
@@ -954,7 +954,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="space_complexity.swift"
|
||||
// 平方阶
|
||||
/* 平方阶 */
|
||||
func quadratic(n: Int) {
|
||||
// 二维列表占用 O(n^2) 空间
|
||||
let numList = Array(repeating: Array(repeating: 0, count: n), count: n)
|
||||
@@ -1047,7 +1047,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="space_complexity.swift"
|
||||
// 平方阶(递归实现)
|
||||
/* 平方阶(递归实现) */
|
||||
func quadraticRecur(n: Int) -> Int {
|
||||
if n <= 0 {
|
||||
return 0
|
||||
@@ -1108,7 +1108,7 @@ $$
|
||||
|
||||
```go title="space_complexity.go"
|
||||
/* 指数阶(建立满二叉树) */
|
||||
func buildTree(n int) *TreeNode {
|
||||
func buildTree(n int) *treeNode {
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -1154,7 +1154,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="space_complexity.swift"
|
||||
// 指数阶(建立满二叉树)
|
||||
/* 指数阶(建立满二叉树) */
|
||||
func buildTree(n: Int) -> TreeNode? {
|
||||
if n == 0 {
|
||||
return nil
|
||||
|
||||
@@ -6,7 +6,7 @@ comments: true
|
||||
|
||||
理想情况下,我们希望算法的时间复杂度和空间复杂度都能够达到最优,而实际上,同时优化时间复杂度和空间复杂度是非常困难的。
|
||||
|
||||
**降低时间复杂度,往往是以提升空间复杂度为代价的,反之亦然。** 我们把牺牲内存空间来提升算法运行速度的思路称为「以空间换时间」;反之,称之为「以时间换空间」。选择哪种思路取决于我们更看重哪个方面。
|
||||
**降低时间复杂度,往往是以提升空间复杂度为代价的,反之亦然**。我们把牺牲内存空间来提升算法运行速度的思路称为「以空间换时间」;反之,称之为「以时间换空间」。选择哪种思路取决于我们更看重哪个方面。
|
||||
|
||||
大多数情况下,时间都是比空间更宝贵的,只要空间复杂度不要太离谱、能接受就行,**因此以空间换时间最为常用**。
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ $$
|
||||
}
|
||||
```
|
||||
|
||||
但实际上, **统计算法的运行时间既不合理也不现实。** 首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。
|
||||
但实际上, **统计算法的运行时间既不合理也不现实**。首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。
|
||||
|
||||
## 统计时间增长趋势
|
||||
|
||||
@@ -363,11 +363,11 @@ $$
|
||||
|
||||
相比直接统计算法运行时间,时间复杂度分析的做法有什么好处呢?以及有什么不足?
|
||||
|
||||
**时间复杂度可以有效评估算法效率。** 算法 `B` 运行时间的增长是线性的,在 $n > 1$ 时慢于算法 `A` ,在 $n > 1000000$ 时慢于算法 `C` 。实质上,只要输入数据大小 $n$ 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。
|
||||
**时间复杂度可以有效评估算法效率**。算法 `B` 运行时间的增长是线性的,在 $n > 1$ 时慢于算法 `A` ,在 $n > 1000000$ 时慢于算法 `C` 。实质上,只要输入数据大小 $n$ 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。
|
||||
|
||||
**时间复杂度分析将统计「计算操作的运行时间」简化为统计「计算操作的数量」。** 这是因为,无论是运行平台、还是计算操作类型,都与算法运行时间的增长趋势无关。因此,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”。
|
||||
**时间复杂度的推算方法更加简便**。在时间复杂度分析中,我们可以将统计「计算操作的运行时间」简化为统计「计算操作的数量」,这是因为,无论是运行平台还是计算操作类型,都与算法运行时间的增长趋势无关。因而,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”,这样的简化做法大大降低了估算难度。
|
||||
|
||||
**时间复杂度也存在一定的局限性。** 比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。即使存在这些问题,计算复杂度仍然是评判算法效率的最有效、最常用方法。
|
||||
**时间复杂度也存在一定的局限性**。比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。对于以上情况,我们很难仅凭时间复杂度来判定算法效率高低。然而,即使存在这些问题,计算复杂度仍然是评判算法效率的最有效且常用的方法。
|
||||
|
||||
## 函数渐近上界
|
||||
|
||||
@@ -421,12 +421,12 @@ $$
|
||||
|
||||
```go title=""
|
||||
func algorithm(n int) {
|
||||
a := 1 // +1
|
||||
a = a + 1 // +1
|
||||
a = a * 2 // +1
|
||||
a := 1 // +1
|
||||
a = a + 1 // +1
|
||||
a = a * 2 // +1
|
||||
// 循环 n 次
|
||||
for i := 0; i < n; i++ { // +1
|
||||
fmt.Println(a) // +1
|
||||
for i := 0; i < n; i++ { // +1
|
||||
fmt.Println(a) // +1
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -538,9 +538,9 @@ $T(n)$ 是个一次函数,说明时间增长趋势是线性的,因此易得
|
||||
|
||||
对着代码,从上到下一行一行地计数即可。然而,**由于上述 $c \cdot f(n)$ 中的常数项 $c$ 可以取任意大小,因此操作数量 $T(n)$ 中的各种系数、常数项都可以被忽略**。根据此原则,可以总结出以下计数偷懒技巧:
|
||||
|
||||
1. **跳过数量与 $n$ 无关的操作。** 因为他们都是 $T(n)$ 中的常数项,对时间复杂度不产生影响。
|
||||
2. **省略所有系数。** 例如,循环 $2n$ 次、$5n + 1$ 次、……,都可以化简记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度也不产生影响。
|
||||
3. **循环嵌套时使用乘法。** 总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 `1.` 和 `2.` 技巧。
|
||||
1. **跳过数量与 $n$ 无关的操作**。因为他们都是 $T(n)$ 中的常数项,对时间复杂度不产生影响。
|
||||
2. **省略所有系数**。例如,循环 $2n$ 次、$5n + 1$ 次、……,都可以化简记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度也不产生影响。
|
||||
3. **循环嵌套时使用乘法**。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 `1.` 和 `2.` 技巧。
|
||||
|
||||
根据以下示例,使用上述技巧前、后的统计结果分别为
|
||||
|
||||
@@ -876,7 +876,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="time_complexity.swift"
|
||||
// 常数阶
|
||||
/* 常数阶 */
|
||||
func constant(n: Int) -> Int {
|
||||
var count = 0
|
||||
let size = 100000
|
||||
@@ -990,7 +990,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="time_complexity.swift"
|
||||
// 线性阶
|
||||
/* 线性阶 */
|
||||
func linear(n: Int) -> Int {
|
||||
var count = 0
|
||||
for _ in 0 ..< n {
|
||||
@@ -1004,7 +1004,7 @@ $$
|
||||
|
||||
!!! tip
|
||||
|
||||
**数据大小 $n$ 是根据输入数据的类型来确定的。** 比如,在上述示例中,我们直接将 $n$ 看作输入数据大小;以下遍历数组示例中,数据大小 $n$ 为数组的长度。
|
||||
**数据大小 $n$ 是根据输入数据的类型来确定的**。比如,在上述示例中,我们直接将 $n$ 看作输入数据大小;以下遍历数组示例中,数据大小 $n$ 为数组的长度。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -1121,7 +1121,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="time_complexity.swift"
|
||||
// 线性阶(遍历数组)
|
||||
/* 线性阶(遍历数组) */
|
||||
func arrayTraversal(nums: [Int]) -> Int {
|
||||
var count = 0
|
||||
// 循环次数与数组长度成正比
|
||||
@@ -1267,7 +1267,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="time_complexity.swift"
|
||||
// 平方阶
|
||||
/* 平方阶 */
|
||||
func quadratic(n: Int) -> Int {
|
||||
var count = 0
|
||||
// 循环次数与数组长度成平方关系
|
||||
@@ -1477,7 +1477,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="time_complexity.swift"
|
||||
// 平方阶(冒泡排序)
|
||||
/* 平方阶(冒泡排序) */
|
||||
func bubbleSort(nums: inout [Int]) -> Int {
|
||||
var count = 0 // 计数器
|
||||
// 外循环:待排序元素数量为 n-1, n-2, ..., 1
|
||||
@@ -1656,7 +1656,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="time_complexity.swift"
|
||||
// 指数阶(循环实现)
|
||||
/* 指数阶(循环实现) */
|
||||
func exponential(n: Int) -> Int {
|
||||
var count = 0
|
||||
var base = 1
|
||||
@@ -1764,7 +1764,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="time_complexity.swift"
|
||||
// 指数阶(递归实现)
|
||||
/* 指数阶(递归实现) */
|
||||
func expRecur(n: Int) -> Int {
|
||||
if n == 1 {
|
||||
return 1
|
||||
@@ -1896,7 +1896,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="time_complexity.swift"
|
||||
// 对数阶(循环实现)
|
||||
/* 对数阶(循环实现) */
|
||||
func logarithmic(n: Int) -> Int {
|
||||
var count = 0
|
||||
var n = n
|
||||
@@ -1999,7 +1999,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="time_complexity.swift"
|
||||
// 对数阶(递归实现)
|
||||
/* 对数阶(递归实现) */
|
||||
func logRecur(n: Int) -> Int {
|
||||
if n <= 1 {
|
||||
return 0
|
||||
@@ -2137,7 +2137,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="time_complexity.swift"
|
||||
// 线性对数阶
|
||||
/* 线性对数阶 */
|
||||
func linearLogRecur(n: Double) -> Int {
|
||||
if n <= 1 {
|
||||
return 1
|
||||
@@ -2288,7 +2288,7 @@ $$
|
||||
=== "Swift"
|
||||
|
||||
```swift title="time_complexity.swift"
|
||||
// 阶乘阶(递归实现)
|
||||
/* 阶乘阶(递归实现) */
|
||||
func factorialRecur(n: Int) -> Int {
|
||||
if n == 0 {
|
||||
return 1
|
||||
@@ -2308,7 +2308,7 @@ $$
|
||||
|
||||
## 最差、最佳、平均时间复杂度
|
||||
|
||||
**某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关。** 举一个例子,输入一个长度为 $n$ 数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 $1$ 的索引。我们可以得出以下结论:
|
||||
**某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关**。举一个例子,输入一个长度为 $n$ 数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 $1$ 的索引。我们可以得出以下结论:
|
||||
|
||||
- 当 `nums = [?, ?, ..., 1]`,即当末尾元素是 $1$ 时,则需完整遍历数组,此时达到 **最差时间复杂度 $O(n)$** ;
|
||||
- 当 `nums = [1, ?, ?, ...]` ,即当首个数字为 $1$ 时,无论数组多长都不需要继续遍历,此时达到 **最佳时间复杂度 $\Omega(1)$** ;
|
||||
@@ -2657,8 +2657,8 @@ $$
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
// 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱
|
||||
```swift title="worst_best_time_complexity.swift"
|
||||
/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */
|
||||
func randomNumbers(n: Int) -> [Int] {
|
||||
// 生成数组 nums = { 1, 2, 3, ..., n }
|
||||
var nums = Array(1 ... n)
|
||||
@@ -2667,7 +2667,7 @@ $$
|
||||
return nums
|
||||
}
|
||||
|
||||
// 查找数组 nums 中数字 1 所在索引
|
||||
/* 查找数组 nums 中数字 1 所在索引 */
|
||||
func findOne(nums: [Int]) -> Int {
|
||||
for i in nums.indices {
|
||||
if nums[i] == 1 {
|
||||
@@ -2677,14 +2677,14 @@ $$
|
||||
return -1
|
||||
}
|
||||
|
||||
// Driver Code
|
||||
/* Driver Code */
|
||||
func main() {
|
||||
for _ in 0 ..< 10 {
|
||||
let n = 100
|
||||
let nums = randomNumbers(n: n)
|
||||
let index = findOne(nums: nums)
|
||||
print("数组 [ 1, 2, ..., n ] 被打乱后 =", nums)
|
||||
print("数字 1 的索引为", index)
|
||||
print("数组 [ 1, 2, ..., n ] 被打乱后 = \(nums)")
|
||||
print("数字 1 的索引为 \(index)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -8,7 +8,7 @@ comments: true
|
||||
|
||||
## 逻辑结构:线性与非线性
|
||||
|
||||
**「逻辑结构」反映了数据之间的逻辑关系。** 数组和链表的数据按照顺序依次排列,反映了数据间的线性关系;树从顶至底按层级排列,反映了祖先与后代之间的派生关系;图由结点和边组成,反映了复杂网络关系。
|
||||
**「逻辑结构」反映了数据之间的逻辑关系**。数组和链表的数据按照顺序依次排列,反映了数据间的线性关系;树从顶至底按层级排列,反映了祖先与后代之间的派生关系;图由结点和边组成,反映了复杂网络关系。
|
||||
|
||||
我们一般将逻辑结构分为「线性」和「非线性」两种。“线性”这个概念很直观,即表明数据在逻辑关系上是排成一条线的;而如果数据之间的逻辑关系是非线形的(例如是网状或树状的),那么就是非线性数据结构。
|
||||
|
||||
@@ -25,13 +25,13 @@ comments: true
|
||||
|
||||
若感到阅读困难,建议先看完下个章节「数组与链表」,再回过头来理解物理结构的含义。
|
||||
|
||||
**「物理结构」反映了数据在计算机内存中的存储方式。** 从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储** 。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。
|
||||
**「物理结构」反映了数据在计算机内存中的存储方式**。从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储** 。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。
|
||||
|
||||

|
||||
|
||||
<p align="center"> Fig. 连续空间存储与离散空间存储 </p>
|
||||
|
||||
**所有数据结构都是基于数组、或链表、或两者组合实现的。** 例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。
|
||||
**所有数据结构都是基于数组、或链表、或两者组合实现的**。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。
|
||||
|
||||
- **基于数组可实现:** 栈、队列、堆、哈希表、矩阵、张量(维度 $\geq 3$ 的数组)等;
|
||||
- **基于链表可实现:** 栈、队列、堆、哈希表、树、图等;
|
||||
|
||||
@@ -117,7 +117,7 @@ comments: true
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
// 使用多种「基本数据类型」来初始化「数组」
|
||||
/* 使用多种「基本数据类型」来初始化「数组」 */
|
||||
let numbers = Array(repeating: Int(), count: 5)
|
||||
let decimals = Array(repeating: Double(), count: 5)
|
||||
let characters = Array(repeating: Character("a"), count: 5)
|
||||
@@ -128,12 +128,12 @@ comments: true
|
||||
|
||||
在计算机中,内存和硬盘是两种主要的存储硬件设备。「硬盘」主要用于长期存储数据,容量较大(通常可达到 TB 级别)、速度较慢。「内存」用于运行程序时暂存数据,速度更快,但容量较小(通常为 GB 级别)。
|
||||
|
||||
**算法运行中,相关数据都被存储在内存中。** 下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。
|
||||
**算法运行中,相关数据都被存储在内存中**。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。
|
||||
|
||||
**系统通过「内存地址 Memory Location」来访问目标内存位置的数据。** 计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。
|
||||
**系统通过「内存地址 Memory Location」来访问目标内存位置的数据**。计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。
|
||||
|
||||

|
||||
|
||||
<p align="center"> Fig. 内存条、内存空间、内存地址 </p>
|
||||
|
||||
**内存资源是设计数据结构与算法的重要考虑因素。** 内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。
|
||||
**内存资源是设计数据结构与算法的重要考虑因素**。内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。
|
||||
|
||||
@@ -108,25 +108,25 @@ comments: true
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title="hash_map_test.go"
|
||||
/* 初始化哈希表 */
|
||||
mapp := make(map[int]string)
|
||||
```go title="hash_map.go"
|
||||
/* 初始化哈希表 */
|
||||
mapp := make(map[int]string)
|
||||
|
||||
/* 添加操作 */
|
||||
// 在哈希表中添加键值对 (key, value)
|
||||
mapp[12836] = "小哈"
|
||||
mapp[15937] = "小啰"
|
||||
mapp[16750] = "小算"
|
||||
mapp[13276] = "小法"
|
||||
mapp[10583] = "小鸭"
|
||||
/* 添加操作 */
|
||||
// 在哈希表中添加键值对 (key, value)
|
||||
mapp[12836] = "小哈"
|
||||
mapp[15937] = "小啰"
|
||||
mapp[16750] = "小算"
|
||||
mapp[13276] = "小法"
|
||||
mapp[10583] = "小鸭"
|
||||
|
||||
/* 查询操作 */
|
||||
// 向哈希表输入键 key ,得到值 value
|
||||
name := mapp[15937]
|
||||
/* 查询操作 */
|
||||
// 向哈希表输入键 key ,得到值 value
|
||||
name := mapp[15937]
|
||||
|
||||
/* 删除操作 */
|
||||
// 在哈希表中删除键值对 (key, value)
|
||||
delete(mapp, 10583)
|
||||
/* 删除操作 */
|
||||
// 在哈希表中删除键值对 (key, value)
|
||||
delete(mapp, 10583)
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
@@ -207,6 +207,12 @@ comments: true
|
||||
map.Remove(10583);
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="hash_map.swift"
|
||||
|
||||
```
|
||||
|
||||
遍历哈希表有三种方式,即 **遍历键值对、遍历键、遍历值**。
|
||||
|
||||
=== "Java"
|
||||
@@ -339,6 +345,12 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="hash_map.swift"
|
||||
|
||||
```
|
||||
|
||||
## 哈希函数
|
||||
|
||||
哈希表中存储元素的数据结构被称为「桶 Bucket」,底层实现可能是数组、链表、二叉树(红黑树),或是它们的组合。
|
||||
@@ -512,30 +524,30 @@ $$
|
||||
|
||||
```go title="array_hash_map.go"
|
||||
/* 键值对 int->String */
|
||||
type Entry struct {
|
||||
type entry struct {
|
||||
key int
|
||||
val string
|
||||
}
|
||||
|
||||
/* 基于数组简易实现的哈希表 */
|
||||
type ArrayHashMap struct {
|
||||
bucket []*Entry
|
||||
type arrayHashMap struct {
|
||||
bucket []*entry
|
||||
}
|
||||
|
||||
func newArrayHashMap() *ArrayHashMap {
|
||||
func newArrayHashMap() *arrayHashMap {
|
||||
// 初始化一个长度为 100 的桶(数组)
|
||||
bucket := make([]*Entry, 100)
|
||||
return &ArrayHashMap{bucket: bucket}
|
||||
bucket := make([]*entry, 100)
|
||||
return &arrayHashMap{bucket: bucket}
|
||||
}
|
||||
|
||||
/* 哈希函数 */
|
||||
func (a *ArrayHashMap) hashFunc(key int) int {
|
||||
func (a *arrayHashMap) hashFunc(key int) int {
|
||||
index := key % 100
|
||||
return index
|
||||
}
|
||||
|
||||
/* 查询操作 */
|
||||
func (a *ArrayHashMap) get(key int) string {
|
||||
func (a *arrayHashMap) get(key int) string {
|
||||
index := a.hashFunc(key)
|
||||
pair := a.bucket[index]
|
||||
if pair == nil {
|
||||
@@ -545,16 +557,16 @@ $$
|
||||
}
|
||||
|
||||
/* 添加操作 */
|
||||
func (a *ArrayHashMap) put(key int, val string) {
|
||||
pair := &Entry{key: key, val: val}
|
||||
func (a *arrayHashMap) put(key int, val string) {
|
||||
pair := &entry{key: key, val: val}
|
||||
index := a.hashFunc(key)
|
||||
a.bucket[index] = pair
|
||||
}
|
||||
|
||||
/* 删除操作 */
|
||||
func (a *ArrayHashMap) remove(key int) {
|
||||
func (a *arrayHashMap) remove(key int) {
|
||||
index := a.hashFunc(key)
|
||||
// 置为空字符,代表删除
|
||||
// 置为 nil ,代表删除
|
||||
a.bucket[index] = nil
|
||||
}
|
||||
```
|
||||
@@ -756,6 +768,12 @@ $$
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="array_hash_map.swift"
|
||||
|
||||
```
|
||||
|
||||
## 哈希冲突
|
||||
|
||||
细心的同学可能会发现,**哈希函数 $f(x) = x \% 100$ 会在某些情况下失效**。具体地,当输入的 key 后两位相同时,哈希函数的计算结果也相同,指向同一个 value 。例如,分别查询两个学号 12836 和 20336 ,则有
|
||||
|
||||
@@ -6,13 +6,13 @@ comments: true
|
||||
|
||||
听到“算法”这个词,我们一般会联想到数学。但实际上,大多数算法并不包含复杂的数学,而更像是在考察基本逻辑,而这些逻辑在我们日常生活中处处可见。
|
||||
|
||||
在正式介绍算法之前,我想告诉你一件有趣的事:**其实,你在过去已经学会了很多算法,并且已经习惯将它们应用到日常生活中。** 接下来,我将介绍两个具体例子来佐证。
|
||||
在正式介绍算法之前,我想告诉你一件有趣的事:**其实,你在过去已经学会了很多算法,并且已经习惯将它们应用到日常生活中**。接下来,我将介绍两个具体例子来佐证。
|
||||
|
||||
**例一:拼积木。** 一套积木,除了有许多部件之外,还会附送详细的拼装说明书。我们按照说明书上一步步操作,即可拼出复杂的积木模型。
|
||||
**例一:拼积木**。一套积木,除了有许多部件之外,还会附送详细的拼装说明书。我们按照说明书上一步步操作,即可拼出复杂的积木模型。
|
||||
|
||||
如果从数据结构与算法的角度看,大大小小的「积木」就是数据结构,而「拼装说明书」上的一系列步骤就是算法。
|
||||
|
||||
**例二:查字典。** 在字典中,每个汉字都有一个对应的拼音,而字典是按照拼音的英文字母表顺序排列的。假设需要在字典中查询任意一个拼音首字母为 $r$ 的字,一般我们会这样做:
|
||||
**例二:查字典**。在字典中,每个汉字都有一个对应的拼音,而字典是按照拼音的英文字母表顺序排列的。假设需要在字典中查询任意一个拼音首字母为 $r$ 的字,一般我们会这样做:
|
||||
|
||||
1. 打开字典大致一半页数的位置,查看此页的首字母是什么(假设为 $m$ );
|
||||
2. 由于在英文字母表中 $r$ 在 $m$ 的后面,因此应排除字典前半部分,查找范围仅剩后半部分;
|
||||
|
||||
@@ -192,6 +192,19 @@ comments: true
|
||||
*/
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
/* 标题注释,用于标注函数、类、测试样例等 */
|
||||
|
||||
// 内容注释,用于详解代码
|
||||
|
||||
/**
|
||||
* 多行
|
||||
* 注释
|
||||
*/
|
||||
```
|
||||
|
||||
"""
|
||||
在 Java, C, C++, C#, Go, JS, TS 的代码注释中,`/* ... */` 用于注释函数、类、测试样例等标题, `// ...` 用于解释代码内容;类似地,在 Python 中,`""" ... """` 用于注释标题, `# ...` 用于解释代码。
|
||||
|
||||
@@ -199,19 +212,19 @@ comments: true
|
||||
|
||||
??? abstract "默认折叠,可以跳过"
|
||||
|
||||
**以实践为主。** 我们知道,学习英语期间光啃书本是远远不够的,需要多听、多说、多写,在实践中培养语感、积累经验。编程语言也是一门语言,因此学习方法也应是类似的,需要多看优秀代码、多敲键盘、多思考代码逻辑。
|
||||
**以实践为主**。我们知道,学习英语期间光啃书本是远远不够的,需要多听、多说、多写,在实践中培养语感、积累经验。编程语言也是一门语言,因此学习方法也应是类似的,需要多看优秀代码、多敲键盘、多思考代码逻辑。
|
||||
|
||||
本书的理论部分占少量篇幅,主要分为两类:一是基础且必要的概念知识,以培养读者对于算法的感性认识;二是重要的分类、对比或总结,这是为了帮助你站在更高视角俯瞰各个知识点,形成连点成面的效果。
|
||||
|
||||
实践部分主要由示例和代码组成。代码配有简要注释,复杂示例会尽可能地使用视觉化的形式呈现。我强烈建议读者对照着代码自己敲一遍,如果时间有限,也至少逐行读、复制并运行一遍,配合着讲解将代码吃透。
|
||||
|
||||
**视觉化学习。** 信息时代以来,视觉化的脚步从未停止。媒体形式经历了文字短信、图文 Email 、动图、短(长)视频、交互式 Web 、3D 游戏等演变过程,信息的视觉化程度越来越高、愈加符合人类感官、信息传播效率大大提升。科技界也在向视觉化迈进,iPhone 就是一个典型例子,其相对于传统手机是高度视觉化的,包含精心设计的字体、主题配色、交互动画等。
|
||||
**视觉化学习**。信息时代以来,视觉化的脚步从未停止。媒体形式经历了文字短信、图文 Email 、动图、短(长)视频、交互式 Web 、3D 游戏等演变过程,信息的视觉化程度越来越高、愈加符合人类感官、信息传播效率大大提升。科技界也在向视觉化迈进,iPhone 就是一个典型例子,其相对于传统手机是高度视觉化的,包含精心设计的字体、主题配色、交互动画等。
|
||||
|
||||
近两年,短视频成为最受欢迎的信息媒介,可以在短时间内将高密度的信息“灌”给我们,有着极其舒适的观看体验。阅读则不然,读者与书本之间天然存在一种“疏离感”,我们看书会累、会走神、会停下来想其他事、会划下喜欢的句子、会思考某一片段的含义,这种疏离感给了读者与书本之间对话的可能,拓宽了想象空间。
|
||||
|
||||
本书作为一本入门教材,希望可以保有书本的“慢节奏”,但也会避免与读者产生过多“疏离感”,而是努力将知识完整清晰地推送到你聪明的小脑袋瓜中。我将采用视觉化的方式(例如配图、动画),尽我可能清晰易懂地讲解复杂概念和抽象示例。
|
||||
|
||||
**内容精简化。** 大多数的经典教科书,会把每个主题都讲的很透彻。虽然透彻性正是其获得读者青睐的原因,但对于想要快速入门的初学者来说,这些教材的实用性不足。本书会避免引入非必要的概念、名词、定义等,也避免展开不必要的理论分析,毕竟这不是一本真正意义上的教材,主要任务是尽快地带领读者入门。
|
||||
**内容精简化**。大多数的经典教科书,会把每个主题都讲的很透彻。虽然透彻性正是其获得读者青睐的原因,但对于想要快速入门的初学者来说,这些教材的实用性不足。本书会避免引入非必要的概念、名词、定义等,也避免展开不必要的理论分析,毕竟这不是一本真正意义上的教材,主要任务是尽快地带领读者入门。
|
||||
|
||||
引入一些生活案例或趣味内容,非常适合作为知识点的引子或者解释的补充,但当融入过多额外元素时,内容会稍显冗长,也许反而使读者容易迷失、抓不住重点,这也是本书需要避免的。
|
||||
|
||||
|
||||
@@ -54,10 +54,10 @@ git clone https://github.com/krahets/hello-algo.git
|
||||
|
||||
## 算法学习“三步走”
|
||||
|
||||
**第一阶段,算法入门,也正是本书的定位。** 熟悉各种数据结构的特点、用法,学习各种算法的工作原理、用途、效率等。
|
||||
**第一阶段,算法入门,也正是本书的定位**。熟悉各种数据结构的特点、用法,学习各种算法的工作原理、用途、效率等。
|
||||
|
||||
**第二阶段,刷算法题。** 可以先从热门题单开刷,推荐 [剑指 Offer](https://leetcode.cn/problem-list/xb9nqhhg/)、[LeetCode 热题 HOT 100](https://leetcode.cn/problem-list/2cktkvj/) ,先积累至少 100 道题量,熟悉大多数的算法问题。刚开始刷题时,“遗忘”是最大的困扰点,但这是很正常的,请不要担心。学习中有一种概念叫“周期性回顾”,同一道题隔段时间做一次,当做了三遍以上,往往就能牢记于心了。
|
||||
**第二阶段,刷算法题**。可以先从热门题单开刷,推荐 [剑指 Offer](https://leetcode.cn/problem-list/xb9nqhhg/)、[LeetCode 热题 HOT 100](https://leetcode.cn/problem-list/2cktkvj/) ,先积累至少 100 道题量,熟悉大多数的算法问题。刚开始刷题时,“遗忘”是最大的困扰点,但这是很正常的,请不要担心。学习中有一种概念叫“周期性回顾”,同一道题隔段时间做一次,当做了三遍以上,往往就能牢记于心了。
|
||||
|
||||
**第三阶段,搭建知识体系。** 在学习方面,可以阅读算法专栏文章、解题框架、算法教材,不断地丰富知识体系。在刷题方面,可以开始采用进阶刷题方案,例如按专题分类、一题多解、一解多题等,刷题方案在社区中可以找到一些讲解,在此不做赘述。
|
||||
**第三阶段,搭建知识体系**。在学习方面,可以阅读算法专栏文章、解题框架、算法教材,不断地丰富知识体系。在刷题方面,可以开始采用进阶刷题方案,例如按专题分类、一题多解、一解多题等,刷题方案在社区中可以找到一些讲解,在此不做赘述。
|
||||
|
||||

|
||||
|
||||
@@ -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;
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
|
||||
```
|
||||
|
||||
## 复杂度分析
|
||||
|
||||
**时间复杂度 $O(\log n)$ :** 其中 $n$ 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。
|
||||
@@ -470,11 +488,11 @@ $$
|
||||
|
||||
二分查找效率很高,体现在:
|
||||
|
||||
- **二分查找时间复杂度低。** 对数阶在数据量很大时具有巨大优势,例如,当数据大小 $n = 2^{20}$ 时,线性查找需要 $2^{20} = 1048576$ 轮循环,而二分查找仅需要 $\log_2 2^{20} = 20$ 轮循环。
|
||||
- **二分查找不需要额外空间。** 相对于借助额外数据结构来实现查找的算法来说,其更加节约空间使用。
|
||||
- **二分查找时间复杂度低**。对数阶在数据量很大时具有巨大优势,例如,当数据大小 $n = 2^{20}$ 时,线性查找需要 $2^{20} = 1048576$ 轮循环,而二分查找仅需要 $\log_2 2^{20} = 20$ 轮循环。
|
||||
- **二分查找不需要额外空间**。相对于借助额外数据结构来实现查找的算法来说,其更加节约空间使用。
|
||||
|
||||
但并不意味着所有情况下都应使用二分查找,这是因为:
|
||||
|
||||
- **二分查找仅适用于有序数据。** 如果输入数据是无序的,为了使用二分查找而专门执行数据排序,那么是得不偿失的,因为排序算法的时间复杂度一般为 $O(n \log n)$ ,比线性查找和二分查找都更差。再例如,对于频繁插入元素的场景,为了保持数组的有序性,需要将元素插入到特定位置,时间复杂度为 $O(n)$ ,也是非常昂贵的。
|
||||
- **二分查找仅适用于数组。** 由于在二分查找中,访问索引是 ”非连续“ 的,因此链表或者基于链表实现的数据结构都无法使用。
|
||||
- **在小数据量下,线性查找的性能更好。** 在线性查找中,每轮只需要 1 次判断操作;而在二分查找中,需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法(减法),共 4 ~ 6 个单元操作;因此,在数据量 $n$ 较小时,线性查找反而比二分查找更快。
|
||||
- **二分查找仅适用于有序数据**。如果输入数据是无序的,为了使用二分查找而专门执行数据排序,那么是得不偿失的,因为排序算法的时间复杂度一般为 $O(n \log n)$ ,比线性查找和二分查找都更差。再例如,对于频繁插入元素的场景,为了保持数组的有序性,需要将元素插入到特定位置,时间复杂度为 $O(n)$ ,也是非常昂贵的。
|
||||
- **二分查找仅适用于数组**。由于在二分查找中,访问索引是 ”非连续“ 的,因此链表或者基于链表实现的数据结构都无法使用。
|
||||
- **在小数据量下,线性查找的性能更好**。在线性查找中,每轮只需要 1 次判断操作;而在二分查找中,需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法(减法),共 4 ~ 6 个单元操作;因此,在数据量 $n$ 较小时,线性查找反而比二分查找更快。
|
||||
|
||||
@@ -95,6 +95,12 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="hashing_search.swift"
|
||||
|
||||
```
|
||||
|
||||
再比如,如果我们想要给定一个目标结点值 `target` ,获取对应的链表结点对象,那么也可以使用哈希查找实现。
|
||||
|
||||

|
||||
@@ -179,6 +185,12 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="hashing_search.swift"
|
||||
|
||||
```
|
||||
|
||||
## 复杂度分析
|
||||
|
||||
**时间复杂度:** $O(1)$ ,哈希表的查找操作使用 $O(1)$ 时间。
|
||||
|
||||
@@ -133,6 +133,12 @@ comments: true
|
||||
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="linear_search.swift"
|
||||
|
||||
```
|
||||
|
||||
再比如,我们想要在给定一个目标结点值 `target` ,返回此结点对象,也可以在链表中进行线性查找。
|
||||
|
||||
=== "Java"
|
||||
@@ -261,6 +267,12 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="linear_search.swift"
|
||||
|
||||
```
|
||||
|
||||
## 复杂度分析
|
||||
|
||||
**时间复杂度 $O(n)$ :** 其中 $n$ 为数组或链表长度。
|
||||
@@ -269,6 +281,6 @@ comments: true
|
||||
|
||||
## 优点与缺点
|
||||
|
||||
**线性查找的通用性极佳。** 由于线性查找是依次访问元素的,即没有跳跃访问元素,因此数组或链表皆适用。
|
||||
**线性查找的通用性极佳**。由于线性查找是依次访问元素的,即没有跳跃访问元素,因此数组或链表皆适用。
|
||||
|
||||
**线性查找的时间复杂度太高。** 在数据量 $n$ 很大时,查找效率很低。
|
||||
**线性查找的时间复杂度太高**。在数据量 $n$ 很大时,查找效率很低。
|
||||
|
||||
@@ -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)$ 时间。
|
||||
@@ -414,3 +420,9 @@ comments: true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="bubble_sort.swift"
|
||||
|
||||
```
|
||||
|
||||
@@ -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)$ 时间。
|
||||
|
||||
@@ -201,36 +201,35 @@ comments: true
|
||||
右子数组区间 [mid + 1, right]
|
||||
*/
|
||||
func merge(nums []int, left, mid, right int) {
|
||||
// 初始化辅助数组 借助 copy模块
|
||||
// 初始化辅助数组 借助 copy 模块
|
||||
tmp := make([]int, right-left+1)
|
||||
for i := left; i <= right; i++ {
|
||||
tmp[i-left] = nums[i]
|
||||
}
|
||||
// 左子数组的起始索引和结束索引
|
||||
left_start, left_end := left-left, mid-left
|
||||
leftStart, leftEnd := left-left, mid-left
|
||||
// 右子数组的起始索引和结束索引
|
||||
right_start, right_end := mid+1-left, right-left
|
||||
rightStart, rightEnd := mid+1-left, right-left
|
||||
// i, j 分别指向左子数组、右子数组的首元素
|
||||
i, j := left_start, right_start
|
||||
i, j := leftStart, rightStart
|
||||
// 通过覆盖原数组 nums 来合并左子数组和右子数组
|
||||
for k := left; k <= right; k++ {
|
||||
// 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
||||
if i > left_end {
|
||||
if i > leftEnd {
|
||||
nums[k] = tmp[j]
|
||||
j++
|
||||
// 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
|
||||
} else if j > right_end || tmp[i] <= tmp[j] {
|
||||
// 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
|
||||
} else if j > rightEnd || tmp[i] <= tmp[j] {
|
||||
nums[k] = tmp[i]
|
||||
i++
|
||||
// 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
||||
// 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
||||
} else {
|
||||
nums[k] = tmp[j]
|
||||
j++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 归并排序 */
|
||||
|
||||
func mergeSort(nums []int, left, right int) {
|
||||
// 终止条件
|
||||
if left >= right {
|
||||
@@ -388,6 +387,12 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="merge_sort.swift"
|
||||
|
||||
```
|
||||
|
||||
下面重点解释一下合并方法 `merge()` 的流程:
|
||||
|
||||
1. 初始化一个辅助数组 `tmp` 暂存待合并区间 `[left, right]` 内的元素,后续通过覆盖原数组 `nums` 的元素来实现合并;
|
||||
|
||||
@@ -111,21 +111,21 @@ comments: true
|
||||
```go title="quick_sort.go"
|
||||
/* 哨兵划分 */
|
||||
func partition(nums []int, left, right int) int {
|
||||
//以 nums[left] 作为基准数
|
||||
// 以 nums[left] 作为基准数
|
||||
i, j := left, right
|
||||
for i < j {
|
||||
for i < j && nums[j] >= nums[left] {
|
||||
j-- //从右向左找首个小于基准数的元素
|
||||
j-- // 从右向左找首个小于基准数的元素
|
||||
}
|
||||
for i < j && nums[i] <= nums[left] {
|
||||
i++ //从左向右找首个大于基准数的元素
|
||||
i++ // 从左向右找首个大于基准数的元素
|
||||
}
|
||||
//元素交换
|
||||
nums[i], nums[j] = nums[j], nums[i]
|
||||
}
|
||||
//将基准数交换至两子数组的分界线
|
||||
// 将基准数交换至两子数组的分界线
|
||||
nums[i], nums[left] = nums[left], nums[i]
|
||||
return i //返回基准数的索引
|
||||
return i // 返回基准数的索引
|
||||
}
|
||||
```
|
||||
|
||||
@@ -223,6 +223,12 @@ comments: true
|
||||
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="quick_sort.swift"
|
||||
|
||||
```
|
||||
|
||||
!!! note "快速排序的分治思想"
|
||||
|
||||
哨兵划分的实质是将 **一个长数组的排序问题** 简化为 **两个短数组的排序问题**。
|
||||
@@ -359,6 +365,12 @@ comments: true
|
||||
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="quick_sort.swift"
|
||||
|
||||
```
|
||||
|
||||
## 算法特性
|
||||
|
||||
**平均时间复杂度 $O(n \log n)$ :** 平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。
|
||||
@@ -383,7 +395,7 @@ comments: true
|
||||
|
||||
## 基准数优化
|
||||
|
||||
**普通快速排序在某些输入下的时间效率变差。** 举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$ 、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。
|
||||
**普通快速排序在某些输入下的时间效率变差**。举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$ 、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。
|
||||
|
||||
为了尽量避免这种情况发生,我们可以优化一下基准数的选取策略。首先,在哨兵划分中,我们可以 **随机选取一个元素作为基准数** 。但如果运气很差,每次都选择到比较差的基准数,那么效率依然不好。
|
||||
|
||||
@@ -574,9 +586,15 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="quick_sort.swift"
|
||||
|
||||
```
|
||||
|
||||
## 尾递归优化
|
||||
|
||||
**普通快速排序在某些输入下的空间效率变差。** 仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。
|
||||
**普通快速排序在某些输入下的空间效率变差**。仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。
|
||||
|
||||
为了避免栈帧空间的累积,我们可以在每轮哨兵排序完成后,判断两个子数组的长度大小,仅递归排序较短的子数组。由于较短的子数组长度不会超过 $\frac{n}{2}$ ,因此这样做能保证递归深度不超过 $\log n$ ,即最差空间复杂度被优化至 $O(\log n)$ 。
|
||||
|
||||
@@ -652,10 +670,10 @@ comments: true
|
||||
// 对两个子数组中较短的那个执行快排
|
||||
if pivot-left < right-pivot {
|
||||
quickSort(nums, left, pivot-1) // 递归排序左子数组
|
||||
left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right]
|
||||
left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right]
|
||||
} else {
|
||||
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"
|
||||
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ comments: true
|
||||
|
||||
## 双向队列常用操作
|
||||
|
||||
双向队列的常用操作见下表,方法名需根据编程语言设定来具体确定。
|
||||
双向队列的常用操作见下表(方法命名以 Java 为例)。
|
||||
|
||||
<p align="center"> Table. 双向队列的常用操作 </p>
|
||||
|
||||
@@ -38,25 +38,25 @@ comments: true
|
||||
```java title="deque.java"
|
||||
/* 初始化双向队列 */
|
||||
Deque<Integer> deque = new LinkedList<>();
|
||||
|
||||
|
||||
/* 元素入队 */
|
||||
deque.offerLast(2); // 添加至队尾
|
||||
deque.offerLast(5);
|
||||
deque.offerLast(4);
|
||||
deque.offerFirst(3); // 添加至队首
|
||||
deque.offerFirst(1);
|
||||
|
||||
|
||||
/* 访问元素 */
|
||||
int peekFirst = deque.peekFirst(); // 队首元素
|
||||
int peekLast = deque.peekLast(); // 队尾元素
|
||||
|
||||
|
||||
/* 元素出队 */
|
||||
int pollFirst = deque.pollFirst(); // 队首元素出队
|
||||
int pollLast = deque.pollLast(); // 队尾元素出队
|
||||
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
int size = deque.size();
|
||||
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
boolean isEmpty = deque.isEmpty();
|
||||
```
|
||||
@@ -66,25 +66,25 @@ comments: true
|
||||
```cpp title="deque.cpp"
|
||||
/* 初始化双向队列 */
|
||||
deque<int> deque;
|
||||
|
||||
|
||||
/* 元素入队 */
|
||||
deque.push_back(2); // 添加至队尾
|
||||
deque.push_back(5);
|
||||
deque.push_back(4);
|
||||
deque.push_front(3); // 添加至队首
|
||||
deque.push_front(1);
|
||||
|
||||
|
||||
/* 访问元素 */
|
||||
int front = deque.front(); // 队首元素
|
||||
int back = deque.back(); // 队尾元素
|
||||
|
||||
|
||||
/* 元素出队 */
|
||||
deque.pop_front(); // 队首元素出队
|
||||
deque.pop_back(); // 队尾元素出队
|
||||
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
int size = deque.size();
|
||||
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
bool empty = deque.empty();
|
||||
```
|
||||
@@ -94,25 +94,25 @@ comments: true
|
||||
```python title="deque.py"
|
||||
""" 初始化双向队列 """
|
||||
duque = deque()
|
||||
|
||||
|
||||
""" 元素入队 """
|
||||
duque.append(2) # 添加至队尾
|
||||
duque.append(5)
|
||||
duque.append(4)
|
||||
duque.appendleft(3) # 添加至队首
|
||||
duque.appendleft(1)
|
||||
|
||||
|
||||
""" 访问元素 """
|
||||
front = duque[0] # 队首元素
|
||||
rear = duque[-1] # 队尾元素
|
||||
|
||||
|
||||
""" 元素出队 """
|
||||
pop_front = duque.popleft() # 队首元素出队
|
||||
pop_rear = duque.pop() # 队尾元素出队
|
||||
|
||||
|
||||
""" 获取双向队列的长度 """
|
||||
size = len(duque)
|
||||
|
||||
|
||||
""" 判断双向队列是否为空 """
|
||||
is_empty = len(duque) == 0
|
||||
```
|
||||
@@ -123,25 +123,25 @@ comments: true
|
||||
/* 初始化双向队列 */
|
||||
// 在 Go 中,将 list 作为双向队列使用
|
||||
deque := list.New()
|
||||
|
||||
|
||||
/* 元素入队 */
|
||||
deque.PushBack(2) // 添加至队尾
|
||||
deque.PushBack(5)
|
||||
deque.PushBack(4)
|
||||
deque.PushFront(3) // 添加至队首
|
||||
deque.PushFront(1)
|
||||
|
||||
|
||||
/* 访问元素 */
|
||||
front := deque.Front() // 队首元素
|
||||
rear := deque.Back() // 队尾元素
|
||||
|
||||
|
||||
/* 元素出队 */
|
||||
deque.Remove(front) // 队首元素出队
|
||||
deque.Remove(rear) // 队尾元素出队
|
||||
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
size := deque.Len()
|
||||
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
isEmpty := deque.Len() == 0
|
||||
```
|
||||
@@ -149,19 +149,19 @@ comments: true
|
||||
=== "JavaScript"
|
||||
|
||||
```js title="deque.js"
|
||||
|
||||
|
||||
```
|
||||
|
||||
=== "TypeScript"
|
||||
|
||||
```typescript title="deque.ts"
|
||||
|
||||
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="deque.c"
|
||||
|
||||
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
@@ -170,25 +170,31 @@ comments: true
|
||||
/* 初始化双向队列 */
|
||||
// 在 C# 中,将链表 LinkedList 看作双向队列来使用
|
||||
LinkedList<int> deque = new LinkedList<int>();
|
||||
|
||||
|
||||
/* 元素入队 */
|
||||
deque.AddLast(2); // 添加至队尾
|
||||
deque.AddLast(5);
|
||||
deque.AddLast(4);
|
||||
deque.AddFirst(3); // 添加至队首
|
||||
deque.AddFirst(1);
|
||||
|
||||
|
||||
/* 访问元素 */
|
||||
int peekFirst = deque.First.Value; // 队首元素
|
||||
int peekLast = deque.Last.Value; // 队尾元素
|
||||
|
||||
|
||||
/* 元素出队 */
|
||||
deque.RemoveFirst(); // 队首元素出队
|
||||
deque.RemoveLast(); // 队尾元素出队
|
||||
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
int size = deque.Count;
|
||||
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
bool isEmpty = deque.Count == 0;
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="deque.swift"
|
||||
|
||||
```
|
||||
|
||||
@@ -14,7 +14,7 @@ comments: true
|
||||
|
||||
## 队列常用操作
|
||||
|
||||
队列的常用操作见下表,方法命名需根据编程语言的设定来具体确定。
|
||||
队列的常用操作见下表(方法命名以 Java 为例)。
|
||||
|
||||
<p align="center"> Table. 队列的常用操作 </p>
|
||||
|
||||
@@ -37,23 +37,23 @@ comments: true
|
||||
```java title="queue.java"
|
||||
/* 初始化队列 */
|
||||
Queue<Integer> queue = new LinkedList<>();
|
||||
|
||||
|
||||
/* 元素入队 */
|
||||
queue.offer(1);
|
||||
queue.offer(3);
|
||||
queue.offer(2);
|
||||
queue.offer(5);
|
||||
queue.offer(4);
|
||||
|
||||
|
||||
/* 访问队首元素 */
|
||||
int peek = queue.peek();
|
||||
|
||||
|
||||
/* 元素出队 */
|
||||
int poll = queue.poll();
|
||||
|
||||
|
||||
/* 获取队列的长度 */
|
||||
int size = queue.size();
|
||||
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
boolean isEmpty = queue.isEmpty();
|
||||
```
|
||||
@@ -91,23 +91,23 @@ comments: true
|
||||
# 在 Python 中,我们一般将双向队列类 deque 看作队列使用
|
||||
# 虽然 queue.Queue() 是纯正的队列类,但不太好用,因此不建议
|
||||
que = collections.deque()
|
||||
|
||||
|
||||
""" 元素入队 """
|
||||
que.append(1)
|
||||
que.append(3)
|
||||
que.append(2)
|
||||
que.append(5)
|
||||
que.append(4)
|
||||
|
||||
|
||||
""" 访问队首元素 """
|
||||
front = que[0];
|
||||
|
||||
|
||||
""" 元素出队 """
|
||||
pop = que.popleft()
|
||||
|
||||
|
||||
""" 获取队列的长度 """
|
||||
size = len(que)
|
||||
|
||||
|
||||
""" 判断队列是否为空 """
|
||||
is_empty = len(que) == 0
|
||||
```
|
||||
@@ -118,24 +118,24 @@ comments: true
|
||||
/* 初始化队列 */
|
||||
// 在 Go 中,将 list 作为队列来使用
|
||||
queue := list.New()
|
||||
|
||||
|
||||
/* 元素入队 */
|
||||
queue.PushBack(1)
|
||||
queue.PushBack(3)
|
||||
queue.PushBack(2)
|
||||
queue.PushBack(5)
|
||||
queue.PushBack(4)
|
||||
|
||||
|
||||
/* 访问队首元素 */
|
||||
peek := queue.Front()
|
||||
|
||||
|
||||
/* 元素出队 */
|
||||
poll := queue.Front()
|
||||
queue.Remove(poll)
|
||||
|
||||
|
||||
/* 获取队列的长度 */
|
||||
size := queue.Len()
|
||||
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
isEmpty := queue.Len() == 0
|
||||
```
|
||||
@@ -146,24 +146,24 @@ comments: true
|
||||
/* 初始化队列 */
|
||||
// JavaScript 没有内置的队列,可以把 Array 当作队列来使用
|
||||
const queue = [];
|
||||
|
||||
|
||||
/* 元素入队 */
|
||||
queue.push(1);
|
||||
queue.push(3);
|
||||
queue.push(2);
|
||||
queue.push(5);
|
||||
queue.push(4);
|
||||
|
||||
|
||||
/* 访问队首元素 */
|
||||
const peek = queue[0];
|
||||
|
||||
|
||||
/* 元素出队 */
|
||||
// 底层是数组,因此 shift() 方法的时间复杂度为 O(n)
|
||||
const poll = queue.shift();
|
||||
|
||||
|
||||
/* 获取队列的长度 */
|
||||
const size = queue.length;
|
||||
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
const empty = queue.length === 0;
|
||||
```
|
||||
@@ -174,24 +174,24 @@ comments: true
|
||||
/* 初始化队列 */
|
||||
// TypeScript 没有内置的队列,可以把 Array 当作队列来使用
|
||||
const queue: number[] = [];
|
||||
|
||||
|
||||
/* 元素入队 */
|
||||
queue.push(1);
|
||||
queue.push(3);
|
||||
queue.push(2);
|
||||
queue.push(5);
|
||||
queue.push(4);
|
||||
|
||||
|
||||
/* 访问队首元素 */
|
||||
const peek = queue[0];
|
||||
|
||||
|
||||
/* 元素出队 */
|
||||
// 底层是数组,因此 shift() 方法的时间复杂度为 O(n)
|
||||
const poll = queue.shift();
|
||||
|
||||
|
||||
/* 获取队列的长度 */
|
||||
const size = queue.length;
|
||||
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
const empty = queue.length === 0;
|
||||
```
|
||||
@@ -199,7 +199,7 @@ comments: true
|
||||
=== "C"
|
||||
|
||||
```c title="queue.c"
|
||||
|
||||
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
@@ -207,27 +207,33 @@ comments: true
|
||||
```csharp title="queue.cs"
|
||||
/* 初始化队列 */
|
||||
Queue<int> queue = new();
|
||||
|
||||
|
||||
/* 元素入队 */
|
||||
queue.Enqueue(1);
|
||||
queue.Enqueue(3);
|
||||
queue.Enqueue(2);
|
||||
queue.Enqueue(5);
|
||||
queue.Enqueue(4);
|
||||
|
||||
|
||||
/* 访问队首元素 */
|
||||
int peek = queue.Peek();
|
||||
|
||||
|
||||
/* 元素出队 */
|
||||
int poll = queue.Dequeue();
|
||||
|
||||
|
||||
/* 获取队列的长度 */
|
||||
int size = queue.Count();
|
||||
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
bool isEmpty = queue.Count() == 0;
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="queue.swift"
|
||||
|
||||
```
|
||||
|
||||
## 队列实现
|
||||
|
||||
队列需要一种可以在一端添加,并在另一端删除的数据结构,也可以使用链表或数组来实现。
|
||||
@@ -243,7 +249,7 @@ comments: true
|
||||
class LinkedListQueue {
|
||||
private ListNode front, rear; // 头结点 front ,尾结点 rear
|
||||
private int queSize = 0;
|
||||
|
||||
|
||||
public LinkedListQueue() {
|
||||
front = null;
|
||||
rear = null;
|
||||
@@ -296,7 +302,7 @@ comments: true
|
||||
private:
|
||||
ListNode *front, *rear; // 头结点 front ,尾结点 rear
|
||||
int queSize;
|
||||
|
||||
|
||||
public:
|
||||
LinkedListQueue() {
|
||||
front = nullptr;
|
||||
@@ -355,15 +361,15 @@ comments: true
|
||||
self.__front = None # 头结点 front
|
||||
self.__rear = None # 尾结点 rear
|
||||
self.__size = 0
|
||||
|
||||
|
||||
""" 获取队列的长度 """
|
||||
def size(self):
|
||||
return self.__size
|
||||
|
||||
|
||||
""" 判断队列是否为空 """
|
||||
def is_empty(self):
|
||||
return not self.__front
|
||||
|
||||
|
||||
""" 入队 """
|
||||
def push(self, num):
|
||||
# 尾结点后添加 num
|
||||
@@ -377,7 +383,7 @@ comments: true
|
||||
self.__rear.next = node
|
||||
self.__rear = node
|
||||
self.__size += 1
|
||||
|
||||
|
||||
""" 出队 """
|
||||
def poll(self):
|
||||
num = self.peek()
|
||||
@@ -385,7 +391,7 @@ comments: true
|
||||
self.__front = self.__front.next
|
||||
self.__size -= 1
|
||||
return num
|
||||
|
||||
|
||||
""" 访问队首元素 """
|
||||
def peek(self):
|
||||
if self.size() == 0:
|
||||
@@ -398,43 +404,49 @@ comments: true
|
||||
|
||||
```go title="linkedlist_queue.go"
|
||||
/* 基于链表实现的队列 */
|
||||
type LinkedListQueue struct {
|
||||
type linkedListQueue struct {
|
||||
// 使用内置包 list 来实现队列
|
||||
data *list.List
|
||||
}
|
||||
// NewLinkedListQueue 初始化链表
|
||||
func NewLinkedListQueue() *LinkedListQueue {
|
||||
return &LinkedListQueue{
|
||||
|
||||
// newLinkedListQueue 初始化链表
|
||||
func newLinkedListQueue() *linkedListQueue {
|
||||
return &linkedListQueue{
|
||||
data: list.New(),
|
||||
}
|
||||
}
|
||||
// Offer 入队
|
||||
func (s *LinkedListQueue) Offer(value any) {
|
||||
|
||||
// offer 入队
|
||||
func (s *linkedListQueue) offer(value any) {
|
||||
s.data.PushBack(value)
|
||||
}
|
||||
// Poll 出队
|
||||
func (s *LinkedListQueue) Poll() any {
|
||||
if s.IsEmpty() {
|
||||
|
||||
// poll 出队
|
||||
func (s *linkedListQueue) poll() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
e := s.data.Front()
|
||||
s.data.Remove(e)
|
||||
return e.Value
|
||||
}
|
||||
// Peek 访问队首元素
|
||||
func (s *LinkedListQueue) Peek() any {
|
||||
if s.IsEmpty() {
|
||||
|
||||
// peek 访问队首元素
|
||||
func (s *linkedListQueue) peek() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
e := s.data.Front()
|
||||
return e.Value
|
||||
}
|
||||
// Size 获取队列的长度
|
||||
func (s *LinkedListQueue) Size() int {
|
||||
|
||||
// size 获取队列的长度
|
||||
func (s *linkedListQueue) size() int {
|
||||
return s.data.Len()
|
||||
}
|
||||
// IsEmpty 判断队列是否为空
|
||||
func (s *LinkedListQueue) IsEmpty() bool {
|
||||
|
||||
// isEmpty 判断队列是否为空
|
||||
func (s *linkedListQueue) isEmpty() bool {
|
||||
return s.data.Len() == 0
|
||||
}
|
||||
```
|
||||
@@ -548,7 +560,7 @@ comments: true
|
||||
=== "C"
|
||||
|
||||
```c title="linkedlist_queue.c"
|
||||
|
||||
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
@@ -612,6 +624,12 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="linkedlist_queue.swift"
|
||||
|
||||
```
|
||||
|
||||
### 基于数组的实现
|
||||
|
||||
数组的删除首元素的时间复杂度为 $O(n)$ ,因此不适合直接用来实现队列。然而,我们可以借助两个指针 `front` , `rear` 来分别记录队首和队尾的索引位置,在入队 / 出队时分别将 `front` / `rear` 向后移动一位即可,这样每次仅需操作一个元素,时间复杂度降至 $O(1)$ 。
|
||||
@@ -630,7 +648,7 @@ comments: true
|
||||
private int[] nums; // 用于存储队列元素的数组
|
||||
private int front = 0; // 头指针,指向队首
|
||||
private int rear = 0; // 尾指针,指向队尾 + 1
|
||||
|
||||
|
||||
public ArrayQueue(int capacity) {
|
||||
// 初始化数组
|
||||
nums = new int[capacity];
|
||||
@@ -686,7 +704,7 @@ comments: true
|
||||
int cap; // 队列容量
|
||||
int front = 0; // 头指针,指向队首
|
||||
int rear = 0; // 尾指针,指向队尾 + 1
|
||||
|
||||
|
||||
public:
|
||||
ArrayQueue(int capacity) {
|
||||
// 初始化数组
|
||||
@@ -741,20 +759,20 @@ comments: true
|
||||
self.__nums = [0] * size # 用于存储队列元素的数组
|
||||
self.__front = 0 # 头指针,指向队首
|
||||
self.__rear = 0 # 尾指针,指向队尾 + 1
|
||||
|
||||
|
||||
""" 获取队列的容量 """
|
||||
def capacity(self):
|
||||
return len(self.__nums)
|
||||
|
||||
|
||||
""" 获取队列的长度 """
|
||||
def size(self):
|
||||
# 由于将数组看作为环形,可能 rear < front ,因此需要取余数
|
||||
return (self.capacity() + self.__rear - self.__front) % self.capacity()
|
||||
|
||||
|
||||
""" 判断队列是否为空 """
|
||||
def is_empty(self):
|
||||
return (self.__rear - self.__front) == 0
|
||||
|
||||
|
||||
""" 入队 """
|
||||
def push(self, val):
|
||||
if self.size() == self.capacity():
|
||||
@@ -764,21 +782,21 @@ comments: true
|
||||
self.__nums[self.__rear] = val
|
||||
# 尾指针向后移动一位,越过尾部后返回到数组头部
|
||||
self.__rear = (self.__rear + 1) % self.capacity()
|
||||
|
||||
|
||||
""" 出队 """
|
||||
def poll(self):
|
||||
num = self.peek()
|
||||
# 队头指针向后移动一位,若越过尾部则返回到数组头部
|
||||
self.__front = (self.__front + 1) % self.capacity()
|
||||
return num
|
||||
|
||||
|
||||
""" 访问队首元素 """
|
||||
def peek(self):
|
||||
if self.is_empty():
|
||||
print("队列为空")
|
||||
return False
|
||||
return self.__nums[self.__front]
|
||||
|
||||
|
||||
""" 返回列表用于打印 """
|
||||
def to_list(self):
|
||||
res = [0] * self.size()
|
||||
@@ -793,34 +811,38 @@ comments: true
|
||||
|
||||
```go title="array_queue.go"
|
||||
/* 基于环形数组实现的队列 */
|
||||
type ArrayQueue struct {
|
||||
type arrayQueue struct {
|
||||
data []int // 用于存储队列元素的数组
|
||||
capacity int // 队列容量(即最多容量的元素个数)
|
||||
front int // 头指针,指向队首
|
||||
rear int // 尾指针,指向队尾 + 1
|
||||
}
|
||||
// NewArrayQueue 基于环形数组实现的队列
|
||||
func NewArrayQueue(capacity int) *ArrayQueue {
|
||||
return &ArrayQueue{
|
||||
|
||||
// newArrayQueue 基于环形数组实现的队列
|
||||
func newArrayQueue(capacity int) *arrayQueue {
|
||||
return &arrayQueue{
|
||||
data: make([]int, capacity),
|
||||
capacity: capacity,
|
||||
front: 0,
|
||||
rear: 0,
|
||||
}
|
||||
}
|
||||
// Size 获取队列的长度
|
||||
func (q *ArrayQueue) Size() int {
|
||||
|
||||
// size 获取队列的长度
|
||||
func (q *arrayQueue) size() int {
|
||||
size := (q.capacity + q.rear - q.front) % q.capacity
|
||||
return size
|
||||
}
|
||||
// IsEmpty 判断队列是否为空
|
||||
func (q *ArrayQueue) IsEmpty() bool {
|
||||
|
||||
// isEmpty 判断队列是否为空
|
||||
func (q *arrayQueue) isEmpty() bool {
|
||||
return q.rear-q.front == 0
|
||||
}
|
||||
// Offer 入队
|
||||
func (q *ArrayQueue) Offer(v int) {
|
||||
|
||||
// offer 入队
|
||||
func (q *arrayQueue) offer(v int) {
|
||||
// 当 rear == capacity 表示队列已满
|
||||
if q.Size() == q.capacity {
|
||||
if q.size() == q.capacity {
|
||||
return
|
||||
}
|
||||
// 尾结点后添加
|
||||
@@ -828,9 +850,10 @@ comments: true
|
||||
// 尾指针向后移动一位,越过尾部后返回到数组头部
|
||||
q.rear = (q.rear + 1) % q.capacity
|
||||
}
|
||||
// Poll 出队
|
||||
func (q *ArrayQueue) Poll() any {
|
||||
if q.IsEmpty() {
|
||||
|
||||
// poll 出队
|
||||
func (q *arrayQueue) poll() any {
|
||||
if q.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
v := q.data[q.front]
|
||||
@@ -838,9 +861,10 @@ comments: true
|
||||
q.front = (q.front + 1) % q.capacity
|
||||
return v
|
||||
}
|
||||
// Peek 访问队首元素
|
||||
func (q *ArrayQueue) Peek() any {
|
||||
if q.IsEmpty() {
|
||||
|
||||
// peek 访问队首元素
|
||||
func (q *arrayQueue) peek() any {
|
||||
if q.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
v := q.data[q.front]
|
||||
@@ -950,7 +974,7 @@ comments: true
|
||||
=== "C"
|
||||
|
||||
```c title="array_queue.c"
|
||||
|
||||
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
@@ -1015,7 +1039,13 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="array_queue.swift"
|
||||
|
||||
```
|
||||
|
||||
## 队列典型应用
|
||||
|
||||
- **淘宝订单。** 购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。
|
||||
- **各种待办事项。** 例如打印机的任务队列、餐厅的出餐队列等等。
|
||||
- **淘宝订单**。购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。
|
||||
- **各种待办事项**。例如打印机的任务队列、餐厅的出餐队列等等。
|
||||
|
||||
@@ -16,7 +16,7 @@ comments: true
|
||||
|
||||
## 栈常用操作
|
||||
|
||||
栈的常用操作见下表,方法名需根据编程语言设定来具体确定。
|
||||
栈的常用操作见下表(方法命名以 Java 为例)。
|
||||
|
||||
<p align="center"> Table. 栈的常用操作 </p>
|
||||
|
||||
@@ -40,23 +40,23 @@ comments: true
|
||||
/* 初始化栈 */
|
||||
// 在 Java 中,推荐将 LinkedList 当作栈来使用
|
||||
LinkedList<Integer> stack = new LinkedList<>();
|
||||
|
||||
|
||||
/* 元素入栈 */
|
||||
stack.addLast(1);
|
||||
stack.addLast(3);
|
||||
stack.addLast(2);
|
||||
stack.addLast(5);
|
||||
stack.addLast(4);
|
||||
|
||||
|
||||
/* 访问栈顶元素 */
|
||||
int peek = stack.peekLast();
|
||||
|
||||
|
||||
/* 元素出栈 */
|
||||
int pop = stack.removeLast();
|
||||
|
||||
|
||||
/* 获取栈的长度 */
|
||||
int size = stack.size();
|
||||
|
||||
|
||||
/* 判断是否为空 */
|
||||
boolean isEmpty = stack.isEmpty();
|
||||
```
|
||||
@@ -66,23 +66,23 @@ comments: true
|
||||
```cpp title="stack.cpp"
|
||||
/* 初始化栈 */
|
||||
stack<int> stack;
|
||||
|
||||
|
||||
/* 元素入栈 */
|
||||
stack.push(1);
|
||||
stack.push(3);
|
||||
stack.push(2);
|
||||
stack.push(5);
|
||||
stack.push(4);
|
||||
|
||||
|
||||
/* 访问栈顶元素 */
|
||||
int top = stack.top();
|
||||
|
||||
|
||||
/* 元素出栈 */
|
||||
stack.pop();
|
||||
|
||||
|
||||
/* 获取栈的长度 */
|
||||
int size = stack.size();
|
||||
|
||||
|
||||
/* 判断是否为空 */
|
||||
bool empty = stack.empty();
|
||||
```
|
||||
@@ -93,23 +93,23 @@ comments: true
|
||||
""" 初始化栈 """
|
||||
# Python 没有内置的栈类,可以把 List 当作栈来使用
|
||||
stack = []
|
||||
|
||||
|
||||
""" 元素入栈 """
|
||||
stack.append(1)
|
||||
stack.append(3)
|
||||
stack.append(2)
|
||||
stack.append(5)
|
||||
stack.append(4)
|
||||
|
||||
|
||||
""" 访问栈顶元素 """
|
||||
peek = stack[-1]
|
||||
|
||||
|
||||
""" 元素出栈 """
|
||||
pop = stack.pop()
|
||||
|
||||
|
||||
""" 获取栈的长度 """
|
||||
size = len(stack)
|
||||
|
||||
|
||||
""" 判断是否为空 """
|
||||
is_empty = len(stack) == 0
|
||||
```
|
||||
@@ -120,24 +120,24 @@ comments: true
|
||||
/* 初始化栈 */
|
||||
// 在 Go 中,推荐将 Slice 当作栈来使用
|
||||
var stack []int
|
||||
|
||||
|
||||
/* 元素入栈 */
|
||||
stack = append(stack, 1)
|
||||
stack = append(stack, 3)
|
||||
stack = append(stack, 2)
|
||||
stack = append(stack, 5)
|
||||
stack = append(stack, 4)
|
||||
|
||||
|
||||
/* 访问栈顶元素 */
|
||||
peek := stack[len(stack)-1]
|
||||
|
||||
|
||||
/* 元素出栈 */
|
||||
pop := stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
|
||||
|
||||
/* 获取栈的长度 */
|
||||
size := len(stack)
|
||||
|
||||
|
||||
/* 判断是否为空 */
|
||||
isEmpty := len(stack) == 0
|
||||
```
|
||||
@@ -148,23 +148,23 @@ comments: true
|
||||
/* 初始化栈 */
|
||||
// Javascript 没有内置的栈类,可以把 Array 当作栈来使用
|
||||
const stack = [];
|
||||
|
||||
|
||||
/* 元素入栈 */
|
||||
stack.push(1);
|
||||
stack.push(3);
|
||||
stack.push(2);
|
||||
stack.push(5);
|
||||
stack.push(4);
|
||||
|
||||
|
||||
/* 访问栈顶元素 */
|
||||
const peek = stack[stack.length-1];
|
||||
|
||||
|
||||
/* 元素出栈 */
|
||||
const pop = stack.pop();
|
||||
|
||||
|
||||
/* 获取栈的长度 */
|
||||
const size = stack.length;
|
||||
|
||||
|
||||
/* 判断是否为空 */
|
||||
const is_empty = stack.length === 0;
|
||||
```
|
||||
@@ -175,23 +175,23 @@ comments: true
|
||||
/* 初始化栈 */
|
||||
// Typescript 没有内置的栈类,可以把 Array 当作栈来使用
|
||||
const stack: number[] = [];
|
||||
|
||||
|
||||
/* 元素入栈 */
|
||||
stack.push(1);
|
||||
stack.push(3);
|
||||
stack.push(2);
|
||||
stack.push(5);
|
||||
stack.push(4);
|
||||
|
||||
|
||||
/* 访问栈顶元素 */
|
||||
const peek = stack[stack.length - 1];
|
||||
|
||||
|
||||
/* 元素出栈 */
|
||||
const pop = stack.pop();
|
||||
|
||||
|
||||
/* 获取栈的长度 */
|
||||
const size = stack.length;
|
||||
|
||||
|
||||
/* 判断是否为空 */
|
||||
const is_empty = stack.length === 0;
|
||||
```
|
||||
@@ -199,7 +199,7 @@ comments: true
|
||||
=== "C"
|
||||
|
||||
```c title="stack.c"
|
||||
|
||||
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
@@ -207,27 +207,33 @@ comments: true
|
||||
```csharp title="stack.cs"
|
||||
/* 初始化栈 */
|
||||
Stack<int> stack = new ();
|
||||
|
||||
|
||||
/* 元素入栈 */
|
||||
stack.Push(1);
|
||||
stack.Push(3);
|
||||
stack.Push(2);
|
||||
stack.Push(5);
|
||||
stack.Push(4);
|
||||
|
||||
|
||||
/* 访问栈顶元素 */
|
||||
int peek = stack.Peek();
|
||||
|
||||
|
||||
/* 元素出栈 */
|
||||
int pop = stack.Pop();
|
||||
|
||||
|
||||
/* 获取栈的长度 */
|
||||
int size = stack.Count();
|
||||
|
||||
|
||||
/* 判断是否为空 */
|
||||
bool isEmpty = stack.Count()==0;
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="stack.swift"
|
||||
|
||||
```
|
||||
|
||||
## 栈的实现
|
||||
|
||||
为了更加清晰地了解栈的运行机制,接下来我们来自己动手实现一个栈类。
|
||||
@@ -291,7 +297,7 @@ comments: true
|
||||
private:
|
||||
ListNode* stackTop; // 将头结点作为栈顶
|
||||
int stkSize; // 栈的长度
|
||||
|
||||
|
||||
public:
|
||||
LinkedListStack() {
|
||||
stackTop = nullptr;
|
||||
@@ -338,29 +344,29 @@ comments: true
|
||||
def __init__(self):
|
||||
self.__peek = None
|
||||
self.__size = 0
|
||||
|
||||
|
||||
""" 获取栈的长度 """
|
||||
def size(self):
|
||||
return self.__size
|
||||
|
||||
|
||||
""" 判断栈是否为空 """
|
||||
def is_empty(self):
|
||||
return not self.__peek
|
||||
|
||||
|
||||
""" 入栈 """
|
||||
def push(self, val):
|
||||
node = ListNode(val)
|
||||
node.next = self.__peek
|
||||
self.__peek = node
|
||||
self.__size += 1
|
||||
|
||||
|
||||
""" 出栈 """
|
||||
def pop(self):
|
||||
num = self.peek()
|
||||
self.__peek = self.__peek.next
|
||||
self.__size -= 1
|
||||
return num
|
||||
|
||||
|
||||
""" 访问栈顶元素 """
|
||||
def peek(self):
|
||||
# 判空处理
|
||||
@@ -372,43 +378,49 @@ comments: true
|
||||
|
||||
```go title="linkedlist_stack.go"
|
||||
/* 基于链表实现的栈 */
|
||||
type LinkedListStack struct {
|
||||
type linkedListStack struct {
|
||||
// 使用内置包 list 来实现栈
|
||||
data *list.List
|
||||
}
|
||||
// NewLinkedListStack 初始化链表
|
||||
func NewLinkedListStack() *LinkedListStack {
|
||||
return &LinkedListStack{
|
||||
|
||||
// newLinkedListStack 初始化链表
|
||||
func newLinkedListStack() *linkedListStack {
|
||||
return &linkedListStack{
|
||||
data: list.New(),
|
||||
}
|
||||
}
|
||||
// Push 入栈
|
||||
func (s *LinkedListStack) Push(value int) {
|
||||
|
||||
// push 入栈
|
||||
func (s *linkedListStack) push(value int) {
|
||||
s.data.PushBack(value)
|
||||
}
|
||||
// Pop 出栈
|
||||
func (s *LinkedListStack) Pop() any {
|
||||
if s.IsEmpty() {
|
||||
|
||||
// pop 出栈
|
||||
func (s *linkedListStack) pop() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
e := s.data.Back()
|
||||
s.data.Remove(e)
|
||||
return e.Value
|
||||
}
|
||||
// Peek 访问栈顶元素
|
||||
func (s *LinkedListStack) Peek() any {
|
||||
if s.IsEmpty() {
|
||||
|
||||
// peek 访问栈顶元素
|
||||
func (s *linkedListStack) peek() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
e := s.data.Back()
|
||||
return e.Value
|
||||
}
|
||||
// Size 获取栈的长度
|
||||
func (s *LinkedListStack) Size() int {
|
||||
|
||||
// size 获取栈的长度
|
||||
func (s *linkedListStack) size() int {
|
||||
return s.data.Len()
|
||||
}
|
||||
// IsEmpty 判断栈是否为空
|
||||
func (s *LinkedListStack) IsEmpty() bool {
|
||||
|
||||
// isEmpty 判断栈是否为空
|
||||
func (s *linkedListStack) isEmpty() bool {
|
||||
return s.data.Len() == 0
|
||||
}
|
||||
```
|
||||
@@ -420,21 +432,21 @@ comments: true
|
||||
class LinkedListStack {
|
||||
#stackPeek; // 将头结点作为栈顶
|
||||
#stkSize = 0; // 栈的长度
|
||||
|
||||
|
||||
constructor() {
|
||||
this.#stackPeek = null;
|
||||
}
|
||||
|
||||
|
||||
/* 获取栈的长度 */
|
||||
get size() {
|
||||
return this.#stkSize;
|
||||
}
|
||||
|
||||
|
||||
/* 判断栈是否为空 */
|
||||
isEmpty() {
|
||||
return this.size == 0;
|
||||
}
|
||||
|
||||
|
||||
/* 入栈 */
|
||||
push(num) {
|
||||
const node = new ListNode(num);
|
||||
@@ -442,7 +454,7 @@ comments: true
|
||||
this.#stackPeek = node;
|
||||
this.#stkSize++;
|
||||
}
|
||||
|
||||
|
||||
/* 出栈 */
|
||||
pop() {
|
||||
const num = this.peek();
|
||||
@@ -453,7 +465,7 @@ comments: true
|
||||
this.#stkSize--;
|
||||
return num;
|
||||
}
|
||||
|
||||
|
||||
/* 访问栈顶元素 */
|
||||
peek() {
|
||||
if (!this.#stackPeek) {
|
||||
@@ -461,7 +473,7 @@ comments: true
|
||||
}
|
||||
return this.#stackPeek.val;
|
||||
}
|
||||
|
||||
|
||||
/* 将链表转化为 Array 并返回 */
|
||||
toArray() {
|
||||
let node = this.#stackPeek;
|
||||
@@ -482,21 +494,21 @@ comments: true
|
||||
class LinkedListStack {
|
||||
private stackPeek: ListNode | null; // 将头结点作为栈顶
|
||||
private stkSize: number = 0; // 栈的长度
|
||||
|
||||
|
||||
constructor() {
|
||||
this.stackPeek = null;
|
||||
}
|
||||
|
||||
|
||||
/* 获取栈的长度 */
|
||||
get size(): number {
|
||||
return this.stkSize;
|
||||
}
|
||||
|
||||
|
||||
/* 判断栈是否为空 */
|
||||
isEmpty(): boolean {
|
||||
return this.size == 0;
|
||||
}
|
||||
|
||||
|
||||
/* 入栈 */
|
||||
push(num: number): void {
|
||||
const node = new ListNode(num);
|
||||
@@ -504,7 +516,7 @@ comments: true
|
||||
this.stackPeek = node;
|
||||
this.stkSize++;
|
||||
}
|
||||
|
||||
|
||||
/* 出栈 */
|
||||
pop(): number {
|
||||
const num = this.peek();
|
||||
@@ -515,7 +527,7 @@ comments: true
|
||||
this.stkSize--;
|
||||
return num;
|
||||
}
|
||||
|
||||
|
||||
/* 访问栈顶元素 */
|
||||
peek(): number {
|
||||
if (!this.stackPeek) {
|
||||
@@ -523,7 +535,7 @@ comments: true
|
||||
}
|
||||
return this.stackPeek.val;
|
||||
}
|
||||
|
||||
|
||||
/* 将链表转化为 Array 并返回 */
|
||||
toArray(): number[] {
|
||||
let node = this.stackPeek;
|
||||
@@ -540,7 +552,7 @@ comments: true
|
||||
=== "C"
|
||||
|
||||
```c title="linkedlist_stack.c"
|
||||
|
||||
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
@@ -591,6 +603,12 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="linkedlist_stack.swift"
|
||||
|
||||
```
|
||||
|
||||
### 基于数组的实现
|
||||
|
||||
使用「数组」实现栈时,将数组的尾部当作栈顶,这样可以保证入栈与出栈操作的时间复杂度都为 $O(1)$ 。准确地说,由于入栈的元素可能是源源不断的,我们需要使用可以动态扩容的「列表」。
|
||||
@@ -676,24 +694,24 @@ comments: true
|
||||
class ArrayStack:
|
||||
def __init__(self):
|
||||
self.__stack = []
|
||||
|
||||
|
||||
""" 获取栈的长度 """
|
||||
def size(self):
|
||||
return len(self.__stack)
|
||||
|
||||
|
||||
""" 判断栈是否为空 """
|
||||
def is_empty(self):
|
||||
return self.__stack == []
|
||||
|
||||
|
||||
""" 入栈 """
|
||||
def push(self, item):
|
||||
self.__stack.append(item)
|
||||
|
||||
|
||||
""" 出栈 """
|
||||
def pop(self):
|
||||
assert not self.is_empty(), "栈为空"
|
||||
return self.__stack.pop()
|
||||
|
||||
|
||||
""" 访问栈顶元素 """
|
||||
def peek(self):
|
||||
assert not self.is_empty(), "栈为空"
|
||||
@@ -704,41 +722,47 @@ comments: true
|
||||
|
||||
```go title="array_stack.go"
|
||||
/* 基于数组实现的栈 */
|
||||
type ArrayStack struct {
|
||||
type arrayStack struct {
|
||||
data []int // 数据
|
||||
}
|
||||
func NewArrayStack() *ArrayStack {
|
||||
return &ArrayStack{
|
||||
|
||||
func newArrayStack() *arrayStack {
|
||||
return &arrayStack{
|
||||
// 设置栈的长度为 0,容量为 16
|
||||
data: make([]int, 0, 16),
|
||||
}
|
||||
}
|
||||
// Size 栈的长度
|
||||
func (s *ArrayStack) Size() int {
|
||||
|
||||
// size 栈的长度
|
||||
func (s *arrayStack) size() int {
|
||||
return len(s.data)
|
||||
}
|
||||
// IsEmpty 栈是否为空
|
||||
func (s *ArrayStack) IsEmpty() bool {
|
||||
return s.Size() == 0
|
||||
|
||||
// isEmpty 栈是否为空
|
||||
func (s *arrayStack) isEmpty() bool {
|
||||
return s.size() == 0
|
||||
}
|
||||
// Push 入栈
|
||||
func (s *ArrayStack) Push(v int) {
|
||||
|
||||
// push 入栈
|
||||
func (s *arrayStack) push(v int) {
|
||||
// 切片会自动扩容
|
||||
s.data = append(s.data, v)
|
||||
}
|
||||
// Pop 出栈
|
||||
func (s *ArrayStack) Pop() any {
|
||||
|
||||
// pop 出栈
|
||||
func (s *arrayStack) pop() any {
|
||||
// 弹出栈前,先判断是否为空
|
||||
if s.IsEmpty() {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
val := s.Peek()
|
||||
val := s.peek()
|
||||
s.data = s.data[:len(s.data)-1]
|
||||
return val
|
||||
}
|
||||
// Peek 获取栈顶元素
|
||||
func (s *ArrayStack) Peek() any {
|
||||
if s.IsEmpty() {
|
||||
|
||||
// peek 获取栈顶元素
|
||||
func (s *arrayStack) peek() any {
|
||||
if s.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
val := s.data[len(s.data)-1]
|
||||
@@ -821,7 +845,7 @@ comments: true
|
||||
=== "C"
|
||||
|
||||
```c title="array_stack.c"
|
||||
|
||||
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
@@ -870,11 +894,17 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="array_stack.swift"
|
||||
|
||||
```
|
||||
|
||||
!!! tip
|
||||
|
||||
某些语言并未专门提供栈类,但我们可以直接把该语言的「数组」或「链表」看作栈来使用,并通过“脑补”来屏蔽无关操作,而无需像上述代码去特意包装一层。
|
||||
|
||||
## 栈典型应用
|
||||
|
||||
- **浏览器中的后退与前进、软件中的撤销与反撤销。** 每当我们打开新的网页,浏览器就讲上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。
|
||||
- **程序内存管理。** 每当调用函数时,系统就会在栈顶添加一个栈帧,用来记录函数的上下文信息。在递归函数中,向下递推会不断执行入栈,向上回溯阶段时出栈。
|
||||
- **浏览器中的后退与前进、软件中的撤销与反撤销**。每当我们打开新的网页,浏览器就讲上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。
|
||||
- **程序内存管理**。每当调用函数时,系统就会在栈顶添加一个栈帧,用来记录函数的上下文信息。在递归函数中,向下递推会不断执行入栈,向上回溯阶段时出栈。
|
||||
|
||||
@@ -60,7 +60,13 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
|
||||
=== "Go"
|
||||
|
||||
```go title="avl_tree.go"
|
||||
|
||||
/* AVL 树结点类 */
|
||||
type TreeNode struct {
|
||||
Val int // 结点值
|
||||
Height int // 结点高度
|
||||
Left *TreeNode // 左子结点引用
|
||||
Right *TreeNode // 右子结点引用
|
||||
}
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
@@ -94,6 +100,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="avl_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1** 。我们封装两个工具函数,分别用于获取与更新结点的高度。
|
||||
|
||||
=== "Java"
|
||||
@@ -122,14 +134,14 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
|
||||
|
||||
```python title="avl_tree.py"
|
||||
""" 获取结点高度 """
|
||||
def height(self, node: typing.Optional[TreeNode]) -> int:
|
||||
def height(self, node: Optional[TreeNode]) -> int:
|
||||
# 空结点高度为 -1 ,叶结点高度为 0
|
||||
if node is not None:
|
||||
return node.height
|
||||
return -1
|
||||
|
||||
""" 更新结点高度 """
|
||||
def __update_height(self, node: TreeNode):
|
||||
def __update_height(self, node: Optional[TreeNode]):
|
||||
# 结点高度等于最高子树高度 + 1
|
||||
node.height = max([self.height(node.left), self.height(node.right)]) + 1
|
||||
```
|
||||
@@ -137,7 +149,26 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
|
||||
=== "Go"
|
||||
|
||||
```go title="avl_tree.go"
|
||||
|
||||
/* 获取结点高度 */
|
||||
func height(node *TreeNode) int {
|
||||
// 空结点高度为 -1 ,叶结点高度为 0
|
||||
if node != nil {
|
||||
return node.Height
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
/* 更新结点高度 */
|
||||
func updateHeight(node *TreeNode) {
|
||||
lh := height(node.Left)
|
||||
rh := height(node.Right)
|
||||
// 结点高度等于最高子树高度 + 1
|
||||
if lh > rh {
|
||||
node.Height = lh + 1
|
||||
} else {
|
||||
node.Height = rh + 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
@@ -176,6 +207,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="avl_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
### 结点平衡因子
|
||||
|
||||
结点的「平衡因子 Balance Factor」是 **结点的左子树高度减去右子树高度**,并定义空结点的平衡因子为 0 。同样地,我们将获取结点平衡因子封装成函数,以便后续使用。
|
||||
@@ -202,7 +239,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
|
||||
|
||||
```python title="avl_tree.py"
|
||||
""" 获取平衡因子 """
|
||||
def balance_factor(self, node: TreeNode) -> int:
|
||||
def balance_factor(self, node: Optional[TreeNode]) -> int:
|
||||
# 空结点平衡因子为 0
|
||||
if node is None:
|
||||
return 0
|
||||
@@ -213,7 +250,15 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
|
||||
=== "Go"
|
||||
|
||||
```go title="avl_tree.go"
|
||||
|
||||
/* 获取平衡因子 */
|
||||
func balanceFactor(node *TreeNode) int {
|
||||
// 空结点平衡因子为 0
|
||||
if node == nil {
|
||||
return 0
|
||||
}
|
||||
// 结点平衡因子 = 左子树高度 - 右子树高度
|
||||
return height(node.Left) - height(node.Right)
|
||||
}
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
@@ -247,13 +292,19 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="avl_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
设平衡因子为 $f$ ,则一棵 AVL 树的任意结点的平衡因子皆满足 $-1 \le f \le 1$ 。
|
||||
|
||||
## AVL 树旋转
|
||||
|
||||
AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影响二叉树中序遍历序列的前提下,使失衡结点重新恢复平衡。** 换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。
|
||||
AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影响二叉树中序遍历序列的前提下,使失衡结点重新恢复平衡**。换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。
|
||||
|
||||
我们将平衡因子的绝对值 $> 1$ 的结点称为「失衡结点」。根据结点的失衡情况,旋转操作分为 **右旋、左旋、先右旋后左旋、先左旋后右旋**,接下来我们来一起来看看它们是如何操作的。
|
||||
|
||||
@@ -304,7 +355,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
|
||||
```python title="avl_tree.py"
|
||||
""" 右旋操作 """
|
||||
def __right_rotate(self, node: TreeNode) -> TreeNode:
|
||||
def __right_rotate(self, node: Optional[TreeNode]) -> TreeNode:
|
||||
child = node.left
|
||||
grand_child = child.right
|
||||
# 以 child 为原点,将 node 向右旋转
|
||||
@@ -320,7 +371,19 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
=== "Go"
|
||||
|
||||
```go title="avl_tree.go"
|
||||
|
||||
/* 右旋操作 */
|
||||
func rightRotate(node *TreeNode) *TreeNode {
|
||||
child := node.Left
|
||||
grandChild := child.Right
|
||||
// 以 child 为原点,将 node 向右旋转
|
||||
child.Right = node
|
||||
node.Left = grandChild
|
||||
// 更新结点高度
|
||||
updateHeight(node)
|
||||
updateHeight(child)
|
||||
// 返回旋转后子树的根节点
|
||||
return child
|
||||
}
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
@@ -361,6 +424,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="avl_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
### Case 2 - 左旋
|
||||
|
||||
类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。
|
||||
@@ -401,7 +470,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
|
||||
```python title="avl_tree.py"
|
||||
""" 左旋操作 """
|
||||
def __left_rotate(self, node: TreeNode) -> TreeNode:
|
||||
def __left_rotate(self, node: Optional[TreeNode]) -> TreeNode:
|
||||
child = node.right
|
||||
grand_child = child.left
|
||||
# 以 child 为原点,将 node 向左旋转
|
||||
@@ -417,7 +486,19 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
=== "Go"
|
||||
|
||||
```go title="avl_tree.go"
|
||||
|
||||
/* 左旋操作 */
|
||||
func leftRotate(node *TreeNode) *TreeNode {
|
||||
child := node.Right
|
||||
grandChild := child.Left
|
||||
// 以 child 为原点,将 node 向左旋转
|
||||
child.Left = node
|
||||
node.Right = grandChild
|
||||
// 更新结点高度
|
||||
updateHeight(node)
|
||||
updateHeight(child)
|
||||
// 返回旋转后子树的根节点
|
||||
return child
|
||||
}
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
@@ -457,6 +538,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="avl_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
### Case 3 - 先左后右
|
||||
|
||||
对于下图的失衡结点 3 ,**单一使用左旋或右旋都无法使子树恢复平衡**,此时需要「先左旋后右旋」,即先对 `child` 执行「左旋」,再对 `node` 执行「右旋」。
|
||||
@@ -534,7 +621,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
|
||||
```python title="avl_tree.py"
|
||||
""" 执行旋转操作,使该子树重新恢复平衡 """
|
||||
def __rotate(self, node: TreeNode) -> TreeNode:
|
||||
def __rotate(self, node: Optional[TreeNode]) -> TreeNode:
|
||||
# 获取结点 node 的平衡因子
|
||||
balance_factor = self.balance_factor(node)
|
||||
# 左偏树
|
||||
@@ -562,7 +649,36 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
=== "Go"
|
||||
|
||||
```go title="avl_tree.go"
|
||||
|
||||
/* 执行旋转操作,使该子树重新恢复平衡 */
|
||||
func rotate(node *TreeNode) *TreeNode {
|
||||
// 获取结点 node 的平衡因子
|
||||
// Go 推荐短变量,这里 bf 指代 balanceFactor
|
||||
bf := balanceFactor(node)
|
||||
// 左偏树
|
||||
if bf > 1 {
|
||||
if balanceFactor(node.Left) >= 0 {
|
||||
// 右旋
|
||||
return rightRotate(node)
|
||||
} else {
|
||||
// 先左旋后右旋
|
||||
node.Left = leftRotate(node.Left)
|
||||
return rightRotate(node)
|
||||
}
|
||||
}
|
||||
// 右偏树
|
||||
if bf < -1 {
|
||||
if balanceFactor(node.Right) <= 0 {
|
||||
// 左旋
|
||||
return leftRotate(node)
|
||||
} else {
|
||||
// 先右旋后左旋
|
||||
node.Right = rightRotate(node.Right)
|
||||
return leftRotate(node)
|
||||
}
|
||||
}
|
||||
// 平衡树,无需旋转,直接返回
|
||||
return node
|
||||
}
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
@@ -626,6 +742,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="avl_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
## AVL 树常用操作
|
||||
|
||||
### 插入结点
|
||||
@@ -674,7 +796,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
return self.root
|
||||
|
||||
""" 递归插入结点(辅助函数)"""
|
||||
def __insert_helper(self, node: typing.Optional[TreeNode], val: int) -> TreeNode:
|
||||
def __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode:
|
||||
if node is None:
|
||||
return TreeNode(val)
|
||||
# 1. 查找插入位置,并插入结点
|
||||
@@ -694,7 +816,32 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
=== "Go"
|
||||
|
||||
```go title="avl_tree.go"
|
||||
|
||||
/* 插入结点 */
|
||||
func (t *avlTree) insert(val int) *TreeNode {
|
||||
t.root = insertHelper(t.root, val)
|
||||
return t.root
|
||||
}
|
||||
/* 递归插入结点(辅助函数) */
|
||||
func insertHelper(node *TreeNode, val int) *TreeNode {
|
||||
if node == nil {
|
||||
return NewTreeNode(val)
|
||||
}
|
||||
/* 1. 查找插入位置,并插入结点 */
|
||||
if val < node.Val {
|
||||
node.Left = insertHelper(node.Left, val)
|
||||
} else if val > node.Val {
|
||||
node.Right = insertHelper(node.Right, val)
|
||||
} else {
|
||||
// 重复结点不插入,直接返回
|
||||
return node
|
||||
}
|
||||
// 更新结点高度
|
||||
updateHeight(node)
|
||||
/* 2. 执行旋转操作,使该子树重新恢复平衡 */
|
||||
node = rotate(node)
|
||||
// 返回子树的根节点
|
||||
return node
|
||||
}
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
@@ -744,6 +891,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="avl_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
### 删除结点
|
||||
|
||||
「AVL 树」删除结点操作与「二叉搜索树」删除结点操作总体相同。类似地,**在删除结点后,也需要从底至顶地执行旋转操作,使所有失衡结点恢复平衡**。
|
||||
@@ -804,7 +957,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
return root
|
||||
|
||||
""" 递归删除结点(辅助函数) """
|
||||
def __remove_helper(self, node: typing.Optional[TreeNode], val: int) -> typing.Optional[TreeNode]:
|
||||
def __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]:
|
||||
if node is None:
|
||||
return None
|
||||
# 1. 查找结点,并删除之
|
||||
@@ -834,7 +987,49 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
=== "Go"
|
||||
|
||||
```go title="avl_tree.go"
|
||||
/* 删除结点 */
|
||||
func (t *avlTree) remove(val int) *TreeNode {
|
||||
root := removeHelper(t.root, val)
|
||||
return root
|
||||
}
|
||||
|
||||
/* 递归删除结点(辅助函数) */
|
||||
func removeHelper(node *TreeNode, val int) *TreeNode {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
/* 1. 查找结点,并删除之 */
|
||||
if val < node.Val {
|
||||
node.Left = removeHelper(node.Left, val)
|
||||
} else if val > node.Val {
|
||||
node.Right = removeHelper(node.Right, val)
|
||||
} else {
|
||||
if node.Left == nil || node.Right == nil {
|
||||
child := node.Left
|
||||
if node.Right != nil {
|
||||
child = node.Right
|
||||
}
|
||||
// 子结点数量 = 0 ,直接删除 node 并返回
|
||||
if child == nil {
|
||||
return nil
|
||||
} else {
|
||||
// 子结点数量 = 1 ,直接删除 node
|
||||
node = child
|
||||
}
|
||||
} else {
|
||||
// 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点
|
||||
temp := getInOrderNext(node.Right)
|
||||
node.Right = removeHelper(node.Right, temp.Val)
|
||||
node.Val = temp.Val
|
||||
}
|
||||
}
|
||||
// 更新结点高度
|
||||
updateHeight(node)
|
||||
/* 2. 执行旋转操作,使该子树重新恢复平衡 */
|
||||
node = rotate(node)
|
||||
// 返回子树的根节点
|
||||
return node
|
||||
}
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
@@ -902,6 +1097,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="avl_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
### 查找结点
|
||||
|
||||
「AVL 树」的结点查找操作与「二叉搜索树」一致,在此不再赘述。
|
||||
|
||||
@@ -83,7 +83,7 @@ comments: true
|
||||
|
||||
```python title="binary_search_tree.py"
|
||||
""" 查找结点 """
|
||||
def search(self, num: int) -> typing.Optional[TreeNode]:
|
||||
def search(self, num: int) -> Optional[TreeNode]:
|
||||
cur = self.root
|
||||
# 循环查找,越过叶结点后跳出
|
||||
while cur is not None:
|
||||
@@ -103,7 +103,7 @@ comments: true
|
||||
|
||||
```go title="binary_search_tree.go"
|
||||
/* 查找结点 */
|
||||
func (bst *BinarySearchTree) Search(num int) *TreeNode {
|
||||
func (bst *binarySearchTree) search(num int) *TreeNode {
|
||||
node := bst.root
|
||||
// 循环查找,越过叶结点后跳出
|
||||
for node != nil {
|
||||
@@ -192,6 +192,12 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="binary_search_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
### 插入结点
|
||||
|
||||
给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根结点 < 右子树”的性质,插入操作分为两步:
|
||||
@@ -259,7 +265,7 @@ comments: true
|
||||
|
||||
```python title="binary_search_tree.py"
|
||||
""" 插入结点 """
|
||||
def insert(self, num: int) -> typing.Optional[TreeNode]:
|
||||
def insert(self, num: int) -> Optional[TreeNode]:
|
||||
root = self.root
|
||||
# 若树为空,直接提前返回
|
||||
if root is None:
|
||||
@@ -293,7 +299,7 @@ comments: true
|
||||
|
||||
```go title="binary_search_tree.go"
|
||||
/* 插入结点 */
|
||||
func (bst *BinarySearchTree) Insert(num int) *TreeNode {
|
||||
func (bst *binarySearchTree) insert(num int) *TreeNode {
|
||||
cur := bst.root
|
||||
// 若树为空,直接提前返回
|
||||
if cur == nil {
|
||||
@@ -422,6 +428,12 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="binary_search_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
为了插入结点,需要借助 **辅助结点 `prev`** 保存上一轮循环的结点,这样在遍历到 $\text{null}$ 时,我们也可以获取到其父结点,从而完成结点插入操作。
|
||||
|
||||
与查找结点相同,插入结点使用 $O(\log n)$ 时间。
|
||||
@@ -430,15 +442,15 @@ comments: true
|
||||
|
||||
与插入结点一样,我们需要在删除操作后维持二叉搜索树的“左子树 < 根结点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除结点。接下来,根据待删除结点的子结点数量,删除操作需要分为三种情况:
|
||||
|
||||
**待删除结点的子结点数量 $= 0$ 。** 表明待删除结点是叶结点,直接删除即可。
|
||||
**待删除结点的子结点数量 $= 0$ **。表明待删除结点是叶结点,直接删除即可。
|
||||
|
||||

|
||||
|
||||
**待删除结点的子结点数量 $= 1$ 。** 将待删除结点替换为其子结点。
|
||||
**待删除结点的子结点数量 $= 1$ **。将待删除结点替换为其子结点。
|
||||
|
||||

|
||||
|
||||
**待删除结点的子结点数量 $= 2$ 。** 删除操作分为三步:
|
||||
**待删除结点的子结点数量 $= 2$ **。删除操作分为三步:
|
||||
|
||||
1. 找到待删除结点在 **中序遍历序列** 中的下一个结点,记为 `nex` ;
|
||||
2. 在树中递归删除结点 `nex` ;
|
||||
@@ -548,7 +560,7 @@ comments: true
|
||||
|
||||
```python title="binary_search_tree.py"
|
||||
""" 删除结点 """
|
||||
def remove(self, num: int) -> typing.Optional[TreeNode]:
|
||||
def remove(self, num: int) -> Optional[TreeNode]:
|
||||
root = self.root
|
||||
# 若树为空,直接提前返回
|
||||
if root is None:
|
||||
@@ -597,7 +609,7 @@ comments: true
|
||||
|
||||
```go title="binary_search_tree.go"
|
||||
/* 删除结点 */
|
||||
func (bst *BinarySearchTree) Remove(num int) *TreeNode {
|
||||
func (bst *binarySearchTree) remove(num int) *TreeNode {
|
||||
cur := bst.root
|
||||
// 若树为空,直接提前返回
|
||||
if cur == nil {
|
||||
@@ -641,10 +653,10 @@ comments: true
|
||||
// 子结点数为 2
|
||||
} else {
|
||||
// 获取中序遍历中待删除结点 cur 的下一个结点
|
||||
next := bst.GetInOrderNext(cur)
|
||||
next := bst.getInOrderNext(cur)
|
||||
temp := next.Val
|
||||
// 递归删除结点 next
|
||||
bst.Remove(next.Val)
|
||||
bst.remove(next.Val)
|
||||
// 将 next 的值复制给 cur
|
||||
cur.Val = temp
|
||||
}
|
||||
@@ -808,6 +820,12 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="binary_search_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
## 二叉搜索树的优势
|
||||
|
||||
假设给定 $n$ 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 78 KiB |
@@ -106,6 +106,12 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
|
||||
```
|
||||
|
||||
结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。
|
||||
|
||||
除了叶结点外,每个结点都有子结点和子树。例如,若将上图的「结点 2」看作父结点,那么其左子结点和右子结点分别为「结点 4」和「结点 5」,左子树和右子树分别为「结点 4 以下的树」和「结点 5 以下的树」。
|
||||
@@ -137,7 +143,7 @@ comments: true
|
||||
|
||||
## 二叉树基本操作
|
||||
|
||||
**初始化二叉树。** 与链表类似,先初始化结点,再构建引用指向(即指针)。
|
||||
**初始化二叉树**。与链表类似,先初始化结点,再构建引用指向(即指针)。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -263,7 +269,13 @@ comments: true
|
||||
n2.right = n5;
|
||||
```
|
||||
|
||||
**插入与删除结点。** 与链表类似,插入与删除结点都可以通过修改指针实现。
|
||||
=== "Swift"
|
||||
|
||||
```swift title="binary_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
**插入与删除结点**。与链表类似,插入与删除结点都可以通过修改指针实现。
|
||||
|
||||

|
||||
|
||||
@@ -358,6 +370,12 @@ comments: true
|
||||
n1.left = n2;
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="binary_tree.swift"
|
||||
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。
|
||||
@@ -495,9 +513,15 @@ comments: true
|
||||
int?[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
|
||||
```
|
||||
|
||||

|
||||
|
||||
回顾「完全二叉树」的满足条件,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。“便于使用数组表示”也是完全二叉树受欢迎的原因之一。
|
||||
回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ comments: true
|
||||
|
||||
```python title="binary_tree_bfs.py"
|
||||
""" 层序遍历 """
|
||||
def hier_order(root: TreeNode):
|
||||
def hier_order(root: Optional[TreeNode]):
|
||||
# 初始化队列,加入根结点
|
||||
queue = collections.deque()
|
||||
queue.append(root)
|
||||
@@ -185,6 +185,12 @@ comments: true
|
||||
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="binary_tree_bfs.swift"
|
||||
|
||||
```
|
||||
|
||||
## 前序、中序、后序遍历
|
||||
|
||||
相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。
|
||||
@@ -271,7 +277,7 @@ comments: true
|
||||
|
||||
```python title="binary_tree_dfs.py"
|
||||
""" 前序遍历 """
|
||||
def pre_order(root: typing.Optional[TreeNode]):
|
||||
def pre_order(root: Optional[TreeNode]):
|
||||
if root is None:
|
||||
return
|
||||
# 访问优先级:根结点 -> 左子树 -> 右子树
|
||||
@@ -280,7 +286,7 @@ comments: true
|
||||
pre_order(root=root.right)
|
||||
|
||||
""" 中序遍历 """
|
||||
def in_order(root: typing.Optional[TreeNode]):
|
||||
def in_order(root: Optional[TreeNode]):
|
||||
if root is None:
|
||||
return
|
||||
# 访问优先级:左子树 -> 根结点 -> 右子树
|
||||
@@ -289,7 +295,7 @@ comments: true
|
||||
in_order(root=root.right)
|
||||
|
||||
""" 后序遍历 """
|
||||
def post_order(root: typing.Optional[TreeNode]):
|
||||
def post_order(root: Optional[TreeNode]):
|
||||
if root is None:
|
||||
return
|
||||
# 访问优先级:左子树 -> 右子树 -> 根结点
|
||||
@@ -443,6 +449,12 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="binary_tree_dfs.swift"
|
||||
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。
|
||||
|
||||
Reference in New Issue
Block a user