From 4fe3455037b9f01d19eb959ba0e2fdfa80bdde43 Mon Sep 17 00:00:00 2001
From: krahets
The English edition is brewing...
+Feel free to engage in Chinese-to-English translation and pull request review! For guidelines, please see #914.
+Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code
Dive In Clone Repo Get PDF
Endorsements
Quote
\"An easy-to-understand book on data structures and algorithms, which guides readers to learn by minds-on and hands-on. Strongly recommended for algorithm beginners!\"
\u2014\u2014 Junhui Deng, Professor of Computer Science, Tsinghua University
Quote
\"If I had 'Hello Algo' when I was learning data structures and algorithms, it would have been 10 times easier!\"
\u2014\u2014 Mu Li, Senior Principal Scientist, Amazon
Animated illustrationsEasy to understandSmooth learning curve
\"A picture is worth a thousand words.\"
Off-the-Shelf CodeMulti programming languagesRun with one click
\"Talk is cheap. Show me the code.\"
Learning TogetherDiscussion and questions welcomeReaders progress together
\"Chase the wind and moon, never stopping\"
\"Beyond the plains, there are spring mountains\"
PrefaceTwo years ago, I shared the \"Sword Offer\" series of problem solutions on LeetCode, which received much love and support from many students. During my interactions with readers, the most common question I encountered was \"How to get started with algorithms.\" Gradually, I developed a deep interest in this question.
Blindly solving problems seems to be the most popular method, being simple, direct, and effective. However, problem-solving is like playing a \"Minesweeper\" game, where students with strong self-learning abilities can successfully clear the mines one by one, but those with insufficient foundations may end up bruised from explosions, retreating step by step in frustration. Thoroughly reading textbooks is also common, but for students aiming for job applications, the energy consumed by graduation, resume submissions, and preparing for written tests and interviews makes tackling thick books a daunting challenge.
If you are facing similar troubles, then you are lucky to have found this book. This book is my answer to this question, not necessarily the best solution, but at least an active attempt. Although this book won't directly land you an Offer, it will guide you through the \"knowledge map\" of data structures and algorithms, help you understand the shape, size, and distribution of different \"mines,\" and equip you with various \"demining methods.\" With these skills, I believe you can more comfortably solve problems and read literature, gradually building a complete knowledge system.
I deeply agree with Professor Feynman's saying: \"Knowledge isn't free. You have to pay attention.\" In this sense, this book is not entirely \"free.\" To not disappoint the precious \"attention\" you pay to this book, I will do my utmost, investing the greatest \"attention\" to complete the creation of this book.
AuthorYudong Jin(Krahets), Senior Algorithm Engineer in a top tech company, Master's degree from Shanghai Jiao Tong University. The highest-read blogger across the entire LeetCode, his published \"Illustration of Algorithm Data Structures\" has been subscribed to by over 300k.
ContributionThis book is continuously improved with the joint efforts of many contributors from the open-source community. Thanks to each writer who invested their time and energy, listed in the order generated by GitHub:
The code review work for this book was completed by Gonglja, gvenusleo, hpstory, justin\u2010tse, krahets, night-cruise, nuomi1, Reanon, and sjinzh (listed in alphabetical order). Thanks to them for their time and effort, ensuring the standardization and uniformity of the code in various languages.
GongljaC, C++ gvenusleoDart hpstoryC# justin-tseJS, TS krahetsJava, Python night-cruiseRust nuomi1Swift ReanonGo, C sjinzhRust, Zig"},{"location":"chapter_computational_complexity/","title":"\u7b2c 2 \u7ae0 \u00a0 Complexity Analysis","text":"Abstract
Complexity analysis is like a space-time guide in the vast universe of algorithms.
It leads us to explore deeply in the dimensions of time and space, in search of more elegant solutions.
"},{"location":"chapter_computational_complexity/#_1","title":"\u672c\u7ae0\u5185\u5bb9","text":"In data structures and algorithms, it is common to repeat a task, which is closely related to the complexity of the algorithm. There are two basic program structures that we usually use to repeat a task: iteration and recursion.
"},{"location":"chapter_computational_complexity/iteration_and_recursion/#221-iteration","title":"2.2.1 \u00a0 Iteration","text":"An \"iteration iteration\" is a control structure that repeats a task. In iteration, a program repeats the execution of a piece of code until the condition is no longer satisfied.
"},{"location":"chapter_computational_complexity/iteration_and_recursion/#1-for-loops","title":"1. \u00a0 For Loops","text":"for
loops are one of the most common forms of iteration, suitable when the number of iterations is known in advance.
The following function implements the summation \\(1 + 2 + \\dots + n\\) based on a for
loop, and the result is recorded using the variable res
. Note that range(a, b)
in Python corresponds to a \"left-closed-right-open\" interval, which is traversed in the range \\(a, a + 1, \\dots, b-1\\).
def for_loop(n: int) -> int:\n \"\"\"for \u5faa\u73af\"\"\"\n res = 0\n # \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for i in range(1, n + 1):\n res += i\n return res\n
iteration.cpp/* for \u5faa\u73af */\nint forLoop(int n) {\n int res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (int i = 1; i <= n; ++i) {\n res += i;\n }\n return res;\n}\n
iteration.java/* for \u5faa\u73af */\nint forLoop(int n) {\n int res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n res += i;\n }\n return res;\n}\n
iteration.cs/* for \u5faa\u73af */\nint ForLoop(int n) {\n int res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n res += i;\n }\n return res;\n}\n
iteration.go/* for \u5faa\u73af */\nfunc forLoop(n int) int {\n res := 0\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for i := 1; i <= n; i++ {\n res += i\n }\n return res\n}\n
iteration.swift/* for \u5faa\u73af */\nfunc forLoop(n: Int) -> Int {\n var res = 0\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for i in 1 ... n {\n res += i\n }\n return res\n}\n
iteration.js/* for \u5faa\u73af */\nfunction forLoop(n) {\n let res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (let i = 1; i <= n; i++) {\n res += i;\n }\n return res;\n}\n
iteration.ts/* for \u5faa\u73af */\nfunction forLoop(n: number): number {\n let res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (let i = 1; i <= n; i++) {\n res += i;\n }\n return res;\n}\n
iteration.dart/* for \u5faa\u73af */\nint forLoop(int n) {\n int res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n res += i;\n }\n return res;\n}\n
iteration.rs/* for \u5faa\u73af */\nfn for_loop(n: i32) -> i32 {\n let mut res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for i in 1..=n {\n res += i;\n }\n res\n} \n
iteration.c/* for \u5faa\u73af */\nint forLoop(int n) {\n int res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n res += i;\n }\n return res;\n}\n
iteration.zig// for \u5faa\u73af\nfn forLoop(n: usize) i32 {\n var res: i32 = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (1..n+1) |i| {\n res = res + @as(i32, @intCast(i));\n }\n return res;\n} \n
The Figure 2-1 shows the flow block diagram of this summation function.
Figure 2-1 \u00a0 Flow block diagram of the summation function
The number of operations in this summation function is proportional to the size of the input data \\(n\\), or a \"linear relationship\". In fact, time complexity describes this \"linear relationship\". This is described in more detail in the next section.
"},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-while-loop","title":"2. \u00a0 While Loop","text":"Similar to a for
loop, a while
loop is a way to implement iteration. In a while
loop, the program first checks the condition at each turn, and if the condition is true, it continues, otherwise it ends the loop.
Below, we use a while
loop to realize the summation \\(1 + 2 + \\dots + n\\) .
def while_loop(n: int) -> int:\n \"\"\"while \u5faa\u73af\"\"\"\n res = 0\n i = 1 # \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n # \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while i <= n:\n res += i\n i += 1 # \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n return res\n
iteration.cpp/* while \u5faa\u73af */\nint whileLoop(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i++; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.java/* while \u5faa\u73af */\nint whileLoop(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i++; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.cs/* while \u5faa\u73af */\nint WhileLoop(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i += 1; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.go/* while \u5faa\u73af */\nfunc whileLoop(n int) int {\n res := 0\n // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n i := 1\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for i <= n {\n res += i\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++\n }\n return res\n}\n
iteration.swift/* while \u5faa\u73af */\nfunc whileLoop(n: Int) -> Int {\n var res = 0\n var i = 1 // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while i <= n {\n res += i\n i += 1 // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res\n}\n
iteration.js/* while \u5faa\u73af */\nfunction whileLoop(n) {\n let res = 0;\n let i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i++; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.ts/* while \u5faa\u73af */\nfunction whileLoop(n: number): number {\n let res = 0;\n let i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i++; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.dart/* while \u5faa\u73af */\nint whileLoop(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i++; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.rs/* while \u5faa\u73af */\nfn while_loop(n: i32) -> i32 {\n let mut res = 0;\n let mut i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while i <= n {\n res += i;\n i += 1; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n res\n}\n
iteration.c/* while \u5faa\u73af */\nint whileLoop(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i++; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.zig// while \u5faa\u73af\nfn whileLoop(n: i32) i32 {\n var res: i32 = 0;\n var i: i32 = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += @intCast(i);\n i += 1;\n }\n return res;\n}\n
In while
loops, since the steps of initializing and updating condition variables are independent of the loop structure, it has more degrees of freedom than for
loops.
For example, in the following code, the condition variable \\(i\\) is updated twice per round, which is not convenient to implement with a for
loop.
def while_loop_ii(n: int) -> int:\n \"\"\"while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09\"\"\"\n res = 0\n i = 1 # \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n # \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while i <= n:\n res += i\n # \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i += 1\n i *= 2\n return res\n
iteration.cpp/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nint whileLoopII(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++;\n i *= 2;\n }\n return res;\n}\n
iteration.java/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nint whileLoopII(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++;\n i *= 2;\n }\n return res;\n}\n
iteration.cs/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nint WhileLoopII(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, 4, 5...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i += 1; \n i *= 2;\n }\n return res;\n}\n
iteration.go/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nfunc whileLoopII(n int) int {\n res := 0\n // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n i := 1\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n for i <= n {\n res += i\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++\n i *= 2\n }\n return res\n}\n
iteration.swift/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nfunc whileLoopII(n: Int) -> Int {\n var res = 0\n var i = 1 // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while i <= n {\n res += i\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i += 1\n i *= 2\n }\n return res\n}\n
iteration.js/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nfunction whileLoopII(n) {\n let res = 0;\n let i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++;\n i *= 2;\n }\n return res;\n}\n
iteration.ts/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nfunction whileLoopII(n: number): number {\n let res = 0;\n let i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++;\n i *= 2;\n }\n return res;\n}\n
iteration.dart/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nint whileLoopII(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++;\n i *= 2;\n }\n return res;\n}\n
iteration.rs/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nfn while_loop_ii(n: i32) -> i32 {\n let mut res = 0;\n let mut i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while i <= n {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i += 1;\n i *= 2;\n }\n res\n}\n
iteration.c/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nint whileLoopII(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++;\n i *= 2;\n }\n return res;\n}\n
iteration.zig// while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09\nfn whileLoopII(n: i32) i32 {\n var res: i32 = 0;\n var i: i32 = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += @intCast(i);\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i += 1;\n i *= 2;\n }\n return res;\n}\n
Overall, for
loops have more compact code and while
loops are more flexible, and both can implement iteration structures. The choice of which one to use should be based on the needs of the particular problem.
We can nest one loop structure inside another, using the for
loop as an example:
def nested_for_loop(n: int) -> str:\n \"\"\"\u53cc\u5c42 for \u5faa\u73af\"\"\"\n res = \"\"\n # \u5faa\u73af i = 1, 2, ..., n-1, n\n for i in range(1, n + 1):\n # \u5faa\u73af j = 1, 2, ..., n-1, n\n for j in range(1, n + 1):\n res += f\"({i}, {j}), \"\n return res\n
iteration.cpp/* \u53cc\u5c42 for \u5faa\u73af */\nstring nestedForLoop(int n) {\n ostringstream res;\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (int i = 1; i <= n; ++i) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (int j = 1; j <= n; ++j) {\n res << \"(\" << i << \", \" << j << \"), \";\n }\n }\n return res.str();\n}\n
iteration.java/* \u53cc\u5c42 for \u5faa\u73af */\nString nestedForLoop(int n) {\n StringBuilder res = new StringBuilder();\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (int j = 1; j <= n; j++) {\n res.append(\"(\" + i + \", \" + j + \"), \");\n }\n }\n return res.toString();\n}\n
iteration.cs/* \u53cc\u5c42 for \u5faa\u73af */\nstring NestedForLoop(int n) {\n StringBuilder res = new();\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (int j = 1; j <= n; j++) {\n res.Append($\"({i}, {j}), \");\n }\n }\n return res.ToString();\n}\n
iteration.go/* \u53cc\u5c42 for \u5faa\u73af */\nfunc nestedForLoop(n int) string {\n res := \"\"\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for i := 1; i <= n; i++ {\n for j := 1; j <= n; j++ {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n res += fmt.Sprintf(\"(%d, %d), \", i, j)\n }\n }\n return res\n}\n
iteration.swift/* \u53cc\u5c42 for \u5faa\u73af */\nfunc nestedForLoop(n: Int) -> String {\n var res = \"\"\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for i in 1 ... n {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for j in 1 ... n {\n res.append(\"(\\(i), \\(j)), \")\n }\n }\n return res\n}\n
iteration.js/* \u53cc\u5c42 for \u5faa\u73af */\nfunction nestedForLoop(n) {\n let res = '';\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (let i = 1; i <= n; i++) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (let j = 1; j <= n; j++) {\n res += `(${i}, ${j}), `;\n }\n }\n return res;\n}\n
iteration.ts/* \u53cc\u5c42 for \u5faa\u73af */\nfunction nestedForLoop(n: number): string {\n let res = '';\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (let i = 1; i <= n; i++) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (let j = 1; j <= n; j++) {\n res += `(${i}, ${j}), `;\n }\n }\n return res;\n}\n
iteration.dart/* \u53cc\u5c42 for \u5faa\u73af */\nString nestedForLoop(int n) {\n String res = \"\";\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (int j = 1; j <= n; j++) {\n res += \"($i, $j), \";\n }\n }\n return res;\n}\n
iteration.rs/* \u53cc\u5c42 for \u5faa\u73af */\nfn nested_for_loop(n: i32) -> String {\n let mut res = vec![];\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for i in 1..=n {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for j in 1..=n {\n res.push(format!(\"({}, {}), \", i, j));\n }\n }\n res.join(\"\")\n}\n
iteration.c/* \u53cc\u5c42 for \u5faa\u73af */\nchar *nestedForLoop(int n) {\n // n * n \u4e3a\u5bf9\u5e94\u70b9\u6570\u91cf\uff0c\"(i, j), \" \u5bf9\u5e94\u5b57\u7b26\u4e32\u957f\u6700\u5927\u4e3a 6+10*2\uff0c\u52a0\u4e0a\u6700\u540e\u4e00\u4e2a\u7a7a\u5b57\u7b26 \\0 \u7684\u989d\u5916\u7a7a\u95f4\n int size = n * n * 26 + 1;\n char *res = malloc(size * sizeof(char));\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (int j = 1; j <= n; j++) {\n char tmp[26];\n snprintf(tmp, sizeof(tmp), \"(%d, %d), \", i, j);\n strncat(res, tmp, size - strlen(res) - 1);\n }\n }\n return res;\n}\n
iteration.zig// \u53cc\u5c42 for \u5faa\u73af\nfn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 {\n var res = std.ArrayList(u8).init(allocator);\n defer res.deinit();\n var buffer: [20]u8 = undefined;\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (1..n+1) |i| {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (1..n+1) |j| {\n var _str = try std.fmt.bufPrint(&buffer, \"({d}, {d}), \", .{i, j});\n try res.appendSlice(_str);\n }\n }\n return res.toOwnedSlice();\n}\n
The Figure 2-2 gives the block diagram of the flow of this nested loop.
Figure 2-2 \u00a0 Block diagram of the flow of nested loops
In this case, the number of operations of the function is proportional to \\(n^2\\), or the algorithm's running time is \"squared\" to the size of the input data \\(n\\).
We can continue to add nested loops, and each nest is a \"dimension up\", which will increase the time complexity to \"cubic relations\", \"quadratic relations\", and so on.
"},{"location":"chapter_computational_complexity/iteration_and_recursion/#222-recursion","title":"2.2.2 \u00a0 Recursion","text":"\"Recursion recursion is an algorithmic strategy to solve a problem by calling the function itself. It consists of two main phases.
And from an implementation point of view, recursion code contains three main elements.
Observe the following code, we only need to call the function recur(n)
, and the calculation of \\(1 + 2 + \\dots + n\\) is done:
def recur(n: int) -> int:\n \"\"\"\u9012\u5f52\"\"\"\n # \u7ec8\u6b62\u6761\u4ef6\n if n == 1:\n return 1\n # \u9012\uff1a\u9012\u5f52\u8c03\u7528\n res = recur(n - 1)\n # \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res\n
recursion.cpp/* \u9012\u5f52 */\nint recur(int n) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 1)\n return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n int res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.java/* \u9012\u5f52 */\nint recur(int n) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 1)\n return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n int res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.cs/* \u9012\u5f52 */\nint Recur(int n) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 1)\n return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n int res = Recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.go/* \u9012\u5f52 */\nfunc recur(n int) int {\n // \u7ec8\u6b62\u6761\u4ef6\n if n == 1 {\n return 1\n }\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n res := recur(n - 1)\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res\n}\n
recursion.swift/* \u9012\u5f52 */\nfunc recur(n: Int) -> Int {\n // \u7ec8\u6b62\u6761\u4ef6\n if n == 1 {\n return 1\n }\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n let res = recur(n: n - 1)\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res\n}\n
recursion.js/* \u9012\u5f52 */\nfunction recur(n) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n === 1) return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n const res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.ts/* \u9012\u5f52 */\nfunction recur(n: number): number {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n === 1) return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n const res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.dart/* \u9012\u5f52 */\nint recur(int n) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 1) return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n int res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.rs/* \u9012\u5f52 */\nfn recur(n: i32) -> i32 {\n // \u7ec8\u6b62\u6761\u4ef6\n if n == 1 {\n return 1;\n }\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n let res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n n + res\n}\n
recursion.c/* \u9012\u5f52 */\nint recur(int n) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 1)\n return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n int res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.zig// \u9012\u5f52\u51fd\u6570\nfn recur(n: i32) i32 {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 1) {\n return 1;\n }\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n var res: i32 = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
The Figure 2-3 shows the recursion of the function.
Figure 2-3 \u00a0 Recursion process for the summation function
Although iteration and recursion can yield the same results from a computational point of view, they represent two completely different paradigms for thinking about and solving problems.
As an example of the above summation function, set the problem \\(f(n) = 1 + 2 + \\dots + n\\) .
Each time a recursion function calls itself, the system allocates memory for the newly opened function to store local variables, call addresses, other information, and so on. This results in two things.
As shown in the Figure 2-4 , before the termination condition is triggered, there are \\(n\\) unreturned recursion functions at the same time, with a recursion depth of \\(n\\) .
Figure 2-4 \u00a0 Recursion call depth
In practice, the depth of recursion allowed by a programming language is usually limited, and too deep a recursion may result in a stack overflow error.
"},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-tail-recursion","title":"2. \u00a0 Tail Recursion","text":"Interestingly, if a function makes a recursion call only at the last step before returning, the function can be optimized by the compiler or interpreter to be comparable to iteration in terms of space efficiency. This situation is called \"tail recursion tail recursion\".
In the case of calculating \\(1 + 2 + \\dots + n\\), for example, we can implement tail recursion by setting the result variable res
as a function parameter.
def tail_recur(n, res):\n \"\"\"\u5c3e\u9012\u5f52\"\"\"\n # \u7ec8\u6b62\u6761\u4ef6\n if n == 0:\n return res\n # \u5c3e\u9012\u5f52\u8c03\u7528\n return tail_recur(n - 1, res + n)\n
recursion.cpp/* \u5c3e\u9012\u5f52 */\nint tailRecur(int n, int res) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 0)\n return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
recursion.java/* \u5c3e\u9012\u5f52 */\nint tailRecur(int n, int res) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 0)\n return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
recursion.cs/* \u5c3e\u9012\u5f52 */\nint TailRecur(int n, int res) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 0)\n return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return TailRecur(n - 1, res + n);\n}\n
recursion.go/* \u5c3e\u9012\u5f52 */\nfunc tailRecur(n int, res int) int {\n // \u7ec8\u6b62\u6761\u4ef6\n if n == 0 {\n return res\n }\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n-1, res+n)\n}\n
recursion.swift/* \u5c3e\u9012\u5f52 */\nfunc tailRecur(n: Int, res: Int) -> Int {\n // \u7ec8\u6b62\u6761\u4ef6\n if n == 0 {\n return res\n }\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n: n - 1, res: res + n)\n}\n
recursion.js/* \u5c3e\u9012\u5f52 */\nfunction tailRecur(n, res) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n === 0) return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
recursion.ts/* \u5c3e\u9012\u5f52 */\nfunction tailRecur(n: number, res: number): number {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n === 0) return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
recursion.dart/* \u5c3e\u9012\u5f52 */\nint tailRecur(int n, int res) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 0) return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
recursion.rs/* \u5c3e\u9012\u5f52 */\nfn tail_recur(n: i32, res: i32) -> i32 {\n // \u7ec8\u6b62\u6761\u4ef6\n if n == 0 {\n return res;\n }\n // \u5c3e\u9012\u5f52\u8c03\u7528\n tail_recur(n - 1, res + n)\n}\n
recursion.c/* \u5c3e\u9012\u5f52 */\nint tailRecur(int n, int res) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 0)\n return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
recursion.zig// \u5c3e\u9012\u5f52\u51fd\u6570\nfn tailRecur(n: i32, res: i32) i32 {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 0) {\n return res;\n }\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
The execution of tail recursion is shown in the Figure 2-5 . Comparing normal recursion and tail recursion, the execution point of the summation operation is different.
Figure 2-5 \u00a0 tail recursion process
Tip
Note that many compilers or interpreters do not support tail recursion optimization. For example, Python does not support tail recursion optimization by default, so even if a function is tail recursive, you may still encounter stack overflow problems.
"},{"location":"chapter_computational_complexity/iteration_and_recursion/#3-recursion-tree","title":"3. \u00a0 Recursion Tree","text":"When dealing with algorithmic problems related to divide and conquer, recursion is often more intuitive and easier to read than iteration. Take the Fibonacci sequence as an example.
Question
Given a Fibonacci series \\(0, 1, 1, 2, 3, 5, 8, 13, \\dots\\) , find the \\(n\\)th number of the series.
Let the \\(n\\)th number of the Fibonacci series be \\(f(n)\\) , which leads to two easy conclusions.
Recursion code can be written by making recursion calls according to the recursion relationship, using the first two numbers as termination conditions. Call fib(n)
to get the \\(n\\)th number of the Fibonacci series.
def fib(n: int) -> int:\n \"\"\"\u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52\"\"\"\n # \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if n == 1 or n == 2:\n return n - 1\n # \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n res = fib(n - 1) + fib(n - 2)\n # \u8fd4\u56de\u7ed3\u679c f(n)\n return res\n
recursion.cpp/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nint fib(int n) {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n int res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.java/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nint fib(int n) {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n int res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.cs/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nint Fib(int n) {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n int res = Fib(n - 1) + Fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.go/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nfunc fib(n int) int {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if n == 1 || n == 2 {\n return n - 1\n }\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n res := fib(n-1) + fib(n-2)\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res\n}\n
recursion.swift/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nfunc fib(n: Int) -> Int {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if n == 1 || n == 2 {\n return n - 1\n }\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n let res = fib(n: n - 1) + fib(n: n - 2)\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res\n}\n
recursion.js/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nfunction fib(n) {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n === 1 || n === 2) return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n const res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.ts/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nfunction fib(n: number): number {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n === 1 || n === 2) return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n const res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.dart/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nint fib(int n) {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2) return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n int res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.rs/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nfn fib(n: i32) -> i32 {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if n == 1 || n == 2 {\n return n - 1;\n }\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n let res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c\n res\n}\n
recursion.c/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nint fib(int n) {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n int res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.zig// \u6590\u6ce2\u90a3\u5951\u6570\u5217\nfn fib(n: i32) i32 {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n == 1 or n == 2) {\n return n - 1;\n }\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n var res: i32 = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
Looking at the above code, we have recursively called two functions within a function, this means that from one call, two call branches are created. As shown in the Figure 2-6 , this recursion will result in a recursion tree with the number of levels \\(n\\).
Figure 2-6 \u00a0 Recursion tree for Fibonacci series
Essentially, recursion embodies the paradigm of \"breaking down a problem into smaller sub-problems\", and this divide and conquer strategy is essential.
To summarize the above, as shown in the Table 2-1 , iteration and recursion differ in implementation, performance and applicability.
Table 2-1 \u00a0 Comparison of iteration and recursion features
iteration recursion implementation circular structure function call itself time-efficient typically efficient, no function call overhead overhead on every function call Memory Usage Usually uses a fixed size of memory space Cumulative function calls may use a lot of stack frame space Applicable Problems For simple cyclic tasks, code is intuitive and readable For sub-problem decomposition, such as trees, graphs, divide and conquer, backtracking, etc., the code structure is concise and clearTip
If you find the following solutions difficult to understand, you can review them after reading the \"Stack\" chapter.
So what is the intrinsic connection between iteration and recursion? In the case of the recursive function described above, the summing operation takes place in the \"return\" phase of the recursion. This means that the function that is initially called is actually the last to complete its summing operation, This mechanism works in the same way as the stack's \"first in, last out\" principle.
In fact, recursion terms like \"call stack\" and \"stack frame space\" already imply a close relationship between recursion and the stack.
Thus, we can use an explicit stack to model the behavior of the call stack, thus transforming recursion into an iteration form:
PythonC++JavaC#GoSwiftJSTSDartRustCZig recursion.pydef for_loop_recur(n: int) -> int:\n \"\"\"\u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52\"\"\"\n # \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n stack = []\n res = 0\n # \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for i in range(n, 0, -1):\n # \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.append(i)\n # \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while stack:\n # \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.pop()\n # res = 1+2+3+...+n\n return res\n
recursion.cpp/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nint forLoopRecur(int n) {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n stack<int> stack;\n int res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (int i = n; i > 0; i--) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.push(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (!stack.empty()) {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.top();\n stack.pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.java/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nint forLoopRecur(int n) {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n Stack<Integer> stack = new Stack<>();\n int res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (int i = n; i > 0; i--) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.push(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (!stack.isEmpty()) {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.cs/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nint ForLoopRecur(int n) {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n Stack<int> stack = new();\n int res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (int i = n; i > 0; i--) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.Push(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (stack.Count > 0) {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.Pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.go/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nfunc forLoopRecur(n int) int {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n stack := list.New()\n res := 0\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for i := n; i > 0; i-- {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.PushBack(i)\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n for stack.Len() != 0 {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.Back().Value.(int)\n stack.Remove(stack.Back())\n }\n // res = 1+2+3+...+n\n return res\n}\n
recursion.swift/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nfunc forLoopRecur(n: Int) -> Int {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n var stack: [Int] = []\n var res = 0\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for i in stride(from: n, to: 0, by: -1) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.append(i)\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while !stack.isEmpty {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.removeLast()\n }\n // res = 1+2+3+...+n\n return res\n}\n
recursion.js/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nfunction forLoopRecur(n) {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n const stack = [];\n let res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (let i = 1; i <= n; i++) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.push(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (stack.length) { \n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.ts/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nfunction forLoopRecur(n: number): number {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808 \n const stack: number[] = [];\n let res: number = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (let i = 1; i <= n; i++) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.push(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (stack.length) { \n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.dart/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nint forLoopRecur(int n) {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n List<int> stack = [];\n int res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (int i = n; i > 0; i--) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.add(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (!stack.isEmpty) {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.removeLast();\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.rs/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nfn for_loop_recur(n: i32) -> i32 {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n let mut stack = Vec::new();\n let mut res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for i in (1..=n).rev() {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.push(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while !stack.is_empty() {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.pop().unwrap();\n }\n // res = 1+2+3+...+n\n res\n}\n
recursion.c/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nint forLoopRecur(int n) {\n int stack[1000]; // \u501f\u52a9\u4e00\u4e2a\u5927\u6570\u7ec4\u6765\u6a21\u62df\u6808\n int top = -1; // \u6808\u9876\u7d22\u5f15\n int res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (int i = n; i > 0; i--) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack[1 + top++] = i;\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (top >= 0) {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack[top--];\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.zig// \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52\nfn forLoopRecur(comptime n: i32) i32 {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n var stack: [n]i32 = undefined;\n var res: i32 = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n var i: usize = n;\n while (i > 0) {\n stack[i - 1] = @intCast(i);\n i -= 1;\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n var index: usize = n;\n while (index > 0) {\n index -= 1;\n res += stack[index];\n }\n // res = 1+2+3+...+n\n return res;\n}\n
Observing the code above, it becomes more complex when recursion is converted to iteration. Although iteration and recursion can be converted to each other in many cases, it is not always worth doing so for two reasons.
In short, the choice of iteration or recursion depends on the nature of the particular problem. In programming practice, it is crucial to weigh the advantages and disadvantages of both and choose the appropriate method based on the context.
"},{"location":"chapter_computational_complexity/performance_evaluation/","title":"2.1 \u00a0 Evaluation Of Algorithm Efficiency","text":"In algorithm design, we aim to achieve two goals in succession:
In other words, once the ability to solve the problem is established, the efficiency of the algorithm emerges as the main benchmark for assessing its quality, which includes the following two aspects.
In short, our goal is to design data structures and algorithms that are both \"fast and economical\". Effectively evaluating algorithm efficiency is crucial, as it allows for the comparison of different algorithms and guides the design and optimization process.
There are mainly two approaches for assessing efficiency: practical testing and theoretical estimation.
"},{"location":"chapter_computational_complexity/performance_evaluation/#211-practical-testing","title":"2.1.1 \u00a0 Practical Testing","text":"Let's consider a scenario where we have two algorithms, A
and B
, both capable of solving the same problem. To compare their efficiency, the most direct method is to use a computer to run both algorithms while monitoring and recording their execution time and memory usage. This approach provides a realistic assessment of their performance, but it also has significant limitations.
On one hand, it's challenging to eliminate the interference of the test environment. Hardware configurations can significantly affect the performance of algorithms. For instance, on one computer, Algorithm A
might run faster than Algorithm B
, but the results could be the opposite on another computer with different specifications. This means we would need to conduct tests on a variety of machines and calculate an average efficiency, which is impractical.
Furthermore, conducting comprehensive tests is resource-intensive. The efficiency of algorithms can vary with different volumes of input data. For example, with smaller data sets, Algorithm A might run faster than Algorithm B; however, this could change with larger data sets. Therefore, to reach a convincing conclusion, it's necessary to test a range of data sizes, which requires excessive computational resources.
"},{"location":"chapter_computational_complexity/performance_evaluation/#212-theoretical-estimation","title":"2.1.2 \u00a0 Theoretical Estimation","text":"Given the significant limitations of practical testing, we can consider assessing algorithm efficiency solely through calculations. This method of estimation is known as 'asymptotic complexity analysis,' often simply referred to as 'complexity analysis.
Complexity analysis illustrates the relationship between the time (and space) resources required by an algorithm and the size of its input data. It describes the growing trend in the time and space required for the execution of an algorithm as the size of the input data increases. This definition might sound a bit complex, so let's break it down into three key points for easier understanding.
Complexity analysis overcomes the drawbacks of practical testing methods in two key ways:.
Tip
If you're still finding the concept of complexity confusing, don't worry. We will cover it in more detail in the subsequent chapters.
Complexity analysis provides us with a 'ruler' for evaluating the efficiency of algorithms, enabling us to measure the time and space resources required to execute a given algorithm and to compare the efficiency of different algorithms.
Complexity is a mathematical concept that might seem abstract and somewhat challenging for beginners. From this perspective, introducing complexity analysis at the very beginning may not be the most suitable approach. However, when discussing the characteristics of a particular data structure or algorithm, analyzing its operational speed and space usage is often inevitable.
Therefore, it is recommended that before diving deeply into data structures and algorithms, one should first gain a basic understanding of complexity analysis. This foundational knowledge will facilitate the complexity analysis of simple algorithms.
"},{"location":"chapter_computational_complexity/space_complexity/","title":"2.4 \u00a0 Space Complexity","text":"The space complexity is used to measure the growth trend of memory consumption as the scale of data increases for an algorithm solution. This concept is analogous to time complexity by replacing \"runtime\" with \"memory space\".
"},{"location":"chapter_computational_complexity/space_complexity/#241-algorithmic-correlation-space","title":"2.4.1 \u00a0 Algorithmic Correlation Space","text":"The memory space used by algorithms during its execution include the following types.
In general, the \"Input Space\" is excluded from the statistics of space complexity.
The Temporary Space can be further divided into three parts.
When analyzing the space complexity of a piece of program, three parts are usually taken into account: Temporary Data, Stack Frame Space and Output Data.
Figure 2-15 \u00a0 Associated spaces used by the algorithm
PythonC++JavaC#GoSwiftJSTSDartRustCZigclass Node:\n \"\"\"Classes\"\"\"\"\n def __init__(self, x: int):\n self.val: int = x # node value\n self.next: Node | None = None # reference to the next node\n\ndef function() -> int:\n \"\"\"\"Functions\"\"\"\"\"\n # Perform certain operations...\n return 0\n\ndef algorithm(n) -> int: # input data\n A = 0 # temporary data (constant, usually in uppercase)\n b = 0 # temporary data (variable)\n node = Node(0) # temporary data (object)\n c = function() # Stack frame space (call function)\n return A + b + c # output data\n
/* Structures */\nstruct Node {\n int val;\n Node *next;\n Node(int x) : val(x), next(nullptr) {}\n};\n\n/* Functions */\nint func() {\n // Perform certain operations...\n return 0;\n}\n\nint algorithm(int n) { // input data\n const int a = 0; // temporary data (constant)\n int b = 0; // temporary data (variable)\n Node* node = new Node(0); // temporary data (object)\n int c = func(); // stack frame space (call function)\n return a + b + c; // output data\n}\n
/* Classes */\nclass Node {\n int val;\n Node next;\n Node(int x) { val = x; }\n}\n\n/* Functions */\nint function() {\n // Perform certain operations...\n return 0;\n}\n\nint algorithm(int n) { // input data\n final int a = 0; // temporary data (constant)\n int b = 0; // temporary data (variable)\n Node node = new Node(0); // temporary data (object)\n int c = function(); // stack frame space (call function)\n return a + b + c; // output data\n}\n
/* Classes */\nclass Node {\n int val;\n Node next;\n Node(int x) { val = x; }\n}\n\n/* Functions */\nint Function() {\n // Perform certain operations...\n return 0;\n}\n\nint Algorithm(int n) { // input data\n const int a = 0; // temporary data (constant)\n int b = 0; // temporary data (variable)\n Node node = new(0); // temporary data (object)\n int c = Function(); // stack frame space (call function)\n return a + b + c; // output data\n}\n
/* Structures */\ntype node struct {\n val int\n next *node\n}\n\n/* Create node structure */\nfunc newNode(val int) *node {\n return &node{val: val}\n}\n\n/* Functions */\nfunc function() int {\n // Perform certain operations...\n return 0\n}\n\nfunc algorithm(n int) int { // input data\n const a = 0 // temporary data (constant)\n b := 0 // temporary storage of data (variable)\n newNode(0) // temporary data (object)\n c := function() // stack frame space (call function)\n return a + b + c // output data\n}\n
/* Classes */\nclass Node {\n var val: Int\n var next: Node?\n\n init(x: Int) {\n val = x\n }\n}\n\n/* Functions */\nfunc function() -> Int {\n // Perform certain operations...\n return 0\n}\n\nfunc algorithm(n: Int) -> Int { // input data\n let a = 0 // temporary data (constant)\n var b = 0 // temporary data (variable)\n let node = Node(x: 0) // temporary data (object)\n let c = function() // stack frame space (call function)\n return a + b + c // output data\n}\n
/* Classes */\nclass Node {\n val;\n next;\n constructor(val) {\n this.val = val === undefined ? 0 : val; // node value\n this.next = null; // reference to the next node\n }\n}\n\n/* Functions */\nfunction constFunc() {\n // Perform certain operations\n return 0;\n}\n\nfunction algorithm(n) { // input data\n const a = 0; // temporary data (constant)\n let b = 0; // temporary data (variable)\n const node = new Node(0); // temporary data (object)\n const c = constFunc(); // Stack frame space (calling function)\n return a + b + c; // output data\n}\n
/* Classes */\nclass Node {\n val: number;\n next: Node | null;\n constructor(val?: number) {\n this.val = val === undefined ? 0 : val; // node value\n this.next = null; // reference to the next node\n }\n}\n\n/* Functions */\nfunction constFunc(): number {\n // Perform certain operations\n return 0;\n}\n\nfunction algorithm(n: number): number { // input data\n const a = 0; // temporary data (constant)\n let b = 0; // temporary data (variable)\n const node = new Node(0); // temporary data (object)\n const c = constFunc(); // Stack frame space (calling function)\n return a + b + c; // output data\n}\n
/* Classes */\nclass Node {\n int val;\n Node next;\n Node(this.val, [this.next]);\n}\n\n/* Functions */\nint function() {\n // Perform certain operations...\n return 0;\n}\n\nint algorithm(int n) { // input data\n const int a = 0; // temporary data (constant)\n int b = 0; // temporary data (variable)\n Node node = Node(0); // temporary data (object)\n int c = function(); // stack frame space (call function)\n return a + b + c; // output data\n}\n
use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Structures */\nstruct Node {\n val: i32,\n next: Option<Rc<RefCell<Node>>>,\n}\n\n/* Creating a Node structure */\nimpl Node {\n fn new(val: i32) -> Self {\n Self { val: val, next: None }\n }\n}\n\n/* Functions */\nfn function() -> i32 { \n // Perform certain operations...\n return 0;\n}\n\nfn algorithm(n: i32) -> i32 { // input data\n const a: i32 = 0; // temporary data (constant)\n let mut b = 0; // temporary data (variable)\n let node = Node::new(0); // temporary data (object)\n let c = function(); // stack frame space (call function)\n return a + b + c; // output data\n}\n
/* Functions */\nint func() {\n // Perform certain operations...\n return 0;\n}\n\nint algorithm(int n) { // input data\n const int a = 0; // temporary data (constant)\n int b = 0; // temporary data (variable)\n int c = func(); // stack frame space (call function)\n return a + b + c; // output data\n}\n
\n
"},{"location":"chapter_computational_complexity/space_complexity/#242-calculation-method","title":"2.4.2 \u00a0 Calculation Method","text":"The calculation method for space complexity is pretty similar to time complexity, with the only difference being that the focus shifts from \"operation count\" to \"space usage size\".
On top of that, unlike time complexity, we usually only focus on the worst-case space complexity. This is because memory space is a hard requirement, and we have to make sure that there is enough memory space reserved for all possibilities incurred by input data.
Looking at the following code, the \"worst\" in worst-case space complexity has two layers of meaning.
nums
occupies \\(O(n)\\) space; thus the worst-case space complexity is \\(O(n)\\).nums
is initialized, the program occupies \\(O(n)\\) space; thus the worst-case space complexity is \\(O(n)\\).def algorithm(n: int):\n a = 0 # O(1)\n b = [0] * 10000 # O(1)\n if n > 10:\n nums = [0] * n # O(n)\n
void algorithm(int n) {\n int a = 0; // O(1)\n vector<int> b(10000); // O(1)\n if (n > 10)\n vector<int> nums(n); // O(n)\n}\n
void algorithm(int n) {\n int a = 0; // O(1)\n int[] b = new int[10000]; // O(1)\n if (n > 10)\n int[] nums = new int[n]; // O(n)\n}\n
void Algorithm(int n) {\n int a = 0; // O(1)\n int[] b = new int[10000]; // O(1)\n if (n > 10) {\n int[] nums = new int[n]; // O(n)\n }\n}\n
func algorithm(n int) {\n a := 0 // O(1)\n b := make([]int, 10000) // O(1)\n var nums []int\n if n > 10 {\n nums := make([]int, n) // O(n)\n }\n fmt.Println(a, b, nums)\n}\n
func algorithm(n: Int) {\n let a = 0 // O(1)\n let b = Array(repeating: 0, count: 10000) // O(1)\n if n > 10 {\n let nums = Array(repeating: 0, count: n) // O(n)\n }\n}\n
function algorithm(n) {\n const a = 0; // O(1)\n const b = new Array(10000); // O(1)\n if (n > 10) {\n const nums = new Array(n); // O(n)\n }\n}\n
function algorithm(n: number): void {\n const a = 0; // O(1)\n const b = new Array(10000); // O(1)\n if (n > 10) {\n const nums = new Array(n); // O(n)\n }\n}\n
void algorithm(int n) {\n int a = 0; // O(1)\n List<int> b = List.filled(10000, 0); // O(1)\n if (n > 10) {\n List<int> nums = List.filled(n, 0); // O(n)\n }\n}\n
fn algorithm(n: i32) {\n let a = 0; // O(1)\n let b = [0; 10000]; // O(1)\n if n > 10 {\n let nums = vec![0; n as usize]; // O(n)\n }\n}\n
void algorithm(int n) {\n int a = 0; // O(1)\n int b[10000]; // O(1)\n if (n > 10)\n int nums[n] = {0}; // O(n)\n}\n
\n
In recursion functions, it is important to take into count the measurement of stack frame space. For example in the following code:
loop()
calls \\(n\\) times function()
in a loop, and each round of function()
returns and frees stack frame space, so the space complexity is still \\(O(1)\\).recur()
will have \\(n\\) unreturned recur()
during runtime, thus occupying \\(O(n)\\) of stack frame space.def function() -> int:\n # Perform certain operations\n return 0\n\ndef loop(n: int):\n \"\"\"Loop O(1)\"\"\"\"\"\n for _ in range(n):\n function()\n\ndef recur(n: int) -> int:\n \"\"\"Recursion O(n)\"\"\"\"\"\n if n == 1: return\n return recur(n - 1)\n
int func() {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nvoid loop(int n) {\n for (int i = 0; i < n; i++) {\n func();\n }\n}\n/* Recursion O(n) */\nvoid recur(int n) {\n if (n == 1) return;\n return recur(n - 1);\n}\n
int function() {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nvoid loop(int n) {\n for (int i = 0; i < n; i++) {\n function();\n }\n}\n/* Recursion O(n) */\nvoid recur(int n) {\n if (n == 1) return;\n return recur(n - 1);\n}\n
int Function() {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nvoid Loop(int n) {\n for (int i = 0; i < n; i++) {\n Function();\n }\n}\n/* Recursion O(n) */\nint Recur(int n) {\n if (n == 1) return 1;\n return Recur(n - 1);\n}\n
func function() int {\n // Perform certain operations\n return 0\n}\n\n/* Cycle O(1) */\nfunc loop(n int) {\n for i := 0; i < n; i++ {\n function()\n }\n}\n\n/* Recursion O(n) */\nfunc recur(n int) {\n if n == 1 {\n return\n }\n recur(n - 1)\n}\n
@discardableResult\nfunc function() -> Int {\n // Perform certain operations\n return 0\n}\n\n/* Cycle O(1) */\nfunc loop(n: Int) {\n for _ in 0 ..< n {\n function()\n }\n}\n\n/* Recursion O(n) */\nfunc recur(n: Int) {\n if n == 1 {\n return\n }\n recur(n: n - 1)\n}\n
function constFunc() {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nfunction loop(n) {\n for (let i = 0; i < n; i++) {\n constFunc();\n }\n}\n/* Recursion O(n) */\nfunction recur(n) {\n if (n === 1) return;\n return recur(n - 1);\n}\n
function constFunc(): number {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nfunction loop(n: number): void {\n for (let i = 0; i < n; i++) {\n constFunc();\n }\n}\n/* Recursion O(n) */\nfunction recur(n: number): void {\n if (n === 1) return;\n return recur(n - 1);\n}\n
int function() {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nvoid loop(int n) {\n for (int i = 0; i < n; i++) {\n function();\n }\n}\n/* Recursion O(n) */\nvoid recur(int n) {\n if (n == 1) return;\n return recur(n - 1);\n}\n
fn function() -> i32 {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nfn loop(n: i32) {\n for i in 0..n {\n function();\n }\n}\n/* Recursion O(n) */\nvoid recur(n: i32) {\n if n == 1 {\n return;\n }\n recur(n - 1);\n}\n
int func() {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nvoid loop(int n) {\n for (int i = 0; i < n; i++) {\n func();\n }\n}\n/* Recursion O(n) */\nvoid recur(int n) {\n if (n == 1) return;\n return recur(n - 1);\n}\n
\n
"},{"location":"chapter_computational_complexity/space_complexity/#243-common-types","title":"2.4.3 \u00a0 Common Types","text":"Assuming the input data size is \\(n\\), the figure illustrates common types of space complexity (ordered from low to high).
\\[ \\begin{aligned} O(1) < O(\\log n) < O(n) < O(n^2) < O(2^n) \\newline \\text{constant order} < \\text{logarithmic order} < \\text{linear order} < \\text{square order} < \\text{exponential order} \\end{aligned} \\]Figure 2-16 \u00a0 Common space complexity types
"},{"location":"chapter_computational_complexity/space_complexity/#1-constant-order-o1","title":"1. \u00a0 Constant Order \\(O(1)\\)","text":"Constant order is common for constants, variables, and objects whose quantity is unrelated to the size of the input data \\(n\\).
It is important to note that memory occupied by initializing a variable or calling a function in a loop is released once the next iteration begins. Therefore, there is no accumulation of occupied space and the space complexity remains \\(O(1)\\) :
PythonC++JavaC#GoSwiftJSTSDartRustCZig space_complexity.pydef function() -> int:\n \"\"\"\u51fd\u6570\"\"\"\n # \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0\n\ndef constant(n: int):\n \"\"\"\u5e38\u6570\u9636\"\"\"\n # \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n a = 0\n nums = [0] * 10000\n node = ListNode(0)\n # \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for _ in range(n):\n c = 0\n # \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for _ in range(n):\n function()\n
space_complexity.cpp/* \u51fd\u6570 */\nint func() {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nvoid constant(int n) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const int a = 0;\n int b = 0;\n vector<int> nums(10000);\n ListNode node(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n int c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n func();\n }\n}\n
space_complexity.java/* \u51fd\u6570 */\nint function() {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nvoid constant(int n) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n final int a = 0;\n int b = 0;\n int[] nums = new int[10000];\n ListNode node = new ListNode(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n int c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n function();\n }\n}\n
space_complexity.cs/* \u51fd\u6570 */\nint Function() {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nvoid Constant(int n) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n int a = 0;\n int b = 0;\n int[] nums = new int[10000];\n ListNode node = new(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n int c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n Function();\n }\n}\n
space_complexity.go/* \u51fd\u6570 */\nfunc function() int {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c...\n return 0\n}\n\n/* \u5e38\u6570\u9636 */\nfunc spaceConstant(n int) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const a = 0\n b := 0\n nums := make([]int, 10000)\n ListNode := newNode(0)\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n var c int\n for i := 0; i < n; i++ {\n c = 0\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for i := 0; i < n; i++ {\n function()\n }\n fmt.Println(a, b, nums, c, ListNode)\n}\n
space_complexity.swift/* \u51fd\u6570 */\n@discardableResult\nfunc function() -> Int {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0\n}\n\n/* \u5e38\u6570\u9636 */\nfunc constant(n: Int) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n let a = 0\n var b = 0\n let nums = Array(repeating: 0, count: 10000)\n let node = ListNode(x: 0)\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for _ in 0 ..< n {\n let c = 0\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for _ in 0 ..< n {\n function()\n }\n}\n
space_complexity.js/* \u51fd\u6570 */\nfunction constFunc() {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nfunction constant(n) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const a = 0;\n const b = 0;\n const nums = new Array(10000);\n const node = new ListNode(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (let i = 0; i < n; i++) {\n const c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (let i = 0; i < n; i++) {\n constFunc();\n }\n}\n
space_complexity.ts/* \u51fd\u6570 */\nfunction constFunc(): number {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nfunction constant(n: number): void {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const a = 0;\n const b = 0;\n const nums = new Array(10000);\n const node = new ListNode(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (let i = 0; i < n; i++) {\n const c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (let i = 0; i < n; i++) {\n constFunc();\n }\n}\n
space_complexity.dart/* \u51fd\u6570 */\nint function() {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nvoid constant(int n) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n final int a = 0;\n int b = 0;\n List<int> nums = List.filled(10000, 0);\n ListNode node = ListNode(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (var i = 0; i < n; i++) {\n int c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (var i = 0; i < n; i++) {\n function();\n }\n}\n
space_complexity.rs/* \u51fd\u6570 */\nfn function() ->i32 {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\n#[allow(unused)]\nfn constant(n: i32) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const A: i32 = 0;\n let b = 0;\n let nums = vec![0; 10000];\n let node = ListNode::new(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for i in 0..n {\n let c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for i in 0..n {\n function();\n }\n}\n
space_complexity.c/* \u51fd\u6570 */\nint func() {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nvoid constant(int n) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const int a = 0;\n int b = 0;\n int nums[1000];\n ListNode *node = newListNode(0);\n free(node);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n int c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n func();\n }\n}\n
space_complexity.zig// \u51fd\u6570\nfn function() i32 {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n// \u5e38\u6570\u9636\nfn constant(n: i32) void {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const a: i32 = 0;\n var b: i32 = 0;\n var nums = [_]i32{0}**10000;\n var node = inc.ListNode(i32){.val = 0};\n var i: i32 = 0;\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n while (i < n) : (i += 1) {\n var c: i32 = 0;\n _ = c;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n i = 0;\n while (i < n) : (i += 1) {\n _ = function();\n }\n _ = a;\n _ = b;\n _ = nums;\n _ = node;\n}\n
"},{"location":"chapter_computational_complexity/space_complexity/#2-linear-order-on","title":"2. \u00a0 Linear Order \\(O(N)\\)","text":"Linear order is commonly found in arrays, linked lists, stacks, queues, and similar structures where the number of elements is proportional to \\(n\\):
PythonC++JavaC#GoSwiftJSTSDartRustCZig space_complexity.pydef linear(n: int):\n \"\"\"\u7ebf\u6027\u9636\"\"\"\n # \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n nums = [0] * n\n # \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n hmap = dict[int, str]()\n for i in range(n):\n hmap[i] = str(i)\n
space_complexity.cpp/* \u7ebf\u6027\u9636 */\nvoid linear(int n) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n vector<int> nums(n);\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n vector<ListNode> nodes;\n for (int i = 0; i < n; i++) {\n nodes.push_back(ListNode(i));\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n unordered_map<int, string> map;\n for (int i = 0; i < n; i++) {\n map[i] = to_string(i);\n }\n}\n
space_complexity.java/* \u7ebf\u6027\u9636 */\nvoid linear(int n) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n int[] nums = new int[n];\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n List<ListNode> nodes = new ArrayList<>();\n for (int i = 0; i < n; i++) {\n nodes.add(new ListNode(i));\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n Map<Integer, String> map = new HashMap<>();\n for (int i = 0; i < n; i++) {\n map.put(i, String.valueOf(i));\n }\n}\n
space_complexity.cs/* \u7ebf\u6027\u9636 */\nvoid Linear(int n) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n int[] nums = new int[n];\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n List<ListNode> nodes = [];\n for (int i = 0; i < n; i++) {\n nodes.Add(new ListNode(i));\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n Dictionary<int, string> map = [];\n for (int i = 0; i < n; i++) {\n map.Add(i, i.ToString());\n }\n}\n
space_complexity.go/* \u7ebf\u6027\u9636 */\nfunc spaceLinear(n int) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n _ = make([]int, n)\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n var nodes []*node\n for i := 0; i < n; i++ {\n nodes = append(nodes, newNode(i))\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n m := make(map[int]string, n)\n for i := 0; i < n; i++ {\n m[i] = strconv.Itoa(i)\n }\n}\n
space_complexity.swift/* \u7ebf\u6027\u9636 */\nfunc linear(n: Int) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n let nums = Array(repeating: 0, count: n)\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n let nodes = (0 ..< n).map { ListNode(x: $0) }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, \"\\($0)\") })\n}\n
space_complexity.js/* \u7ebf\u6027\u9636 */\nfunction linear(n) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n const nums = new Array(n);\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n const nodes = [];\n for (let i = 0; i < n; i++) {\n nodes.push(new ListNode(i));\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n const map = new Map();\n for (let i = 0; i < n; i++) {\n map.set(i, i.toString());\n }\n}\n
space_complexity.ts/* \u7ebf\u6027\u9636 */\nfunction linear(n: number): void {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n const nums = new Array(n);\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n const nodes: ListNode[] = [];\n for (let i = 0; i < n; i++) {\n nodes.push(new ListNode(i));\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n const map = new Map();\n for (let i = 0; i < n; i++) {\n map.set(i, i.toString());\n }\n}\n
space_complexity.dart/* \u7ebf\u6027\u9636 */\nvoid linear(int n) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n List<int> nums = List.filled(n, 0);\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n List<ListNode> nodes = [];\n for (var i = 0; i < n; i++) {\n nodes.add(ListNode(i));\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n Map<int, String> map = HashMap();\n for (var i = 0; i < n; i++) {\n map.putIfAbsent(i, () => i.toString());\n }\n}\n
space_complexity.rs/* \u7ebf\u6027\u9636 */\n#[allow(unused)]\nfn linear(n: i32) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n let mut nums = vec![0; n as usize];\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n let mut nodes = Vec::new();\n for i in 0..n {\n nodes.push(ListNode::new(i))\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n let mut map = HashMap::new();\n for i in 0..n {\n map.insert(i, i.to_string());\n }\n}\n
space_complexity.c/* \u54c8\u5e0c\u8868 */\ntypedef struct {\n int key;\n int val;\n UT_hash_handle hh; // \u57fa\u4e8e uthash.h \u5b9e\u73b0\n} HashTable;\n\n/* \u7ebf\u6027\u9636 */\nvoid linear(int n) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n int *nums = malloc(sizeof(int) * n);\n free(nums);\n\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n ListNode **nodes = malloc(sizeof(ListNode *) * n);\n for (int i = 0; i < n; i++) {\n nodes[i] = newListNode(i);\n }\n // \u5185\u5b58\u91ca\u653e\n for (int i = 0; i < n; i++) {\n free(nodes[i]);\n }\n free(nodes);\n\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n HashTable *h = NULL;\n for (int i = 0; i < n; i++) {\n HashTable *tmp = malloc(sizeof(HashTable));\n tmp->key = i;\n tmp->val = i;\n HASH_ADD_INT(h, key, tmp);\n }\n\n // \u5185\u5b58\u91ca\u653e\n HashTable *curr, *tmp;\n HASH_ITER(hh, h, curr, tmp) {\n HASH_DEL(h, curr);\n free(curr);\n }\n}\n
space_complexity.zig// \u7ebf\u6027\u9636\nfn linear(comptime n: i32) !void {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n var nums = [_]i32{0}**n;\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n var nodes = std.ArrayList(i32).init(std.heap.page_allocator);\n defer nodes.deinit();\n var i: i32 = 0;\n while (i < n) : (i += 1) {\n try nodes.append(i);\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n var map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator);\n defer map.deinit();\n var j: i32 = 0;\n while (j < n) : (j += 1) {\n const string = try std.fmt.allocPrint(std.heap.page_allocator, \"{d}\", .{j});\n defer std.heap.page_allocator.free(string);\n try map.put(i, string);\n }\n _ = nums;\n}\n
As shown in the Figure 2-17 , the depth of recursion for this function is \\(n\\), which means that there are \\(n\\) unreturned linear_recur()
functions at the same time, using \\(O(n)\\) size stack frame space:
def linear_recur(n: int):\n \"\"\"\u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\n print(\"\u9012\u5f52 n =\", n)\n if n == 1:\n return\n linear_recur(n - 1)\n
space_complexity.cpp/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid linearRecur(int n) {\n cout << \"\u9012\u5f52 n = \" << n << endl;\n if (n == 1)\n return;\n linearRecur(n - 1);\n}\n
space_complexity.java/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid linearRecur(int n) {\n System.out.println(\"\u9012\u5f52 n = \" + n);\n if (n == 1)\n return;\n linearRecur(n - 1);\n}\n
space_complexity.cs/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid LinearRecur(int n) {\n Console.WriteLine(\"\u9012\u5f52 n = \" + n);\n if (n == 1) return;\n LinearRecur(n - 1);\n}\n
space_complexity.go/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc spaceLinearRecur(n int) {\n fmt.Println(\"\u9012\u5f52 n =\", n)\n if n == 1 {\n return\n }\n spaceLinearRecur(n - 1)\n}\n
space_complexity.swift/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc linearRecur(n: Int) {\n print(\"\u9012\u5f52 n = \\(n)\")\n if n == 1 {\n return\n }\n linearRecur(n: n - 1)\n}\n
space_complexity.js/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction linearRecur(n) {\n console.log(`\u9012\u5f52 n = ${n}`);\n if (n === 1) return;\n linearRecur(n - 1);\n}\n
space_complexity.ts/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction linearRecur(n: number): void {\n console.log(`\u9012\u5f52 n = ${n}`);\n if (n === 1) return;\n linearRecur(n - 1);\n}\n
space_complexity.dart/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid linearRecur(int n) {\n print('\u9012\u5f52 n = $n');\n if (n == 1) return;\n linearRecur(n - 1);\n}\n
space_complexity.rs/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfn linear_recur(n: i32) {\n println!(\"\u9012\u5f52 n = {}\", n);\n if n == 1 {return};\n linear_recur(n - 1);\n}\n
space_complexity.c/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid linearRecur(int n) {\n printf(\"\u9012\u5f52 n = %d\\r\\n\", n);\n if (n == 1)\n return;\n linearRecur(n - 1);\n}\n
space_complexity.zig// \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn linearRecur(comptime n: i32) void {\n std.debug.print(\"\u9012\u5f52 n = {}\\n\", .{n});\n if (n == 1) return;\n linearRecur(n - 1);\n}\n
Figure 2-17 \u00a0 Linear order space complexity generated by recursion function
"},{"location":"chapter_computational_complexity/space_complexity/#3-quadratic-order-on2","title":"3. \u00a0 Quadratic Order \\(O(N^2)\\)","text":"Quadratic order is common in matrices and graphs, where the number of elements is in a square relationship with \\(n\\):
PythonC++JavaC#GoSwiftJSTSDartRustCZig space_complexity.pydef quadratic(n: int):\n \"\"\"\u5e73\u65b9\u9636\"\"\"\n # \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n num_matrix = [[0] * n for _ in range(n)]\n
space_complexity.cpp/* \u5e73\u65b9\u9636 */\nvoid quadratic(int n) {\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n vector<vector<int>> numMatrix;\n for (int i = 0; i < n; i++) {\n vector<int> tmp;\n for (int j = 0; j < n; j++) {\n tmp.push_back(0);\n }\n numMatrix.push_back(tmp);\n }\n}\n
space_complexity.java/* \u5e73\u65b9\u9636 */\nvoid quadratic(int n) {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n int[][] numMatrix = new int[n][n];\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n List<List<Integer>> numList = new ArrayList<>();\n for (int i = 0; i < n; i++) {\n List<Integer> tmp = new ArrayList<>();\n for (int j = 0; j < n; j++) {\n tmp.add(0);\n }\n numList.add(tmp);\n }\n}\n
space_complexity.cs/* \u5e73\u65b9\u9636 */\nvoid Quadratic(int n) {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n int[,] numMatrix = new int[n, n];\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n List<List<int>> numList = [];\n for (int i = 0; i < n; i++) {\n List<int> tmp = [];\n for (int j = 0; j < n; j++) {\n tmp.Add(0);\n }\n numList.Add(tmp);\n }\n}\n
space_complexity.go/* \u5e73\u65b9\u9636 */\nfunc spaceQuadratic(n int) {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n numMatrix := make([][]int, n)\n for i := 0; i < n; i++ {\n numMatrix[i] = make([]int, n)\n }\n}\n
space_complexity.swift/* \u5e73\u65b9\u9636 */\nfunc quadratic(n: Int) {\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n let numList = Array(repeating: Array(repeating: 0, count: n), count: n)\n}\n
space_complexity.js/* \u5e73\u65b9\u9636 */\nfunction quadratic(n) {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n const numMatrix = Array(n)\n .fill(null)\n .map(() => Array(n).fill(null));\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n const numList = [];\n for (let i = 0; i < n; i++) {\n const tmp = [];\n for (let j = 0; j < n; j++) {\n tmp.push(0);\n }\n numList.push(tmp);\n }\n}\n
space_complexity.ts/* \u5e73\u65b9\u9636 */\nfunction quadratic(n: number): void {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n const numMatrix = Array(n)\n .fill(null)\n .map(() => Array(n).fill(null));\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n const numList = [];\n for (let i = 0; i < n; i++) {\n const tmp = [];\n for (let j = 0; j < n; j++) {\n tmp.push(0);\n }\n numList.push(tmp);\n }\n}\n
space_complexity.dart/* \u5e73\u65b9\u9636 */\nvoid quadratic(int n) {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n List<List<int>> numMatrix = List.generate(n, (_) => List.filled(n, 0));\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n List<List<int>> numList = [];\n for (var i = 0; i < n; i++) {\n List<int> tmp = [];\n for (int j = 0; j < n; j++) {\n tmp.add(0);\n }\n numList.add(tmp);\n }\n}\n
space_complexity.rs/* \u5e73\u65b9\u9636 */\n#[allow(unused)]\nfn quadratic(n: i32) {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n let num_matrix = vec![vec![0; n as usize]; n as usize];\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n let mut num_list = Vec::new();\n for i in 0..n {\n let mut tmp = Vec::new();\n for j in 0..n {\n tmp.push(0);\n }\n num_list.push(tmp);\n }\n}\n
space_complexity.c/* \u5e73\u65b9\u9636 */\nvoid quadratic(int n) {\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n int **numMatrix = malloc(sizeof(int *) * n);\n for (int i = 0; i < n; i++) {\n int *tmp = malloc(sizeof(int) * n);\n for (int j = 0; j < n; j++) {\n tmp[j] = 0;\n }\n numMatrix[i] = tmp;\n }\n\n // \u5185\u5b58\u91ca\u653e\n for (int i = 0; i < n; i++) {\n free(numMatrix[i]);\n }\n free(numMatrix);\n}\n
space_complexity.zig// \u5e73\u65b9\u9636\nfn quadratic(n: i32) !void {\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n var nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator);\n defer nodes.deinit();\n var i: i32 = 0;\n while (i < n) : (i += 1) {\n var tmp = std.ArrayList(i32).init(std.heap.page_allocator);\n defer tmp.deinit();\n var j: i32 = 0;\n while (j < n) : (j += 1) {\n try tmp.append(0);\n }\n try nodes.append(tmp);\n }\n}\n
As shown in the Figure 2-18 , the recursion depth of this function is \\(n\\), and an array is initialized in each recursion function with lengths \\(n\\), \\(n-1\\), \\(\\dots\\), \\(2\\), \\(1\\), and an average length of \\(n / 2\\), thus occupying \\(O(n^2)\\) space overall:
PythonC++JavaC#GoSwiftJSTSDartRustCZig space_complexity.pydef quadratic_recur(n: int) -> int:\n \"\"\"\u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\n if n <= 0:\n return 0\n # \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\n nums = [0] * n\n return quadratic_recur(n - 1)\n
space_complexity.cpp/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint quadraticRecur(int n) {\n if (n <= 0)\n return 0;\n vector<int> nums(n);\n cout << \"\u9012\u5f52 n = \" << n << \" \u4e2d\u7684 nums \u957f\u5ea6 = \" << nums.size() << endl;\n return quadraticRecur(n - 1);\n}\n
space_complexity.java/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint quadraticRecur(int n) {\n if (n <= 0)\n return 0;\n // \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\n int[] nums = new int[n];\n System.out.println(\"\u9012\u5f52 n = \" + n + \" \u4e2d\u7684 nums \u957f\u5ea6 = \" + nums.length);\n return quadraticRecur(n - 1);\n}\n
space_complexity.cs/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint QuadraticRecur(int n) {\n if (n <= 0) return 0;\n int[] nums = new int[n];\n Console.WriteLine(\"\u9012\u5f52 n = \" + n + \" \u4e2d\u7684 nums \u957f\u5ea6 = \" + nums.Length);\n return QuadraticRecur(n - 1);\n}\n
space_complexity.go/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc spaceQuadraticRecur(n int) int {\n if n <= 0 {\n return 0\n }\n nums := make([]int, n)\n fmt.Printf(\"\u9012\u5f52 n = %d \u4e2d\u7684 nums \u957f\u5ea6 = %d \\n\", n, len(nums))\n return spaceQuadraticRecur(n - 1)\n}\n
space_complexity.swift/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\n@discardableResult\nfunc quadraticRecur(n: Int) -> Int {\n if n <= 0 {\n return 0\n }\n // \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\n let nums = Array(repeating: 0, count: n)\n print(\"\u9012\u5f52 n = \\(n) \u4e2d\u7684 nums \u957f\u5ea6 = \\(nums.count)\")\n return quadraticRecur(n: n - 1)\n}\n
space_complexity.js/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction quadraticRecur(n) {\n if (n <= 0) return 0;\n const nums = new Array(n);\n console.log(`\u9012\u5f52 n = ${n} \u4e2d\u7684 nums \u957f\u5ea6 = ${nums.length}`);\n return quadraticRecur(n - 1);\n}\n
space_complexity.ts/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction quadraticRecur(n: number): number {\n if (n <= 0) return 0;\n const nums = new Array(n);\n console.log(`\u9012\u5f52 n = ${n} \u4e2d\u7684 nums \u957f\u5ea6 = ${nums.length}`);\n return quadraticRecur(n - 1);\n}\n
space_complexity.dart/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint quadraticRecur(int n) {\n if (n <= 0) return 0;\n List<int> nums = List.filled(n, 0);\n print('\u9012\u5f52 n = $n \u4e2d\u7684 nums \u957f\u5ea6 = ${nums.length}');\n return quadraticRecur(n - 1);\n}\n
space_complexity.rs/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfn quadratic_recur(n: i32) -> i32 {\n if n <= 0 {return 0};\n // \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\n let nums = vec![0; n as usize];\n println!(\"\u9012\u5f52 n = {} \u4e2d\u7684 nums \u957f\u5ea6 = {}\", n, nums.len());\n return quadratic_recur(n - 1);\n}\n
space_complexity.c/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint quadraticRecur(int n) {\n if (n <= 0)\n return 0;\n int *nums = malloc(sizeof(int) * n);\n printf(\"\u9012\u5f52 n = %d \u4e2d\u7684 nums \u957f\u5ea6 = %d\\r\\n\", n, n);\n int res = quadraticRecur(n - 1);\n free(nums);\n return res;\n}\n
space_complexity.zig// \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn quadraticRecur(comptime n: i32) i32 {\n if (n <= 0) return 0;\n var nums = [_]i32{0}**n;\n std.debug.print(\"\u9012\u5f52 n = {} \u4e2d\u7684 nums \u957f\u5ea6 = {}\\n\", .{n, nums.len});\n return quadraticRecur(n - 1);\n}\n
Figure 2-18 \u00a0 Square-order space complexity generated by the recursion function
"},{"location":"chapter_computational_complexity/space_complexity/#4-exponential-order-o2n","title":"4. \u00a0 Exponential Order \\(O(2^N)\\)","text":"Exponential order is common in binary trees. Looking at the Figure 2-19 , a \"full binary tree\" of degree \\(n\\) has \\(2^n - 1\\) nodes, occupying \\(O(2^n)\\) space:
PythonC++JavaC#GoSwiftJSTSDartRustCZig space_complexity.pydef build_tree(n: int) -> TreeNode | None:\n \"\"\"\u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09\"\"\"\n if n == 0:\n return None\n root = TreeNode(0)\n root.left = build_tree(n - 1)\n root.right = build_tree(n - 1)\n return root\n
space_complexity.cpp/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode *buildTree(int n) {\n if (n == 0)\n return nullptr;\n TreeNode *root = new TreeNode(0);\n root->left = buildTree(n - 1);\n root->right = buildTree(n - 1);\n return root;\n}\n
space_complexity.java/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode buildTree(int n) {\n if (n == 0)\n return null;\n TreeNode root = new TreeNode(0);\n root.left = buildTree(n - 1);\n root.right = buildTree(n - 1);\n return root;\n}\n
space_complexity.cs/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode? BuildTree(int n) {\n if (n == 0) return null;\n TreeNode root = new(0) {\n left = BuildTree(n - 1),\n right = BuildTree(n - 1)\n };\n return root;\n}\n
space_complexity.go/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfunc buildTree(n int) *treeNode {\n if n == 0 {\n return nil\n }\n root := newTreeNode(0)\n root.left = buildTree(n - 1)\n root.right = buildTree(n - 1)\n return root\n}\n
space_complexity.swift/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfunc buildTree(n: Int) -> TreeNode? {\n if n == 0 {\n return nil\n }\n let root = TreeNode(x: 0)\n root.left = buildTree(n: n - 1)\n root.right = buildTree(n: n - 1)\n return root\n}\n
space_complexity.js/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfunction buildTree(n) {\n if (n === 0) return null;\n const root = new TreeNode(0);\n root.left = buildTree(n - 1);\n root.right = buildTree(n - 1);\n return root;\n}\n
space_complexity.ts/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfunction buildTree(n: number): TreeNode | null {\n if (n === 0) return null;\n const root = new TreeNode(0);\n root.left = buildTree(n - 1);\n root.right = buildTree(n - 1);\n return root;\n}\n
space_complexity.dart/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode? buildTree(int n) {\n if (n == 0) return null;\n TreeNode root = TreeNode(0);\n root.left = buildTree(n - 1);\n root.right = buildTree(n - 1);\n return root;\n}\n
space_complexity.rs/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfn build_tree(n: i32) -> Option<Rc<RefCell<TreeNode>>> {\n if n == 0 {return None};\n let root = TreeNode::new(0);\n root.borrow_mut().left = build_tree(n - 1);\n root.borrow_mut().right = build_tree(n - 1);\n return Some(root);\n}\n
space_complexity.c/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode *buildTree(int n) {\n if (n == 0)\n return NULL;\n TreeNode *root = newTreeNode(0);\n root->left = buildTree(n - 1);\n root->right = buildTree(n - 1);\n return root;\n}\n
space_complexity.zig// \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09\nfn buildTree(mem_allocator: std.mem.Allocator, n: i32) !?*inc.TreeNode(i32) {\n if (n == 0) return null;\n const root = try mem_allocator.create(inc.TreeNode(i32));\n root.init(0);\n root.left = try buildTree(mem_allocator, n - 1);\n root.right = try buildTree(mem_allocator, n - 1);\n return root;\n}\n
Figure 2-19 \u00a0 Exponential order space complexity generated by a full binary tree
"},{"location":"chapter_computational_complexity/space_complexity/#5-logarithmic-order-olog-n","title":"5. \u00a0 Logarithmic Order \\(O(\\Log N)\\)","text":"Logarithmic order is commonly used in divide and conquer algorithms. For example, in a merge sort, given an array of length \\(n\\) as the input, each round of recursion divides the array in half from its midpoint to form a recursion tree of height \\(\\log n\\), using \\(O(\\log n)\\) stack frame space.
Another example is to convert a number into a string. Given a positive integer \\(n\\) with a digit count of \\(\\log_{10} n + 1\\), the corresponding string length is \\(\\log_{10} n + 1\\). Therefore, the space complexity is \\(O(\\log_{10} n + 1) = O(\\log n)\\).
"},{"location":"chapter_computational_complexity/space_complexity/#244-weighing-time-and-space","title":"2.4.4 \u00a0 Weighing Time And Space","text":"Ideally, we would like to optimize both the time complexity and the space complexity of an algorithm. However, in reality, simultaneously optimizing time and space complexity is often challenging.
Reducing time complexity usually comes at the expense of increasing space complexity, and vice versa. The approach of sacrificing memory space to improve algorithm speed is known as \"trading space for time\", while the opposite is called \"trading time for space\".
The choice between these approaches depends on which aspect we prioritize. In most cases, time is more valuable than space, so \"trading space for time\" is usually the more common strategy. Of course, in situations with large data volumes, controlling space complexity is also crucial.
"},{"location":"chapter_computational_complexity/summary/","title":"2.5 \u00a0 Summary","text":""},{"location":"chapter_computational_complexity/summary/#1-highlights","title":"1. \u00a0 Highlights","text":"Evaluation of Algorithm Efficiency
Time Complexity
Space Complexity
Is the space complexity of tail recursion \\(O(1)\\)?
Theoretically, the space complexity of a tail recursion function can be optimized to \\(O(1)\\). However, most programming languages (e.g., Java, Python, C++, Go, C#, etc.) do not support auto-optimization for tail recursion, so the space complexity is usually considered as \\(O(n)\\).
What is the difference between the terms function and method?
A function can be executed independently, and all arguments are passed explicitly. A method is associated with an object and is implicitly passed to the object that calls it, allowing it to operate on the data contained within an instance of a class.
Let's illustrate with a few common programming languages.
Does the figure \"Common Types of Space Complexity\" reflect the absolute size of the occupied space?
No, that figure shows the space complexity, which reflects the growth trend, not the absolute size of the space occupied.
For example, if you take \\(n = 8\\) , the values of each curve do not align with the function because each curve contains a constant term used to compress the range of values to a visually comfortable range.
In practice, since we usually don't know each method's \"constant term\" complexity, it is generally impossible to choose the optimal solution for \\(n = 8\\) based on complexity alone. But it's easier to choose for \\(n = 8^5\\) as the growth trend is already dominant.
"},{"location":"chapter_computational_complexity/time_complexity/","title":"2.3 \u00a0 Time Complexity","text":"Runtime can be a visual and accurate reflection of the efficiency of an algorithm. What should we do if we want to accurately predict the runtime of a piece of code?
+
takes 1 ns, the multiplication operation *
takes 10 ns, the print operation print()
takes 5 ns, and so on.For example, in the following code, the input data size is \\(n\\) :
PythonC++JavaC#GoSwiftJSTSDartRustCZig# Under an operating platform\ndef algorithm(n: int):\n a = 2 # 1 ns\n a = a + 1 # 1 ns\n a = a * 2 # 10 ns\n # Cycle n times\n for _ in range(n): # 1 ns\n print(0) # 5 ns\n
// Under a particular operating platform\nvoid algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n cout << 0 << endl; // 5 ns\n }\n}\n
// Under a particular operating platform\nvoid algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n System.out.println(0); // 5 ns\n }\n}\n
// Under a particular operating platform\nvoid Algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n Console.WriteLine(0); // 5 ns\n }\n}\n
// Under a particular operating platform\nfunc algorithm(n int) {\n a := 2 // 1 ns\n a = a + 1 // 1 ns\n a = a * 2 // 10 ns\n // Loop n times\n for i := 0; i < n; i++ { // 1 ns\n fmt.Println(a) // 5 ns\n }\n}\n
// Under a particular operating platform\nfunc algorithm(n: Int) {\n var a = 2 // 1 ns\n a = a + 1 // 1 ns\n a = a * 2 // 10 ns\n // Loop n times\n for _ in 0 ..< n { // 1 ns\n print(0) // 5 ns\n }\n}\n
// Under a particular operating platform\nfunction algorithm(n) {\n var a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for(let i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n console.log(0); // 5 ns\n }\n}\n
// Under a particular operating platform\nfunction algorithm(n: number): void {\n var a: number = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for(let i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n console.log(0); // 5 ns\n }\n}\n
// Under a particular operating platform\nvoid algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n print(0); // 5 ns\n }\n}\n
// Under a particular operating platform\nfn algorithm(n: i32) {\n let mut a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for _ in 0..n { // 1 ns for each round i++\n println!(\"{}\", 0); // 5 ns\n }\n}\n
// Under a particular operating platform\nvoid algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n printf(\"%d\", 0); // 5 ns\n }\n}\n
// Under a particular operating platform\nfn algorithm(n: usize) void {\n var a: i32 = 2; // 1 ns\n a += 1; // 1 ns\n a *= 2; // 10 ns\n // Loop n times\n for (0..n) |_| { // 1 ns\n std.debug.print(\"{}\\n\", .{0}); // 5 ns\n }\n}\n
Based on the above method, the algorithm running time can be obtained as \\(6n + 12\\) ns :
\\[ 1 + 1 + 10 + (1 + 5) \\times n = 6n + 12 \\]In practice, however, statistical algorithm runtimes are neither reasonable nor realistic. First, we do not want to tie the estimation time to the operation platform, because the algorithm needs to run on a variety of different platforms. Second, it is difficult for us to be informed of the runtime of each operation, which makes the prediction process extremely difficult.
"},{"location":"chapter_computational_complexity/time_complexity/#231-trends-in-statistical-time-growth","title":"2.3.1 \u00a0 Trends In Statistical Time Growth","text":"The time complexity analysis counts not the algorithm running time, but the tendency of the algorithm running time to increase as the amount of data gets larger.
The concept of \"time-growing trend\" is rather abstract, so let's try to understand it through an example. Suppose the size of the input data is \\(n\\), and given three algorithmic functions A
, B
and C
:
# Time complexity of algorithm A: constant order\ndef algorithm_A(n: int):\n print(0)\n# Time complexity of algorithm B: linear order\ndef algorithm_B(n: int):\n for _ in range(n):\n print(0)\n# Time complexity of algorithm C: constant order\ndef algorithm_C(n: int):\n for _ in range(1000000):\n print(0)\n
// Time complexity of algorithm A: constant order\nvoid algorithm_A(int n) {\n cout << 0 << endl;\n}\n// Time complexity of algorithm B: linear order\nvoid algorithm_B(int n) {\n for (int i = 0; i < n; i++) {\n cout << 0 << endl;\n }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithm_C(int n) {\n for (int i = 0; i < 1000000; i++) {\n cout << 0 << endl;\n }\n}\n
// Time complexity of algorithm A: constant order\nvoid algorithm_A(int n) {\n System.out.println(0);\n}\n// Time complexity of algorithm B: linear order\nvoid algorithm_B(int n) {\n for (int i = 0; i < n; i++) {\n System.out.println(0);\n }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithm_C(int n) {\n for (int i = 0; i < 1000000; i++) {\n System.out.println(0);\n }\n}\n
// Time complexity of algorithm A: constant order\nvoid AlgorithmA(int n) {\n Console.WriteLine(0);\n}\n// Time complexity of algorithm B: linear order\nvoid AlgorithmB(int n) {\n for (int i = 0; i < n; i++) {\n Console.WriteLine(0);\n }\n}\n// Time complexity of algorithm C: constant order\nvoid AlgorithmC(int n) {\n for (int i = 0; i < 1000000; i++) {\n Console.WriteLine(0);\n }\n}\n
// Time complexity of algorithm A: constant order\nfunc algorithm_A(n int) {\n fmt.Println(0)\n}\n// Time complexity of algorithm B: linear order\nfunc algorithm_B(n int) {\n for i := 0; i < n; i++ {\n fmt.Println(0)\n }\n}\n// Time complexity of algorithm C: constant order\nfunc algorithm_C(n int) {\n for i := 0; i < 1000000; i++ {\n fmt.Println(0)\n }\n}\n
// Time complexity of algorithm A: constant order\nfunc algorithmA(n: Int) {\n print(0)\n}\n\n// Time complexity of algorithm B: linear order\nfunc algorithmB(n: Int) {\n for _ in 0 ..< n {\n print(0)\n }\n}\n\n// Time complexity of algorithm C: constant order\nfunc algorithmC(n: Int) {\n for _ in 0 ..< 1000000 {\n print(0)\n }\n}\n
// Time complexity of algorithm A: constant order\nfunction algorithm_A(n) {\n console.log(0);\n}\n// Time complexity of algorithm B: linear order\nfunction algorithm_B(n) {\n for (let i = 0; i < n; i++) {\n console.log(0);\n }\n}\n// Time complexity of algorithm C: constant order\nfunction algorithm_C(n) {\n for (let i = 0; i < 1000000; i++) {\n console.log(0);\n }\n}\n
// Time complexity of algorithm A: constant order\nfunction algorithm_A(n: number): void {\n console.log(0);\n}\n// Time complexity of algorithm B: linear order\nfunction algorithm_B(n: number): void {\n for (let i = 0; i < n; i++) {\n console.log(0);\n }\n}\n// Time complexity of algorithm C: constant order\nfunction algorithm_C(n: number): void {\n for (let i = 0; i < 1000000; i++) {\n console.log(0);\n }\n}\n
// Time complexity of algorithm A: constant order\nvoid algorithmA(int n) {\n print(0);\n}\n// Time complexity of algorithm B: linear order\nvoid algorithmB(int n) {\n for (int i = 0; i < n; i++) {\n print(0);\n }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithmC(int n) {\n for (int i = 0; i < 1000000; i++) {\n print(0);\n }\n}\n
// Time complexity of algorithm A: constant order\nfn algorithm_A(n: i32) {\n println!(\"{}\", 0);\n}\n// Time complexity of algorithm B: linear order\nfn algorithm_B(n: i32) {\n for _ in 0..n {\n println!(\"{}\", 0);\n }\n}\n// Time complexity of algorithm C: constant order\nfn algorithm_C(n: i32) {\n for _ in 0..1000000 {\n println!(\"{}\", 0);\n }\n}\n
// Time complexity of algorithm A: constant order\nvoid algorithm_A(int n) {\n printf(\"%d\", 0);\n}\n// Time complexity of algorithm B: linear order\nvoid algorithm_B(int n) {\n for (int i = 0; i < n; i++) {\n printf(\"%d\", 0);\n }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithm_C(int n) {\n for (int i = 0; i < 1000000; i++) {\n printf(\"%d\", 0);\n }\n}\n
// Time complexity of algorithm A: constant order\nfn algorithm_A(n: usize) void {\n _ = n;\n std.debug.print(\"{}\\n\", .{0});\n}\n// Time complexity of algorithm B: linear order\nfn algorithm_B(n: i32) void {\n for (0..n) |_| {\n std.debug.print(\"{}\\n\", .{0});\n }\n}\n// Time complexity of algorithm C: constant order\nfn algorithm_C(n: i32) void {\n _ = n;\n for (0..1000000) |_| {\n std.debug.print(\"{}\\n\", .{0});\n }\n}\n
The Figure 2-7 shows the time complexity of the above three algorithmic functions.
A
has only \\(1\\) print operations, and the running time of the algorithm does not increase with \\(n\\). We call the time complexity of this algorithm \"constant order\".B
requires \\(n\\) cycles, and the running time of the algorithm increases linearly with \\(n\\). The time complexity of this algorithm is called \"linear order\".C
requires \\(1000000\\) loops, which is a long runtime, but it is independent of the size of the input data \\(n\\). Therefore, the time complexity of C
is the same as that of A
, which is still of \"constant order\".Figure 2-7 \u00a0 Time growth trends for algorithms A, B and C
What are the characteristics of time complexity analysis compared to direct statistical algorithmic running time?
B
increases linearly and is slower than algorithm A
for \\(n > 1\\) and slower than algorithm C
for \\(n > 1,000,000\\). In fact, as long as the input data size \\(n\\) is large enough, algorithms with \"constant order\" of complexity will always outperform algorithms with \"linear order\", which is exactly what the time complexity trend means.A
and C
have the same time complexity, the actual running time varies greatly. Similarly, although the time complexity of algorithm B
is higher than that of C
, algorithm B
significantly outperforms algorithm C
when the size of the input data \\(n\\) is small. In these cases, it is difficult to judge the efficiency of an algorithm based on time complexity alone. Of course, despite the above problems, complexity analysis is still the most effective and commonly used method to judge the efficiency of algorithms.Given a function with input size \\(n\\):
PythonC++JavaC#GoSwiftJSTSDartRustCZigdef algorithm(n: int):\n a = 1 # +1\n a = a + 1 # +1\n a = a * 2 # +1\n # Cycle n times\n for i in range(n): # +1\n print(0) # +1\n
void algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // Loop n times\n for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)\n cout << 0 << endl; // +1\n }\n}\n
void algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // Loop n times\n for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)\n System.out.println(0); // +1\n }\n}\n
void Algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // Loop n times\n for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)\n Console.WriteLine(0); // +1\n }\n}\n
func algorithm(n int) {\n a := 1 // +1\n a = a + 1 // +1\n a = a * 2 // +1\n // Loop n times\n for i := 0; i < n; i++ { // +1\n fmt.Println(a) // +1\n }\n}\n
func algorithm(n: Int) {\n var a = 1 // +1\n a = a + 1 // +1\n a = a * 2 // +1\n // Loop n times\n for _ in 0 ..< n { // +1\n print(0) // +1\n }\n}\n
function algorithm(n) {\n var a = 1; // +1\n a += 1; // +1\n a *= 2; // +1\n // Loop n times\n for(let i = 0; i < n; i++){ // +1 (execute i ++ every round)\n console.log(0); // +1\n }\n}\n
function algorithm(n: number): void{\n var a: number = 1; // +1\n a += 1; // +1\n a *= 2; // +1\n // Loop n times\n for(let i = 0; i < n; i++){ // +1 (execute i ++ every round)\n console.log(0); // +1\n }\n}\n
void algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // Loop n times\n for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)\n print(0); // +1\n }\n}\n
fn algorithm(n: i32) {\n let mut a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n\n // Loop n times\n for _ in 0..n { // +1 (execute i ++ every round)\n println!(\"{}\", 0); // +1\n }\n}\n
void algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // Loop n times\n for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)\n printf(\"%d\", 0); // +1\n }\n} \n
fn algorithm(n: usize) void {\n var a: i32 = 1; // +1\n a += 1; // +1\n a *= 2; // +1\n // Loop n times\n for (0..n) |_| { // +1 (execute i ++ every round)\n std.debug.print(\"{}\\n\", .{0}); // +1\n }\n}\n
Let the number of operations of the algorithm be a function of the size of the input data \\(n\\), denoted as \\(T(n)\\) , then the number of operations of the above function is:
\\[ T(n) = 3 + 2n \\]\\(T(n)\\) is a primary function, which indicates that the trend of its running time growth is linear, and thus its time complexity is of linear order.
We denote the time complexity of the linear order as \\(O(n)\\) , and this mathematical notation is called the \"big \\(O\\) notation big-\\(O\\) notation\", which denotes the \"asymptotic upper bound\" of the function \\(T(n)\\).
Time complexity analysis is essentially the computation of asymptotic upper bounds on the \"number of operations function \\(T(n)\\)\", which has a clear mathematical definition.
Function asymptotic upper bound
If there exists a positive real number \\(c\\) and a real number \\(n_0\\) such that \\(T(n) \\leq c \\cdot f(n)\\) for all \\(n > n_0\\) , then it can be argued that \\(f(n)\\) gives an asymptotic upper bound on \\(T(n)\\) , denoted as \\(T(n) = O(f(n))\\) .
As shown in the Figure 2-8 , computing the asymptotic upper bound is a matter of finding a function \\(f(n)\\) such that \\(T(n)\\) and \\(f(n)\\) are at the same growth level as \\(n\\) tends to infinity, differing only by a multiple of the constant term \\(c\\).
Figure 2-8 \u00a0 asymptotic upper bound of function
"},{"location":"chapter_computational_complexity/time_complexity/#233-method-of-projection","title":"2.3.3 \u00a0 Method Of Projection","text":"Asymptotic upper bounds are a bit heavy on math, so don't worry if you feel you don't have a full solution. Because in practice, we only need to master the projection method, and the mathematical meaning can be gradually comprehended.
By definition, after determining \\(f(n)\\), we can get the time complexity \\(O(f(n))\\). So how to determine the asymptotic upper bound \\(f(n)\\)? The overall is divided into two steps: first count the number of operations, and then determine the asymptotic upper bound.
"},{"location":"chapter_computational_complexity/time_complexity/#1-the-first-step-counting-the-number-of-operations","title":"1. \u00a0 The First Step: Counting The Number Of Operations","text":"For the code, it is sufficient to calculate from top to bottom line by line. However, since the constant term \\(c \\cdot f(n)\\) in the above \\(c \\cdot f(n)\\) can take any size, the various coefficients and constant terms in the number of operations \\(T(n)\\) can be ignored. Based on this principle, the following counting simplification techniques can be summarized.
1.
and 2.
respectively.Given a function, we can use the above trick to count the number of operations.
PythonC++JavaC#GoSwiftJSTSDartRustCZigdef algorithm(n: int):\n a = 1 # +0 (trick 1)\n a = a + n # +0 (trick 1)\n # +n (technique 2)\n for i in range(5 * n + 1):\n print(0)\n # +n*n (technique 3)\n for i in range(2 * n):\n for j in range(n + 1):\n print(0)\n
void algorithm(int n) {\n int a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (int i = 0; i < 5 * n + 1; i++) {\n cout << 0 << endl;\n }\n // +n*n (technique 3)\n for (int i = 0; i < 2 * n; i++) {\n for (int j = 0; j < n + 1; j++) {\n cout << 0 << endl;\n }\n }\n}\n
void algorithm(int n) {\n int a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (int i = 0; i < 5 * n + 1; i++) {\n System.out.println(0);\n }\n // +n*n (technique 3)\n for (int i = 0; i < 2 * n; i++) {\n for (int j = 0; j < n + 1; j++) {\n System.out.println(0);\n }\n }\n}\n
void Algorithm(int n) {\n int a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (int i = 0; i < 5 * n + 1; i++) {\n Console.WriteLine(0);\n }\n // +n*n (technique 3)\n for (int i = 0; i < 2 * n; i++) {\n for (int j = 0; j < n + 1; j++) {\n Console.WriteLine(0);\n }\n }\n}\n
func algorithm(n int) {\n a := 1 // +0 (trick 1)\n a = a + n // +0 (trick 1)\n // +n (technique 2)\n for i := 0; i < 5 * n + 1; i++ {\n fmt.Println(0)\n }\n // +n*n (technique 3)\n for i := 0; i < 2 * n; i++ {\n for j := 0; j < n + 1; j++ {\n fmt.Println(0)\n }\n }\n}\n
func algorithm(n: Int) {\n var a = 1 // +0 (trick 1)\n a = a + n // +0 (trick 1)\n // +n (technique 2)\n for _ in 0 ..< (5 * n + 1) {\n print(0)\n }\n // +n*n (technique 3)\n for _ in 0 ..< (2 * n) {\n for _ in 0 ..< (n + 1) {\n print(0)\n }\n }\n}\n
function algorithm(n) {\n let a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (let i = 0; i < 5 * n + 1; i++) {\n console.log(0);\n }\n // +n*n (technique 3)\n for (let i = 0; i < 2 * n; i++) {\n for (let j = 0; j < n + 1; j++) {\n console.log(0);\n }\n }\n}\n
function algorithm(n: number): void {\n let a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (let i = 0; i < 5 * n + 1; i++) {\n console.log(0);\n }\n // +n*n (technique 3)\n for (let i = 0; i < 2 * n; i++) {\n for (let j = 0; j < n + 1; j++) {\n console.log(0);\n }\n }\n}\n
void algorithm(int n) {\n int a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (int i = 0; i < 5 * n + 1; i++) {\n print(0);\n }\n // +n*n (technique 3)\n for (int i = 0; i < 2 * n; i++) {\n for (int j = 0; j < n + 1; j++) {\n print(0);\n }\n }\n}\n
fn algorithm(n: i32) {\n let mut a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n\n // +n (technique 2)\n for i in 0..(5 * n + 1) {\n println!(\"{}\", 0);\n }\n\n // +n*n (technique 3)\n for i in 0..(2 * n) {\n for j in 0..(n + 1) {\n println!(\"{}\", 0);\n }\n }\n}\n
void algorithm(int n) {\n int a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (int i = 0; i < 5 * n + 1; i++) {\n printf(\"%d\", 0);\n }\n // +n*n (technique 3)\n for (int i = 0; i < 2 * n; i++) {\n for (int j = 0; j < n + 1; j++) {\n printf(\"%d\", 0);\n }\n }\n}\n
fn algorithm(n: usize) void {\n var a: i32 = 1; // +0 (trick 1)\n a = a + @as(i32, @intCast(n)); // +0 (trick 1)\n\n // +n (technique 2)\n for(0..(5 * n + 1)) |_| {\n std.debug.print(\"{}\\n\", .{0});\n }\n\n // +n*n (technique 3)\n for(0..(2 * n)) |_| {\n for(0..(n + 1)) |_| {\n std.debug.print(\"{}\\n\", .{0});\n }\n }\n}\n
The following equations show the statistical results before and after using the above technique, both of which were introduced with a time complexity of \\(O(n^2)\\) .
\\[ \\begin{aligned} T(n) & = 2n(n + 1) + (5n + 1) + 2 & \\text{complete statistics (-.-|||)} \\newline & = 2n^2 + 7n + 3 \\newline T(n) & = n^2 + n & \\text{Lazy Stats (o.O)} \\end{aligned} \\]"},{"location":"chapter_computational_complexity/time_complexity/#2-step-2-judging-the-asymptotic-upper-bounds","title":"2. \u00a0 Step 2: Judging The Asymptotic Upper Bounds","text":"The time complexity is determined by the highest order term in the polynomial \\(T(n)\\). This is because as \\(n\\) tends to infinity, the highest order term will play a dominant role and the effects of all other terms can be ignored.
The Table 2-2 shows some examples, some of which have exaggerated values to emphasize the conclusion that \"the coefficients can't touch the order\". As \\(n\\) tends to infinity, these constants become irrelevant.
Table 2-2 \u00a0 Time complexity corresponding to different number of operations
number of operations \\(T(n)\\) time complexity \\(O(f(n))\\) \\(100000\\) \\(O(1)\\) \\(3n + 2\\) \\(O(n)\\) \\(2n^2 + 3n + 2\\) \\(O(n^2)\\) \\(n^3 + 10000n^2\\) \\(O(n^3)\\) \\(2^n + 10000n^{10000}\\) \\(O(2^n)\\)"},{"location":"chapter_computational_complexity/time_complexity/#234-common-types","title":"2.3.4 \u00a0 Common Types","text":"Let the input data size be \\(n\\) , the common types of time complexity are shown in the Figure 2-9 (in descending order).
\\[ \\begin{aligned} O(1) < O(\\log n) < O(n) < O(n \\log n) < O(n^2) < O(2^n) < O(n!) \\newline \\text{constant order} < \\text{logarithmic order} < \\text{linear order} < \\text{linear logarithmic order} < \\text{square order} < \\text{exponential order} < \\text{multiplication order} \\end{aligned} \\]Figure 2-9 \u00a0 Common time complexity types
"},{"location":"chapter_computational_complexity/time_complexity/#1-constant-order-o1","title":"1. \u00a0 Constant Order \\(O(1)\\)","text":"The number of operations of the constant order is independent of the input data size \\(n\\), i.e., it does not change with \\(n\\).
In the following function, although the number of operations size
may be large, the time complexity is still \\(O(1)\\) because it is independent of the input data size \\(n\\) :
def constant(n: int) -> int:\n \"\"\"\u5e38\u6570\u9636\"\"\"\n count = 0\n size = 100000\n for _ in range(size):\n count += 1\n return count\n
time_complexity.cpp/* \u5e38\u6570\u9636 */\nint constant(int n) {\n int count = 0;\n int size = 100000;\n for (int i = 0; i < size; i++)\n count++;\n return count;\n}\n
time_complexity.java/* \u5e38\u6570\u9636 */\nint constant(int n) {\n int count = 0;\n int size = 100000;\n for (int i = 0; i < size; i++)\n count++;\n return count;\n}\n
time_complexity.cs/* \u5e38\u6570\u9636 */\nint Constant(int n) {\n int count = 0;\n int size = 100000;\n for (int i = 0; i < size; i++)\n count++;\n return count;\n}\n
time_complexity.go/* \u5e38\u6570\u9636 */\nfunc constant(n int) int {\n count := 0\n size := 100000\n for i := 0; i < size; i++ {\n count++\n }\n return count\n}\n
time_complexity.swift/* \u5e38\u6570\u9636 */\nfunc constant(n: Int) -> Int {\n var count = 0\n let size = 100_000\n for _ in 0 ..< size {\n count += 1\n }\n return count\n}\n
time_complexity.js/* \u5e38\u6570\u9636 */\nfunction constant(n) {\n let count = 0;\n const size = 100000;\n for (let i = 0; i < size; i++) count++;\n return count;\n}\n
time_complexity.ts/* \u5e38\u6570\u9636 */\nfunction constant(n: number): number {\n let count = 0;\n const size = 100000;\n for (let i = 0; i < size; i++) count++;\n return count;\n}\n
time_complexity.dart/* \u5e38\u6570\u9636 */\nint constant(int n) {\n int count = 0;\n int size = 100000;\n for (var i = 0; i < size; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.rs/* \u5e38\u6570\u9636 */\nfn constant(n: i32) -> i32 {\n _ = n;\n let mut count = 0;\n let size = 100_000;\n for _ in 0..size {\n count += 1;\n }\n count\n}\n
time_complexity.c/* \u5e38\u6570\u9636 */\nint constant(int n) {\n int count = 0;\n int size = 100000;\n int i = 0;\n for (int i = 0; i < size; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.zig// \u5e38\u6570\u9636\nfn constant(n: i32) i32 {\n _ = n;\n var count: i32 = 0;\n const size: i32 = 100_000;\n var i: i32 = 0;\n while(i<size) : (i += 1) {\n count += 1;\n }\n return count;\n}\n
"},{"location":"chapter_computational_complexity/time_complexity/#2-linear-order-on","title":"2. \u00a0 Linear Order \\(O(N)\\)","text":"The number of operations in a linear order grows in linear steps relative to the input data size \\(n\\). Linear orders are usually found in single level loops:
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef linear(n: int) -> int:\n \"\"\"\u7ebf\u6027\u9636\"\"\"\n count = 0\n for _ in range(n):\n count += 1\n return count\n
time_complexity.cpp/* \u7ebf\u6027\u9636 */\nint linear(int n) {\n int count = 0;\n for (int i = 0; i < n; i++)\n count++;\n return count;\n}\n
time_complexity.java/* \u7ebf\u6027\u9636 */\nint linear(int n) {\n int count = 0;\n for (int i = 0; i < n; i++)\n count++;\n return count;\n}\n
time_complexity.cs/* \u7ebf\u6027\u9636 */\nint Linear(int n) {\n int count = 0;\n for (int i = 0; i < n; i++)\n count++;\n return count;\n}\n
time_complexity.go/* \u7ebf\u6027\u9636 */\nfunc linear(n int) int {\n count := 0\n for i := 0; i < n; i++ {\n count++\n }\n return count\n}\n
time_complexity.swift/* \u7ebf\u6027\u9636 */\nfunc linear(n: Int) -> Int {\n var count = 0\n for _ in 0 ..< n {\n count += 1\n }\n return count\n}\n
time_complexity.js/* \u7ebf\u6027\u9636 */\nfunction linear(n) {\n let count = 0;\n for (let i = 0; i < n; i++) count++;\n return count;\n}\n
time_complexity.ts/* \u7ebf\u6027\u9636 */\nfunction linear(n: number): number {\n let count = 0;\n for (let i = 0; i < n; i++) count++;\n return count;\n}\n
time_complexity.dart/* \u7ebf\u6027\u9636 */\nint linear(int n) {\n int count = 0;\n for (var i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.rs/* \u7ebf\u6027\u9636 */\nfn linear(n: i32) -> i32 {\n let mut count = 0;\n for _ in 0..n {\n count += 1;\n }\n count\n}\n
time_complexity.c/* \u7ebf\u6027\u9636 */\nint linear(int n) {\n int count = 0;\n for (int i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.zig// \u7ebf\u6027\u9636\nfn linear(n: i32) i32 {\n var count: i32 = 0;\n var i: i32 = 0;\n while (i < n) : (i += 1) {\n count += 1;\n }\n return count;\n}\n
The time complexity of operations such as traversing an array and traversing a linked list is \\(O(n)\\) , where \\(n\\) is the length of the array or linked list:
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef array_traversal(nums: list[int]) -> int:\n \"\"\"\u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09\"\"\"\n count = 0\n # \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for num in nums:\n count += 1\n return count\n
time_complexity.cpp/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint arrayTraversal(vector<int> &nums) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (int num : nums) {\n count++;\n }\n return count;\n}\n
time_complexity.java/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint arrayTraversal(int[] nums) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (int num : nums) {\n count++;\n }\n return count;\n}\n
time_complexity.cs/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint ArrayTraversal(int[] nums) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n foreach (int num in nums) {\n count++;\n }\n return count;\n}\n
time_complexity.go/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfunc arrayTraversal(nums []int) int {\n count := 0\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for range nums {\n count++\n }\n return count\n}\n
time_complexity.swift/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfunc arrayTraversal(nums: [Int]) -> Int {\n var count = 0\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for _ in nums {\n count += 1\n }\n return count\n}\n
time_complexity.js/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfunction arrayTraversal(nums) {\n let count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (let i = 0; i < nums.length; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.ts/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfunction arrayTraversal(nums: number[]): number {\n let count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (let i = 0; i < nums.length; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.dart/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint arrayTraversal(List<int> nums) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (var _num in nums) {\n count++;\n }\n return count;\n}\n
time_complexity.rs/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfn array_traversal(nums: &[i32]) -> i32 {\n let mut count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for _ in nums {\n count += 1;\n }\n count\n}\n
time_complexity.c/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint arrayTraversal(int *nums, int n) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (int i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.zig// \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09\nfn arrayTraversal(nums: []i32) i32 {\n var count: i32 = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (nums) |_| {\n count += 1;\n }\n return count;\n}\n
It is worth noting that Input data size \\(n\\) needs to be determined specifically according to the type of input data. For example, in the first example, the variable \\(n\\) is the input data size; in the second example, the array length \\(n\\) is the data size.
"},{"location":"chapter_computational_complexity/time_complexity/#3-squared-order-on2","title":"3. \u00a0 Squared Order \\(O(N^2)\\)","text":"The number of operations in the square order grows in square steps with respect to the size of the input data \\(n\\). The squared order is usually found in nested loops, where both the outer and inner levels are \\(O(n)\\) and therefore overall \\(O(n^2)\\):
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef quadratic(n: int) -> int:\n \"\"\"\u5e73\u65b9\u9636\"\"\"\n count = 0\n # \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for i in range(n):\n for j in range(n):\n count += 1\n return count\n
time_complexity.cpp/* \u5e73\u65b9\u9636 */\nint quadratic(int n) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.java/* \u5e73\u65b9\u9636 */\nint quadratic(int n) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.cs/* \u5e73\u65b9\u9636 */\nint Quadratic(int n) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.go/* \u5e73\u65b9\u9636 */\nfunc quadratic(n int) int {\n count := 0\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for i := 0; i < n; i++ {\n for j := 0; j < n; j++ {\n count++\n }\n }\n return count\n}\n
time_complexity.swift/* \u5e73\u65b9\u9636 */\nfunc quadratic(n: Int) -> Int {\n var count = 0\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for _ in 0 ..< n {\n for _ in 0 ..< n {\n count += 1\n }\n }\n return count\n}\n
time_complexity.js/* \u5e73\u65b9\u9636 */\nfunction quadratic(n) {\n let count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (let i = 0; i < n; i++) {\n for (let j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.ts/* \u5e73\u65b9\u9636 */\nfunction quadratic(n: number): number {\n let count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (let i = 0; i < n; i++) {\n for (let j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.dart/* \u5e73\u65b9\u9636 */\nint quadratic(int n) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.rs/* \u5e73\u65b9\u9636 */\nfn quadratic(n: i32) -> i32 {\n let mut count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for _ in 0..n {\n for _ in 0..n {\n count += 1;\n }\n }\n count\n}\n
time_complexity.c/* \u5e73\u65b9\u9636 */\nint quadratic(int n) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.zig// \u5e73\u65b9\u9636\nfn quadratic(n: i32) i32 {\n var count: i32 = 0;\n var i: i32 = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n while (i < n) : (i += 1) {\n var j: i32 = 0;\n while (j < n) : (j += 1) {\n count += 1;\n }\n }\n return count;\n}\n
The Figure 2-10 compares the three time complexities of constant order, linear order and squared order.
Figure 2-10 \u00a0 Time complexity of constant, linear and quadratic orders
Taking bubble sort as an example, the outer loop executes \\(n - 1\\) times, and the inner loop executes \\(n-1\\), \\(n-2\\), \\(\\dots\\), \\(2\\), \\(1\\) times, which averages out to \\(n / 2\\) times, resulting in a time complexity of \\(O((n - 1) n / 2) = O(n^2)\\) .
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef bubble_sort(nums: list[int]) -> int:\n \"\"\"\u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09\"\"\"\n count = 0 # \u8ba1\u6570\u5668\n # \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for i in range(len(nums) - 1, 0, -1):\n # \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for j in range(i):\n if nums[j] > nums[j + 1]:\n # \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n tmp: int = nums[j]\n nums[j] = nums[j + 1]\n nums[j + 1] = tmp\n count += 3 # \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n return count\n
time_complexity.cpp/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint bubbleSort(vector<int> &nums) {\n int count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (int i = nums.size() - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for (int j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.java/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint bubbleSort(int[] nums) {\n int count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (int i = nums.length - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for (int j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.cs/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint BubbleSort(int[] nums) {\n int count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (int i = nums.Length - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef \n for (int j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.go/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfunc bubbleSort(nums []int) int {\n count := 0 // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for i := len(nums) - 1; i > 0; i-- {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for j := 0; j < i; j++ {\n if nums[j] > nums[j+1] {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n tmp := nums[j]\n nums[j] = nums[j+1]\n nums[j+1] = tmp\n count += 3 // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count\n}\n
time_complexity.swift/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfunc bubbleSort(nums: inout [Int]) -> Int {\n var count = 0 // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for i in stride(from: nums.count - 1, to: 0, by: -1) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef \n for j in 0 ..< i {\n if nums[j] > nums[j + 1] {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n let tmp = nums[j]\n nums[j] = nums[j + 1]\n nums[j + 1] = tmp\n count += 3 // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count\n}\n
time_complexity.js/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfunction bubbleSort(nums) {\n let count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (let i = nums.length - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for (let j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.ts/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfunction bubbleSort(nums: number[]): number {\n let count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (let i = nums.length - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for (let j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.dart/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint bubbleSort(List<int> nums) {\n int count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (var i = nums.length - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for (var j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.rs/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfn bubble_sort(nums: &mut [i32]) -> i32 {\n let mut count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for i in (1..nums.len()).rev() {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef \n for j in 0..i {\n if nums[j] > nums[j + 1] {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n count\n}\n
time_complexity.c/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint bubbleSort(int *nums, int n) {\n int count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (int i = n - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for (int j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.zig// \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09\nfn bubbleSort(nums: []i32) i32 {\n var count: i32 = 0; // \u8ba1\u6570\u5668 \n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n var i: i32 = @as(i32, @intCast(nums.len)) - 1;\n while (i > 0) : (i -= 1) {\n var j: usize = 0;\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef \n while (j < i) : (j += 1) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n var tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
"},{"location":"chapter_computational_complexity/time_complexity/#235-exponential-order-o2n","title":"2.3.5 \u00a0 Exponential Order \\(O(2^N)\\)","text":"Cell division in biology is a typical example of exponential growth: the initial state is \\(1\\) cells, after one round of division it becomes \\(2\\), after two rounds of division it becomes \\(4\\), and so on, after \\(n\\) rounds of division there are \\(2^n\\) cells.
The Figure 2-11 and the following code simulate the process of cell division with a time complexity of \\(O(2^n)\\) .
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef exponential(n: int) -> int:\n \"\"\"\u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09\"\"\"\n count = 0\n base = 1\n # \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for _ in range(n):\n for _ in range(base):\n count += 1\n base *= 2\n # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count\n
time_complexity.cpp/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint exponential(int n) {\n int count = 0, base = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.java/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint exponential(int n) {\n int count = 0, base = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.cs/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint Exponential(int n) {\n int count = 0, bas = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < bas; j++) {\n count++;\n }\n bas *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.go/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09*/\nfunc exponential(n int) int {\n count, base := 0, 1\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for i := 0; i < n; i++ {\n for j := 0; j < base; j++ {\n count++\n }\n base *= 2\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count\n}\n
time_complexity.swift/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunc exponential(n: Int) -> Int {\n var count = 0\n var base = 1\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for _ in 0 ..< n {\n for _ in 0 ..< base {\n count += 1\n }\n base *= 2\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count\n}\n
time_complexity.js/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunction exponential(n) {\n let count = 0,\n base = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (let i = 0; i < n; i++) {\n for (let j = 0; j < base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.ts/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunction exponential(n: number): number {\n let count = 0,\n base = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (let i = 0; i < n; i++) {\n for (let j = 0; j < base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.dart/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint exponential(int n) {\n int count = 0, base = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (var i = 0; i < n; i++) {\n for (var j = 0; j < base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.rs/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfn exponential(n: i32) -> i32 {\n let mut count = 0;\n let mut base = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for _ in 0..n {\n for _ in 0..base {\n count += 1\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n count\n}\n
time_complexity.c/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint exponential(int n) {\n int count = 0;\n int bas = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < bas; j++) {\n count++;\n }\n bas *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.zig// \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09\nfn exponential(n: i32) i32 {\n var count: i32 = 0;\n var bas: i32 = 1;\n var i: i32 = 0;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n while (i < n) : (i += 1) {\n var j: i32 = 0;\n while (j < bas) : (j += 1) {\n count += 1;\n }\n bas *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
Figure 2-11 \u00a0 time complexity of exponential order
In practical algorithms, exponential orders are often found in recursion functions. For example, in the following code, it recursively splits in two and stops after \\(n\\) splits:
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef exp_recur(n: int) -> int:\n \"\"\"\u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\n if n == 1:\n return 1\n return exp_recur(n - 1) + exp_recur(n - 1) + 1\n
time_complexity.cpp/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint expRecur(int n) {\n if (n == 1)\n return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.java/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint expRecur(int n) {\n if (n == 1)\n return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.cs/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint ExpRecur(int n) {\n if (n == 1) return 1;\n return ExpRecur(n - 1) + ExpRecur(n - 1) + 1;\n}\n
time_complexity.go/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09*/\nfunc expRecur(n int) int {\n if n == 1 {\n return 1\n }\n return expRecur(n-1) + expRecur(n-1) + 1\n}\n
time_complexity.swift/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc expRecur(n: Int) -> Int {\n if n == 1 {\n return 1\n }\n return expRecur(n: n - 1) + expRecur(n: n - 1) + 1\n}\n
time_complexity.js/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction expRecur(n) {\n if (n === 1) return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.ts/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction expRecur(n: number): number {\n if (n === 1) return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.dart/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint expRecur(int n) {\n if (n == 1) return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.rs/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfn exp_recur(n: i32) -> i32 {\n if n == 1 {\n return 1;\n }\n exp_recur(n - 1) + exp_recur(n - 1) + 1\n}\n
time_complexity.c/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint expRecur(int n) {\n if (n == 1)\n return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.zig// \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn expRecur(n: i32) i32 {\n if (n == 1) return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
Exponential order grows very rapidly and is more common in exhaustive methods (brute force search, backtracking, etc.). For problems with large data sizes, exponential order is unacceptable and usually requires the use of algorithms such as dynamic programming or greedy algorithms to solve.
"},{"location":"chapter_computational_complexity/time_complexity/#1-logarithmic-order-olog-n","title":"1. \u00a0 Logarithmic Order \\(O(\\Log N)\\)","text":"In contrast to the exponential order, the logarithmic order reflects the \"each round is reduced to half\" case. Let the input data size be \\(n\\), and since each round is reduced to half, the number of loops is \\(\\log_2 n\\), which is the inverse function of \\(2^n\\).
The Figure 2-12 and the code below simulate the process of \"reducing each round to half\" with a time complexity of \\(O(\\log_2 n)\\), which is abbreviated as \\(O(\\log n)\\).
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef logarithmic(n: float) -> int:\n \"\"\"\u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09\"\"\"\n count = 0\n while n > 1:\n n = n / 2\n count += 1\n return count\n
time_complexity.cpp/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint logarithmic(float n) {\n int count = 0;\n while (n > 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n
time_complexity.java/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint logarithmic(float n) {\n int count = 0;\n while (n > 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n
time_complexity.cs/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint Logarithmic(float n) {\n int count = 0;\n while (n > 1) {\n n /= 2;\n count++;\n }\n return count;\n}\n
time_complexity.go/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09*/\nfunc logarithmic(n float64) int {\n count := 0\n for n > 1 {\n n = n / 2\n count++\n }\n return count\n}\n
time_complexity.swift/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunc logarithmic(n: Double) -> Int {\n var count = 0\n var n = n\n while n > 1 {\n n = n / 2\n count += 1\n }\n return count\n}\n
time_complexity.js/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunction logarithmic(n) {\n let count = 0;\n while (n > 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n
time_complexity.ts/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunction logarithmic(n: number): number {\n let count = 0;\n while (n > 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n
time_complexity.dart/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint logarithmic(num n) {\n int count = 0;\n while (n > 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n
time_complexity.rs/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfn logarithmic(mut n: f32) -> i32 {\n let mut count = 0;\n while n > 1.0 {\n n = n / 2.0;\n count += 1;\n }\n count\n}\n
time_complexity.c/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint logarithmic(float n) {\n int count = 0;\n while (n > 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n
time_complexity.zig// \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09\nfn logarithmic(n: f32) i32 {\n var count: i32 = 0;\n var n_var = n;\n while (n_var > 1)\n {\n n_var = n_var / 2;\n count +=1;\n }\n return count;\n}\n
Figure 2-12 \u00a0 time complexity of logarithmic order
Similar to the exponential order, the logarithmic order is often found in recursion functions. The following code forms a recursion tree of height \\(\\log_2 n\\):
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef log_recur(n: float) -> int:\n \"\"\"\u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\n if n <= 1:\n return 0\n return log_recur(n / 2) + 1\n
time_complexity.cpp/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint logRecur(float n) {\n if (n <= 1)\n return 0;\n return logRecur(n / 2) + 1;\n}\n
time_complexity.java/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint logRecur(float n) {\n if (n <= 1)\n return 0;\n return logRecur(n / 2) + 1;\n}\n
time_complexity.cs/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint LogRecur(float n) {\n if (n <= 1) return 0;\n return LogRecur(n / 2) + 1;\n}\n
time_complexity.go/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09*/\nfunc logRecur(n float64) int {\n if n <= 1 {\n return 0\n }\n return logRecur(n/2) + 1\n}\n
time_complexity.swift/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc logRecur(n: Double) -> Int {\n if n <= 1 {\n return 0\n }\n return logRecur(n: n / 2) + 1\n}\n
time_complexity.js/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction logRecur(n) {\n if (n <= 1) return 0;\n return logRecur(n / 2) + 1;\n}\n
time_complexity.ts/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction logRecur(n: number): number {\n if (n <= 1) return 0;\n return logRecur(n / 2) + 1;\n}\n
time_complexity.dart/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint logRecur(num n) {\n if (n <= 1) return 0;\n return logRecur(n / 2) + 1;\n}\n
time_complexity.rs/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfn log_recur(n: f32) -> i32 {\n if n <= 1.0 {\n return 0;\n }\n log_recur(n / 2.0) + 1\n}\n
time_complexity.c/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint logRecur(float n) {\n if (n <= 1)\n return 0;\n return logRecur(n / 2) + 1;\n}\n
time_complexity.zig// \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn logRecur(n: f32) i32 {\n if (n <= 1) return 0;\n return logRecur(n / 2) + 1;\n}\n
Logarithmic order is often found in algorithms based on the divide and conquer strategy, which reflects the algorithmic ideas of \"dividing one into many\" and \"simplifying the complexity into simplicity\". It grows slowly and is the second most desirable time complexity after constant order.
What is the base of \\(O(\\log n)\\)?
To be precise, the corresponding time complexity of \"one divided into \\(m\\)\" is \\(O(\\log_m n)\\) . And by using the logarithmic permutation formula, we can get equal time complexity with different bases:
\\[ O(\\log_m n) = O(\\log_k n / \\log_k m) = O(\\log_k n) \\]That is, the base \\(m\\) can be converted without affecting the complexity. Therefore we usually omit the base \\(m\\) and write the logarithmic order directly as \\(O(\\log n)\\).
"},{"location":"chapter_computational_complexity/time_complexity/#2-linear-logarithmic-order-on-log-n","title":"2. \u00a0 Linear Logarithmic Order \\(O(N \\Log N)\\)","text":"The linear logarithmic order is often found in nested loops, and the time complexity of the two levels of loops is \\(O(\\log n)\\) and \\(O(n)\\) respectively. The related code is as follows:
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef linear_log_recur(n: float) -> int:\n \"\"\"\u7ebf\u6027\u5bf9\u6570\u9636\"\"\"\n if n <= 1:\n return 1\n count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2)\n for _ in range(n):\n count += 1\n return count\n
time_complexity.cpp/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint linearLogRecur(float n) {\n if (n <= 1)\n return 1;\n int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (int i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.java/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint linearLogRecur(float n) {\n if (n <= 1)\n return 1;\n int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (int i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.cs/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint LinearLogRecur(float n) {\n if (n <= 1) return 1;\n int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2);\n for (int i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.go/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfunc linearLogRecur(n float64) int {\n if n <= 1 {\n return 1\n }\n count := linearLogRecur(n/2) + linearLogRecur(n/2)\n for i := 0.0; i < n; i++ {\n count++\n }\n return count\n}\n
time_complexity.swift/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfunc linearLogRecur(n: Double) -> Int {\n if n <= 1 {\n return 1\n }\n var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2)\n for _ in stride(from: 0, to: n, by: 1) {\n count += 1\n }\n return count\n}\n
time_complexity.js/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfunction linearLogRecur(n) {\n if (n <= 1) return 1;\n let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (let i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.ts/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfunction linearLogRecur(n: number): number {\n if (n <= 1) return 1;\n let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (let i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.dart/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint linearLogRecur(num n) {\n if (n <= 1) return 1;\n int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (var i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.rs/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfn linear_log_recur(n: f32) -> i32 {\n if n <= 1.0 {\n return 1;\n }\n let mut count = linear_log_recur(n / 2.0) + linear_log_recur(n / 2.0);\n for _ in 0 ..n as i32 {\n count += 1;\n }\n return count\n}\n
time_complexity.c/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint linearLogRecur(float n) {\n if (n <= 1)\n return 1;\n int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (int i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.zig// \u7ebf\u6027\u5bf9\u6570\u9636\nfn linearLogRecur(n: f32) i32 {\n if (n <= 1) return 1;\n var count: i32 = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n var i: f32 = 0;\n while (i < n) : (i += 1) {\n count += 1;\n }\n return count;\n}\n
The Figure 2-13 shows how the linear logarithmic order is generated. The total number of operations at each level of the binary tree is \\(n\\) , and the tree has a total of \\(\\log_2 n + 1\\) levels, resulting in a time complexity of \\(O(n\\log n)\\) .
Figure 2-13 \u00a0 Time complexity of linear logarithmic order
Mainstream sorting algorithms typically have a time complexity of \\(O(n \\log n)\\) , such as quick sort, merge sort, heap sort, etc.
"},{"location":"chapter_computational_complexity/time_complexity/#3-the-factorial-order-on","title":"3. \u00a0 The Factorial Order \\(O(N!)\\)","text":"The factorial order corresponds to the mathematical \"permutations problem\". Given \\(n\\) elements that do not repeat each other, find all possible permutations of them, the number of permutations being:
\\[ n! = n \\times (n - 1) \\times (n - 2) \\times \\dots \\times 2 \\times 1 \\]Factorials are usually implemented using recursion. As shown in the Figure 2-14 and in the code below, the first level splits \\(n\\), the second level splits \\(n - 1\\), and so on, until the splitting stops at the \\(n\\)th level:
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef factorial_recur(n: int) -> int:\n \"\"\"\u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\n if n == 0:\n return 1\n count = 0\n # \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for _ in range(n):\n count += factorial_recur(n - 1)\n return count\n
time_complexity.cpp/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint factorialRecur(int n) {\n if (n == 0)\n return 1;\n int count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for (int i = 0; i < n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.java/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint factorialRecur(int n) {\n if (n == 0)\n return 1;\n int count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for (int i = 0; i < n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.cs/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint FactorialRecur(int n) {\n if (n == 0) return 1;\n int count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for (int i = 0; i < n; i++) {\n count += FactorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.go/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc factorialRecur(n int) int {\n if n == 0 {\n return 1\n }\n count := 0\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for i := 0; i < n; i++ {\n count += factorialRecur(n - 1)\n }\n return count\n}\n
time_complexity.swift/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc factorialRecur(n: Int) -> Int {\n if n == 0 {\n return 1\n }\n var count = 0\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for _ in 0 ..< n {\n count += factorialRecur(n: n - 1)\n }\n return count\n}\n
time_complexity.js/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction factorialRecur(n) {\n if (n === 0) return 1;\n let count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for (let i = 0; i < n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.ts/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction factorialRecur(n: number): number {\n if (n === 0) return 1;\n let count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for (let i = 0; i < n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.dart/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint factorialRecur(int n) {\n if (n == 0) return 1;\n int count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for (var i = 0; i < n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.rs/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfn factorial_recur(n: i32) -> i32 {\n if n == 0 {\n return 1;\n }\n let mut count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for _ in 0..n {\n count += factorial_recur(n - 1);\n }\n count\n}\n
time_complexity.c/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint factorialRecur(int n) {\n if (n == 0)\n return 1;\n int count = 0;\n for (int i = 0; i < n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.zig// \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn factorialRecur(n: i32) i32 {\n if (n == 0) return 1;\n var count: i32 = 0;\n var i: i32 = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n while (i < n) : (i += 1) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
Figure 2-14 \u00a0 Time complexity of the factorial order
Note that since there is always \\(n! > 2^n\\) when \\(n \\geq 4\\), the factorial order grows faster than the exponential order, and is also unacceptable when \\(n\\) is large.
"},{"location":"chapter_computational_complexity/time_complexity/#236-worst-best-average-time-complexity","title":"2.3.6 \u00a0 Worst, Best, Average Time Complexity","text":"The time efficiency of algorithms is often not fixed, but is related to the distribution of the input data. Suppose an array nums
of length \\(n\\) is input, where nums
consists of numbers from \\(1\\) to \\(n\\), each of which occurs only once; however, the order of the elements is randomly upset, and the goal of the task is to return the index of element \\(1\\). We can draw the following conclusion.
nums = [? , ? , ... , 1]
, i.e., when the end element is \\(1\\), a complete traversal of the array is required, to reach the worst time complexity \\(O(n)\\) .nums = [1, ? , ? , ...]
, i.e., when the first element is \\(1\\) , there is no need to continue traversing the array no matter how long it is, reaching the optimal time complexity \\(\\Omega(1)\\) .The \"worst time complexity\" corresponds to the asymptotic upper bound of the function and is denoted by the large \\(O\\) notation. Correspondingly, the \"optimal time complexity\" corresponds to the asymptotic lower bound of the function and is denoted in \\(\\Omega\\) notation:
PythonC++JavaC#GoSwiftJSTSDartRustCZig worst_best_time_complexity.pydef random_numbers(n: int) -> list[int]:\n \"\"\"\u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a: 1, 2, ..., n \uff0c\u987a\u5e8f\u88ab\u6253\u4e71\"\"\"\n # \u751f\u6210\u6570\u7ec4 nums =: 1, 2, 3, ..., n\n nums = [i for i in range(1, n + 1)]\n # \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n random.shuffle(nums)\n return nums\n\ndef find_one(nums: list[int]) -> int:\n \"\"\"\u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15\"\"\"\n for i in range(len(nums)):\n # \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n # \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if nums[i] == 1:\n return i\n return -1\n
worst_best_time_complexity.cpp/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nvector<int> randomNumbers(int n) {\n vector<int> nums(n);\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (int i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n // \u4f7f\u7528\u7cfb\u7edf\u65f6\u95f4\u751f\u6210\u968f\u673a\u79cd\u5b50\n unsigned seed = chrono::system_clock::now().time_since_epoch().count();\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n shuffle(nums.begin(), nums.end(), default_random_engine(seed));\n return nums;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint findOne(vector<int> &nums) {\n for (int i = 0; i < nums.size(); i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] == 1)\n return i;\n }\n return -1;\n}\n
worst_best_time_complexity.java/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nint[] randomNumbers(int n) {\n Integer[] nums = new Integer[n];\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (int i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n Collections.shuffle(Arrays.asList(nums));\n // Integer[] -> int[]\n int[] res = new int[n];\n for (int i = 0; i < n; i++) {\n res[i] = nums[i];\n }\n return res;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint findOne(int[] nums) {\n for (int i = 0; i < nums.length; i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] == 1)\n return i;\n }\n return -1;\n}\n
worst_best_time_complexity.cs/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nint[] RandomNumbers(int n) {\n int[] nums = new int[n];\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (int i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n for (int i = 0; i < nums.Length; i++) {\n int index = new Random().Next(i, nums.Length);\n (nums[i], nums[index]) = (nums[index], nums[i]);\n }\n return nums;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint FindOne(int[] nums) {\n for (int i = 0; i < nums.Length; i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] == 1)\n return i;\n }\n return -1;\n}\n
worst_best_time_complexity.go/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfunc randomNumbers(n int) []int {\n nums := make([]int, n)\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for i := 0; i < n; i++ {\n nums[i] = i + 1\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n rand.Shuffle(len(nums), func(i, j int) {\n nums[i], nums[j] = nums[j], nums[i]\n })\n return nums\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfunc findOne(nums []int) int {\n for i := 0; i < len(nums); i++ {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if nums[i] == 1 {\n return i\n }\n }\n return -1\n}\n
worst_best_time_complexity.swift/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfunc randomNumbers(n: Int) -> [Int] {\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n var nums = Array(1 ... n)\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n nums.shuffle()\n return nums\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfunc findOne(nums: [Int]) -> Int {\n for i in nums.indices {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if nums[i] == 1 {\n return i\n }\n }\n return -1\n}\n
worst_best_time_complexity.js/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfunction randomNumbers(n) {\n const nums = Array(n);\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (let i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n for (let i = 0; i < n; i++) {\n const r = Math.floor(Math.random() * (i + 1));\n const temp = nums[i];\n nums[i] = nums[r];\n nums[r] = temp;\n }\n return nums;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfunction findOne(nums) {\n for (let i = 0; i < nums.length; i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] === 1) {\n return i;\n }\n }\n return -1;\n}\n
worst_best_time_complexity.ts/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfunction randomNumbers(n: number): number[] {\n const nums = Array(n);\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (let i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n for (let i = 0; i < n; i++) {\n const r = Math.floor(Math.random() * (i + 1));\n const temp = nums[i];\n nums[i] = nums[r];\n nums[r] = temp;\n }\n return nums;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfunction findOne(nums: number[]): number {\n for (let i = 0; i < nums.length; i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] === 1) {\n return i;\n }\n }\n return -1;\n}\n
worst_best_time_complexity.dart/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nList<int> randomNumbers(int n) {\n final nums = List.filled(n, 0);\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (var i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n nums.shuffle();\n\n return nums;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint findOne(List<int> nums) {\n for (var i = 0; i < nums.length; i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] == 1) return i;\n }\n\n return -1;\n}\n
worst_best_time_complexity.rs/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfn random_numbers(n: i32) -> Vec<i32> {\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n let mut nums = (1..=n).collect::<Vec<i32>>();\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n nums.shuffle(&mut thread_rng());\n nums\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfn find_one(nums: &[i32]) -> Option<usize> {\n for i in 0..nums.len() {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if nums[i] == 1 {\n return Some(i);\n }\n }\n None\n}\n
worst_best_time_complexity.c/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nint *randomNumbers(int n) {\n // \u5206\u914d\u5806\u533a\u5185\u5b58\uff08\u521b\u5efa\u4e00\u7ef4\u53ef\u53d8\u957f\u6570\u7ec4\uff1a\u6570\u7ec4\u4e2d\u5143\u7d20\u6570\u91cf\u4e3a n \uff0c\u5143\u7d20\u7c7b\u578b\u4e3a int \uff09\n int *nums = (int *)malloc(n * sizeof(int));\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (int i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n for (int i = n - 1; i > 0; i--) {\n int j = rand() % (i + 1);\n int temp = nums[i];\n nums[i] = nums[j];\n nums[j] = temp;\n }\n return nums;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint findOne(int *nums, int n) {\n for (int i = 0; i < n; i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] == 1)\n return i;\n }\n return -1;\n}\n
worst_best_time_complexity.zig// \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71\nfn randomNumbers(comptime n: usize) [n]i32 {\n var nums: [n]i32 = undefined;\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (&nums, 0..) |*num, i| {\n num.* = @as(i32, @intCast(i)) + 1;\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n const rand = std.crypto.random;\n rand.shuffle(i32, &nums);\n return nums;\n}\n\n// \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15\nfn findOne(nums: []i32) i32 {\n for (nums, 0..) |num, i| {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (num == 1) return @intCast(i);\n }\n return -1;\n}\n
It is worth stating that we rarely use the optimal time complexity in practice because it is usually only attainable with a small probability and may be somewhat misleading. whereas the worst time complexity is more practical because it gives a safe value for efficiency and allows us to use the algorithm with confidence.
From the above examples, it can be seen that the worst or best time complexity only occurs in \"special data distributions\", and the probability of these cases may be very small, which does not truly reflect the efficiency of the algorithm. In contrast, the average time complexity of can reflect the efficiency of the algorithm under random input data, which is denoted by the \\(\\Theta\\) notation.
For some algorithms, we can simply derive the average case under a random data distribution. For example, in the above example, since the input array is scrambled, the probability of an element \\(1\\) appearing at any index is equal, so the average number of loops of the algorithm is half of the length of the array \\(n / 2\\) , and the average time complexity is \\(\\Theta(n / 2) = \\Theta(n)\\) .
However, for more complex algorithms, calculating the average time complexity is often difficult because it is hard to analyze the overall mathematical expectation given the data distribution. In this case, we usually use the worst time complexity as a criterion for the efficiency of the algorithm.
Why do you rarely see the \\(\\Theta\\) symbol?
Perhaps because the \\(O\\) symbol is so catchy, we often use it to denote average time complexity. However, this practice is not standardized in the strict sense. In this book and other sources, if you encounter a statement like \"average time complexity \\(O(n)\\)\", please understand it as \\(\\Theta(n)\\).
"},{"location":"chapter_data_structure/","title":"Data Structure","text":"Abstract
Data structures are like a solid and varied framework.
It provides a blueprint for the orderly organization of data upon which algorithms can come alive.
"},{"location":"chapter_data_structure/classification_of_data_structure/","title":"Classification Of Data Structures","text":"Common data structures include arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. They can be divided into two categories: logical structure and physical structure.
"},{"location":"chapter_data_structure/classification_of_data_structure/#logical-structures-linear-and-non-linear","title":"Logical Structures: Linear And Non-linear","text":"Logical structures reveal logical relationships between data elements. In arrays and linked lists, data are arranged in sequential order, reflecting the linear relationship between data; while in trees, data are arranged hierarchically from the top down, showing the derived relationship between ancestors and descendants; and graphs are composed of nodes and edges, reflecting the complex network relationship.
As shown in the figure below, logical structures can further be divided into \"linear data structure\" and \"non-linear data structure\". Linear data structures are more intuitive, meaning that the data are arranged linearly in terms of logical relationships; non-linear data structures, on the other hand, are arranged non-linearly.
Non-linear data structures can be further divided into tree and graph structures.
When an algorithm is running, the data being processed is stored in memory. The figure below shows a computer memory module where each black square represents a memory space. We can think of the memory as a giant Excel sheet in which each cell can store data of a certain size.
The system accesses the data at the target location by means of a memory address. As shown in the figure below, the computer assigns a unique identifier to each cell in the table according to specific rules, ensuring that each memory space has a unique memory address. With these addresses, the program can access the data in memory.
Tip
It is worth noting that comparing memory to the Excel sheet is a simplified analogy. The actual memory working mechanism is more complicated, involving the concepts of address, space, memory management, cache mechanism, virtual and physical memory.
Memory is a shared resource for all programs, and when a block of memory is occupied by one program, it cannot be used by other programs at the same time. Therefore, considering memory resources is crucial in designing data structures and algorithms. For example, the algorithm's peak memory usage should not exceed the remaining free memory of the system; if there is a lack of contiguous memory blocks, then the data structure chosen must be able to be stored in non-contiguous memory blocks.
As shown in the figure below, Physical structure reflects the way data is stored in computer memory and it can be divided into consecutive space storage (arrays) and distributed space storage (linked lists). The physical structure determines how data is accessed, updated, added, deleted, etc. Logical and physical structure complement each other in terms of time efficiency and space efficiency.
It is worth stating that all data structures are implemented based on arrays, linked lists, or a combination of the two. For example, stacks and queues can be implemented using both arrays and linked lists; and implementations of hash tables may contain both arrays and linked lists.
Data structures based on arrays are also known as \"static data structures\", which means that such structures' length remains constant after initialization. In contrast, data structures based on linked lists are called \"dynamic data structures\", meaning that their length can be adjusted during program execution after initialization.
Tip
If you find it difficult to understand the physical structure, it is recommended that you read the next chapter, \"Arrays and Linked Lists,\" before reviewing this section.
"},{"location":"chapter_introduction/","title":"\u7b2c 1 \u7ae0 \u00a0 Introduction to Algorithms","text":"Abstract
A graceful maiden dances, intertwined with the data, her skirt swaying to the melody of algorithms.
She invites you to a dance, follow her steps, and enter the world of algorithms full of logic and beauty.
"},{"location":"chapter_introduction/#_1","title":"\u672c\u7ae0\u5185\u5bb9","text":"When we hear the word \"algorithm\", we naturally think of mathematics. However, many algorithms do not involve complex mathematics but rely more on basic logic, which is ubiquitous in our daily lives.
Before we formally discuss algorithms, an interesting fact is worth sharing: you have already learned many algorithms unconsciously and have become accustomed to applying them in your daily life. Below, I will give a few specific examples to prove this point.
Example 1: Looking Up a Dictionary. In a standard dictionary, each word corresponds to a phonetic transcription and the dictionary is organized alphabetically based on these transcriptions. Let's say we're looking for a word that begins with the letter \\(r\\). This is typically done in the following way:
1.
and 2.
until you find the page where the word begins with the letter \\(r\\).Figure 1-1 \u00a0 Dictionary search step
The skill of looking up a dictionary, essential for elementary school students, is actually the renowned binary search algorithm. Through the lens of data structures, we can view the dictionary as a sorted \"array\"; while from an algorithmic perspective, the series of operations in looking up a dictionary can be seen as \"binary search\".
Example 2: Organizing Playing Cards. When playing cards, we need to arrange the cards in ascending order each game, as shown in the following process.
2.
, each iteration involving insertion of one card from the unordered segment into the ordered portion, until all cards are appropriately ordered.Figure 1-2 \u00a0 Playing cards sorting process
The above method of organizing playing cards is essentially the \"insertion sort\" algorithm, which is very efficient for small datasets. Many programming languages' sorting library functions include insertion sort.
Example 3: Making Change. Suppose we buy goods worth \\(69\\) yuan at a supermarket and give the cashier \\(100\\) yuan, then the cashier needs to give us \\(31\\) yuan in change. They would naturally complete the thought process as shown below.
Figure 1-3 \u00a0 Change making process
In the aforementioned steps, at each stage, we make the optimal choice (utilizing the highest denomination possible), ultimately deriving at a feasible change-making approach. From the perspective of data structures and algorithms, this approach is essentially a \"greedy\" algorithm.
From preparing a dish to traversing interstellar realms, virtually every problem-solving endeavor relies on algorithms. The emergence of computers enables us to store data structures in memory and write code to call CPUs and GPUs to execute algorithms. Consequently, we can transfer real-life predicaments to computers, efficiently addressing a myriad of complex issues.
Tip
If concepts such as data structures, algorithms, arrays, and binary search still seem somewhat obsecure, I encourage you to continue reading. This book will gently guide you into the realm of understanding data structures and algorithms.
"},{"location":"chapter_introduction/summary/","title":"1.3 \u00a0 Summary","text":"An \"algorithm\" is a set of instructions or steps to solve a specific problem within a finite amount of time. It has the following characteristics:
A \"data structure\" is a way of organizing and storing data in a computer, with the following design goals:
Designing data structures is a balancing act, often requiring trade-offs. If you want to improve in one aspect, you often need to compromise in another. Here are two examples:
As shown in the diagram below, data structures and algorithms are highly related and closely integrated, specifically in the following three aspects:
Figure 1-4 \u00a0 Relationship between data structures and algorithms
Data structures and algorithms can be likened to a set of building blocks, as illustrated in the Figure 1-5 . A building block set includes numerous pieces, accompanied by detailed assembly instructions. Following these instructions step by step allows us to construct an intricate block model.
Figure 1-5 \u00a0 Assembling blocks
The detailed correspondence between the two is shown in the Table 1-1 .
Table 1-1 \u00a0 Comparing Data Structures and Algorithms to Building Blocks
Data Structures and Algorithms Building Blocks Input data Unassembled blocks Data structure Organization of blocks, including shape, size, connections, etc Algorithm A series of steps to assemble the blocks into the desired shape Output data Completed Block modelIt's worth noting that data structures and algorithms are independent of programming languages. For this reason, this book is able to provide implementations in multiple programming languages.
Conventional Abbreviation
In real-life discussions, we often refer to \"Data Structures and Algorithms\" simply as \"Algorithms\". For example, the well-known LeetCode algorithm problems actually test both data structure and algorithm knowledge.
"},{"location":"chapter_preface/","title":"\u7b2c 0 \u7ae0 \u00a0 Preface","text":"Abstract
Algorithms are like a beautiful symphony, with each line of code flowing like a rhythm.
May this book ring softly in your head, leaving a unique and profound melody.
"},{"location":"chapter_preface/#_1","title":"\u672c\u7ae0\u5185\u5bb9","text":"The aim of this project is to create an open source, free, novice-friendly introductory tutorial on data structures and algorithms.
If you are a beginner to algorithms, have never touched an algorithm before, or already have some experience brushing up on data structures and algorithms, and have a vague understanding of data structures and algorithms, repeatedly jumping sideways between what you can and can't do, then this book is just for you!
If you have already accumulated a certain amount of questions and are familiar with most of the question types, then this book can help you review and organize the algorithm knowledge system, and the repository source code can be used as a \"brushing tool library\" or \"algorithm dictionary\".
If you are an algorithm expert, we look forward to receiving your valuable suggestions or participate in the creation together.
precondition
You will need to have at least a basic knowledge of programming in any language and be able to read and write simple code.
"},{"location":"chapter_preface/about_the_book/#012-content-structure","title":"0.1.2 \u00a0 Content Structure","text":"The main contents of the book are shown in the Figure 0-1 .
Figure 0-1 \u00a0 Hello Algo content structure
"},{"location":"chapter_preface/about_the_book/#013-acknowledgements","title":"0.1.3 \u00a0 Acknowledgements","text":"During the creation of this book, I received help from many people, including but not limited to:
During the writing process, I read many textbooks and articles on data structures and algorithms. These works provide excellent models for this book and ensure the accuracy and quality of its contents. I would like to thank all my teachers and predecessors for their outstanding contributions!
This book promotes a hands-on approach to learning, and in this respect is heavily inspired by \"Hands-On Learning for Depth\". I highly recommend this excellent book to all readers.
A heartfelt thank you to my parents, it is your constant support and encouragement that gives me the opportunity to do this fun-filled thing.
"},{"location":"chapter_preface/suggestions/","title":"0.2 \u00a0 How To Read","text":"Tip
For the best reading experience, it is recommended that you read through this section.
"},{"location":"chapter_preface/suggestions/#021-conventions-of-style","title":"0.2.1 \u00a0 Conventions Of Style","text":"*
after the title are optional chapters with relatively difficult content. If you have limited time, it is advisable to skip them.\"double quotes\"
to avoid ambiguity.\" \"
in parentheses, e.g. \"array array\"
. It is recommended to memorize them for reading the literature.\"\"\"Header comments for labeling functions, classes, test samples, etc.\"\"\"\"\n\n# Content comments for detailed code solutions\n\n\"\"\"\nmulti-line\nmarginal notes\n\"\"\"\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
// Header comments for labeling functions, classes, test samples, etc.\n\n// Content comments for detailed code solutions.\n\n// Multi-line\n// Annotation\n
"},{"location":"chapter_preface/suggestions/#022-learn-efficiently-in-animated-graphic-solutions","title":"0.2.2 \u00a0 Learn Efficiently In Animated Graphic Solutions","text":"Compared with text, videos and pictures have a higher degree of information density and structure and are easier to understand. In this book, key and difficult knowledge will be presented mainly in the form of animations and graphs, while the text serves as an explanation and supplement to the animations and graphs.
If, while reading the book, you find that a particular paragraph provides an animation or a graphic solution as shown below, please use the figure as the primary source and the text as a supplement and synthesize the two to understand the content.
Figure 0-2 \u00a0 Example animation
"},{"location":"chapter_preface/suggestions/#023-deeper-understanding-in-code-practice","title":"0.2.3 \u00a0 Deeper Understanding In Code Practice","text":"The companion code for this book is hosted in the GitHub repository. As shown in the Figure 0-3 , the source code is accompanied by test samples that can be run with a single click.
If time permits, it is recommended that you refer to the code and knock it through on your own. If you have limited time to study, please read through and run all the code at least once.
The process of writing code is often more rewarding than reading it. Learning by doing is really learning.
Figure 0-3 \u00a0 Running code example
The preliminaries for running the code are divided into three main steps.
Step 1: Install the local programming environment. Please refer to Appendix Tutorial for installation, or skip this step if already installed.
Step 2: Clone or download the code repository. If Git is already installed, you can clone this repository with the following command.
git clone https://github.com/krahets/hello-algo.git\n
Of course, you can also in the location shown in the Figure 0-4 , click \"Download ZIP\" directly download the code zip, and then in the local solution.
Figure 0-4 \u00a0 Clone repository with download code
Step 3: Run the source code. As shown in the Figure 0-5 , for the code block labeled with the file name at the top, we can find the corresponding source code file in the codes
folder of the repository. The source code files can be run with a single click, which will help you save unnecessary debugging time and allow you to focus on what you are learning.
Figure 0-5 \u00a0 Code block with corresponding source file
"},{"location":"chapter_preface/suggestions/#024-growing-together-in-questioning-and-discussion","title":"0.2.4 \u00a0 Growing Together In Questioning And Discussion","text":"While reading this book, please don't skip over the points that you didn't learn. Feel free to ask your questions in the comment section. We will be happy to answer them and can usually respond within two days.
As you can see in the Figure 0-6 , each post comes with a comment section at the bottom. I hope you'll pay more attention to the comments section. On the one hand, you can learn about the problems that people encounter, so as to check the gaps and stimulate deeper thinking. On the other hand, we expect you to generously answer other partners' questions, share your insights, and help others improve.
Figure 0-6 \u00a0 Example of comment section
"},{"location":"chapter_preface/suggestions/#025-algorithm-learning-route","title":"0.2.5 \u00a0 Algorithm Learning Route","text":"From a general point of view, we can divide the process of learning data structures and algorithms into three stages.
As shown in the Figure 0-7 , this book mainly covers \"Phase 1\" and is designed to help you start Phase 2 and 3 more efficiently.
Figure 0-7 \u00a0 algorithm learning route
"},{"location":"chapter_preface/summary/","title":"0.3 \u00a0 Summary","text":"Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code
Dive In Clone Repo Get PDF
The English edition is brewing...
Feel free to engage in Chinese-to-English translation and pull request review! For guidelines, please see #914.
EndorsementsQuote
\"An easy-to-understand book on data structures and algorithms, which guides readers to learn by minds-on and hands-on. Strongly recommended for algorithm beginners!\"
\u2014\u2014 Junhui Deng, Professor of Computer Science, Tsinghua University
Quote
\"If I had 'Hello Algo' when I was learning data structures and algorithms, it would have been 10 times easier!\"
\u2014\u2014 Mu Li, Senior Principal Scientist, Amazon
Animated illustrationsEasy to understandSmooth learning curve
\"A picture is worth a thousand words.\"
Off-the-Shelf CodeMulti programming languagesRun with one click
\"Talk is cheap. Show me the code.\"
Learning TogetherDiscussion and questions welcomeReaders progress together
\"Chase the wind and moon, never stopping\"
\"Beyond the plains, there are spring mountains\"
PrefaceTwo years ago, I shared the \"Sword Offer\" series of problem solutions on LeetCode, which received much love and support from many students. During my interactions with readers, the most common question I encountered was \"How to get started with algorithms.\" Gradually, I developed a deep interest in this question.
Blindly solving problems seems to be the most popular method, being simple, direct, and effective. However, problem-solving is like playing a \"Minesweeper\" game, where students with strong self-learning abilities can successfully clear the mines one by one, but those with insufficient foundations may end up bruised from explosions, retreating step by step in frustration. Thoroughly reading textbooks is also common, but for students aiming for job applications, the energy consumed by graduation, resume submissions, and preparing for written tests and interviews makes tackling thick books a daunting challenge.
If you are facing similar troubles, then you are lucky to have found this book. This book is my answer to this question, not necessarily the best solution, but at least an active attempt. Although this book won't directly land you an Offer, it will guide you through the \"knowledge map\" of data structures and algorithms, help you understand the shape, size, and distribution of different \"mines,\" and equip you with various \"demining methods.\" With these skills, I believe you can more comfortably solve problems and read literature, gradually building a complete knowledge system.
I deeply agree with Professor Feynman's saying: \"Knowledge isn't free. You have to pay attention.\" In this sense, this book is not entirely \"free.\" To not disappoint the precious \"attention\" you pay to this book, I will do my utmost, investing the greatest \"attention\" to complete the creation of this book.
AuthorYudong Jin(Krahets), Senior Algorithm Engineer in a top tech company, Master's degree from Shanghai Jiao Tong University. The highest-read blogger across the entire LeetCode, his published \"Illustration of Algorithm Data Structures\" has been subscribed to by over 300k.
ContributionThis book is continuously improved with the joint efforts of many contributors from the open-source community. Thanks to each writer who invested their time and energy, listed in the order generated by GitHub:
The code review work for this book was completed by Gonglja, gvenusleo, hpstory, justin\u2010tse, krahets, night-cruise, nuomi1, Reanon, and sjinzh (listed in alphabetical order). Thanks to them for their time and effort, ensuring the standardization and uniformity of the code in various languages.
GongljaC, C++ gvenusleoDart hpstoryC# justin-tseJS, TS krahetsJava, Python night-cruiseRust nuomi1Swift ReanonGo, C sjinzhRust, Zig"},{"location":"chapter_computational_complexity/","title":"\u7b2c 2 \u7ae0 \u00a0 Complexity Analysis","text":"Abstract
Complexity analysis is like a space-time guide in the vast universe of algorithms.
It leads us to explore deeply in the dimensions of time and space, in search of more elegant solutions.
"},{"location":"chapter_computational_complexity/#_1","title":"\u672c\u7ae0\u5185\u5bb9","text":"In data structures and algorithms, it is common to repeat a task, which is closely related to the complexity of the algorithm. There are two basic program structures that we usually use to repeat a task: iteration and recursion.
"},{"location":"chapter_computational_complexity/iteration_and_recursion/#221-iteration","title":"2.2.1 \u00a0 Iteration","text":"An \"iteration iteration\" is a control structure that repeats a task. In iteration, a program repeats the execution of a piece of code until the condition is no longer satisfied.
"},{"location":"chapter_computational_complexity/iteration_and_recursion/#1-for-loops","title":"1. \u00a0 For Loops","text":"for
loops are one of the most common forms of iteration, suitable when the number of iterations is known in advance.
The following function implements the summation \\(1 + 2 + \\dots + n\\) based on a for
loop, and the result is recorded using the variable res
. Note that range(a, b)
in Python corresponds to a \"left-closed-right-open\" interval, which is traversed in the range \\(a, a + 1, \\dots, b-1\\).
def for_loop(n: int) -> int:\n \"\"\"for \u5faa\u73af\"\"\"\n res = 0\n # \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for i in range(1, n + 1):\n res += i\n return res\n
iteration.cpp/* for \u5faa\u73af */\nint forLoop(int n) {\n int res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (int i = 1; i <= n; ++i) {\n res += i;\n }\n return res;\n}\n
iteration.java/* for \u5faa\u73af */\nint forLoop(int n) {\n int res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n res += i;\n }\n return res;\n}\n
iteration.cs/* for \u5faa\u73af */\nint ForLoop(int n) {\n int res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n res += i;\n }\n return res;\n}\n
iteration.go/* for \u5faa\u73af */\nfunc forLoop(n int) int {\n res := 0\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for i := 1; i <= n; i++ {\n res += i\n }\n return res\n}\n
iteration.swift/* for \u5faa\u73af */\nfunc forLoop(n: Int) -> Int {\n var res = 0\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for i in 1 ... n {\n res += i\n }\n return res\n}\n
iteration.js/* for \u5faa\u73af */\nfunction forLoop(n) {\n let res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (let i = 1; i <= n; i++) {\n res += i;\n }\n return res;\n}\n
iteration.ts/* for \u5faa\u73af */\nfunction forLoop(n: number): number {\n let res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (let i = 1; i <= n; i++) {\n res += i;\n }\n return res;\n}\n
iteration.dart/* for \u5faa\u73af */\nint forLoop(int n) {\n int res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n res += i;\n }\n return res;\n}\n
iteration.rs/* for \u5faa\u73af */\nfn for_loop(n: i32) -> i32 {\n let mut res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for i in 1..=n {\n res += i;\n }\n res\n} \n
iteration.c/* for \u5faa\u73af */\nint forLoop(int n) {\n int res = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n res += i;\n }\n return res;\n}\n
iteration.zig// for \u5faa\u73af\nfn forLoop(n: usize) i32 {\n var res: i32 = 0;\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for (1..n+1) |i| {\n res = res + @as(i32, @intCast(i));\n }\n return res;\n} \n
The Figure 2-1 shows the flow block diagram of this summation function.
Figure 2-1 \u00a0 Flow block diagram of the summation function
The number of operations in this summation function is proportional to the size of the input data \\(n\\), or a \"linear relationship\". In fact, time complexity describes this \"linear relationship\". This is described in more detail in the next section.
"},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-while-loop","title":"2. \u00a0 While Loop","text":"Similar to a for
loop, a while
loop is a way to implement iteration. In a while
loop, the program first checks the condition at each turn, and if the condition is true, it continues, otherwise it ends the loop.
Below, we use a while
loop to realize the summation \\(1 + 2 + \\dots + n\\) .
def while_loop(n: int) -> int:\n \"\"\"while \u5faa\u73af\"\"\"\n res = 0\n i = 1 # \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n # \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while i <= n:\n res += i\n i += 1 # \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n return res\n
iteration.cpp/* while \u5faa\u73af */\nint whileLoop(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i++; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.java/* while \u5faa\u73af */\nint whileLoop(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i++; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.cs/* while \u5faa\u73af */\nint WhileLoop(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i += 1; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.go/* while \u5faa\u73af */\nfunc whileLoop(n int) int {\n res := 0\n // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n i := 1\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n for i <= n {\n res += i\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++\n }\n return res\n}\n
iteration.swift/* while \u5faa\u73af */\nfunc whileLoop(n: Int) -> Int {\n var res = 0\n var i = 1 // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while i <= n {\n res += i\n i += 1 // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res\n}\n
iteration.js/* while \u5faa\u73af */\nfunction whileLoop(n) {\n let res = 0;\n let i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i++; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.ts/* while \u5faa\u73af */\nfunction whileLoop(n: number): number {\n let res = 0;\n let i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i++; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.dart/* while \u5faa\u73af */\nint whileLoop(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i++; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.rs/* while \u5faa\u73af */\nfn while_loop(n: i32) -> i32 {\n let mut res = 0;\n let mut i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while i <= n {\n res += i;\n i += 1; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n res\n}\n
iteration.c/* while \u5faa\u73af */\nint whileLoop(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += i;\n i++; // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n }\n return res;\n}\n
iteration.zig// while \u5faa\u73af\nfn whileLoop(n: i32) i32 {\n var res: i32 = 0;\n var i: i32 = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, ..., n-1, n\n while (i <= n) {\n res += @intCast(i);\n i += 1;\n }\n return res;\n}\n
In while
loops, since the steps of initializing and updating condition variables are independent of the loop structure, it has more degrees of freedom than for
loops.
For example, in the following code, the condition variable \\(i\\) is updated twice per round, which is not convenient to implement with a for
loop.
def while_loop_ii(n: int) -> int:\n \"\"\"while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09\"\"\"\n res = 0\n i = 1 # \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n # \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while i <= n:\n res += i\n # \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i += 1\n i *= 2\n return res\n
iteration.cpp/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nint whileLoopII(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++;\n i *= 2;\n }\n return res;\n}\n
iteration.java/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nint whileLoopII(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++;\n i *= 2;\n }\n return res;\n}\n
iteration.cs/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nint WhileLoopII(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 2, 4, 5...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i += 1; \n i *= 2;\n }\n return res;\n}\n
iteration.go/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nfunc whileLoopII(n int) int {\n res := 0\n // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n i := 1\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n for i <= n {\n res += i\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++\n i *= 2\n }\n return res\n}\n
iteration.swift/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nfunc whileLoopII(n: Int) -> Int {\n var res = 0\n var i = 1 // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while i <= n {\n res += i\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i += 1\n i *= 2\n }\n return res\n}\n
iteration.js/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nfunction whileLoopII(n) {\n let res = 0;\n let i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++;\n i *= 2;\n }\n return res;\n}\n
iteration.ts/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nfunction whileLoopII(n: number): number {\n let res = 0;\n let i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++;\n i *= 2;\n }\n return res;\n}\n
iteration.dart/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nint whileLoopII(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++;\n i *= 2;\n }\n return res;\n}\n
iteration.rs/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nfn while_loop_ii(n: i32) -> i32 {\n let mut res = 0;\n let mut i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while i <= n {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i += 1;\n i *= 2;\n }\n res\n}\n
iteration.c/* while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09 */\nint whileLoopII(int n) {\n int res = 0;\n int i = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += i;\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i++;\n i *= 2;\n }\n return res;\n}\n
iteration.zig// while \u5faa\u73af\uff08\u4e24\u6b21\u66f4\u65b0\uff09\nfn whileLoopII(n: i32) i32 {\n var res: i32 = 0;\n var i: i32 = 1; // \u521d\u59cb\u5316\u6761\u4ef6\u53d8\u91cf\n // \u5faa\u73af\u6c42\u548c 1, 4, 10, ...\n while (i <= n) {\n res += @intCast(i);\n // \u66f4\u65b0\u6761\u4ef6\u53d8\u91cf\n i += 1;\n i *= 2;\n }\n return res;\n}\n
Overall, for
loops have more compact code and while
loops are more flexible, and both can implement iteration structures. The choice of which one to use should be based on the needs of the particular problem.
We can nest one loop structure inside another, using the for
loop as an example:
def nested_for_loop(n: int) -> str:\n \"\"\"\u53cc\u5c42 for \u5faa\u73af\"\"\"\n res = \"\"\n # \u5faa\u73af i = 1, 2, ..., n-1, n\n for i in range(1, n + 1):\n # \u5faa\u73af j = 1, 2, ..., n-1, n\n for j in range(1, n + 1):\n res += f\"({i}, {j}), \"\n return res\n
iteration.cpp/* \u53cc\u5c42 for \u5faa\u73af */\nstring nestedForLoop(int n) {\n ostringstream res;\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (int i = 1; i <= n; ++i) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (int j = 1; j <= n; ++j) {\n res << \"(\" << i << \", \" << j << \"), \";\n }\n }\n return res.str();\n}\n
iteration.java/* \u53cc\u5c42 for \u5faa\u73af */\nString nestedForLoop(int n) {\n StringBuilder res = new StringBuilder();\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (int j = 1; j <= n; j++) {\n res.append(\"(\" + i + \", \" + j + \"), \");\n }\n }\n return res.toString();\n}\n
iteration.cs/* \u53cc\u5c42 for \u5faa\u73af */\nstring NestedForLoop(int n) {\n StringBuilder res = new();\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (int j = 1; j <= n; j++) {\n res.Append($\"({i}, {j}), \");\n }\n }\n return res.ToString();\n}\n
iteration.go/* \u53cc\u5c42 for \u5faa\u73af */\nfunc nestedForLoop(n int) string {\n res := \"\"\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for i := 1; i <= n; i++ {\n for j := 1; j <= n; j++ {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n res += fmt.Sprintf(\"(%d, %d), \", i, j)\n }\n }\n return res\n}\n
iteration.swift/* \u53cc\u5c42 for \u5faa\u73af */\nfunc nestedForLoop(n: Int) -> String {\n var res = \"\"\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for i in 1 ... n {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for j in 1 ... n {\n res.append(\"(\\(i), \\(j)), \")\n }\n }\n return res\n}\n
iteration.js/* \u53cc\u5c42 for \u5faa\u73af */\nfunction nestedForLoop(n) {\n let res = '';\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (let i = 1; i <= n; i++) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (let j = 1; j <= n; j++) {\n res += `(${i}, ${j}), `;\n }\n }\n return res;\n}\n
iteration.ts/* \u53cc\u5c42 for \u5faa\u73af */\nfunction nestedForLoop(n: number): string {\n let res = '';\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (let i = 1; i <= n; i++) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (let j = 1; j <= n; j++) {\n res += `(${i}, ${j}), `;\n }\n }\n return res;\n}\n
iteration.dart/* \u53cc\u5c42 for \u5faa\u73af */\nString nestedForLoop(int n) {\n String res = \"\";\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (int j = 1; j <= n; j++) {\n res += \"($i, $j), \";\n }\n }\n return res;\n}\n
iteration.rs/* \u53cc\u5c42 for \u5faa\u73af */\nfn nested_for_loop(n: i32) -> String {\n let mut res = vec![];\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for i in 1..=n {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for j in 1..=n {\n res.push(format!(\"({}, {}), \", i, j));\n }\n }\n res.join(\"\")\n}\n
iteration.c/* \u53cc\u5c42 for \u5faa\u73af */\nchar *nestedForLoop(int n) {\n // n * n \u4e3a\u5bf9\u5e94\u70b9\u6570\u91cf\uff0c\"(i, j), \" \u5bf9\u5e94\u5b57\u7b26\u4e32\u957f\u6700\u5927\u4e3a 6+10*2\uff0c\u52a0\u4e0a\u6700\u540e\u4e00\u4e2a\u7a7a\u5b57\u7b26 \\0 \u7684\u989d\u5916\u7a7a\u95f4\n int size = n * n * 26 + 1;\n char *res = malloc(size * sizeof(char));\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (int i = 1; i <= n; i++) {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (int j = 1; j <= n; j++) {\n char tmp[26];\n snprintf(tmp, sizeof(tmp), \"(%d, %d), \", i, j);\n strncat(res, tmp, size - strlen(res) - 1);\n }\n }\n return res;\n}\n
iteration.zig// \u53cc\u5c42 for \u5faa\u73af\nfn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 {\n var res = std.ArrayList(u8).init(allocator);\n defer res.deinit();\n var buffer: [20]u8 = undefined;\n // \u5faa\u73af i = 1, 2, ..., n-1, n\n for (1..n+1) |i| {\n // \u5faa\u73af j = 1, 2, ..., n-1, n\n for (1..n+1) |j| {\n var _str = try std.fmt.bufPrint(&buffer, \"({d}, {d}), \", .{i, j});\n try res.appendSlice(_str);\n }\n }\n return res.toOwnedSlice();\n}\n
The Figure 2-2 gives the block diagram of the flow of this nested loop.
Figure 2-2 \u00a0 Block diagram of the flow of nested loops
In this case, the number of operations of the function is proportional to \\(n^2\\), or the algorithm's running time is \"squared\" to the size of the input data \\(n\\).
We can continue to add nested loops, and each nest is a \"dimension up\", which will increase the time complexity to \"cubic relations\", \"quadratic relations\", and so on.
"},{"location":"chapter_computational_complexity/iteration_and_recursion/#222-recursion","title":"2.2.2 \u00a0 Recursion","text":"\"Recursion recursion is an algorithmic strategy to solve a problem by calling the function itself. It consists of two main phases.
And from an implementation point of view, recursion code contains three main elements.
Observe the following code, we only need to call the function recur(n)
, and the calculation of \\(1 + 2 + \\dots + n\\) is done:
def recur(n: int) -> int:\n \"\"\"\u9012\u5f52\"\"\"\n # \u7ec8\u6b62\u6761\u4ef6\n if n == 1:\n return 1\n # \u9012\uff1a\u9012\u5f52\u8c03\u7528\n res = recur(n - 1)\n # \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res\n
recursion.cpp/* \u9012\u5f52 */\nint recur(int n) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 1)\n return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n int res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.java/* \u9012\u5f52 */\nint recur(int n) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 1)\n return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n int res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.cs/* \u9012\u5f52 */\nint Recur(int n) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 1)\n return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n int res = Recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.go/* \u9012\u5f52 */\nfunc recur(n int) int {\n // \u7ec8\u6b62\u6761\u4ef6\n if n == 1 {\n return 1\n }\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n res := recur(n - 1)\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res\n}\n
recursion.swift/* \u9012\u5f52 */\nfunc recur(n: Int) -> Int {\n // \u7ec8\u6b62\u6761\u4ef6\n if n == 1 {\n return 1\n }\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n let res = recur(n: n - 1)\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res\n}\n
recursion.js/* \u9012\u5f52 */\nfunction recur(n) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n === 1) return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n const res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.ts/* \u9012\u5f52 */\nfunction recur(n: number): number {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n === 1) return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n const res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.dart/* \u9012\u5f52 */\nint recur(int n) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 1) return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n int res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.rs/* \u9012\u5f52 */\nfn recur(n: i32) -> i32 {\n // \u7ec8\u6b62\u6761\u4ef6\n if n == 1 {\n return 1;\n }\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n let res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n n + res\n}\n
recursion.c/* \u9012\u5f52 */\nint recur(int n) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 1)\n return 1;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n int res = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
recursion.zig// \u9012\u5f52\u51fd\u6570\nfn recur(n: i32) i32 {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 1) {\n return 1;\n }\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n var res: i32 = recur(n - 1);\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n return n + res;\n}\n
The Figure 2-3 shows the recursion of the function.
Figure 2-3 \u00a0 Recursion process for the summation function
Although iteration and recursion can yield the same results from a computational point of view, they represent two completely different paradigms for thinking about and solving problems.
As an example of the above summation function, set the problem \\(f(n) = 1 + 2 + \\dots + n\\) .
Each time a recursion function calls itself, the system allocates memory for the newly opened function to store local variables, call addresses, other information, and so on. This results in two things.
As shown in the Figure 2-4 , before the termination condition is triggered, there are \\(n\\) unreturned recursion functions at the same time, with a recursion depth of \\(n\\) .
Figure 2-4 \u00a0 Recursion call depth
In practice, the depth of recursion allowed by a programming language is usually limited, and too deep a recursion may result in a stack overflow error.
"},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-tail-recursion","title":"2. \u00a0 Tail Recursion","text":"Interestingly, if a function makes a recursion call only at the last step before returning, the function can be optimized by the compiler or interpreter to be comparable to iteration in terms of space efficiency. This situation is called \"tail recursion tail recursion\".
In the case of calculating \\(1 + 2 + \\dots + n\\), for example, we can implement tail recursion by setting the result variable res
as a function parameter.
def tail_recur(n, res):\n \"\"\"\u5c3e\u9012\u5f52\"\"\"\n # \u7ec8\u6b62\u6761\u4ef6\n if n == 0:\n return res\n # \u5c3e\u9012\u5f52\u8c03\u7528\n return tail_recur(n - 1, res + n)\n
recursion.cpp/* \u5c3e\u9012\u5f52 */\nint tailRecur(int n, int res) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 0)\n return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
recursion.java/* \u5c3e\u9012\u5f52 */\nint tailRecur(int n, int res) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 0)\n return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
recursion.cs/* \u5c3e\u9012\u5f52 */\nint TailRecur(int n, int res) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 0)\n return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return TailRecur(n - 1, res + n);\n}\n
recursion.go/* \u5c3e\u9012\u5f52 */\nfunc tailRecur(n int, res int) int {\n // \u7ec8\u6b62\u6761\u4ef6\n if n == 0 {\n return res\n }\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n-1, res+n)\n}\n
recursion.swift/* \u5c3e\u9012\u5f52 */\nfunc tailRecur(n: Int, res: Int) -> Int {\n // \u7ec8\u6b62\u6761\u4ef6\n if n == 0 {\n return res\n }\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n: n - 1, res: res + n)\n}\n
recursion.js/* \u5c3e\u9012\u5f52 */\nfunction tailRecur(n, res) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n === 0) return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
recursion.ts/* \u5c3e\u9012\u5f52 */\nfunction tailRecur(n: number, res: number): number {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n === 0) return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
recursion.dart/* \u5c3e\u9012\u5f52 */\nint tailRecur(int n, int res) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 0) return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
recursion.rs/* \u5c3e\u9012\u5f52 */\nfn tail_recur(n: i32, res: i32) -> i32 {\n // \u7ec8\u6b62\u6761\u4ef6\n if n == 0 {\n return res;\n }\n // \u5c3e\u9012\u5f52\u8c03\u7528\n tail_recur(n - 1, res + n)\n}\n
recursion.c/* \u5c3e\u9012\u5f52 */\nint tailRecur(int n, int res) {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 0)\n return res;\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
recursion.zig// \u5c3e\u9012\u5f52\u51fd\u6570\nfn tailRecur(n: i32, res: i32) i32 {\n // \u7ec8\u6b62\u6761\u4ef6\n if (n == 0) {\n return res;\n }\n // \u5c3e\u9012\u5f52\u8c03\u7528\n return tailRecur(n - 1, res + n);\n}\n
The execution of tail recursion is shown in the Figure 2-5 . Comparing normal recursion and tail recursion, the execution point of the summation operation is different.
Figure 2-5 \u00a0 tail recursion process
Tip
Note that many compilers or interpreters do not support tail recursion optimization. For example, Python does not support tail recursion optimization by default, so even if a function is tail recursive, you may still encounter stack overflow problems.
"},{"location":"chapter_computational_complexity/iteration_and_recursion/#3-recursion-tree","title":"3. \u00a0 Recursion Tree","text":"When dealing with algorithmic problems related to divide and conquer, recursion is often more intuitive and easier to read than iteration. Take the Fibonacci sequence as an example.
Question
Given a Fibonacci series \\(0, 1, 1, 2, 3, 5, 8, 13, \\dots\\) , find the \\(n\\)th number of the series.
Let the \\(n\\)th number of the Fibonacci series be \\(f(n)\\) , which leads to two easy conclusions.
Recursion code can be written by making recursion calls according to the recursion relationship, using the first two numbers as termination conditions. Call fib(n)
to get the \\(n\\)th number of the Fibonacci series.
def fib(n: int) -> int:\n \"\"\"\u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52\"\"\"\n # \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if n == 1 or n == 2:\n return n - 1\n # \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n res = fib(n - 1) + fib(n - 2)\n # \u8fd4\u56de\u7ed3\u679c f(n)\n return res\n
recursion.cpp/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nint fib(int n) {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n int res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.java/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nint fib(int n) {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n int res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.cs/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nint Fib(int n) {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n int res = Fib(n - 1) + Fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.go/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nfunc fib(n int) int {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if n == 1 || n == 2 {\n return n - 1\n }\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n res := fib(n-1) + fib(n-2)\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res\n}\n
recursion.swift/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nfunc fib(n: Int) -> Int {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if n == 1 || n == 2 {\n return n - 1\n }\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n let res = fib(n: n - 1) + fib(n: n - 2)\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res\n}\n
recursion.js/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nfunction fib(n) {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n === 1 || n === 2) return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n const res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.ts/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nfunction fib(n: number): number {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n === 1 || n === 2) return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n const res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.dart/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nint fib(int n) {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2) return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n int res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.rs/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nfn fib(n: i32) -> i32 {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if n == 1 || n == 2 {\n return n - 1;\n }\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n let res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c\n res\n}\n
recursion.c/* \u6590\u6ce2\u90a3\u5951\u6570\u5217\uff1a\u9012\u5f52 */\nint fib(int n) {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1;\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n int res = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
recursion.zig// \u6590\u6ce2\u90a3\u5951\u6570\u5217\nfn fib(n: i32) i32 {\n // \u7ec8\u6b62\u6761\u4ef6 f(1) = 0, f(2) = 1\n if (n == 1 or n == 2) {\n return n - 1;\n }\n // \u9012\u5f52\u8c03\u7528 f(n) = f(n-1) + f(n-2)\n var res: i32 = fib(n - 1) + fib(n - 2);\n // \u8fd4\u56de\u7ed3\u679c f(n)\n return res;\n}\n
Looking at the above code, we have recursively called two functions within a function, this means that from one call, two call branches are created. As shown in the Figure 2-6 , this recursion will result in a recursion tree with the number of levels \\(n\\).
Figure 2-6 \u00a0 Recursion tree for Fibonacci series
Essentially, recursion embodies the paradigm of \"breaking down a problem into smaller sub-problems\", and this divide and conquer strategy is essential.
To summarize the above, as shown in the Table 2-1 , iteration and recursion differ in implementation, performance and applicability.
Table 2-1 \u00a0 Comparison of iteration and recursion features
iteration recursion implementation circular structure function call itself time-efficient typically efficient, no function call overhead overhead on every function call Memory Usage Usually uses a fixed size of memory space Cumulative function calls may use a lot of stack frame space Applicable Problems For simple cyclic tasks, code is intuitive and readable For sub-problem decomposition, such as trees, graphs, divide and conquer, backtracking, etc., the code structure is concise and clearTip
If you find the following solutions difficult to understand, you can review them after reading the \"Stack\" chapter.
So what is the intrinsic connection between iteration and recursion? In the case of the recursive function described above, the summing operation takes place in the \"return\" phase of the recursion. This means that the function that is initially called is actually the last to complete its summing operation, This mechanism works in the same way as the stack's \"first in, last out\" principle.
In fact, recursion terms like \"call stack\" and \"stack frame space\" already imply a close relationship between recursion and the stack.
Thus, we can use an explicit stack to model the behavior of the call stack, thus transforming recursion into an iteration form:
PythonC++JavaC#GoSwiftJSTSDartRustCZig recursion.pydef for_loop_recur(n: int) -> int:\n \"\"\"\u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52\"\"\"\n # \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n stack = []\n res = 0\n # \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for i in range(n, 0, -1):\n # \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.append(i)\n # \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while stack:\n # \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.pop()\n # res = 1+2+3+...+n\n return res\n
recursion.cpp/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nint forLoopRecur(int n) {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n stack<int> stack;\n int res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (int i = n; i > 0; i--) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.push(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (!stack.empty()) {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.top();\n stack.pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.java/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nint forLoopRecur(int n) {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n Stack<Integer> stack = new Stack<>();\n int res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (int i = n; i > 0; i--) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.push(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (!stack.isEmpty()) {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.cs/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nint ForLoopRecur(int n) {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n Stack<int> stack = new();\n int res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (int i = n; i > 0; i--) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.Push(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (stack.Count > 0) {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.Pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.go/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nfunc forLoopRecur(n int) int {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n stack := list.New()\n res := 0\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for i := n; i > 0; i-- {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.PushBack(i)\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n for stack.Len() != 0 {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.Back().Value.(int)\n stack.Remove(stack.Back())\n }\n // res = 1+2+3+...+n\n return res\n}\n
recursion.swift/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nfunc forLoopRecur(n: Int) -> Int {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n var stack: [Int] = []\n var res = 0\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for i in stride(from: n, to: 0, by: -1) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.append(i)\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while !stack.isEmpty {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.removeLast()\n }\n // res = 1+2+3+...+n\n return res\n}\n
recursion.js/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nfunction forLoopRecur(n) {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n const stack = [];\n let res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (let i = 1; i <= n; i++) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.push(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (stack.length) { \n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.ts/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nfunction forLoopRecur(n: number): number {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808 \n const stack: number[] = [];\n let res: number = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (let i = 1; i <= n; i++) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.push(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (stack.length) { \n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.dart/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nint forLoopRecur(int n) {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n List<int> stack = [];\n int res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (int i = n; i > 0; i--) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.add(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (!stack.isEmpty) {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.removeLast();\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.rs/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nfn for_loop_recur(n: i32) -> i32 {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n let mut stack = Vec::new();\n let mut res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for i in (1..=n).rev() {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack.push(i);\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while !stack.is_empty() {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack.pop().unwrap();\n }\n // res = 1+2+3+...+n\n res\n}\n
recursion.c/* \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52 */\nint forLoopRecur(int n) {\n int stack[1000]; // \u501f\u52a9\u4e00\u4e2a\u5927\u6570\u7ec4\u6765\u6a21\u62df\u6808\n int top = -1; // \u6808\u9876\u7d22\u5f15\n int res = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n for (int i = n; i > 0; i--) {\n // \u901a\u8fc7\u201c\u5165\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u9012\u201d\n stack[1 + top++] = i;\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n while (top >= 0) {\n // \u901a\u8fc7\u201c\u51fa\u6808\u64cd\u4f5c\u201d\u6a21\u62df\u201c\u5f52\u201d\n res += stack[top--];\n }\n // res = 1+2+3+...+n\n return res;\n}\n
recursion.zig// \u4f7f\u7528\u8fed\u4ee3\u6a21\u62df\u9012\u5f52\nfn forLoopRecur(comptime n: i32) i32 {\n // \u4f7f\u7528\u4e00\u4e2a\u663e\u5f0f\u7684\u6808\u6765\u6a21\u62df\u7cfb\u7edf\u8c03\u7528\u6808\n var stack: [n]i32 = undefined;\n var res: i32 = 0;\n // \u9012\uff1a\u9012\u5f52\u8c03\u7528\n var i: usize = n;\n while (i > 0) {\n stack[i - 1] = @intCast(i);\n i -= 1;\n }\n // \u5f52\uff1a\u8fd4\u56de\u7ed3\u679c\n var index: usize = n;\n while (index > 0) {\n index -= 1;\n res += stack[index];\n }\n // res = 1+2+3+...+n\n return res;\n}\n
Observing the code above, it becomes more complex when recursion is converted to iteration. Although iteration and recursion can be converted to each other in many cases, it is not always worth doing so for two reasons.
In short, the choice of iteration or recursion depends on the nature of the particular problem. In programming practice, it is crucial to weigh the advantages and disadvantages of both and choose the appropriate method based on the context.
"},{"location":"chapter_computational_complexity/performance_evaluation/","title":"2.1 \u00a0 Evaluation Of Algorithm Efficiency","text":"In algorithm design, we aim to achieve two goals in succession:
In other words, once the ability to solve the problem is established, the efficiency of the algorithm emerges as the main benchmark for assessing its quality, which includes the following two aspects.
In short, our goal is to design data structures and algorithms that are both \"fast and economical\". Effectively evaluating algorithm efficiency is crucial, as it allows for the comparison of different algorithms and guides the design and optimization process.
There are mainly two approaches for assessing efficiency: practical testing and theoretical estimation.
"},{"location":"chapter_computational_complexity/performance_evaluation/#211-practical-testing","title":"2.1.1 \u00a0 Practical Testing","text":"Let's consider a scenario where we have two algorithms, A
and B
, both capable of solving the same problem. To compare their efficiency, the most direct method is to use a computer to run both algorithms while monitoring and recording their execution time and memory usage. This approach provides a realistic assessment of their performance, but it also has significant limitations.
On one hand, it's challenging to eliminate the interference of the test environment. Hardware configurations can significantly affect the performance of algorithms. For instance, on one computer, Algorithm A
might run faster than Algorithm B
, but the results could be the opposite on another computer with different specifications. This means we would need to conduct tests on a variety of machines and calculate an average efficiency, which is impractical.
Furthermore, conducting comprehensive tests is resource-intensive. The efficiency of algorithms can vary with different volumes of input data. For example, with smaller data sets, Algorithm A might run faster than Algorithm B; however, this could change with larger data sets. Therefore, to reach a convincing conclusion, it's necessary to test a range of data sizes, which requires excessive computational resources.
"},{"location":"chapter_computational_complexity/performance_evaluation/#212-theoretical-estimation","title":"2.1.2 \u00a0 Theoretical Estimation","text":"Given the significant limitations of practical testing, we can consider assessing algorithm efficiency solely through calculations. This method of estimation is known as 'asymptotic complexity analysis,' often simply referred to as 'complexity analysis.
Complexity analysis illustrates the relationship between the time (and space) resources required by an algorithm and the size of its input data. It describes the growing trend in the time and space required for the execution of an algorithm as the size of the input data increases. This definition might sound a bit complex, so let's break it down into three key points for easier understanding.
Complexity analysis overcomes the drawbacks of practical testing methods in two key ways:.
Tip
If you're still finding the concept of complexity confusing, don't worry. We will cover it in more detail in the subsequent chapters.
Complexity analysis provides us with a 'ruler' for evaluating the efficiency of algorithms, enabling us to measure the time and space resources required to execute a given algorithm and to compare the efficiency of different algorithms.
Complexity is a mathematical concept that might seem abstract and somewhat challenging for beginners. From this perspective, introducing complexity analysis at the very beginning may not be the most suitable approach. However, when discussing the characteristics of a particular data structure or algorithm, analyzing its operational speed and space usage is often inevitable.
Therefore, it is recommended that before diving deeply into data structures and algorithms, one should first gain a basic understanding of complexity analysis. This foundational knowledge will facilitate the complexity analysis of simple algorithms.
"},{"location":"chapter_computational_complexity/space_complexity/","title":"2.4 \u00a0 Space Complexity","text":"The space complexity is used to measure the growth trend of memory consumption as the scale of data increases for an algorithm solution. This concept is analogous to time complexity by replacing \"runtime\" with \"memory space\".
"},{"location":"chapter_computational_complexity/space_complexity/#241-algorithmic-correlation-space","title":"2.4.1 \u00a0 Algorithmic Correlation Space","text":"The memory space used by algorithms during its execution include the following types.
In general, the \"Input Space\" is excluded from the statistics of space complexity.
The Temporary Space can be further divided into three parts.
When analyzing the space complexity of a piece of program, three parts are usually taken into account: Temporary Data, Stack Frame Space and Output Data.
Figure 2-15 \u00a0 Associated spaces used by the algorithm
PythonC++JavaC#GoSwiftJSTSDartRustCZigclass Node:\n \"\"\"Classes\"\"\"\"\n def __init__(self, x: int):\n self.val: int = x # node value\n self.next: Node | None = None # reference to the next node\n\ndef function() -> int:\n \"\"\"\"Functions\"\"\"\"\"\n # Perform certain operations...\n return 0\n\ndef algorithm(n) -> int: # input data\n A = 0 # temporary data (constant, usually in uppercase)\n b = 0 # temporary data (variable)\n node = Node(0) # temporary data (object)\n c = function() # Stack frame space (call function)\n return A + b + c # output data\n
/* Structures */\nstruct Node {\n int val;\n Node *next;\n Node(int x) : val(x), next(nullptr) {}\n};\n\n/* Functions */\nint func() {\n // Perform certain operations...\n return 0;\n}\n\nint algorithm(int n) { // input data\n const int a = 0; // temporary data (constant)\n int b = 0; // temporary data (variable)\n Node* node = new Node(0); // temporary data (object)\n int c = func(); // stack frame space (call function)\n return a + b + c; // output data\n}\n
/* Classes */\nclass Node {\n int val;\n Node next;\n Node(int x) { val = x; }\n}\n\n/* Functions */\nint function() {\n // Perform certain operations...\n return 0;\n}\n\nint algorithm(int n) { // input data\n final int a = 0; // temporary data (constant)\n int b = 0; // temporary data (variable)\n Node node = new Node(0); // temporary data (object)\n int c = function(); // stack frame space (call function)\n return a + b + c; // output data\n}\n
/* Classes */\nclass Node {\n int val;\n Node next;\n Node(int x) { val = x; }\n}\n\n/* Functions */\nint Function() {\n // Perform certain operations...\n return 0;\n}\n\nint Algorithm(int n) { // input data\n const int a = 0; // temporary data (constant)\n int b = 0; // temporary data (variable)\n Node node = new(0); // temporary data (object)\n int c = Function(); // stack frame space (call function)\n return a + b + c; // output data\n}\n
/* Structures */\ntype node struct {\n val int\n next *node\n}\n\n/* Create node structure */\nfunc newNode(val int) *node {\n return &node{val: val}\n}\n\n/* Functions */\nfunc function() int {\n // Perform certain operations...\n return 0\n}\n\nfunc algorithm(n int) int { // input data\n const a = 0 // temporary data (constant)\n b := 0 // temporary storage of data (variable)\n newNode(0) // temporary data (object)\n c := function() // stack frame space (call function)\n return a + b + c // output data\n}\n
/* Classes */\nclass Node {\n var val: Int\n var next: Node?\n\n init(x: Int) {\n val = x\n }\n}\n\n/* Functions */\nfunc function() -> Int {\n // Perform certain operations...\n return 0\n}\n\nfunc algorithm(n: Int) -> Int { // input data\n let a = 0 // temporary data (constant)\n var b = 0 // temporary data (variable)\n let node = Node(x: 0) // temporary data (object)\n let c = function() // stack frame space (call function)\n return a + b + c // output data\n}\n
/* Classes */\nclass Node {\n val;\n next;\n constructor(val) {\n this.val = val === undefined ? 0 : val; // node value\n this.next = null; // reference to the next node\n }\n}\n\n/* Functions */\nfunction constFunc() {\n // Perform certain operations\n return 0;\n}\n\nfunction algorithm(n) { // input data\n const a = 0; // temporary data (constant)\n let b = 0; // temporary data (variable)\n const node = new Node(0); // temporary data (object)\n const c = constFunc(); // Stack frame space (calling function)\n return a + b + c; // output data\n}\n
/* Classes */\nclass Node {\n val: number;\n next: Node | null;\n constructor(val?: number) {\n this.val = val === undefined ? 0 : val; // node value\n this.next = null; // reference to the next node\n }\n}\n\n/* Functions */\nfunction constFunc(): number {\n // Perform certain operations\n return 0;\n}\n\nfunction algorithm(n: number): number { // input data\n const a = 0; // temporary data (constant)\n let b = 0; // temporary data (variable)\n const node = new Node(0); // temporary data (object)\n const c = constFunc(); // Stack frame space (calling function)\n return a + b + c; // output data\n}\n
/* Classes */\nclass Node {\n int val;\n Node next;\n Node(this.val, [this.next]);\n}\n\n/* Functions */\nint function() {\n // Perform certain operations...\n return 0;\n}\n\nint algorithm(int n) { // input data\n const int a = 0; // temporary data (constant)\n int b = 0; // temporary data (variable)\n Node node = Node(0); // temporary data (object)\n int c = function(); // stack frame space (call function)\n return a + b + c; // output data\n}\n
use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Structures */\nstruct Node {\n val: i32,\n next: Option<Rc<RefCell<Node>>>,\n}\n\n/* Creating a Node structure */\nimpl Node {\n fn new(val: i32) -> Self {\n Self { val: val, next: None }\n }\n}\n\n/* Functions */\nfn function() -> i32 { \n // Perform certain operations...\n return 0;\n}\n\nfn algorithm(n: i32) -> i32 { // input data\n const a: i32 = 0; // temporary data (constant)\n let mut b = 0; // temporary data (variable)\n let node = Node::new(0); // temporary data (object)\n let c = function(); // stack frame space (call function)\n return a + b + c; // output data\n}\n
/* Functions */\nint func() {\n // Perform certain operations...\n return 0;\n}\n\nint algorithm(int n) { // input data\n const int a = 0; // temporary data (constant)\n int b = 0; // temporary data (variable)\n int c = func(); // stack frame space (call function)\n return a + b + c; // output data\n}\n
\n
"},{"location":"chapter_computational_complexity/space_complexity/#242-calculation-method","title":"2.4.2 \u00a0 Calculation Method","text":"The calculation method for space complexity is pretty similar to time complexity, with the only difference being that the focus shifts from \"operation count\" to \"space usage size\".
On top of that, unlike time complexity, we usually only focus on the worst-case space complexity. This is because memory space is a hard requirement, and we have to make sure that there is enough memory space reserved for all possibilities incurred by input data.
Looking at the following code, the \"worst\" in worst-case space complexity has two layers of meaning.
nums
occupies \\(O(n)\\) space; thus the worst-case space complexity is \\(O(n)\\).nums
is initialized, the program occupies \\(O(n)\\) space; thus the worst-case space complexity is \\(O(n)\\).def algorithm(n: int):\n a = 0 # O(1)\n b = [0] * 10000 # O(1)\n if n > 10:\n nums = [0] * n # O(n)\n
void algorithm(int n) {\n int a = 0; // O(1)\n vector<int> b(10000); // O(1)\n if (n > 10)\n vector<int> nums(n); // O(n)\n}\n
void algorithm(int n) {\n int a = 0; // O(1)\n int[] b = new int[10000]; // O(1)\n if (n > 10)\n int[] nums = new int[n]; // O(n)\n}\n
void Algorithm(int n) {\n int a = 0; // O(1)\n int[] b = new int[10000]; // O(1)\n if (n > 10) {\n int[] nums = new int[n]; // O(n)\n }\n}\n
func algorithm(n int) {\n a := 0 // O(1)\n b := make([]int, 10000) // O(1)\n var nums []int\n if n > 10 {\n nums := make([]int, n) // O(n)\n }\n fmt.Println(a, b, nums)\n}\n
func algorithm(n: Int) {\n let a = 0 // O(1)\n let b = Array(repeating: 0, count: 10000) // O(1)\n if n > 10 {\n let nums = Array(repeating: 0, count: n) // O(n)\n }\n}\n
function algorithm(n) {\n const a = 0; // O(1)\n const b = new Array(10000); // O(1)\n if (n > 10) {\n const nums = new Array(n); // O(n)\n }\n}\n
function algorithm(n: number): void {\n const a = 0; // O(1)\n const b = new Array(10000); // O(1)\n if (n > 10) {\n const nums = new Array(n); // O(n)\n }\n}\n
void algorithm(int n) {\n int a = 0; // O(1)\n List<int> b = List.filled(10000, 0); // O(1)\n if (n > 10) {\n List<int> nums = List.filled(n, 0); // O(n)\n }\n}\n
fn algorithm(n: i32) {\n let a = 0; // O(1)\n let b = [0; 10000]; // O(1)\n if n > 10 {\n let nums = vec![0; n as usize]; // O(n)\n }\n}\n
void algorithm(int n) {\n int a = 0; // O(1)\n int b[10000]; // O(1)\n if (n > 10)\n int nums[n] = {0}; // O(n)\n}\n
\n
In recursion functions, it is important to take into count the measurement of stack frame space. For example in the following code:
loop()
calls \\(n\\) times function()
in a loop, and each round of function()
returns and frees stack frame space, so the space complexity is still \\(O(1)\\).recur()
will have \\(n\\) unreturned recur()
during runtime, thus occupying \\(O(n)\\) of stack frame space.def function() -> int:\n # Perform certain operations\n return 0\n\ndef loop(n: int):\n \"\"\"Loop O(1)\"\"\"\"\"\n for _ in range(n):\n function()\n\ndef recur(n: int) -> int:\n \"\"\"Recursion O(n)\"\"\"\"\"\n if n == 1: return\n return recur(n - 1)\n
int func() {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nvoid loop(int n) {\n for (int i = 0; i < n; i++) {\n func();\n }\n}\n/* Recursion O(n) */\nvoid recur(int n) {\n if (n == 1) return;\n return recur(n - 1);\n}\n
int function() {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nvoid loop(int n) {\n for (int i = 0; i < n; i++) {\n function();\n }\n}\n/* Recursion O(n) */\nvoid recur(int n) {\n if (n == 1) return;\n return recur(n - 1);\n}\n
int Function() {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nvoid Loop(int n) {\n for (int i = 0; i < n; i++) {\n Function();\n }\n}\n/* Recursion O(n) */\nint Recur(int n) {\n if (n == 1) return 1;\n return Recur(n - 1);\n}\n
func function() int {\n // Perform certain operations\n return 0\n}\n\n/* Cycle O(1) */\nfunc loop(n int) {\n for i := 0; i < n; i++ {\n function()\n }\n}\n\n/* Recursion O(n) */\nfunc recur(n int) {\n if n == 1 {\n return\n }\n recur(n - 1)\n}\n
@discardableResult\nfunc function() -> Int {\n // Perform certain operations\n return 0\n}\n\n/* Cycle O(1) */\nfunc loop(n: Int) {\n for _ in 0 ..< n {\n function()\n }\n}\n\n/* Recursion O(n) */\nfunc recur(n: Int) {\n if n == 1 {\n return\n }\n recur(n: n - 1)\n}\n
function constFunc() {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nfunction loop(n) {\n for (let i = 0; i < n; i++) {\n constFunc();\n }\n}\n/* Recursion O(n) */\nfunction recur(n) {\n if (n === 1) return;\n return recur(n - 1);\n}\n
function constFunc(): number {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nfunction loop(n: number): void {\n for (let i = 0; i < n; i++) {\n constFunc();\n }\n}\n/* Recursion O(n) */\nfunction recur(n: number): void {\n if (n === 1) return;\n return recur(n - 1);\n}\n
int function() {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nvoid loop(int n) {\n for (int i = 0; i < n; i++) {\n function();\n }\n}\n/* Recursion O(n) */\nvoid recur(int n) {\n if (n == 1) return;\n return recur(n - 1);\n}\n
fn function() -> i32 {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nfn loop(n: i32) {\n for i in 0..n {\n function();\n }\n}\n/* Recursion O(n) */\nvoid recur(n: i32) {\n if n == 1 {\n return;\n }\n recur(n - 1);\n}\n
int func() {\n // Perform certain operations\n return 0;\n}\n/* Cycle O(1) */\nvoid loop(int n) {\n for (int i = 0; i < n; i++) {\n func();\n }\n}\n/* Recursion O(n) */\nvoid recur(int n) {\n if (n == 1) return;\n return recur(n - 1);\n}\n
\n
"},{"location":"chapter_computational_complexity/space_complexity/#243-common-types","title":"2.4.3 \u00a0 Common Types","text":"Assuming the input data size is \\(n\\), the figure illustrates common types of space complexity (ordered from low to high).
\\[ \\begin{aligned} O(1) < O(\\log n) < O(n) < O(n^2) < O(2^n) \\newline \\text{constant order} < \\text{logarithmic order} < \\text{linear order} < \\text{square order} < \\text{exponential order} \\end{aligned} \\]Figure 2-16 \u00a0 Common space complexity types
"},{"location":"chapter_computational_complexity/space_complexity/#1-constant-order-o1","title":"1. \u00a0 Constant Order \\(O(1)\\)","text":"Constant order is common for constants, variables, and objects whose quantity is unrelated to the size of the input data \\(n\\).
It is important to note that memory occupied by initializing a variable or calling a function in a loop is released once the next iteration begins. Therefore, there is no accumulation of occupied space and the space complexity remains \\(O(1)\\) :
PythonC++JavaC#GoSwiftJSTSDartRustCZig space_complexity.pydef function() -> int:\n \"\"\"\u51fd\u6570\"\"\"\n # \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0\n\ndef constant(n: int):\n \"\"\"\u5e38\u6570\u9636\"\"\"\n # \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n a = 0\n nums = [0] * 10000\n node = ListNode(0)\n # \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for _ in range(n):\n c = 0\n # \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for _ in range(n):\n function()\n
space_complexity.cpp/* \u51fd\u6570 */\nint func() {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nvoid constant(int n) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const int a = 0;\n int b = 0;\n vector<int> nums(10000);\n ListNode node(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n int c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n func();\n }\n}\n
space_complexity.java/* \u51fd\u6570 */\nint function() {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nvoid constant(int n) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n final int a = 0;\n int b = 0;\n int[] nums = new int[10000];\n ListNode node = new ListNode(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n int c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n function();\n }\n}\n
space_complexity.cs/* \u51fd\u6570 */\nint Function() {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nvoid Constant(int n) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n int a = 0;\n int b = 0;\n int[] nums = new int[10000];\n ListNode node = new(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n int c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n Function();\n }\n}\n
space_complexity.go/* \u51fd\u6570 */\nfunc function() int {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c...\n return 0\n}\n\n/* \u5e38\u6570\u9636 */\nfunc spaceConstant(n int) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const a = 0\n b := 0\n nums := make([]int, 10000)\n ListNode := newNode(0)\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n var c int\n for i := 0; i < n; i++ {\n c = 0\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for i := 0; i < n; i++ {\n function()\n }\n fmt.Println(a, b, nums, c, ListNode)\n}\n
space_complexity.swift/* \u51fd\u6570 */\n@discardableResult\nfunc function() -> Int {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0\n}\n\n/* \u5e38\u6570\u9636 */\nfunc constant(n: Int) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n let a = 0\n var b = 0\n let nums = Array(repeating: 0, count: 10000)\n let node = ListNode(x: 0)\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for _ in 0 ..< n {\n let c = 0\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for _ in 0 ..< n {\n function()\n }\n}\n
space_complexity.js/* \u51fd\u6570 */\nfunction constFunc() {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nfunction constant(n) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const a = 0;\n const b = 0;\n const nums = new Array(10000);\n const node = new ListNode(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (let i = 0; i < n; i++) {\n const c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (let i = 0; i < n; i++) {\n constFunc();\n }\n}\n
space_complexity.ts/* \u51fd\u6570 */\nfunction constFunc(): number {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nfunction constant(n: number): void {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const a = 0;\n const b = 0;\n const nums = new Array(10000);\n const node = new ListNode(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (let i = 0; i < n; i++) {\n const c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (let i = 0; i < n; i++) {\n constFunc();\n }\n}\n
space_complexity.dart/* \u51fd\u6570 */\nint function() {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nvoid constant(int n) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n final int a = 0;\n int b = 0;\n List<int> nums = List.filled(10000, 0);\n ListNode node = ListNode(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (var i = 0; i < n; i++) {\n int c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (var i = 0; i < n; i++) {\n function();\n }\n}\n
space_complexity.rs/* \u51fd\u6570 */\nfn function() ->i32 {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\n#[allow(unused)]\nfn constant(n: i32) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const A: i32 = 0;\n let b = 0;\n let nums = vec![0; 10000];\n let node = ListNode::new(0);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for i in 0..n {\n let c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for i in 0..n {\n function();\n }\n}\n
space_complexity.c/* \u51fd\u6570 */\nint func() {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n/* \u5e38\u6570\u9636 */\nvoid constant(int n) {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const int a = 0;\n int b = 0;\n int nums[1000];\n ListNode *node = newListNode(0);\n free(node);\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n int c = 0;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n for (int i = 0; i < n; i++) {\n func();\n }\n}\n
space_complexity.zig// \u51fd\u6570\nfn function() i32 {\n // \u6267\u884c\u67d0\u4e9b\u64cd\u4f5c\n return 0;\n}\n\n// \u5e38\u6570\u9636\nfn constant(n: i32) void {\n // \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\n const a: i32 = 0;\n var b: i32 = 0;\n var nums = [_]i32{0}**10000;\n var node = inc.ListNode(i32){.val = 0};\n var i: i32 = 0;\n // \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\n while (i < n) : (i += 1) {\n var c: i32 = 0;\n _ = c;\n }\n // \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\n i = 0;\n while (i < n) : (i += 1) {\n _ = function();\n }\n _ = a;\n _ = b;\n _ = nums;\n _ = node;\n}\n
"},{"location":"chapter_computational_complexity/space_complexity/#2-linear-order-on","title":"2. \u00a0 Linear Order \\(O(N)\\)","text":"Linear order is commonly found in arrays, linked lists, stacks, queues, and similar structures where the number of elements is proportional to \\(n\\):
PythonC++JavaC#GoSwiftJSTSDartRustCZig space_complexity.pydef linear(n: int):\n \"\"\"\u7ebf\u6027\u9636\"\"\"\n # \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n nums = [0] * n\n # \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n hmap = dict[int, str]()\n for i in range(n):\n hmap[i] = str(i)\n
space_complexity.cpp/* \u7ebf\u6027\u9636 */\nvoid linear(int n) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n vector<int> nums(n);\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n vector<ListNode> nodes;\n for (int i = 0; i < n; i++) {\n nodes.push_back(ListNode(i));\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n unordered_map<int, string> map;\n for (int i = 0; i < n; i++) {\n map[i] = to_string(i);\n }\n}\n
space_complexity.java/* \u7ebf\u6027\u9636 */\nvoid linear(int n) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n int[] nums = new int[n];\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n List<ListNode> nodes = new ArrayList<>();\n for (int i = 0; i < n; i++) {\n nodes.add(new ListNode(i));\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n Map<Integer, String> map = new HashMap<>();\n for (int i = 0; i < n; i++) {\n map.put(i, String.valueOf(i));\n }\n}\n
space_complexity.cs/* \u7ebf\u6027\u9636 */\nvoid Linear(int n) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n int[] nums = new int[n];\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n List<ListNode> nodes = [];\n for (int i = 0; i < n; i++) {\n nodes.Add(new ListNode(i));\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n Dictionary<int, string> map = [];\n for (int i = 0; i < n; i++) {\n map.Add(i, i.ToString());\n }\n}\n
space_complexity.go/* \u7ebf\u6027\u9636 */\nfunc spaceLinear(n int) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n _ = make([]int, n)\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n var nodes []*node\n for i := 0; i < n; i++ {\n nodes = append(nodes, newNode(i))\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n m := make(map[int]string, n)\n for i := 0; i < n; i++ {\n m[i] = strconv.Itoa(i)\n }\n}\n
space_complexity.swift/* \u7ebf\u6027\u9636 */\nfunc linear(n: Int) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n let nums = Array(repeating: 0, count: n)\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n let nodes = (0 ..< n).map { ListNode(x: $0) }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, \"\\($0)\") })\n}\n
space_complexity.js/* \u7ebf\u6027\u9636 */\nfunction linear(n) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n const nums = new Array(n);\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n const nodes = [];\n for (let i = 0; i < n; i++) {\n nodes.push(new ListNode(i));\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n const map = new Map();\n for (let i = 0; i < n; i++) {\n map.set(i, i.toString());\n }\n}\n
space_complexity.ts/* \u7ebf\u6027\u9636 */\nfunction linear(n: number): void {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n const nums = new Array(n);\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n const nodes: ListNode[] = [];\n for (let i = 0; i < n; i++) {\n nodes.push(new ListNode(i));\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n const map = new Map();\n for (let i = 0; i < n; i++) {\n map.set(i, i.toString());\n }\n}\n
space_complexity.dart/* \u7ebf\u6027\u9636 */\nvoid linear(int n) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n List<int> nums = List.filled(n, 0);\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n List<ListNode> nodes = [];\n for (var i = 0; i < n; i++) {\n nodes.add(ListNode(i));\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n Map<int, String> map = HashMap();\n for (var i = 0; i < n; i++) {\n map.putIfAbsent(i, () => i.toString());\n }\n}\n
space_complexity.rs/* \u7ebf\u6027\u9636 */\n#[allow(unused)]\nfn linear(n: i32) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n let mut nums = vec![0; n as usize];\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n let mut nodes = Vec::new();\n for i in 0..n {\n nodes.push(ListNode::new(i))\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n let mut map = HashMap::new();\n for i in 0..n {\n map.insert(i, i.to_string());\n }\n}\n
space_complexity.c/* \u54c8\u5e0c\u8868 */\ntypedef struct {\n int key;\n int val;\n UT_hash_handle hh; // \u57fa\u4e8e uthash.h \u5b9e\u73b0\n} HashTable;\n\n/* \u7ebf\u6027\u9636 */\nvoid linear(int n) {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n int *nums = malloc(sizeof(int) * n);\n free(nums);\n\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n ListNode **nodes = malloc(sizeof(ListNode *) * n);\n for (int i = 0; i < n; i++) {\n nodes[i] = newListNode(i);\n }\n // \u5185\u5b58\u91ca\u653e\n for (int i = 0; i < n; i++) {\n free(nodes[i]);\n }\n free(nodes);\n\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n HashTable *h = NULL;\n for (int i = 0; i < n; i++) {\n HashTable *tmp = malloc(sizeof(HashTable));\n tmp->key = i;\n tmp->val = i;\n HASH_ADD_INT(h, key, tmp);\n }\n\n // \u5185\u5b58\u91ca\u653e\n HashTable *curr, *tmp;\n HASH_ITER(hh, h, curr, tmp) {\n HASH_DEL(h, curr);\n free(curr);\n }\n}\n
space_complexity.zig// \u7ebf\u6027\u9636\nfn linear(comptime n: i32) !void {\n // \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n var nums = [_]i32{0}**n;\n // \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n var nodes = std.ArrayList(i32).init(std.heap.page_allocator);\n defer nodes.deinit();\n var i: i32 = 0;\n while (i < n) : (i += 1) {\n try nodes.append(i);\n }\n // \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\n var map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator);\n defer map.deinit();\n var j: i32 = 0;\n while (j < n) : (j += 1) {\n const string = try std.fmt.allocPrint(std.heap.page_allocator, \"{d}\", .{j});\n defer std.heap.page_allocator.free(string);\n try map.put(i, string);\n }\n _ = nums;\n}\n
As shown in the Figure 2-17 , the depth of recursion for this function is \\(n\\), which means that there are \\(n\\) unreturned linear_recur()
functions at the same time, using \\(O(n)\\) size stack frame space:
def linear_recur(n: int):\n \"\"\"\u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\n print(\"\u9012\u5f52 n =\", n)\n if n == 1:\n return\n linear_recur(n - 1)\n
space_complexity.cpp/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid linearRecur(int n) {\n cout << \"\u9012\u5f52 n = \" << n << endl;\n if (n == 1)\n return;\n linearRecur(n - 1);\n}\n
space_complexity.java/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid linearRecur(int n) {\n System.out.println(\"\u9012\u5f52 n = \" + n);\n if (n == 1)\n return;\n linearRecur(n - 1);\n}\n
space_complexity.cs/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid LinearRecur(int n) {\n Console.WriteLine(\"\u9012\u5f52 n = \" + n);\n if (n == 1) return;\n LinearRecur(n - 1);\n}\n
space_complexity.go/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc spaceLinearRecur(n int) {\n fmt.Println(\"\u9012\u5f52 n =\", n)\n if n == 1 {\n return\n }\n spaceLinearRecur(n - 1)\n}\n
space_complexity.swift/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc linearRecur(n: Int) {\n print(\"\u9012\u5f52 n = \\(n)\")\n if n == 1 {\n return\n }\n linearRecur(n: n - 1)\n}\n
space_complexity.js/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction linearRecur(n) {\n console.log(`\u9012\u5f52 n = ${n}`);\n if (n === 1) return;\n linearRecur(n - 1);\n}\n
space_complexity.ts/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction linearRecur(n: number): void {\n console.log(`\u9012\u5f52 n = ${n}`);\n if (n === 1) return;\n linearRecur(n - 1);\n}\n
space_complexity.dart/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid linearRecur(int n) {\n print('\u9012\u5f52 n = $n');\n if (n == 1) return;\n linearRecur(n - 1);\n}\n
space_complexity.rs/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfn linear_recur(n: i32) {\n println!(\"\u9012\u5f52 n = {}\", n);\n if n == 1 {return};\n linear_recur(n - 1);\n}\n
space_complexity.c/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid linearRecur(int n) {\n printf(\"\u9012\u5f52 n = %d\\r\\n\", n);\n if (n == 1)\n return;\n linearRecur(n - 1);\n}\n
space_complexity.zig// \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn linearRecur(comptime n: i32) void {\n std.debug.print(\"\u9012\u5f52 n = {}\\n\", .{n});\n if (n == 1) return;\n linearRecur(n - 1);\n}\n
Figure 2-17 \u00a0 Linear order space complexity generated by recursion function
"},{"location":"chapter_computational_complexity/space_complexity/#3-quadratic-order-on2","title":"3. \u00a0 Quadratic Order \\(O(N^2)\\)","text":"Quadratic order is common in matrices and graphs, where the number of elements is in a square relationship with \\(n\\):
PythonC++JavaC#GoSwiftJSTSDartRustCZig space_complexity.pydef quadratic(n: int):\n \"\"\"\u5e73\u65b9\u9636\"\"\"\n # \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n num_matrix = [[0] * n for _ in range(n)]\n
space_complexity.cpp/* \u5e73\u65b9\u9636 */\nvoid quadratic(int n) {\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n vector<vector<int>> numMatrix;\n for (int i = 0; i < n; i++) {\n vector<int> tmp;\n for (int j = 0; j < n; j++) {\n tmp.push_back(0);\n }\n numMatrix.push_back(tmp);\n }\n}\n
space_complexity.java/* \u5e73\u65b9\u9636 */\nvoid quadratic(int n) {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n int[][] numMatrix = new int[n][n];\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n List<List<Integer>> numList = new ArrayList<>();\n for (int i = 0; i < n; i++) {\n List<Integer> tmp = new ArrayList<>();\n for (int j = 0; j < n; j++) {\n tmp.add(0);\n }\n numList.add(tmp);\n }\n}\n
space_complexity.cs/* \u5e73\u65b9\u9636 */\nvoid Quadratic(int n) {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n int[,] numMatrix = new int[n, n];\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n List<List<int>> numList = [];\n for (int i = 0; i < n; i++) {\n List<int> tmp = [];\n for (int j = 0; j < n; j++) {\n tmp.Add(0);\n }\n numList.Add(tmp);\n }\n}\n
space_complexity.go/* \u5e73\u65b9\u9636 */\nfunc spaceQuadratic(n int) {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n numMatrix := make([][]int, n)\n for i := 0; i < n; i++ {\n numMatrix[i] = make([]int, n)\n }\n}\n
space_complexity.swift/* \u5e73\u65b9\u9636 */\nfunc quadratic(n: Int) {\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n let numList = Array(repeating: Array(repeating: 0, count: n), count: n)\n}\n
space_complexity.js/* \u5e73\u65b9\u9636 */\nfunction quadratic(n) {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n const numMatrix = Array(n)\n .fill(null)\n .map(() => Array(n).fill(null));\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n const numList = [];\n for (let i = 0; i < n; i++) {\n const tmp = [];\n for (let j = 0; j < n; j++) {\n tmp.push(0);\n }\n numList.push(tmp);\n }\n}\n
space_complexity.ts/* \u5e73\u65b9\u9636 */\nfunction quadratic(n: number): void {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n const numMatrix = Array(n)\n .fill(null)\n .map(() => Array(n).fill(null));\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n const numList = [];\n for (let i = 0; i < n; i++) {\n const tmp = [];\n for (let j = 0; j < n; j++) {\n tmp.push(0);\n }\n numList.push(tmp);\n }\n}\n
space_complexity.dart/* \u5e73\u65b9\u9636 */\nvoid quadratic(int n) {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n List<List<int>> numMatrix = List.generate(n, (_) => List.filled(n, 0));\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n List<List<int>> numList = [];\n for (var i = 0; i < n; i++) {\n List<int> tmp = [];\n for (int j = 0; j < n; j++) {\n tmp.add(0);\n }\n numList.add(tmp);\n }\n}\n
space_complexity.rs/* \u5e73\u65b9\u9636 */\n#[allow(unused)]\nfn quadratic(n: i32) {\n // \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\n let num_matrix = vec![vec![0; n as usize]; n as usize];\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n let mut num_list = Vec::new();\n for i in 0..n {\n let mut tmp = Vec::new();\n for j in 0..n {\n tmp.push(0);\n }\n num_list.push(tmp);\n }\n}\n
space_complexity.c/* \u5e73\u65b9\u9636 */\nvoid quadratic(int n) {\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n int **numMatrix = malloc(sizeof(int *) * n);\n for (int i = 0; i < n; i++) {\n int *tmp = malloc(sizeof(int) * n);\n for (int j = 0; j < n; j++) {\n tmp[j] = 0;\n }\n numMatrix[i] = tmp;\n }\n\n // \u5185\u5b58\u91ca\u653e\n for (int i = 0; i < n; i++) {\n free(numMatrix[i]);\n }\n free(numMatrix);\n}\n
space_complexity.zig// \u5e73\u65b9\u9636\nfn quadratic(n: i32) !void {\n // \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\n var nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator);\n defer nodes.deinit();\n var i: i32 = 0;\n while (i < n) : (i += 1) {\n var tmp = std.ArrayList(i32).init(std.heap.page_allocator);\n defer tmp.deinit();\n var j: i32 = 0;\n while (j < n) : (j += 1) {\n try tmp.append(0);\n }\n try nodes.append(tmp);\n }\n}\n
As shown in the Figure 2-18 , the recursion depth of this function is \\(n\\), and an array is initialized in each recursion function with lengths \\(n\\), \\(n-1\\), \\(\\dots\\), \\(2\\), \\(1\\), and an average length of \\(n / 2\\), thus occupying \\(O(n^2)\\) space overall:
PythonC++JavaC#GoSwiftJSTSDartRustCZig space_complexity.pydef quadratic_recur(n: int) -> int:\n \"\"\"\u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\n if n <= 0:\n return 0\n # \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\n nums = [0] * n\n return quadratic_recur(n - 1)\n
space_complexity.cpp/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint quadraticRecur(int n) {\n if (n <= 0)\n return 0;\n vector<int> nums(n);\n cout << \"\u9012\u5f52 n = \" << n << \" \u4e2d\u7684 nums \u957f\u5ea6 = \" << nums.size() << endl;\n return quadraticRecur(n - 1);\n}\n
space_complexity.java/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint quadraticRecur(int n) {\n if (n <= 0)\n return 0;\n // \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\n int[] nums = new int[n];\n System.out.println(\"\u9012\u5f52 n = \" + n + \" \u4e2d\u7684 nums \u957f\u5ea6 = \" + nums.length);\n return quadraticRecur(n - 1);\n}\n
space_complexity.cs/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint QuadraticRecur(int n) {\n if (n <= 0) return 0;\n int[] nums = new int[n];\n Console.WriteLine(\"\u9012\u5f52 n = \" + n + \" \u4e2d\u7684 nums \u957f\u5ea6 = \" + nums.Length);\n return QuadraticRecur(n - 1);\n}\n
space_complexity.go/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc spaceQuadraticRecur(n int) int {\n if n <= 0 {\n return 0\n }\n nums := make([]int, n)\n fmt.Printf(\"\u9012\u5f52 n = %d \u4e2d\u7684 nums \u957f\u5ea6 = %d \\n\", n, len(nums))\n return spaceQuadraticRecur(n - 1)\n}\n
space_complexity.swift/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\n@discardableResult\nfunc quadraticRecur(n: Int) -> Int {\n if n <= 0 {\n return 0\n }\n // \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\n let nums = Array(repeating: 0, count: n)\n print(\"\u9012\u5f52 n = \\(n) \u4e2d\u7684 nums \u957f\u5ea6 = \\(nums.count)\")\n return quadraticRecur(n: n - 1)\n}\n
space_complexity.js/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction quadraticRecur(n) {\n if (n <= 0) return 0;\n const nums = new Array(n);\n console.log(`\u9012\u5f52 n = ${n} \u4e2d\u7684 nums \u957f\u5ea6 = ${nums.length}`);\n return quadraticRecur(n - 1);\n}\n
space_complexity.ts/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction quadraticRecur(n: number): number {\n if (n <= 0) return 0;\n const nums = new Array(n);\n console.log(`\u9012\u5f52 n = ${n} \u4e2d\u7684 nums \u957f\u5ea6 = ${nums.length}`);\n return quadraticRecur(n - 1);\n}\n
space_complexity.dart/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint quadraticRecur(int n) {\n if (n <= 0) return 0;\n List<int> nums = List.filled(n, 0);\n print('\u9012\u5f52 n = $n \u4e2d\u7684 nums \u957f\u5ea6 = ${nums.length}');\n return quadraticRecur(n - 1);\n}\n
space_complexity.rs/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfn quadratic_recur(n: i32) -> i32 {\n if n <= 0 {return 0};\n // \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\n let nums = vec![0; n as usize];\n println!(\"\u9012\u5f52 n = {} \u4e2d\u7684 nums \u957f\u5ea6 = {}\", n, nums.len());\n return quadratic_recur(n - 1);\n}\n
space_complexity.c/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint quadraticRecur(int n) {\n if (n <= 0)\n return 0;\n int *nums = malloc(sizeof(int) * n);\n printf(\"\u9012\u5f52 n = %d \u4e2d\u7684 nums \u957f\u5ea6 = %d\\r\\n\", n, n);\n int res = quadraticRecur(n - 1);\n free(nums);\n return res;\n}\n
space_complexity.zig// \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn quadraticRecur(comptime n: i32) i32 {\n if (n <= 0) return 0;\n var nums = [_]i32{0}**n;\n std.debug.print(\"\u9012\u5f52 n = {} \u4e2d\u7684 nums \u957f\u5ea6 = {}\\n\", .{n, nums.len});\n return quadraticRecur(n - 1);\n}\n
Figure 2-18 \u00a0 Square-order space complexity generated by the recursion function
"},{"location":"chapter_computational_complexity/space_complexity/#4-exponential-order-o2n","title":"4. \u00a0 Exponential Order \\(O(2^N)\\)","text":"Exponential order is common in binary trees. Looking at the Figure 2-19 , a \"full binary tree\" of degree \\(n\\) has \\(2^n - 1\\) nodes, occupying \\(O(2^n)\\) space:
PythonC++JavaC#GoSwiftJSTSDartRustCZig space_complexity.pydef build_tree(n: int) -> TreeNode | None:\n \"\"\"\u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09\"\"\"\n if n == 0:\n return None\n root = TreeNode(0)\n root.left = build_tree(n - 1)\n root.right = build_tree(n - 1)\n return root\n
space_complexity.cpp/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode *buildTree(int n) {\n if (n == 0)\n return nullptr;\n TreeNode *root = new TreeNode(0);\n root->left = buildTree(n - 1);\n root->right = buildTree(n - 1);\n return root;\n}\n
space_complexity.java/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode buildTree(int n) {\n if (n == 0)\n return null;\n TreeNode root = new TreeNode(0);\n root.left = buildTree(n - 1);\n root.right = buildTree(n - 1);\n return root;\n}\n
space_complexity.cs/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode? BuildTree(int n) {\n if (n == 0) return null;\n TreeNode root = new(0) {\n left = BuildTree(n - 1),\n right = BuildTree(n - 1)\n };\n return root;\n}\n
space_complexity.go/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfunc buildTree(n int) *treeNode {\n if n == 0 {\n return nil\n }\n root := newTreeNode(0)\n root.left = buildTree(n - 1)\n root.right = buildTree(n - 1)\n return root\n}\n
space_complexity.swift/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfunc buildTree(n: Int) -> TreeNode? {\n if n == 0 {\n return nil\n }\n let root = TreeNode(x: 0)\n root.left = buildTree(n: n - 1)\n root.right = buildTree(n: n - 1)\n return root\n}\n
space_complexity.js/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfunction buildTree(n) {\n if (n === 0) return null;\n const root = new TreeNode(0);\n root.left = buildTree(n - 1);\n root.right = buildTree(n - 1);\n return root;\n}\n
space_complexity.ts/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfunction buildTree(n: number): TreeNode | null {\n if (n === 0) return null;\n const root = new TreeNode(0);\n root.left = buildTree(n - 1);\n root.right = buildTree(n - 1);\n return root;\n}\n
space_complexity.dart/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode? buildTree(int n) {\n if (n == 0) return null;\n TreeNode root = TreeNode(0);\n root.left = buildTree(n - 1);\n root.right = buildTree(n - 1);\n return root;\n}\n
space_complexity.rs/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfn build_tree(n: i32) -> Option<Rc<RefCell<TreeNode>>> {\n if n == 0 {return None};\n let root = TreeNode::new(0);\n root.borrow_mut().left = build_tree(n - 1);\n root.borrow_mut().right = build_tree(n - 1);\n return Some(root);\n}\n
space_complexity.c/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode *buildTree(int n) {\n if (n == 0)\n return NULL;\n TreeNode *root = newTreeNode(0);\n root->left = buildTree(n - 1);\n root->right = buildTree(n - 1);\n return root;\n}\n
space_complexity.zig// \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09\nfn buildTree(mem_allocator: std.mem.Allocator, n: i32) !?*inc.TreeNode(i32) {\n if (n == 0) return null;\n const root = try mem_allocator.create(inc.TreeNode(i32));\n root.init(0);\n root.left = try buildTree(mem_allocator, n - 1);\n root.right = try buildTree(mem_allocator, n - 1);\n return root;\n}\n
Figure 2-19 \u00a0 Exponential order space complexity generated by a full binary tree
"},{"location":"chapter_computational_complexity/space_complexity/#5-logarithmic-order-olog-n","title":"5. \u00a0 Logarithmic Order \\(O(\\Log N)\\)","text":"Logarithmic order is commonly used in divide and conquer algorithms. For example, in a merge sort, given an array of length \\(n\\) as the input, each round of recursion divides the array in half from its midpoint to form a recursion tree of height \\(\\log n\\), using \\(O(\\log n)\\) stack frame space.
Another example is to convert a number into a string. Given a positive integer \\(n\\) with a digit count of \\(\\log_{10} n + 1\\), the corresponding string length is \\(\\log_{10} n + 1\\). Therefore, the space complexity is \\(O(\\log_{10} n + 1) = O(\\log n)\\).
"},{"location":"chapter_computational_complexity/space_complexity/#244-weighing-time-and-space","title":"2.4.4 \u00a0 Weighing Time And Space","text":"Ideally, we would like to optimize both the time complexity and the space complexity of an algorithm. However, in reality, simultaneously optimizing time and space complexity is often challenging.
Reducing time complexity usually comes at the expense of increasing space complexity, and vice versa. The approach of sacrificing memory space to improve algorithm speed is known as \"trading space for time\", while the opposite is called \"trading time for space\".
The choice between these approaches depends on which aspect we prioritize. In most cases, time is more valuable than space, so \"trading space for time\" is usually the more common strategy. Of course, in situations with large data volumes, controlling space complexity is also crucial.
"},{"location":"chapter_computational_complexity/summary/","title":"2.5 \u00a0 Summary","text":""},{"location":"chapter_computational_complexity/summary/#1-highlights","title":"1. \u00a0 Highlights","text":"Evaluation of Algorithm Efficiency
Time Complexity
Space Complexity
Is the space complexity of tail recursion \\(O(1)\\)?
Theoretically, the space complexity of a tail recursion function can be optimized to \\(O(1)\\). However, most programming languages (e.g., Java, Python, C++, Go, C#, etc.) do not support auto-optimization for tail recursion, so the space complexity is usually considered as \\(O(n)\\).
What is the difference between the terms function and method?
A function can be executed independently, and all arguments are passed explicitly. A method is associated with an object and is implicitly passed to the object that calls it, allowing it to operate on the data contained within an instance of a class.
Let's illustrate with a few common programming languages.
Does the figure \"Common Types of Space Complexity\" reflect the absolute size of the occupied space?
No, that figure shows the space complexity, which reflects the growth trend, not the absolute size of the space occupied.
For example, if you take \\(n = 8\\) , the values of each curve do not align with the function because each curve contains a constant term used to compress the range of values to a visually comfortable range.
In practice, since we usually don't know each method's \"constant term\" complexity, it is generally impossible to choose the optimal solution for \\(n = 8\\) based on complexity alone. But it's easier to choose for \\(n = 8^5\\) as the growth trend is already dominant.
"},{"location":"chapter_computational_complexity/time_complexity/","title":"2.3 \u00a0 Time Complexity","text":"Runtime can be a visual and accurate reflection of the efficiency of an algorithm. What should we do if we want to accurately predict the runtime of a piece of code?
+
takes 1 ns, the multiplication operation *
takes 10 ns, the print operation print()
takes 5 ns, and so on.For example, in the following code, the input data size is \\(n\\) :
PythonC++JavaC#GoSwiftJSTSDartRustCZig# Under an operating platform\ndef algorithm(n: int):\n a = 2 # 1 ns\n a = a + 1 # 1 ns\n a = a * 2 # 10 ns\n # Cycle n times\n for _ in range(n): # 1 ns\n print(0) # 5 ns\n
// Under a particular operating platform\nvoid algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n cout << 0 << endl; // 5 ns\n }\n}\n
// Under a particular operating platform\nvoid algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n System.out.println(0); // 5 ns\n }\n}\n
// Under a particular operating platform\nvoid Algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n Console.WriteLine(0); // 5 ns\n }\n}\n
// Under a particular operating platform\nfunc algorithm(n int) {\n a := 2 // 1 ns\n a = a + 1 // 1 ns\n a = a * 2 // 10 ns\n // Loop n times\n for i := 0; i < n; i++ { // 1 ns\n fmt.Println(a) // 5 ns\n }\n}\n
// Under a particular operating platform\nfunc algorithm(n: Int) {\n var a = 2 // 1 ns\n a = a + 1 // 1 ns\n a = a * 2 // 10 ns\n // Loop n times\n for _ in 0 ..< n { // 1 ns\n print(0) // 5 ns\n }\n}\n
// Under a particular operating platform\nfunction algorithm(n) {\n var a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for(let i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n console.log(0); // 5 ns\n }\n}\n
// Under a particular operating platform\nfunction algorithm(n: number): void {\n var a: number = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for(let i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n console.log(0); // 5 ns\n }\n}\n
// Under a particular operating platform\nvoid algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n print(0); // 5 ns\n }\n}\n
// Under a particular operating platform\nfn algorithm(n: i32) {\n let mut a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for _ in 0..n { // 1 ns for each round i++\n println!(\"{}\", 0); // 5 ns\n }\n}\n
// Under a particular operating platform\nvoid algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // Loop n times\n for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed\n printf(\"%d\", 0); // 5 ns\n }\n}\n
// Under a particular operating platform\nfn algorithm(n: usize) void {\n var a: i32 = 2; // 1 ns\n a += 1; // 1 ns\n a *= 2; // 10 ns\n // Loop n times\n for (0..n) |_| { // 1 ns\n std.debug.print(\"{}\\n\", .{0}); // 5 ns\n }\n}\n
Based on the above method, the algorithm running time can be obtained as \\(6n + 12\\) ns :
\\[ 1 + 1 + 10 + (1 + 5) \\times n = 6n + 12 \\]In practice, however, statistical algorithm runtimes are neither reasonable nor realistic. First, we do not want to tie the estimation time to the operation platform, because the algorithm needs to run on a variety of different platforms. Second, it is difficult for us to be informed of the runtime of each operation, which makes the prediction process extremely difficult.
"},{"location":"chapter_computational_complexity/time_complexity/#231-trends-in-statistical-time-growth","title":"2.3.1 \u00a0 Trends In Statistical Time Growth","text":"The time complexity analysis counts not the algorithm running time, but the tendency of the algorithm running time to increase as the amount of data gets larger.
The concept of \"time-growing trend\" is rather abstract, so let's try to understand it through an example. Suppose the size of the input data is \\(n\\), and given three algorithmic functions A
, B
and C
:
# Time complexity of algorithm A: constant order\ndef algorithm_A(n: int):\n print(0)\n# Time complexity of algorithm B: linear order\ndef algorithm_B(n: int):\n for _ in range(n):\n print(0)\n# Time complexity of algorithm C: constant order\ndef algorithm_C(n: int):\n for _ in range(1000000):\n print(0)\n
// Time complexity of algorithm A: constant order\nvoid algorithm_A(int n) {\n cout << 0 << endl;\n}\n// Time complexity of algorithm B: linear order\nvoid algorithm_B(int n) {\n for (int i = 0; i < n; i++) {\n cout << 0 << endl;\n }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithm_C(int n) {\n for (int i = 0; i < 1000000; i++) {\n cout << 0 << endl;\n }\n}\n
// Time complexity of algorithm A: constant order\nvoid algorithm_A(int n) {\n System.out.println(0);\n}\n// Time complexity of algorithm B: linear order\nvoid algorithm_B(int n) {\n for (int i = 0; i < n; i++) {\n System.out.println(0);\n }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithm_C(int n) {\n for (int i = 0; i < 1000000; i++) {\n System.out.println(0);\n }\n}\n
// Time complexity of algorithm A: constant order\nvoid AlgorithmA(int n) {\n Console.WriteLine(0);\n}\n// Time complexity of algorithm B: linear order\nvoid AlgorithmB(int n) {\n for (int i = 0; i < n; i++) {\n Console.WriteLine(0);\n }\n}\n// Time complexity of algorithm C: constant order\nvoid AlgorithmC(int n) {\n for (int i = 0; i < 1000000; i++) {\n Console.WriteLine(0);\n }\n}\n
// Time complexity of algorithm A: constant order\nfunc algorithm_A(n int) {\n fmt.Println(0)\n}\n// Time complexity of algorithm B: linear order\nfunc algorithm_B(n int) {\n for i := 0; i < n; i++ {\n fmt.Println(0)\n }\n}\n// Time complexity of algorithm C: constant order\nfunc algorithm_C(n int) {\n for i := 0; i < 1000000; i++ {\n fmt.Println(0)\n }\n}\n
// Time complexity of algorithm A: constant order\nfunc algorithmA(n: Int) {\n print(0)\n}\n\n// Time complexity of algorithm B: linear order\nfunc algorithmB(n: Int) {\n for _ in 0 ..< n {\n print(0)\n }\n}\n\n// Time complexity of algorithm C: constant order\nfunc algorithmC(n: Int) {\n for _ in 0 ..< 1000000 {\n print(0)\n }\n}\n
// Time complexity of algorithm A: constant order\nfunction algorithm_A(n) {\n console.log(0);\n}\n// Time complexity of algorithm B: linear order\nfunction algorithm_B(n) {\n for (let i = 0; i < n; i++) {\n console.log(0);\n }\n}\n// Time complexity of algorithm C: constant order\nfunction algorithm_C(n) {\n for (let i = 0; i < 1000000; i++) {\n console.log(0);\n }\n}\n
// Time complexity of algorithm A: constant order\nfunction algorithm_A(n: number): void {\n console.log(0);\n}\n// Time complexity of algorithm B: linear order\nfunction algorithm_B(n: number): void {\n for (let i = 0; i < n; i++) {\n console.log(0);\n }\n}\n// Time complexity of algorithm C: constant order\nfunction algorithm_C(n: number): void {\n for (let i = 0; i < 1000000; i++) {\n console.log(0);\n }\n}\n
// Time complexity of algorithm A: constant order\nvoid algorithmA(int n) {\n print(0);\n}\n// Time complexity of algorithm B: linear order\nvoid algorithmB(int n) {\n for (int i = 0; i < n; i++) {\n print(0);\n }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithmC(int n) {\n for (int i = 0; i < 1000000; i++) {\n print(0);\n }\n}\n
// Time complexity of algorithm A: constant order\nfn algorithm_A(n: i32) {\n println!(\"{}\", 0);\n}\n// Time complexity of algorithm B: linear order\nfn algorithm_B(n: i32) {\n for _ in 0..n {\n println!(\"{}\", 0);\n }\n}\n// Time complexity of algorithm C: constant order\nfn algorithm_C(n: i32) {\n for _ in 0..1000000 {\n println!(\"{}\", 0);\n }\n}\n
// Time complexity of algorithm A: constant order\nvoid algorithm_A(int n) {\n printf(\"%d\", 0);\n}\n// Time complexity of algorithm B: linear order\nvoid algorithm_B(int n) {\n for (int i = 0; i < n; i++) {\n printf(\"%d\", 0);\n }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithm_C(int n) {\n for (int i = 0; i < 1000000; i++) {\n printf(\"%d\", 0);\n }\n}\n
// Time complexity of algorithm A: constant order\nfn algorithm_A(n: usize) void {\n _ = n;\n std.debug.print(\"{}\\n\", .{0});\n}\n// Time complexity of algorithm B: linear order\nfn algorithm_B(n: i32) void {\n for (0..n) |_| {\n std.debug.print(\"{}\\n\", .{0});\n }\n}\n// Time complexity of algorithm C: constant order\nfn algorithm_C(n: i32) void {\n _ = n;\n for (0..1000000) |_| {\n std.debug.print(\"{}\\n\", .{0});\n }\n}\n
The Figure 2-7 shows the time complexity of the above three algorithmic functions.
A
has only \\(1\\) print operations, and the running time of the algorithm does not increase with \\(n\\). We call the time complexity of this algorithm \"constant order\".B
requires \\(n\\) cycles, and the running time of the algorithm increases linearly with \\(n\\). The time complexity of this algorithm is called \"linear order\".C
requires \\(1000000\\) loops, which is a long runtime, but it is independent of the size of the input data \\(n\\). Therefore, the time complexity of C
is the same as that of A
, which is still of \"constant order\".Figure 2-7 \u00a0 Time growth trends for algorithms A, B and C
What are the characteristics of time complexity analysis compared to direct statistical algorithmic running time?
B
increases linearly and is slower than algorithm A
for \\(n > 1\\) and slower than algorithm C
for \\(n > 1,000,000\\). In fact, as long as the input data size \\(n\\) is large enough, algorithms with \"constant order\" of complexity will always outperform algorithms with \"linear order\", which is exactly what the time complexity trend means.A
and C
have the same time complexity, the actual running time varies greatly. Similarly, although the time complexity of algorithm B
is higher than that of C
, algorithm B
significantly outperforms algorithm C
when the size of the input data \\(n\\) is small. In these cases, it is difficult to judge the efficiency of an algorithm based on time complexity alone. Of course, despite the above problems, complexity analysis is still the most effective and commonly used method to judge the efficiency of algorithms.Given a function with input size \\(n\\):
PythonC++JavaC#GoSwiftJSTSDartRustCZigdef algorithm(n: int):\n a = 1 # +1\n a = a + 1 # +1\n a = a * 2 # +1\n # Cycle n times\n for i in range(n): # +1\n print(0) # +1\n
void algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // Loop n times\n for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)\n cout << 0 << endl; // +1\n }\n}\n
void algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // Loop n times\n for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)\n System.out.println(0); // +1\n }\n}\n
void Algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // Loop n times\n for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)\n Console.WriteLine(0); // +1\n }\n}\n
func algorithm(n int) {\n a := 1 // +1\n a = a + 1 // +1\n a = a * 2 // +1\n // Loop n times\n for i := 0; i < n; i++ { // +1\n fmt.Println(a) // +1\n }\n}\n
func algorithm(n: Int) {\n var a = 1 // +1\n a = a + 1 // +1\n a = a * 2 // +1\n // Loop n times\n for _ in 0 ..< n { // +1\n print(0) // +1\n }\n}\n
function algorithm(n) {\n var a = 1; // +1\n a += 1; // +1\n a *= 2; // +1\n // Loop n times\n for(let i = 0; i < n; i++){ // +1 (execute i ++ every round)\n console.log(0); // +1\n }\n}\n
function algorithm(n: number): void{\n var a: number = 1; // +1\n a += 1; // +1\n a *= 2; // +1\n // Loop n times\n for(let i = 0; i < n; i++){ // +1 (execute i ++ every round)\n console.log(0); // +1\n }\n}\n
void algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // Loop n times\n for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)\n print(0); // +1\n }\n}\n
fn algorithm(n: i32) {\n let mut a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n\n // Loop n times\n for _ in 0..n { // +1 (execute i ++ every round)\n println!(\"{}\", 0); // +1\n }\n}\n
void algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // Loop n times\n for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)\n printf(\"%d\", 0); // +1\n }\n} \n
fn algorithm(n: usize) void {\n var a: i32 = 1; // +1\n a += 1; // +1\n a *= 2; // +1\n // Loop n times\n for (0..n) |_| { // +1 (execute i ++ every round)\n std.debug.print(\"{}\\n\", .{0}); // +1\n }\n}\n
Let the number of operations of the algorithm be a function of the size of the input data \\(n\\), denoted as \\(T(n)\\) , then the number of operations of the above function is:
\\[ T(n) = 3 + 2n \\]\\(T(n)\\) is a primary function, which indicates that the trend of its running time growth is linear, and thus its time complexity is of linear order.
We denote the time complexity of the linear order as \\(O(n)\\) , and this mathematical notation is called the \"big \\(O\\) notation big-\\(O\\) notation\", which denotes the \"asymptotic upper bound\" of the function \\(T(n)\\).
Time complexity analysis is essentially the computation of asymptotic upper bounds on the \"number of operations function \\(T(n)\\)\", which has a clear mathematical definition.
Function asymptotic upper bound
If there exists a positive real number \\(c\\) and a real number \\(n_0\\) such that \\(T(n) \\leq c \\cdot f(n)\\) for all \\(n > n_0\\) , then it can be argued that \\(f(n)\\) gives an asymptotic upper bound on \\(T(n)\\) , denoted as \\(T(n) = O(f(n))\\) .
As shown in the Figure 2-8 , computing the asymptotic upper bound is a matter of finding a function \\(f(n)\\) such that \\(T(n)\\) and \\(f(n)\\) are at the same growth level as \\(n\\) tends to infinity, differing only by a multiple of the constant term \\(c\\).
Figure 2-8 \u00a0 asymptotic upper bound of function
"},{"location":"chapter_computational_complexity/time_complexity/#233-method-of-projection","title":"2.3.3 \u00a0 Method Of Projection","text":"Asymptotic upper bounds are a bit heavy on math, so don't worry if you feel you don't have a full solution. Because in practice, we only need to master the projection method, and the mathematical meaning can be gradually comprehended.
By definition, after determining \\(f(n)\\), we can get the time complexity \\(O(f(n))\\). So how to determine the asymptotic upper bound \\(f(n)\\)? The overall is divided into two steps: first count the number of operations, and then determine the asymptotic upper bound.
"},{"location":"chapter_computational_complexity/time_complexity/#1-the-first-step-counting-the-number-of-operations","title":"1. \u00a0 The First Step: Counting The Number Of Operations","text":"For the code, it is sufficient to calculate from top to bottom line by line. However, since the constant term \\(c \\cdot f(n)\\) in the above \\(c \\cdot f(n)\\) can take any size, the various coefficients and constant terms in the number of operations \\(T(n)\\) can be ignored. Based on this principle, the following counting simplification techniques can be summarized.
1.
and 2.
respectively.Given a function, we can use the above trick to count the number of operations.
PythonC++JavaC#GoSwiftJSTSDartRustCZigdef algorithm(n: int):\n a = 1 # +0 (trick 1)\n a = a + n # +0 (trick 1)\n # +n (technique 2)\n for i in range(5 * n + 1):\n print(0)\n # +n*n (technique 3)\n for i in range(2 * n):\n for j in range(n + 1):\n print(0)\n
void algorithm(int n) {\n int a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (int i = 0; i < 5 * n + 1; i++) {\n cout << 0 << endl;\n }\n // +n*n (technique 3)\n for (int i = 0; i < 2 * n; i++) {\n for (int j = 0; j < n + 1; j++) {\n cout << 0 << endl;\n }\n }\n}\n
void algorithm(int n) {\n int a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (int i = 0; i < 5 * n + 1; i++) {\n System.out.println(0);\n }\n // +n*n (technique 3)\n for (int i = 0; i < 2 * n; i++) {\n for (int j = 0; j < n + 1; j++) {\n System.out.println(0);\n }\n }\n}\n
void Algorithm(int n) {\n int a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (int i = 0; i < 5 * n + 1; i++) {\n Console.WriteLine(0);\n }\n // +n*n (technique 3)\n for (int i = 0; i < 2 * n; i++) {\n for (int j = 0; j < n + 1; j++) {\n Console.WriteLine(0);\n }\n }\n}\n
func algorithm(n int) {\n a := 1 // +0 (trick 1)\n a = a + n // +0 (trick 1)\n // +n (technique 2)\n for i := 0; i < 5 * n + 1; i++ {\n fmt.Println(0)\n }\n // +n*n (technique 3)\n for i := 0; i < 2 * n; i++ {\n for j := 0; j < n + 1; j++ {\n fmt.Println(0)\n }\n }\n}\n
func algorithm(n: Int) {\n var a = 1 // +0 (trick 1)\n a = a + n // +0 (trick 1)\n // +n (technique 2)\n for _ in 0 ..< (5 * n + 1) {\n print(0)\n }\n // +n*n (technique 3)\n for _ in 0 ..< (2 * n) {\n for _ in 0 ..< (n + 1) {\n print(0)\n }\n }\n}\n
function algorithm(n) {\n let a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (let i = 0; i < 5 * n + 1; i++) {\n console.log(0);\n }\n // +n*n (technique 3)\n for (let i = 0; i < 2 * n; i++) {\n for (let j = 0; j < n + 1; j++) {\n console.log(0);\n }\n }\n}\n
function algorithm(n: number): void {\n let a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (let i = 0; i < 5 * n + 1; i++) {\n console.log(0);\n }\n // +n*n (technique 3)\n for (let i = 0; i < 2 * n; i++) {\n for (let j = 0; j < n + 1; j++) {\n console.log(0);\n }\n }\n}\n
void algorithm(int n) {\n int a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (int i = 0; i < 5 * n + 1; i++) {\n print(0);\n }\n // +n*n (technique 3)\n for (int i = 0; i < 2 * n; i++) {\n for (int j = 0; j < n + 1; j++) {\n print(0);\n }\n }\n}\n
fn algorithm(n: i32) {\n let mut a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n\n // +n (technique 2)\n for i in 0..(5 * n + 1) {\n println!(\"{}\", 0);\n }\n\n // +n*n (technique 3)\n for i in 0..(2 * n) {\n for j in 0..(n + 1) {\n println!(\"{}\", 0);\n }\n }\n}\n
void algorithm(int n) {\n int a = 1; // +0 (trick 1)\n a = a + n; // +0 (trick 1)\n // +n (technique 2)\n for (int i = 0; i < 5 * n + 1; i++) {\n printf(\"%d\", 0);\n }\n // +n*n (technique 3)\n for (int i = 0; i < 2 * n; i++) {\n for (int j = 0; j < n + 1; j++) {\n printf(\"%d\", 0);\n }\n }\n}\n
fn algorithm(n: usize) void {\n var a: i32 = 1; // +0 (trick 1)\n a = a + @as(i32, @intCast(n)); // +0 (trick 1)\n\n // +n (technique 2)\n for(0..(5 * n + 1)) |_| {\n std.debug.print(\"{}\\n\", .{0});\n }\n\n // +n*n (technique 3)\n for(0..(2 * n)) |_| {\n for(0..(n + 1)) |_| {\n std.debug.print(\"{}\\n\", .{0});\n }\n }\n}\n
The following equations show the statistical results before and after using the above technique, both of which were introduced with a time complexity of \\(O(n^2)\\) .
\\[ \\begin{aligned} T(n) & = 2n(n + 1) + (5n + 1) + 2 & \\text{complete statistics (-.-|||)} \\newline & = 2n^2 + 7n + 3 \\newline T(n) & = n^2 + n & \\text{Lazy Stats (o.O)} \\end{aligned} \\]"},{"location":"chapter_computational_complexity/time_complexity/#2-step-2-judging-the-asymptotic-upper-bounds","title":"2. \u00a0 Step 2: Judging The Asymptotic Upper Bounds","text":"The time complexity is determined by the highest order term in the polynomial \\(T(n)\\). This is because as \\(n\\) tends to infinity, the highest order term will play a dominant role and the effects of all other terms can be ignored.
The Table 2-2 shows some examples, some of which have exaggerated values to emphasize the conclusion that \"the coefficients can't touch the order\". As \\(n\\) tends to infinity, these constants become irrelevant.
Table 2-2 \u00a0 Time complexity corresponding to different number of operations
number of operations \\(T(n)\\) time complexity \\(O(f(n))\\) \\(100000\\) \\(O(1)\\) \\(3n + 2\\) \\(O(n)\\) \\(2n^2 + 3n + 2\\) \\(O(n^2)\\) \\(n^3 + 10000n^2\\) \\(O(n^3)\\) \\(2^n + 10000n^{10000}\\) \\(O(2^n)\\)"},{"location":"chapter_computational_complexity/time_complexity/#234-common-types","title":"2.3.4 \u00a0 Common Types","text":"Let the input data size be \\(n\\) , the common types of time complexity are shown in the Figure 2-9 (in descending order).
\\[ \\begin{aligned} O(1) < O(\\log n) < O(n) < O(n \\log n) < O(n^2) < O(2^n) < O(n!) \\newline \\text{constant order} < \\text{logarithmic order} < \\text{linear order} < \\text{linear logarithmic order} < \\text{square order} < \\text{exponential order} < \\text{multiplication order} \\end{aligned} \\]Figure 2-9 \u00a0 Common time complexity types
"},{"location":"chapter_computational_complexity/time_complexity/#1-constant-order-o1","title":"1. \u00a0 Constant Order \\(O(1)\\)","text":"The number of operations of the constant order is independent of the input data size \\(n\\), i.e., it does not change with \\(n\\).
In the following function, although the number of operations size
may be large, the time complexity is still \\(O(1)\\) because it is independent of the input data size \\(n\\) :
def constant(n: int) -> int:\n \"\"\"\u5e38\u6570\u9636\"\"\"\n count = 0\n size = 100000\n for _ in range(size):\n count += 1\n return count\n
time_complexity.cpp/* \u5e38\u6570\u9636 */\nint constant(int n) {\n int count = 0;\n int size = 100000;\n for (int i = 0; i < size; i++)\n count++;\n return count;\n}\n
time_complexity.java/* \u5e38\u6570\u9636 */\nint constant(int n) {\n int count = 0;\n int size = 100000;\n for (int i = 0; i < size; i++)\n count++;\n return count;\n}\n
time_complexity.cs/* \u5e38\u6570\u9636 */\nint Constant(int n) {\n int count = 0;\n int size = 100000;\n for (int i = 0; i < size; i++)\n count++;\n return count;\n}\n
time_complexity.go/* \u5e38\u6570\u9636 */\nfunc constant(n int) int {\n count := 0\n size := 100000\n for i := 0; i < size; i++ {\n count++\n }\n return count\n}\n
time_complexity.swift/* \u5e38\u6570\u9636 */\nfunc constant(n: Int) -> Int {\n var count = 0\n let size = 100_000\n for _ in 0 ..< size {\n count += 1\n }\n return count\n}\n
time_complexity.js/* \u5e38\u6570\u9636 */\nfunction constant(n) {\n let count = 0;\n const size = 100000;\n for (let i = 0; i < size; i++) count++;\n return count;\n}\n
time_complexity.ts/* \u5e38\u6570\u9636 */\nfunction constant(n: number): number {\n let count = 0;\n const size = 100000;\n for (let i = 0; i < size; i++) count++;\n return count;\n}\n
time_complexity.dart/* \u5e38\u6570\u9636 */\nint constant(int n) {\n int count = 0;\n int size = 100000;\n for (var i = 0; i < size; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.rs/* \u5e38\u6570\u9636 */\nfn constant(n: i32) -> i32 {\n _ = n;\n let mut count = 0;\n let size = 100_000;\n for _ in 0..size {\n count += 1;\n }\n count\n}\n
time_complexity.c/* \u5e38\u6570\u9636 */\nint constant(int n) {\n int count = 0;\n int size = 100000;\n int i = 0;\n for (int i = 0; i < size; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.zig// \u5e38\u6570\u9636\nfn constant(n: i32) i32 {\n _ = n;\n var count: i32 = 0;\n const size: i32 = 100_000;\n var i: i32 = 0;\n while(i<size) : (i += 1) {\n count += 1;\n }\n return count;\n}\n
"},{"location":"chapter_computational_complexity/time_complexity/#2-linear-order-on","title":"2. \u00a0 Linear Order \\(O(N)\\)","text":"The number of operations in a linear order grows in linear steps relative to the input data size \\(n\\). Linear orders are usually found in single level loops:
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef linear(n: int) -> int:\n \"\"\"\u7ebf\u6027\u9636\"\"\"\n count = 0\n for _ in range(n):\n count += 1\n return count\n
time_complexity.cpp/* \u7ebf\u6027\u9636 */\nint linear(int n) {\n int count = 0;\n for (int i = 0; i < n; i++)\n count++;\n return count;\n}\n
time_complexity.java/* \u7ebf\u6027\u9636 */\nint linear(int n) {\n int count = 0;\n for (int i = 0; i < n; i++)\n count++;\n return count;\n}\n
time_complexity.cs/* \u7ebf\u6027\u9636 */\nint Linear(int n) {\n int count = 0;\n for (int i = 0; i < n; i++)\n count++;\n return count;\n}\n
time_complexity.go/* \u7ebf\u6027\u9636 */\nfunc linear(n int) int {\n count := 0\n for i := 0; i < n; i++ {\n count++\n }\n return count\n}\n
time_complexity.swift/* \u7ebf\u6027\u9636 */\nfunc linear(n: Int) -> Int {\n var count = 0\n for _ in 0 ..< n {\n count += 1\n }\n return count\n}\n
time_complexity.js/* \u7ebf\u6027\u9636 */\nfunction linear(n) {\n let count = 0;\n for (let i = 0; i < n; i++) count++;\n return count;\n}\n
time_complexity.ts/* \u7ebf\u6027\u9636 */\nfunction linear(n: number): number {\n let count = 0;\n for (let i = 0; i < n; i++) count++;\n return count;\n}\n
time_complexity.dart/* \u7ebf\u6027\u9636 */\nint linear(int n) {\n int count = 0;\n for (var i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.rs/* \u7ebf\u6027\u9636 */\nfn linear(n: i32) -> i32 {\n let mut count = 0;\n for _ in 0..n {\n count += 1;\n }\n count\n}\n
time_complexity.c/* \u7ebf\u6027\u9636 */\nint linear(int n) {\n int count = 0;\n for (int i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.zig// \u7ebf\u6027\u9636\nfn linear(n: i32) i32 {\n var count: i32 = 0;\n var i: i32 = 0;\n while (i < n) : (i += 1) {\n count += 1;\n }\n return count;\n}\n
The time complexity of operations such as traversing an array and traversing a linked list is \\(O(n)\\) , where \\(n\\) is the length of the array or linked list:
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef array_traversal(nums: list[int]) -> int:\n \"\"\"\u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09\"\"\"\n count = 0\n # \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for num in nums:\n count += 1\n return count\n
time_complexity.cpp/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint arrayTraversal(vector<int> &nums) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (int num : nums) {\n count++;\n }\n return count;\n}\n
time_complexity.java/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint arrayTraversal(int[] nums) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (int num : nums) {\n count++;\n }\n return count;\n}\n
time_complexity.cs/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint ArrayTraversal(int[] nums) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n foreach (int num in nums) {\n count++;\n }\n return count;\n}\n
time_complexity.go/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfunc arrayTraversal(nums []int) int {\n count := 0\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for range nums {\n count++\n }\n return count\n}\n
time_complexity.swift/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfunc arrayTraversal(nums: [Int]) -> Int {\n var count = 0\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for _ in nums {\n count += 1\n }\n return count\n}\n
time_complexity.js/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfunction arrayTraversal(nums) {\n let count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (let i = 0; i < nums.length; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.ts/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfunction arrayTraversal(nums: number[]): number {\n let count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (let i = 0; i < nums.length; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.dart/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint arrayTraversal(List<int> nums) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (var _num in nums) {\n count++;\n }\n return count;\n}\n
time_complexity.rs/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfn array_traversal(nums: &[i32]) -> i32 {\n let mut count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for _ in nums {\n count += 1;\n }\n count\n}\n
time_complexity.c/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint arrayTraversal(int *nums, int n) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (int i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.zig// \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09\nfn arrayTraversal(nums: []i32) i32 {\n var count: i32 = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\n for (nums) |_| {\n count += 1;\n }\n return count;\n}\n
It is worth noting that Input data size \\(n\\) needs to be determined specifically according to the type of input data. For example, in the first example, the variable \\(n\\) is the input data size; in the second example, the array length \\(n\\) is the data size.
"},{"location":"chapter_computational_complexity/time_complexity/#3-squared-order-on2","title":"3. \u00a0 Squared Order \\(O(N^2)\\)","text":"The number of operations in the square order grows in square steps with respect to the size of the input data \\(n\\). The squared order is usually found in nested loops, where both the outer and inner levels are \\(O(n)\\) and therefore overall \\(O(n^2)\\):
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef quadratic(n: int) -> int:\n \"\"\"\u5e73\u65b9\u9636\"\"\"\n count = 0\n # \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for i in range(n):\n for j in range(n):\n count += 1\n return count\n
time_complexity.cpp/* \u5e73\u65b9\u9636 */\nint quadratic(int n) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.java/* \u5e73\u65b9\u9636 */\nint quadratic(int n) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.cs/* \u5e73\u65b9\u9636 */\nint Quadratic(int n) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.go/* \u5e73\u65b9\u9636 */\nfunc quadratic(n int) int {\n count := 0\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for i := 0; i < n; i++ {\n for j := 0; j < n; j++ {\n count++\n }\n }\n return count\n}\n
time_complexity.swift/* \u5e73\u65b9\u9636 */\nfunc quadratic(n: Int) -> Int {\n var count = 0\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for _ in 0 ..< n {\n for _ in 0 ..< n {\n count += 1\n }\n }\n return count\n}\n
time_complexity.js/* \u5e73\u65b9\u9636 */\nfunction quadratic(n) {\n let count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (let i = 0; i < n; i++) {\n for (let j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.ts/* \u5e73\u65b9\u9636 */\nfunction quadratic(n: number): number {\n let count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (let i = 0; i < n; i++) {\n for (let j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.dart/* \u5e73\u65b9\u9636 */\nint quadratic(int n) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.rs/* \u5e73\u65b9\u9636 */\nfn quadratic(n: i32) -> i32 {\n let mut count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for _ in 0..n {\n for _ in 0..n {\n count += 1;\n }\n }\n count\n}\n
time_complexity.c/* \u5e73\u65b9\u9636 */\nint quadratic(int n) {\n int count = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < n; j++) {\n count++;\n }\n }\n return count;\n}\n
time_complexity.zig// \u5e73\u65b9\u9636\nfn quadratic(n: i32) i32 {\n var count: i32 = 0;\n var i: i32 = 0;\n // \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\n while (i < n) : (i += 1) {\n var j: i32 = 0;\n while (j < n) : (j += 1) {\n count += 1;\n }\n }\n return count;\n}\n
The Figure 2-10 compares the three time complexities of constant order, linear order and squared order.
Figure 2-10 \u00a0 Time complexity of constant, linear and quadratic orders
Taking bubble sort as an example, the outer loop executes \\(n - 1\\) times, and the inner loop executes \\(n-1\\), \\(n-2\\), \\(\\dots\\), \\(2\\), \\(1\\) times, which averages out to \\(n / 2\\) times, resulting in a time complexity of \\(O((n - 1) n / 2) = O(n^2)\\) .
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef bubble_sort(nums: list[int]) -> int:\n \"\"\"\u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09\"\"\"\n count = 0 # \u8ba1\u6570\u5668\n # \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for i in range(len(nums) - 1, 0, -1):\n # \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for j in range(i):\n if nums[j] > nums[j + 1]:\n # \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n tmp: int = nums[j]\n nums[j] = nums[j + 1]\n nums[j + 1] = tmp\n count += 3 # \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n return count\n
time_complexity.cpp/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint bubbleSort(vector<int> &nums) {\n int count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (int i = nums.size() - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for (int j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.java/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint bubbleSort(int[] nums) {\n int count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (int i = nums.length - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for (int j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.cs/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint BubbleSort(int[] nums) {\n int count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (int i = nums.Length - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef \n for (int j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.go/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfunc bubbleSort(nums []int) int {\n count := 0 // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for i := len(nums) - 1; i > 0; i-- {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for j := 0; j < i; j++ {\n if nums[j] > nums[j+1] {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n tmp := nums[j]\n nums[j] = nums[j+1]\n nums[j+1] = tmp\n count += 3 // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count\n}\n
time_complexity.swift/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfunc bubbleSort(nums: inout [Int]) -> Int {\n var count = 0 // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for i in stride(from: nums.count - 1, to: 0, by: -1) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef \n for j in 0 ..< i {\n if nums[j] > nums[j + 1] {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n let tmp = nums[j]\n nums[j] = nums[j + 1]\n nums[j + 1] = tmp\n count += 3 // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count\n}\n
time_complexity.js/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfunction bubbleSort(nums) {\n let count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (let i = nums.length - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for (let j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.ts/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfunction bubbleSort(nums: number[]): number {\n let count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (let i = nums.length - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for (let j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.dart/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint bubbleSort(List<int> nums) {\n int count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (var i = nums.length - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for (var j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.rs/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfn bubble_sort(nums: &mut [i32]) -> i32 {\n let mut count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for i in (1..nums.len()).rev() {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef \n for j in 0..i {\n if nums[j] > nums[j + 1] {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n count\n}\n
time_complexity.c/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint bubbleSort(int *nums, int n) {\n int count = 0; // \u8ba1\u6570\u5668\n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n for (int i = n - 1; i > 0; i--) {\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef\n for (int j = 0; j < i; j++) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
time_complexity.zig// \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09\nfn bubbleSort(nums: []i32) i32 {\n var count: i32 = 0; // \u8ba1\u6570\u5668 \n // \u5916\u5faa\u73af\uff1a\u672a\u6392\u5e8f\u533a\u95f4\u4e3a [0, i]\n var i: i32 = @as(i32, @intCast(nums.len)) - 1;\n while (i > 0) : (i -= 1) {\n var j: usize = 0;\n // \u5185\u5faa\u73af\uff1a\u5c06\u672a\u6392\u5e8f\u533a\u95f4 [0, i] \u4e2d\u7684\u6700\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u8be5\u533a\u95f4\u7684\u6700\u53f3\u7aef \n while (j < i) : (j += 1) {\n if (nums[j] > nums[j + 1]) {\n // \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n var tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n }\n }\n }\n return count;\n}\n
"},{"location":"chapter_computational_complexity/time_complexity/#235-exponential-order-o2n","title":"2.3.5 \u00a0 Exponential Order \\(O(2^N)\\)","text":"Cell division in biology is a typical example of exponential growth: the initial state is \\(1\\) cells, after one round of division it becomes \\(2\\), after two rounds of division it becomes \\(4\\), and so on, after \\(n\\) rounds of division there are \\(2^n\\) cells.
The Figure 2-11 and the following code simulate the process of cell division with a time complexity of \\(O(2^n)\\) .
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef exponential(n: int) -> int:\n \"\"\"\u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09\"\"\"\n count = 0\n base = 1\n # \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for _ in range(n):\n for _ in range(base):\n count += 1\n base *= 2\n # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count\n
time_complexity.cpp/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint exponential(int n) {\n int count = 0, base = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.java/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint exponential(int n) {\n int count = 0, base = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.cs/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint Exponential(int n) {\n int count = 0, bas = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < bas; j++) {\n count++;\n }\n bas *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.go/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09*/\nfunc exponential(n int) int {\n count, base := 0, 1\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for i := 0; i < n; i++ {\n for j := 0; j < base; j++ {\n count++\n }\n base *= 2\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count\n}\n
time_complexity.swift/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunc exponential(n: Int) -> Int {\n var count = 0\n var base = 1\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for _ in 0 ..< n {\n for _ in 0 ..< base {\n count += 1\n }\n base *= 2\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count\n}\n
time_complexity.js/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunction exponential(n) {\n let count = 0,\n base = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (let i = 0; i < n; i++) {\n for (let j = 0; j < base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.ts/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunction exponential(n: number): number {\n let count = 0,\n base = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (let i = 0; i < n; i++) {\n for (let j = 0; j < base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.dart/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint exponential(int n) {\n int count = 0, base = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (var i = 0; i < n; i++) {\n for (var j = 0; j < base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.rs/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfn exponential(n: i32) -> i32 {\n let mut count = 0;\n let mut base = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for _ in 0..n {\n for _ in 0..base {\n count += 1\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n count\n}\n
time_complexity.c/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint exponential(int n) {\n int count = 0;\n int bas = 1;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < bas; j++) {\n count++;\n }\n bas *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
time_complexity.zig// \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09\nfn exponential(n: i32) i32 {\n var count: i32 = 0;\n var bas: i32 = 1;\n var i: i32 = 0;\n // \u7ec6\u80de\u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\n while (i < n) : (i += 1) {\n var j: i32 = 0;\n while (j < bas) : (j += 1) {\n count += 1;\n }\n bas *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n
Figure 2-11 \u00a0 time complexity of exponential order
In practical algorithms, exponential orders are often found in recursion functions. For example, in the following code, it recursively splits in two and stops after \\(n\\) splits:
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef exp_recur(n: int) -> int:\n \"\"\"\u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\n if n == 1:\n return 1\n return exp_recur(n - 1) + exp_recur(n - 1) + 1\n
time_complexity.cpp/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint expRecur(int n) {\n if (n == 1)\n return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.java/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint expRecur(int n) {\n if (n == 1)\n return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.cs/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint ExpRecur(int n) {\n if (n == 1) return 1;\n return ExpRecur(n - 1) + ExpRecur(n - 1) + 1;\n}\n
time_complexity.go/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09*/\nfunc expRecur(n int) int {\n if n == 1 {\n return 1\n }\n return expRecur(n-1) + expRecur(n-1) + 1\n}\n
time_complexity.swift/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc expRecur(n: Int) -> Int {\n if n == 1 {\n return 1\n }\n return expRecur(n: n - 1) + expRecur(n: n - 1) + 1\n}\n
time_complexity.js/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction expRecur(n) {\n if (n === 1) return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.ts/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction expRecur(n: number): number {\n if (n === 1) return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.dart/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint expRecur(int n) {\n if (n == 1) return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.rs/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfn exp_recur(n: i32) -> i32 {\n if n == 1 {\n return 1;\n }\n exp_recur(n - 1) + exp_recur(n - 1) + 1\n}\n
time_complexity.c/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint expRecur(int n) {\n if (n == 1)\n return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.zig// \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn expRecur(n: i32) i32 {\n if (n == 1) return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
Exponential order grows very rapidly and is more common in exhaustive methods (brute force search, backtracking, etc.). For problems with large data sizes, exponential order is unacceptable and usually requires the use of algorithms such as dynamic programming or greedy algorithms to solve.
"},{"location":"chapter_computational_complexity/time_complexity/#1-logarithmic-order-olog-n","title":"1. \u00a0 Logarithmic Order \\(O(\\Log N)\\)","text":"In contrast to the exponential order, the logarithmic order reflects the \"each round is reduced to half\" case. Let the input data size be \\(n\\), and since each round is reduced to half, the number of loops is \\(\\log_2 n\\), which is the inverse function of \\(2^n\\).
The Figure 2-12 and the code below simulate the process of \"reducing each round to half\" with a time complexity of \\(O(\\log_2 n)\\), which is abbreviated as \\(O(\\log n)\\).
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef logarithmic(n: float) -> int:\n \"\"\"\u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09\"\"\"\n count = 0\n while n > 1:\n n = n / 2\n count += 1\n return count\n
time_complexity.cpp/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint logarithmic(float n) {\n int count = 0;\n while (n > 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n
time_complexity.java/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint logarithmic(float n) {\n int count = 0;\n while (n > 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n
time_complexity.cs/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint Logarithmic(float n) {\n int count = 0;\n while (n > 1) {\n n /= 2;\n count++;\n }\n return count;\n}\n
time_complexity.go/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09*/\nfunc logarithmic(n float64) int {\n count := 0\n for n > 1 {\n n = n / 2\n count++\n }\n return count\n}\n
time_complexity.swift/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunc logarithmic(n: Double) -> Int {\n var count = 0\n var n = n\n while n > 1 {\n n = n / 2\n count += 1\n }\n return count\n}\n
time_complexity.js/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunction logarithmic(n) {\n let count = 0;\n while (n > 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n
time_complexity.ts/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunction logarithmic(n: number): number {\n let count = 0;\n while (n > 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n
time_complexity.dart/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint logarithmic(num n) {\n int count = 0;\n while (n > 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n
time_complexity.rs/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfn logarithmic(mut n: f32) -> i32 {\n let mut count = 0;\n while n > 1.0 {\n n = n / 2.0;\n count += 1;\n }\n count\n}\n
time_complexity.c/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint logarithmic(float n) {\n int count = 0;\n while (n > 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n
time_complexity.zig// \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09\nfn logarithmic(n: f32) i32 {\n var count: i32 = 0;\n var n_var = n;\n while (n_var > 1)\n {\n n_var = n_var / 2;\n count +=1;\n }\n return count;\n}\n
Figure 2-12 \u00a0 time complexity of logarithmic order
Similar to the exponential order, the logarithmic order is often found in recursion functions. The following code forms a recursion tree of height \\(\\log_2 n\\):
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef log_recur(n: float) -> int:\n \"\"\"\u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\n if n <= 1:\n return 0\n return log_recur(n / 2) + 1\n
time_complexity.cpp/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint logRecur(float n) {\n if (n <= 1)\n return 0;\n return logRecur(n / 2) + 1;\n}\n
time_complexity.java/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint logRecur(float n) {\n if (n <= 1)\n return 0;\n return logRecur(n / 2) + 1;\n}\n
time_complexity.cs/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint LogRecur(float n) {\n if (n <= 1) return 0;\n return LogRecur(n / 2) + 1;\n}\n
time_complexity.go/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09*/\nfunc logRecur(n float64) int {\n if n <= 1 {\n return 0\n }\n return logRecur(n/2) + 1\n}\n
time_complexity.swift/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc logRecur(n: Double) -> Int {\n if n <= 1 {\n return 0\n }\n return logRecur(n: n / 2) + 1\n}\n
time_complexity.js/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction logRecur(n) {\n if (n <= 1) return 0;\n return logRecur(n / 2) + 1;\n}\n
time_complexity.ts/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction logRecur(n: number): number {\n if (n <= 1) return 0;\n return logRecur(n / 2) + 1;\n}\n
time_complexity.dart/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint logRecur(num n) {\n if (n <= 1) return 0;\n return logRecur(n / 2) + 1;\n}\n
time_complexity.rs/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfn log_recur(n: f32) -> i32 {\n if n <= 1.0 {\n return 0;\n }\n log_recur(n / 2.0) + 1\n}\n
time_complexity.c/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint logRecur(float n) {\n if (n <= 1)\n return 0;\n return logRecur(n / 2) + 1;\n}\n
time_complexity.zig// \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn logRecur(n: f32) i32 {\n if (n <= 1) return 0;\n return logRecur(n / 2) + 1;\n}\n
Logarithmic order is often found in algorithms based on the divide and conquer strategy, which reflects the algorithmic ideas of \"dividing one into many\" and \"simplifying the complexity into simplicity\". It grows slowly and is the second most desirable time complexity after constant order.
What is the base of \\(O(\\log n)\\)?
To be precise, the corresponding time complexity of \"one divided into \\(m\\)\" is \\(O(\\log_m n)\\) . And by using the logarithmic permutation formula, we can get equal time complexity with different bases:
\\[ O(\\log_m n) = O(\\log_k n / \\log_k m) = O(\\log_k n) \\]That is, the base \\(m\\) can be converted without affecting the complexity. Therefore we usually omit the base \\(m\\) and write the logarithmic order directly as \\(O(\\log n)\\).
"},{"location":"chapter_computational_complexity/time_complexity/#2-linear-logarithmic-order-on-log-n","title":"2. \u00a0 Linear Logarithmic Order \\(O(N \\Log N)\\)","text":"The linear logarithmic order is often found in nested loops, and the time complexity of the two levels of loops is \\(O(\\log n)\\) and \\(O(n)\\) respectively. The related code is as follows:
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef linear_log_recur(n: float) -> int:\n \"\"\"\u7ebf\u6027\u5bf9\u6570\u9636\"\"\"\n if n <= 1:\n return 1\n count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2)\n for _ in range(n):\n count += 1\n return count\n
time_complexity.cpp/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint linearLogRecur(float n) {\n if (n <= 1)\n return 1;\n int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (int i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.java/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint linearLogRecur(float n) {\n if (n <= 1)\n return 1;\n int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (int i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.cs/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint LinearLogRecur(float n) {\n if (n <= 1) return 1;\n int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2);\n for (int i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.go/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfunc linearLogRecur(n float64) int {\n if n <= 1 {\n return 1\n }\n count := linearLogRecur(n/2) + linearLogRecur(n/2)\n for i := 0.0; i < n; i++ {\n count++\n }\n return count\n}\n
time_complexity.swift/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfunc linearLogRecur(n: Double) -> Int {\n if n <= 1 {\n return 1\n }\n var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2)\n for _ in stride(from: 0, to: n, by: 1) {\n count += 1\n }\n return count\n}\n
time_complexity.js/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfunction linearLogRecur(n) {\n if (n <= 1) return 1;\n let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (let i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.ts/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfunction linearLogRecur(n: number): number {\n if (n <= 1) return 1;\n let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (let i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.dart/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint linearLogRecur(num n) {\n if (n <= 1) return 1;\n int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (var i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.rs/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfn linear_log_recur(n: f32) -> i32 {\n if n <= 1.0 {\n return 1;\n }\n let mut count = linear_log_recur(n / 2.0) + linear_log_recur(n / 2.0);\n for _ in 0 ..n as i32 {\n count += 1;\n }\n return count\n}\n
time_complexity.c/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint linearLogRecur(float n) {\n if (n <= 1)\n return 1;\n int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (int i = 0; i < n; i++) {\n count++;\n }\n return count;\n}\n
time_complexity.zig// \u7ebf\u6027\u5bf9\u6570\u9636\nfn linearLogRecur(n: f32) i32 {\n if (n <= 1) return 1;\n var count: i32 = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n var i: f32 = 0;\n while (i < n) : (i += 1) {\n count += 1;\n }\n return count;\n}\n
The Figure 2-13 shows how the linear logarithmic order is generated. The total number of operations at each level of the binary tree is \\(n\\) , and the tree has a total of \\(\\log_2 n + 1\\) levels, resulting in a time complexity of \\(O(n\\log n)\\) .
Figure 2-13 \u00a0 Time complexity of linear logarithmic order
Mainstream sorting algorithms typically have a time complexity of \\(O(n \\log n)\\) , such as quick sort, merge sort, heap sort, etc.
"},{"location":"chapter_computational_complexity/time_complexity/#3-the-factorial-order-on","title":"3. \u00a0 The Factorial Order \\(O(N!)\\)","text":"The factorial order corresponds to the mathematical \"permutations problem\". Given \\(n\\) elements that do not repeat each other, find all possible permutations of them, the number of permutations being:
\\[ n! = n \\times (n - 1) \\times (n - 2) \\times \\dots \\times 2 \\times 1 \\]Factorials are usually implemented using recursion. As shown in the Figure 2-14 and in the code below, the first level splits \\(n\\), the second level splits \\(n - 1\\), and so on, until the splitting stops at the \\(n\\)th level:
PythonC++JavaC#GoSwiftJSTSDartRustCZig time_complexity.pydef factorial_recur(n: int) -> int:\n \"\"\"\u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\n if n == 0:\n return 1\n count = 0\n # \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for _ in range(n):\n count += factorial_recur(n - 1)\n return count\n
time_complexity.cpp/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint factorialRecur(int n) {\n if (n == 0)\n return 1;\n int count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for (int i = 0; i < n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.java/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint factorialRecur(int n) {\n if (n == 0)\n return 1;\n int count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for (int i = 0; i < n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.cs/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint FactorialRecur(int n) {\n if (n == 0) return 1;\n int count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for (int i = 0; i < n; i++) {\n count += FactorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.go/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc factorialRecur(n int) int {\n if n == 0 {\n return 1\n }\n count := 0\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for i := 0; i < n; i++ {\n count += factorialRecur(n - 1)\n }\n return count\n}\n
time_complexity.swift/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc factorialRecur(n: Int) -> Int {\n if n == 0 {\n return 1\n }\n var count = 0\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for _ in 0 ..< n {\n count += factorialRecur(n: n - 1)\n }\n return count\n}\n
time_complexity.js/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction factorialRecur(n) {\n if (n === 0) return 1;\n let count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for (let i = 0; i < n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.ts/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction factorialRecur(n: number): number {\n if (n === 0) return 1;\n let count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for (let i = 0; i < n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.dart/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint factorialRecur(int n) {\n if (n == 0) return 1;\n int count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for (var i = 0; i < n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.rs/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfn factorial_recur(n: i32) -> i32 {\n if n == 0 {\n return 1;\n }\n let mut count = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n for _ in 0..n {\n count += factorial_recur(n - 1);\n }\n count\n}\n
time_complexity.c/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint factorialRecur(int n) {\n if (n == 0)\n return 1;\n int count = 0;\n for (int i = 0; i < n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
time_complexity.zig// \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn factorialRecur(n: i32) i32 {\n if (n == 0) return 1;\n var count: i32 = 0;\n var i: i32 = 0;\n // \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\n while (i < n) : (i += 1) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n
Figure 2-14 \u00a0 Time complexity of the factorial order
Note that since there is always \\(n! > 2^n\\) when \\(n \\geq 4\\), the factorial order grows faster than the exponential order, and is also unacceptable when \\(n\\) is large.
"},{"location":"chapter_computational_complexity/time_complexity/#236-worst-best-average-time-complexity","title":"2.3.6 \u00a0 Worst, Best, Average Time Complexity","text":"The time efficiency of algorithms is often not fixed, but is related to the distribution of the input data. Suppose an array nums
of length \\(n\\) is input, where nums
consists of numbers from \\(1\\) to \\(n\\), each of which occurs only once; however, the order of the elements is randomly upset, and the goal of the task is to return the index of element \\(1\\). We can draw the following conclusion.
nums = [? , ? , ... , 1]
, i.e., when the end element is \\(1\\), a complete traversal of the array is required, to reach the worst time complexity \\(O(n)\\) .nums = [1, ? , ? , ...]
, i.e., when the first element is \\(1\\) , there is no need to continue traversing the array no matter how long it is, reaching the optimal time complexity \\(\\Omega(1)\\) .The \"worst time complexity\" corresponds to the asymptotic upper bound of the function and is denoted by the large \\(O\\) notation. Correspondingly, the \"optimal time complexity\" corresponds to the asymptotic lower bound of the function and is denoted in \\(\\Omega\\) notation:
PythonC++JavaC#GoSwiftJSTSDartRustCZig worst_best_time_complexity.pydef random_numbers(n: int) -> list[int]:\n \"\"\"\u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a: 1, 2, ..., n \uff0c\u987a\u5e8f\u88ab\u6253\u4e71\"\"\"\n # \u751f\u6210\u6570\u7ec4 nums =: 1, 2, 3, ..., n\n nums = [i for i in range(1, n + 1)]\n # \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n random.shuffle(nums)\n return nums\n\ndef find_one(nums: list[int]) -> int:\n \"\"\"\u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15\"\"\"\n for i in range(len(nums)):\n # \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n # \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if nums[i] == 1:\n return i\n return -1\n
worst_best_time_complexity.cpp/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nvector<int> randomNumbers(int n) {\n vector<int> nums(n);\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (int i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n // \u4f7f\u7528\u7cfb\u7edf\u65f6\u95f4\u751f\u6210\u968f\u673a\u79cd\u5b50\n unsigned seed = chrono::system_clock::now().time_since_epoch().count();\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n shuffle(nums.begin(), nums.end(), default_random_engine(seed));\n return nums;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint findOne(vector<int> &nums) {\n for (int i = 0; i < nums.size(); i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] == 1)\n return i;\n }\n return -1;\n}\n
worst_best_time_complexity.java/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nint[] randomNumbers(int n) {\n Integer[] nums = new Integer[n];\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (int i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n Collections.shuffle(Arrays.asList(nums));\n // Integer[] -> int[]\n int[] res = new int[n];\n for (int i = 0; i < n; i++) {\n res[i] = nums[i];\n }\n return res;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint findOne(int[] nums) {\n for (int i = 0; i < nums.length; i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] == 1)\n return i;\n }\n return -1;\n}\n
worst_best_time_complexity.cs/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nint[] RandomNumbers(int n) {\n int[] nums = new int[n];\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (int i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n for (int i = 0; i < nums.Length; i++) {\n int index = new Random().Next(i, nums.Length);\n (nums[i], nums[index]) = (nums[index], nums[i]);\n }\n return nums;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint FindOne(int[] nums) {\n for (int i = 0; i < nums.Length; i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] == 1)\n return i;\n }\n return -1;\n}\n
worst_best_time_complexity.go/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfunc randomNumbers(n int) []int {\n nums := make([]int, n)\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for i := 0; i < n; i++ {\n nums[i] = i + 1\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n rand.Shuffle(len(nums), func(i, j int) {\n nums[i], nums[j] = nums[j], nums[i]\n })\n return nums\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfunc findOne(nums []int) int {\n for i := 0; i < len(nums); i++ {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if nums[i] == 1 {\n return i\n }\n }\n return -1\n}\n
worst_best_time_complexity.swift/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfunc randomNumbers(n: Int) -> [Int] {\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n var nums = Array(1 ... n)\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n nums.shuffle()\n return nums\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfunc findOne(nums: [Int]) -> Int {\n for i in nums.indices {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if nums[i] == 1 {\n return i\n }\n }\n return -1\n}\n
worst_best_time_complexity.js/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfunction randomNumbers(n) {\n const nums = Array(n);\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (let i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n for (let i = 0; i < n; i++) {\n const r = Math.floor(Math.random() * (i + 1));\n const temp = nums[i];\n nums[i] = nums[r];\n nums[r] = temp;\n }\n return nums;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfunction findOne(nums) {\n for (let i = 0; i < nums.length; i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] === 1) {\n return i;\n }\n }\n return -1;\n}\n
worst_best_time_complexity.ts/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfunction randomNumbers(n: number): number[] {\n const nums = Array(n);\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (let i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n for (let i = 0; i < n; i++) {\n const r = Math.floor(Math.random() * (i + 1));\n const temp = nums[i];\n nums[i] = nums[r];\n nums[r] = temp;\n }\n return nums;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfunction findOne(nums: number[]): number {\n for (let i = 0; i < nums.length; i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] === 1) {\n return i;\n }\n }\n return -1;\n}\n
worst_best_time_complexity.dart/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nList<int> randomNumbers(int n) {\n final nums = List.filled(n, 0);\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (var i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n nums.shuffle();\n\n return nums;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint findOne(List<int> nums) {\n for (var i = 0; i < nums.length; i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] == 1) return i;\n }\n\n return -1;\n}\n
worst_best_time_complexity.rs/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfn random_numbers(n: i32) -> Vec<i32> {\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n let mut nums = (1..=n).collect::<Vec<i32>>();\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n nums.shuffle(&mut thread_rng());\n nums\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfn find_one(nums: &[i32]) -> Option<usize> {\n for i in 0..nums.len() {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if nums[i] == 1 {\n return Some(i);\n }\n }\n None\n}\n
worst_best_time_complexity.c/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nint *randomNumbers(int n) {\n // \u5206\u914d\u5806\u533a\u5185\u5b58\uff08\u521b\u5efa\u4e00\u7ef4\u53ef\u53d8\u957f\u6570\u7ec4\uff1a\u6570\u7ec4\u4e2d\u5143\u7d20\u6570\u91cf\u4e3a n \uff0c\u5143\u7d20\u7c7b\u578b\u4e3a int \uff09\n int *nums = (int *)malloc(n * sizeof(int));\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (int i = 0; i < n; i++) {\n nums[i] = i + 1;\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n for (int i = n - 1; i > 0; i--) {\n int j = rand() % (i + 1);\n int temp = nums[i];\n nums[i] = nums[j];\n nums[j] = temp;\n }\n return nums;\n}\n\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint findOne(int *nums, int n) {\n for (int i = 0; i < n; i++) {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (nums[i] == 1)\n return i;\n }\n return -1;\n}\n
worst_best_time_complexity.zig// \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71\nfn randomNumbers(comptime n: usize) [n]i32 {\n var nums: [n]i32 = undefined;\n // \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\n for (&nums, 0..) |*num, i| {\n num.* = @as(i32, @intCast(i)) + 1;\n }\n // \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\n const rand = std.crypto.random;\n rand.shuffle(i32, &nums);\n return nums;\n}\n\n// \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15\nfn findOne(nums: []i32) i32 {\n for (nums, 0..) |num, i| {\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n // \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\n if (num == 1) return @intCast(i);\n }\n return -1;\n}\n
It is worth stating that we rarely use the optimal time complexity in practice because it is usually only attainable with a small probability and may be somewhat misleading. whereas the worst time complexity is more practical because it gives a safe value for efficiency and allows us to use the algorithm with confidence.
From the above examples, it can be seen that the worst or best time complexity only occurs in \"special data distributions\", and the probability of these cases may be very small, which does not truly reflect the efficiency of the algorithm. In contrast, the average time complexity of can reflect the efficiency of the algorithm under random input data, which is denoted by the \\(\\Theta\\) notation.
For some algorithms, we can simply derive the average case under a random data distribution. For example, in the above example, since the input array is scrambled, the probability of an element \\(1\\) appearing at any index is equal, so the average number of loops of the algorithm is half of the length of the array \\(n / 2\\) , and the average time complexity is \\(\\Theta(n / 2) = \\Theta(n)\\) .
However, for more complex algorithms, calculating the average time complexity is often difficult because it is hard to analyze the overall mathematical expectation given the data distribution. In this case, we usually use the worst time complexity as a criterion for the efficiency of the algorithm.
Why do you rarely see the \\(\\Theta\\) symbol?
Perhaps because the \\(O\\) symbol is so catchy, we often use it to denote average time complexity. However, this practice is not standardized in the strict sense. In this book and other sources, if you encounter a statement like \"average time complexity \\(O(n)\\)\", please understand it as \\(\\Theta(n)\\).
"},{"location":"chapter_data_structure/","title":"Data Structure","text":"Abstract
Data structures are like a solid and varied framework.
It provides a blueprint for the orderly organization of data upon which algorithms can come alive.
"},{"location":"chapter_data_structure/classification_of_data_structure/","title":"Classification Of Data Structures","text":"Common data structures include arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. They can be divided into two categories: logical structure and physical structure.
"},{"location":"chapter_data_structure/classification_of_data_structure/#logical-structures-linear-and-non-linear","title":"Logical Structures: Linear And Non-linear","text":"Logical structures reveal logical relationships between data elements. In arrays and linked lists, data are arranged in sequential order, reflecting the linear relationship between data; while in trees, data are arranged hierarchically from the top down, showing the derived relationship between ancestors and descendants; and graphs are composed of nodes and edges, reflecting the complex network relationship.
As shown in the figure below, logical structures can further be divided into \"linear data structure\" and \"non-linear data structure\". Linear data structures are more intuitive, meaning that the data are arranged linearly in terms of logical relationships; non-linear data structures, on the other hand, are arranged non-linearly.
Non-linear data structures can be further divided into tree and graph structures.
When an algorithm is running, the data being processed is stored in memory. The figure below shows a computer memory module where each black square represents a memory space. We can think of the memory as a giant Excel sheet in which each cell can store data of a certain size.
The system accesses the data at the target location by means of a memory address. As shown in the figure below, the computer assigns a unique identifier to each cell in the table according to specific rules, ensuring that each memory space has a unique memory address. With these addresses, the program can access the data in memory.
Tip
It is worth noting that comparing memory to the Excel sheet is a simplified analogy. The actual memory working mechanism is more complicated, involving the concepts of address, space, memory management, cache mechanism, virtual and physical memory.
Memory is a shared resource for all programs, and when a block of memory is occupied by one program, it cannot be used by other programs at the same time. Therefore, considering memory resources is crucial in designing data structures and algorithms. For example, the algorithm's peak memory usage should not exceed the remaining free memory of the system; if there is a lack of contiguous memory blocks, then the data structure chosen must be able to be stored in non-contiguous memory blocks.
As shown in the figure below, Physical structure reflects the way data is stored in computer memory and it can be divided into consecutive space storage (arrays) and distributed space storage (linked lists). The physical structure determines how data is accessed, updated, added, deleted, etc. Logical and physical structure complement each other in terms of time efficiency and space efficiency.
It is worth stating that all data structures are implemented based on arrays, linked lists, or a combination of the two. For example, stacks and queues can be implemented using both arrays and linked lists; and implementations of hash tables may contain both arrays and linked lists.
Data structures based on arrays are also known as \"static data structures\", which means that such structures' length remains constant after initialization. In contrast, data structures based on linked lists are called \"dynamic data structures\", meaning that their length can be adjusted during program execution after initialization.
Tip
If you find it difficult to understand the physical structure, it is recommended that you read the next chapter, \"Arrays and Linked Lists,\" before reviewing this section.
"},{"location":"chapter_introduction/","title":"\u7b2c 1 \u7ae0 \u00a0 Introduction to Algorithms","text":"Abstract
A graceful maiden dances, intertwined with the data, her skirt swaying to the melody of algorithms.
She invites you to a dance, follow her steps, and enter the world of algorithms full of logic and beauty.
"},{"location":"chapter_introduction/#_1","title":"\u672c\u7ae0\u5185\u5bb9","text":"When we hear the word \"algorithm\", we naturally think of mathematics. However, many algorithms do not involve complex mathematics but rely more on basic logic, which is ubiquitous in our daily lives.
Before we formally discuss algorithms, an interesting fact is worth sharing: you have already learned many algorithms unconsciously and have become accustomed to applying them in your daily life. Below, I will give a few specific examples to prove this point.
Example 1: Looking Up a Dictionary. In a standard dictionary, each word corresponds to a phonetic transcription and the dictionary is organized alphabetically based on these transcriptions. Let's say we're looking for a word that begins with the letter \\(r\\). This is typically done in the following way:
1.
and 2.
until you find the page where the word begins with the letter \\(r\\).Figure 1-1 \u00a0 Dictionary search step
The skill of looking up a dictionary, essential for elementary school students, is actually the renowned binary search algorithm. Through the lens of data structures, we can view the dictionary as a sorted \"array\"; while from an algorithmic perspective, the series of operations in looking up a dictionary can be seen as \"binary search\".
Example 2: Organizing Playing Cards. When playing cards, we need to arrange the cards in ascending order each game, as shown in the following process.
2.
, each iteration involving insertion of one card from the unordered segment into the ordered portion, until all cards are appropriately ordered.Figure 1-2 \u00a0 Playing cards sorting process
The above method of organizing playing cards is essentially the \"insertion sort\" algorithm, which is very efficient for small datasets. Many programming languages' sorting library functions include insertion sort.
Example 3: Making Change. Suppose we buy goods worth \\(69\\) yuan at a supermarket and give the cashier \\(100\\) yuan, then the cashier needs to give us \\(31\\) yuan in change. They would naturally complete the thought process as shown below.
Figure 1-3 \u00a0 Change making process
In the aforementioned steps, at each stage, we make the optimal choice (utilizing the highest denomination possible), ultimately deriving at a feasible change-making approach. From the perspective of data structures and algorithms, this approach is essentially a \"greedy\" algorithm.
From preparing a dish to traversing interstellar realms, virtually every problem-solving endeavor relies on algorithms. The emergence of computers enables us to store data structures in memory and write code to call CPUs and GPUs to execute algorithms. Consequently, we can transfer real-life predicaments to computers, efficiently addressing a myriad of complex issues.
Tip
If concepts such as data structures, algorithms, arrays, and binary search still seem somewhat obsecure, I encourage you to continue reading. This book will gently guide you into the realm of understanding data structures and algorithms.
"},{"location":"chapter_introduction/summary/","title":"1.3 \u00a0 Summary","text":"An \"algorithm\" is a set of instructions or steps to solve a specific problem within a finite amount of time. It has the following characteristics:
A \"data structure\" is a way of organizing and storing data in a computer, with the following design goals:
Designing data structures is a balancing act, often requiring trade-offs. If you want to improve in one aspect, you often need to compromise in another. Here are two examples:
As shown in the diagram below, data structures and algorithms are highly related and closely integrated, specifically in the following three aspects:
Figure 1-4 \u00a0 Relationship between data structures and algorithms
Data structures and algorithms can be likened to a set of building blocks, as illustrated in the Figure 1-5 . A building block set includes numerous pieces, accompanied by detailed assembly instructions. Following these instructions step by step allows us to construct an intricate block model.
Figure 1-5 \u00a0 Assembling blocks
The detailed correspondence between the two is shown in the Table 1-1 .
Table 1-1 \u00a0 Comparing Data Structures and Algorithms to Building Blocks
Data Structures and Algorithms Building Blocks Input data Unassembled blocks Data structure Organization of blocks, including shape, size, connections, etc Algorithm A series of steps to assemble the blocks into the desired shape Output data Completed Block modelIt's worth noting that data structures and algorithms are independent of programming languages. For this reason, this book is able to provide implementations in multiple programming languages.
Conventional Abbreviation
In real-life discussions, we often refer to \"Data Structures and Algorithms\" simply as \"Algorithms\". For example, the well-known LeetCode algorithm problems actually test both data structure and algorithm knowledge.
"},{"location":"chapter_preface/","title":"\u7b2c 0 \u7ae0 \u00a0 Preface","text":"Abstract
Algorithms are like a beautiful symphony, with each line of code flowing like a rhythm.
May this book ring softly in your head, leaving a unique and profound melody.
"},{"location":"chapter_preface/#_1","title":"\u672c\u7ae0\u5185\u5bb9","text":"The aim of this project is to create an open source, free, novice-friendly introductory tutorial on data structures and algorithms.
If you are a beginner to algorithms, have never touched an algorithm before, or already have some experience brushing up on data structures and algorithms, and have a vague understanding of data structures and algorithms, repeatedly jumping sideways between what you can and can't do, then this book is just for you!
If you have already accumulated a certain amount of questions and are familiar with most of the question types, then this book can help you review and organize the algorithm knowledge system, and the repository source code can be used as a \"brushing tool library\" or \"algorithm dictionary\".
If you are an algorithm expert, we look forward to receiving your valuable suggestions or participate in the creation together.
precondition
You will need to have at least a basic knowledge of programming in any language and be able to read and write simple code.
"},{"location":"chapter_preface/about_the_book/#012-content-structure","title":"0.1.2 \u00a0 Content Structure","text":"The main contents of the book are shown in the Figure 0-1 .
Figure 0-1 \u00a0 Hello Algo content structure
"},{"location":"chapter_preface/about_the_book/#013-acknowledgements","title":"0.1.3 \u00a0 Acknowledgements","text":"During the creation of this book, I received help from many people, including but not limited to:
During the writing process, I read many textbooks and articles on data structures and algorithms. These works provide excellent models for this book and ensure the accuracy and quality of its contents. I would like to thank all my teachers and predecessors for their outstanding contributions!
This book promotes a hands-on approach to learning, and in this respect is heavily inspired by \"Hands-On Learning for Depth\". I highly recommend this excellent book to all readers.
A heartfelt thank you to my parents, it is your constant support and encouragement that gives me the opportunity to do this fun-filled thing.
"},{"location":"chapter_preface/suggestions/","title":"0.2 \u00a0 How To Read","text":"Tip
For the best reading experience, it is recommended that you read through this section.
"},{"location":"chapter_preface/suggestions/#021-conventions-of-style","title":"0.2.1 \u00a0 Conventions Of Style","text":"*
after the title are optional chapters with relatively difficult content. If you have limited time, it is advisable to skip them.\"double quotes\"
to avoid ambiguity.\" \"
in parentheses, e.g. \"array array\"
. It is recommended to memorize them for reading the literature.\"\"\"Header comments for labeling functions, classes, test samples, etc.\"\"\"\"\n\n# Content comments for detailed code solutions\n\n\"\"\"\nmulti-line\nmarginal notes\n\"\"\"\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
/* Header comments for labeling functions, classes, test samples, etc. */\n\n// Content comments for detailed code solutions.\n\n/**\n * multi-line\n * marginal notes\n */\n
// Header comments for labeling functions, classes, test samples, etc.\n\n// Content comments for detailed code solutions.\n\n// Multi-line\n// Annotation\n
"},{"location":"chapter_preface/suggestions/#022-learn-efficiently-in-animated-graphic-solutions","title":"0.2.2 \u00a0 Learn Efficiently In Animated Graphic Solutions","text":"Compared with text, videos and pictures have a higher degree of information density and structure and are easier to understand. In this book, key and difficult knowledge will be presented mainly in the form of animations and graphs, while the text serves as an explanation and supplement to the animations and graphs.
If, while reading the book, you find that a particular paragraph provides an animation or a graphic solution as shown below, please use the figure as the primary source and the text as a supplement and synthesize the two to understand the content.
Figure 0-2 \u00a0 Example animation
"},{"location":"chapter_preface/suggestions/#023-deeper-understanding-in-code-practice","title":"0.2.3 \u00a0 Deeper Understanding In Code Practice","text":"The companion code for this book is hosted in the GitHub repository. As shown in the Figure 0-3 , the source code is accompanied by test samples that can be run with a single click.
If time permits, it is recommended that you refer to the code and knock it through on your own. If you have limited time to study, please read through and run all the code at least once.
The process of writing code is often more rewarding than reading it. Learning by doing is really learning.
Figure 0-3 \u00a0 Running code example
The preliminaries for running the code are divided into three main steps.
Step 1: Install the local programming environment. Please refer to Appendix Tutorial for installation, or skip this step if already installed.
Step 2: Clone or download the code repository. If Git is already installed, you can clone this repository with the following command.
git clone https://github.com/krahets/hello-algo.git\n
Of course, you can also in the location shown in the Figure 0-4 , click \"Download ZIP\" directly download the code zip, and then in the local solution.
Figure 0-4 \u00a0 Clone repository with download code
Step 3: Run the source code. As shown in the Figure 0-5 , for the code block labeled with the file name at the top, we can find the corresponding source code file in the codes
folder of the repository. The source code files can be run with a single click, which will help you save unnecessary debugging time and allow you to focus on what you are learning.
Figure 0-5 \u00a0 Code block with corresponding source file
"},{"location":"chapter_preface/suggestions/#024-growing-together-in-questioning-and-discussion","title":"0.2.4 \u00a0 Growing Together In Questioning And Discussion","text":"While reading this book, please don't skip over the points that you didn't learn. Feel free to ask your questions in the comment section. We will be happy to answer them and can usually respond within two days.
As you can see in the Figure 0-6 , each post comes with a comment section at the bottom. I hope you'll pay more attention to the comments section. On the one hand, you can learn about the problems that people encounter, so as to check the gaps and stimulate deeper thinking. On the other hand, we expect you to generously answer other partners' questions, share your insights, and help others improve.
Figure 0-6 \u00a0 Example of comment section
"},{"location":"chapter_preface/suggestions/#025-algorithm-learning-route","title":"0.2.5 \u00a0 Algorithm Learning Route","text":"From a general point of view, we can divide the process of learning data structures and algorithms into three stages.
As shown in the Figure 0-7 , this book mainly covers \"Phase 1\" and is designed to help you start Phase 2 and 3 more efficiently.
Figure 0-7 \u00a0 algorithm learning route
"},{"location":"chapter_preface/summary/","title":"0.3 \u00a0 Summary","text":"