mirror of
https://github.com/krahets/hello-algo.git
synced 2024-12-25 12:16:29 +08:00
(PR #204) update c code and doc for time_complexity
This commit is contained in:
commit
ea867eadac
23 changed files with 447 additions and 172 deletions
112
codes/c/chapter_array_and_linkedlist/array.c
Normal file
112
codes/c/chapter_array_and_linkedlist/array.c
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* File: array.c
|
||||
* Created Time: 2022-12-20
|
||||
* Author: MolDuM (moldum@163.com)
|
||||
*/
|
||||
|
||||
#include "../include/include.h"
|
||||
|
||||
/* 随机返回一个数组元素 */
|
||||
int randomAccess(int* nums, int size) {
|
||||
// 在区间 [0, size) 中随机抽取一个数字
|
||||
int randomIndex = rand() % size;
|
||||
// 获取并返回随机元素
|
||||
int randomNum = nums[randomIndex];
|
||||
return randomNum;
|
||||
}
|
||||
|
||||
/* 扩展数组长度 */
|
||||
int* extend(int* nums, int size, int enlarge) {
|
||||
// 初始化一个扩展长度后的数组
|
||||
int* res = (int *)malloc(sizeof(int) * (size + enlarge));
|
||||
// 将原数组中的所有元素复制到新数组
|
||||
for (int i = 0; i < size; i++) {
|
||||
res[i] = nums[i];
|
||||
}
|
||||
// 初始化扩展后的空间
|
||||
for (int i = size; i < size + enlarge; i++) {
|
||||
res[i] = 0;
|
||||
}
|
||||
// 返回扩展后的新数组
|
||||
return res;
|
||||
}
|
||||
|
||||
/* 在数组的索引 index 处插入元素 num */
|
||||
void insert(int* nums, int size, int num, int index) {
|
||||
// 把索引 index 以及之后的所有元素向后移动一位
|
||||
for (int i = size - 1; i > index; i--) {
|
||||
nums[i] = nums[i - 1];
|
||||
}
|
||||
// 将 num 赋给 index 处元素
|
||||
nums[index] = num;
|
||||
}
|
||||
|
||||
/* 删除索引 index 处元素 */
|
||||
void removeItem(int* nums, int size, int index) {
|
||||
// 把索引 index 之后的所有元素向前移动一位
|
||||
for (int i = index; i < size - 1; i++) {
|
||||
nums[i] = nums[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
/* 遍历数组 */
|
||||
void traverse(int* nums, int size) {
|
||||
int count = 0;
|
||||
// 通过索引遍历数组
|
||||
for (int i = 0; i < size; i++) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
/* 在数组中查找指定元素 */
|
||||
int find(int* nums, int size, int target) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (nums[i] == target)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* Driver Code */
|
||||
int main() {
|
||||
/* 初始化数组 */
|
||||
int size = 5;
|
||||
int arr[5];
|
||||
printf("数组 arr = ");
|
||||
printArray(arr, size);
|
||||
|
||||
int nums[5] = { 1, 3, 2, 5, 4 };
|
||||
printf("数组 nums = ");
|
||||
printArray(nums, size);
|
||||
|
||||
/* 随机访问 */
|
||||
int randomNum = randomAccess(nums, size);
|
||||
printf("在 nums 中获取随机元素 %d", randomNum);
|
||||
|
||||
/* 长度扩展 */
|
||||
int enlarge = 3;
|
||||
int* res = extend(nums, size, enlarge);
|
||||
size += enlarge;
|
||||
printf("将数组长度扩展至 8 ,得到 nums = ");
|
||||
printArray(res, size);
|
||||
|
||||
/* 插入元素 */
|
||||
insert(res, size, 6, 3);
|
||||
printf("在索引 3 处插入数字 6 ,得到 nums = ");
|
||||
printArray(res, size);
|
||||
|
||||
/* 删除元素 */
|
||||
removeItem(res, size, 2);
|
||||
printf("删除索引 2 处的元素,得到 nums = ");
|
||||
printArray(res, size);
|
||||
|
||||
/* 遍历数组 */
|
||||
traverse(res, size);
|
||||
|
||||
/* 查找元素 */
|
||||
int index = find(res, size, 3);
|
||||
printf("在 res 中查找元素 3 ,得到索引 = %d\n", index);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -7,8 +7,7 @@
|
|||
#include "../include/include.h"
|
||||
|
||||
/* 常数阶 */
|
||||
int constant(int n)
|
||||
{
|
||||
int constant(int n) {
|
||||
int count = 0;
|
||||
int size = 100000;
|
||||
int i = 0;
|
||||
|
@ -19,8 +18,7 @@ int constant(int n)
|
|||
}
|
||||
|
||||
/* 线性阶 */
|
||||
int linear(int n)
|
||||
{
|
||||
int linear(int n) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
count ++;
|
||||
|
@ -29,8 +27,7 @@ int linear(int n)
|
|||
}
|
||||
|
||||
/* 线性阶(遍历数组) */
|
||||
int arrayTraversal(int *nums, int n)
|
||||
{
|
||||
int arrayTraversal(int *nums, int n) {
|
||||
int count = 0;
|
||||
// 循环次数与数组长度成正比
|
||||
for (int i = 0; i < n; i++) {
|
||||
|
@ -53,35 +50,29 @@ int quadratic(int n)
|
|||
}
|
||||
|
||||
/* 平方阶(冒泡排序) */
|
||||
int bubbleSort(int *nums, int n)
|
||||
{
|
||||
int bubbleSort(int *nums, int n) {
|
||||
int count = 0; // 计数器
|
||||
// 外循环:待排序元素数量为 n-1, n-2, ..., 1
|
||||
for (int i = n - 1; i > 0; i--) {
|
||||
// 内循环:冒泡操作
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
for (int j = 0; j < i; j++) {
|
||||
// 交换 nums[j] 与 nums[j + 1]
|
||||
int tmp = nums[j];
|
||||
nums[j] = nums[j + 1];
|
||||
nums[j + 1] = tmp;
|
||||
count += 3; // 元素交换包含 3 个单元操作
|
||||
}
|
||||
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* 指数阶(循环实现) */
|
||||
int exponential(int n)
|
||||
{
|
||||
int exponential(int n) {
|
||||
int count = 0;
|
||||
int bas = 1;
|
||||
// cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
for (int j = 0; j < bas; j++)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (int j = 0; j < bas; j++) {
|
||||
count++;
|
||||
}
|
||||
bas *= 2;
|
||||
|
@ -91,18 +82,15 @@ int exponential(int n)
|
|||
}
|
||||
|
||||
/* 指数阶(递归实现) */
|
||||
int expRecur(int n)
|
||||
{
|
||||
int expRecur(int n) {
|
||||
if (n == 1) return 1;
|
||||
return expRecur(n - 1) + expRecur(n - 1) + 1;
|
||||
}
|
||||
|
||||
/* 对数阶(循环实现) */
|
||||
int logarithmic(float n)
|
||||
{
|
||||
int logarithmic(float n) {
|
||||
int count = 0;
|
||||
while (n > 1)
|
||||
{
|
||||
while (n > 1) {
|
||||
n = n / 2;
|
||||
count++;
|
||||
}
|
||||
|
@ -110,40 +98,34 @@ int logarithmic(float n)
|
|||
}
|
||||
|
||||
/* 对数阶(递归实现) */
|
||||
int logRecur(float n)
|
||||
{
|
||||
int logRecur(float n) {
|
||||
if (n <= 1) return 0;
|
||||
return logRecur(n / 2) + 1;
|
||||
}
|
||||
|
||||
/* 线性对数阶 */
|
||||
int linearLogRecur(float n)
|
||||
{
|
||||
int linearLogRecur(float n) {
|
||||
if (n <= 1) return 1;
|
||||
int count = linearLogRecur(n / 2) +
|
||||
linearLogRecur(n / 2);
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
count ++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* 阶乘阶(递归实现) */
|
||||
int factorialRecur(int n)
|
||||
{
|
||||
int factorialRecur(int n) {
|
||||
if (n == 0) return 1;
|
||||
int count = 0;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
count += factorialRecur(n - 1);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Driver Code */
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int main(int argc, char *argv[]) {
|
||||
// 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势
|
||||
int n = 8;
|
||||
printf("输入数据大小 n = %d\n", n);
|
||||
|
|
|
@ -7,18 +7,15 @@
|
|||
#include "../include/include.h"
|
||||
|
||||
/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */
|
||||
int *randomNumbers(int n)
|
||||
{
|
||||
int *randomNumbers(int n) {
|
||||
// 分配堆区内存(创建一维可变长数组:数组中元素数量为n,元素类型为int)
|
||||
int *nums = (int *)malloc(n * sizeof(int));
|
||||
// 生成数组 nums = { 1, 2, 3, ..., n }
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
nums[i] = i + 1;
|
||||
}
|
||||
// 随机打乱数组元素
|
||||
for (int i = n - 1; i > 0; i--)
|
||||
{
|
||||
for (int i = n - 1; i > 0; i--) {
|
||||
int j = rand() % (i + 1);
|
||||
int temp = nums[i];
|
||||
nums[i] = nums[j];
|
||||
|
@ -28,31 +25,24 @@ int *randomNumbers(int n)
|
|||
}
|
||||
|
||||
/* 查找数组 nums 中数字 1 所在索引 */
|
||||
int findOne(int *nums, int n)
|
||||
{
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int findOne(int *nums, int n) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (nums[i] == 1) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Driver Code */
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int main(int argc, char *argv[]) {
|
||||
// 初始化随机数种子
|
||||
srand((unsigned int)time(NULL));
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int n = 100;
|
||||
int *nums = randomNumbers(n);
|
||||
int index = findOne(nums, n);
|
||||
printf("\n数组 [ 1, 2, ..., n ] 被打乱后 = ");
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
printf("%d%s", nums[j], (j == n-1)? "" : "," );
|
||||
}
|
||||
printf("\n数字 1 的索引为 %d\n", index);
|
||||
printArray(nums, n);
|
||||
printf("数字 1 的索引为 %d\n", index);
|
||||
// 释放堆区内存
|
||||
if (nums != NULL) {
|
||||
free(nums);
|
||||
|
|
30
codes/c/include/PrintUtil.h
Normal file
30
codes/c/include/PrintUtil.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* File: PrintUtil.h
|
||||
* Created Time: 2022-12-21
|
||||
* Author: MolDum (moldum@163.com)
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// #include "ListNode.h"
|
||||
// #include "TreeNode.h"
|
||||
|
||||
/**
|
||||
* @brief Print an Array
|
||||
*
|
||||
* @param arr
|
||||
* @param n
|
||||
*/
|
||||
|
||||
static void printArray(int* arr, int n)
|
||||
{
|
||||
printf("[");
|
||||
for (int i = 0; i < n - 1; i++) {
|
||||
printf("%d, ", arr[i]);
|
||||
}
|
||||
printf("%d]\n", arr[n-1]);
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,15 @@
|
|||
/*
|
||||
* File: include.h
|
||||
* Created Time: 2022-12-20
|
||||
* Author: MolDuM (moldum@163.com)
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "PrintUtil.h"
|
||||
|
||||
|
||||
|
|
|
@ -50,11 +50,10 @@ public:
|
|||
}
|
||||
|
||||
/* 出队 */
|
||||
int poll() {
|
||||
void poll() {
|
||||
int num = peek();
|
||||
// 队头指针向后移动一位,若越过尾部则返回到数组头部
|
||||
front = (front + 1) % capacity();
|
||||
return num;
|
||||
}
|
||||
|
||||
/* 访问队首元素 */
|
||||
|
@ -98,8 +97,8 @@ int main() {
|
|||
cout << "队首元素 peek = " << peek << endl;
|
||||
|
||||
/* 元素出队 */
|
||||
int poll = queue->poll();
|
||||
cout << "出队元素 poll = " << poll << ",出队后 queue = ";
|
||||
queue->poll();
|
||||
cout << "出队元素 poll = " << peek << ",出队后 queue = ";
|
||||
PrintUtil::printVector(queue->toVector());
|
||||
|
||||
/* 获取队列的长度 */
|
||||
|
|
|
@ -28,10 +28,9 @@ public:
|
|||
}
|
||||
|
||||
/* 出栈 */
|
||||
int pop() {
|
||||
void pop() {
|
||||
int oldTop = top();
|
||||
stack.pop_back();
|
||||
return oldTop;
|
||||
}
|
||||
|
||||
/* 访问栈顶元素 */
|
||||
|
@ -67,8 +66,8 @@ int main() {
|
|||
cout << "栈顶元素 top = " << top << endl;
|
||||
|
||||
/* 元素出栈 */
|
||||
int pop = stack->pop();
|
||||
cout << "出栈元素 pop = " << pop << ",出栈后 stack = ";
|
||||
stack->pop();
|
||||
cout << "出栈元素 pop = " << top << ",出栈后 stack = ";
|
||||
PrintUtil::printVector(stack->toVector());
|
||||
|
||||
/* 获取栈的长度 */
|
||||
|
|
|
@ -47,7 +47,7 @@ public:
|
|||
}
|
||||
|
||||
/* 出队 */
|
||||
int poll() {
|
||||
void poll() {
|
||||
int num = peek();
|
||||
// 删除头结点
|
||||
ListNode *tmp = front;
|
||||
|
@ -55,7 +55,6 @@ public:
|
|||
// 释放内存
|
||||
delete tmp;
|
||||
queSize--;
|
||||
return num;
|
||||
}
|
||||
|
||||
/* 访问队首元素 */
|
||||
|
@ -97,8 +96,8 @@ int main() {
|
|||
cout << "队首元素 peek = " << peek << endl;
|
||||
|
||||
/* 元素出队 */
|
||||
int poll = queue->poll();
|
||||
cout << "出队元素 poll = " << poll << ",出队后 queue = ";
|
||||
queue->poll();
|
||||
cout << "出队元素 poll = " << peek << ",出队后 queue = ";
|
||||
PrintUtil::printVector(queue->toVector());
|
||||
|
||||
/* 获取队列的长度 */
|
||||
|
|
|
@ -37,14 +37,13 @@ public:
|
|||
}
|
||||
|
||||
/* 出栈 */
|
||||
int pop() {
|
||||
void pop() {
|
||||
int num = top();
|
||||
ListNode *tmp = stackTop;
|
||||
stackTop = stackTop->next;
|
||||
// 释放内存
|
||||
delete tmp;
|
||||
stkSize--;
|
||||
return num;
|
||||
}
|
||||
|
||||
/* 访问栈顶元素 */
|
||||
|
@ -86,8 +85,8 @@ int main() {
|
|||
cout << "栈顶元素 top = " << top << endl;
|
||||
|
||||
/* 元素出栈 */
|
||||
int pop = stack->pop();
|
||||
cout << "出栈元素 pop = " << pop << ",出栈后 stack = ";
|
||||
stack->pop();
|
||||
cout << "出栈元素 pop = " << top << ",出栈后 stack = ";
|
||||
PrintUtil::printVector(stack->toVector());
|
||||
|
||||
/* 获取栈的长度 */
|
||||
|
|
|
@ -8,11 +8,13 @@ let package = Package(
|
|||
.executable(name: "time_complexity", targets: ["time_complexity"]),
|
||||
.executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]),
|
||||
.executable(name: "space_complexity", targets: ["space_complexity"]),
|
||||
.executable(name: "leetcode_two_sum", targets: ["leetcode_two_sum"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "utils", path: "utils"),
|
||||
.executableTarget(name: "time_complexity", path: "chapter_computational_complexity", sources: ["time_complexity.swift"]),
|
||||
.executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]),
|
||||
.executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]),
|
||||
.executableTarget(name: "leetcode_two_sum", path: "chapter_computational_complexity", sources: ["leetcode_two_sum.swift"]),
|
||||
]
|
||||
)
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* File: leetcode_two_sum.swift
|
||||
* Created Time: 2023-01-03
|
||||
* Author: nuomi1 (nuomi1@qq.com)
|
||||
*/
|
||||
|
||||
func twoSumBruteForce(nums: [Int], target: Int) -> [Int] {
|
||||
// 两层循环,时间复杂度 O(n^2)
|
||||
for i in nums.indices.dropLast() {
|
||||
for j in nums.indices.dropFirst(i + 1) {
|
||||
if nums[i] + nums[j] == target {
|
||||
return [i, j]
|
||||
}
|
||||
}
|
||||
}
|
||||
return [0]
|
||||
}
|
||||
|
||||
func twoSumHashTable(nums: [Int], target: Int) -> [Int] {
|
||||
// 辅助哈希表,空间复杂度 O(n)
|
||||
var dic: [Int: Int] = [:]
|
||||
// 单层循环,时间复杂度 O(n)
|
||||
for i in nums.indices {
|
||||
if let j = dic[target - nums[i]] {
|
||||
return [j, i]
|
||||
}
|
||||
dic[nums[i]] = i
|
||||
}
|
||||
return [0]
|
||||
}
|
||||
|
||||
@main
|
||||
enum LeetcodeTwoSum {
|
||||
static func main() {
|
||||
// ======= Test Case =======
|
||||
let nums = [2, 7, 11, 15]
|
||||
let target = 9
|
||||
// ====== Driver Code ======
|
||||
// 方法一
|
||||
var res = twoSumBruteForce(nums: nums, target: target)
|
||||
print("方法一 res = \(res)")
|
||||
// 方法二
|
||||
res = twoSumHashTable(nums: nums, target: target)
|
||||
print("方法二 res = \(res)")
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ comments: true
|
|||
|
||||
# 小结
|
||||
|
||||
- 数组和链表是两种基本数据结构,代表了数据在计算机内存中的两种存储方式,即连续空间存储和离散空间存储。两者的优缺点呈现出此消彼长的关系。
|
||||
- 数组和链表是两种基本数据结构,代表了数据在计算机内存中的两种存储方式,即连续空间存储和离散空间存储。两者的优点与缺点呈现出此消彼长的关系。
|
||||
- 数组支持随机访问、内存空间占用小;但插入与删除元素效率低,且初始化后长度不可变。
|
||||
- 链表可通过更改指针实现高效的结点插入与删除,并且可以灵活地修改长度;但结点访问效率低、占用内存多。常见的链表类型有单向链表、循环链表、双向链表。
|
||||
- 列表又称动态数组,是基于数组实现的一种数据结构,其保存了数组的优势,且可以灵活改变长度。列表的出现大大提升了数组的实用性,但副作用是会造成部分内存空间浪费。
|
||||
|
|
|
@ -149,6 +149,22 @@ comments: true
|
|||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="leetcode_two_sum.swift"
|
||||
func twoSumBruteForce(nums: [Int], target: Int) -> [Int] {
|
||||
// 两层循环,时间复杂度 O(n^2)
|
||||
for i in nums.indices.dropLast() {
|
||||
for j in nums.indices.dropFirst(i + 1) {
|
||||
if nums[i] + nums[j] == target {
|
||||
return [i, j]
|
||||
}
|
||||
}
|
||||
}
|
||||
return [0]
|
||||
}
|
||||
```
|
||||
|
||||
### 方法二:辅助哈希表
|
||||
|
||||
时间复杂度 $O(N)$ ,空间复杂度 $O(N)$ ,属于「空间换时间」。
|
||||
|
@ -294,3 +310,20 @@ comments: true
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="leetcode_two_sum.swift"
|
||||
func twoSumHashTable(nums: [Int], target: Int) -> [Int] {
|
||||
// 辅助哈希表,空间复杂度 O(n)
|
||||
var dic: [Int: Int] = [:]
|
||||
// 单层循环,时间复杂度 O(n)
|
||||
for i in nums.indices {
|
||||
if let j = dic[target - nums[i]] {
|
||||
return [j, i]
|
||||
}
|
||||
dic[nums[i]] = i
|
||||
}
|
||||
return [0]
|
||||
}
|
||||
```
|
||||
|
|
|
@ -92,8 +92,7 @@ $$
|
|||
|
||||
```c title=""
|
||||
// 在某运行平台下
|
||||
void algorithm(int n)
|
||||
{
|
||||
void algorithm(int n) {
|
||||
int a = 2; // 1 ns
|
||||
a = a + 1; // 1 ns
|
||||
a = a * 2; // 10 ns
|
||||
|
@ -243,20 +242,17 @@ $$
|
|||
|
||||
```c title=""
|
||||
// 算法 A 时间复杂度:常数阶
|
||||
void algorithm_A(int n)
|
||||
{
|
||||
void algorithm_A(int n) {
|
||||
printf("%d", 0);
|
||||
}
|
||||
// 算法 B 时间复杂度:线性阶
|
||||
void algorithm_B(int n)
|
||||
{
|
||||
void algorithm_B(int n) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
printf("%d", 0);
|
||||
}
|
||||
}
|
||||
// 算法 C 时间复杂度:常数阶
|
||||
void algorithm_C(int n)
|
||||
{
|
||||
void algorithm_C(int n) {
|
||||
for (int i = 0; i < 1000000; i++) {
|
||||
printf("%d", 0);
|
||||
}
|
||||
|
@ -401,8 +397,7 @@ $$
|
|||
=== "C"
|
||||
|
||||
```c title=""
|
||||
void algorithm(int n)
|
||||
{
|
||||
void algorithm(int n) {
|
||||
int a = 1; // +1
|
||||
a = a + 1; // +1
|
||||
a = a * 2; // +1
|
||||
|
@ -579,8 +574,7 @@ $$
|
|||
=== "C"
|
||||
|
||||
```c title=""
|
||||
void algorithm(int n)
|
||||
{
|
||||
void algorithm(int n) {
|
||||
int a = 1; // +0(技巧 1)
|
||||
a = a + n; // +0(技巧 1)
|
||||
// +n(技巧 2)
|
||||
|
@ -749,8 +743,7 @@ $$
|
|||
|
||||
```c title="time_complexity.c"
|
||||
/* 常数阶 */
|
||||
int constant(int n)
|
||||
{
|
||||
int constant(int n) {
|
||||
int count = 0;
|
||||
int size = 100000;
|
||||
int i = 0;
|
||||
|
@ -857,8 +850,7 @@ $$
|
|||
|
||||
```c title="time_complexity.c"
|
||||
/* 线性阶 */
|
||||
int linear(int n)
|
||||
{
|
||||
int linear(int n) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
count ++;
|
||||
|
@ -969,8 +961,7 @@ $$
|
|||
|
||||
```c title="time_complexity.c"
|
||||
/* 线性阶(遍历数组) */
|
||||
int arrayTraversal(int *nums, int n)
|
||||
{
|
||||
int arrayTraversal(int *nums, int n) {
|
||||
int count = 0;
|
||||
// 循环次数与数组长度成正比
|
||||
for (int i = 0; i < n; i++) {
|
||||
|
@ -1091,8 +1082,7 @@ $$
|
|||
|
||||
```c title="time_complexity.c"
|
||||
/* 平方阶 */
|
||||
int quadratic(int n)
|
||||
{
|
||||
int quadratic(int n) {
|
||||
int count = 0;
|
||||
// 循环次数与数组长度成平方关系
|
||||
for (int i = 0; i < n; i++) {
|
||||
|
@ -1253,14 +1243,12 @@ $$
|
|||
|
||||
```c title="time_complexity.c"
|
||||
/* 平方阶(冒泡排序) */
|
||||
int bubbleSort(int *nums, int n)
|
||||
{
|
||||
int bubbleSort(int *nums, int n) {
|
||||
int count = 0; // 计数器
|
||||
// 外循环:待排序元素数量为 n-1, n-2, ..., 1
|
||||
for (int i = n - 1; i > 0; i--) {
|
||||
// 内循环:冒泡操作
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
for (int j = 0; j < i; j++) {
|
||||
// 交换 nums[j] 与 nums[j + 1]
|
||||
int tmp = nums[j];
|
||||
nums[j] = nums[j + 1];
|
||||
|
@ -1417,15 +1405,12 @@ $$
|
|||
|
||||
```c title="time_complexity.c"
|
||||
/* 指数阶(循环实现) */
|
||||
int exponential(int n)
|
||||
{
|
||||
int exponential(int n) {
|
||||
int count = 0;
|
||||
int bas = 1;
|
||||
// cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
for (int j = 0; j < bas; j++)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (int j = 0; j < bas; j++) {
|
||||
count++;
|
||||
}
|
||||
bas *= 2;
|
||||
|
@ -1538,8 +1523,7 @@ $$
|
|||
|
||||
```c title="time_complexity.c"
|
||||
/* 指数阶(递归实现) */
|
||||
int expRecur(int n)
|
||||
{
|
||||
int expRecur(int n) {
|
||||
if (n == 1) return 1;
|
||||
return expRecur(n - 1) + expRecur(n - 1) + 1;
|
||||
}
|
||||
|
@ -1646,11 +1630,9 @@ $$
|
|||
|
||||
```c title="time_complexity.c"
|
||||
/* 对数阶(循环实现) */
|
||||
int logarithmic(float n)
|
||||
{
|
||||
int logarithmic(float n) {
|
||||
int count = 0;
|
||||
while (n > 1)
|
||||
{
|
||||
while (n > 1) {
|
||||
n = n / 2;
|
||||
count++;
|
||||
}
|
||||
|
@ -1752,8 +1734,7 @@ $$
|
|||
|
||||
```c title="time_complexity.c"
|
||||
/* 对数阶(递归实现) */
|
||||
int logRecur(float n)
|
||||
{
|
||||
int logRecur(float n) {
|
||||
if (n <= 1) return 0;
|
||||
return logRecur(n / 2) + 1;
|
||||
}
|
||||
|
@ -1864,13 +1845,11 @@ $$
|
|||
|
||||
```c title="time_complexity.c"
|
||||
/* 线性对数阶 */
|
||||
int linearLogRecur(float n)
|
||||
{
|
||||
int linearLogRecur(float n) {
|
||||
if (n <= 1) return 1;
|
||||
int count = linearLogRecur(n / 2) +
|
||||
linearLogRecur(n / 2);
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
count ++;
|
||||
}
|
||||
return count;
|
||||
|
@ -2000,12 +1979,10 @@ $$
|
|||
|
||||
```c title="time_complexity.c"
|
||||
/* 阶乘阶(递归实现) */
|
||||
int factorialRecur(int n)
|
||||
{
|
||||
int factorialRecur(int n) {
|
||||
if (n == 0) return 1;
|
||||
int count = 0;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
count += factorialRecur(n - 1);
|
||||
}
|
||||
return count;
|
||||
|
@ -2226,18 +2203,15 @@ $$
|
|||
|
||||
```c title="worst_best_time_complexity.c"
|
||||
/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */
|
||||
int *randomNumbers(int n)
|
||||
{
|
||||
int *randomNumbers(int n) {
|
||||
// 分配堆区内存(创建一维可变长数组:数组中元素数量为n,元素类型为int)
|
||||
int *nums = (int *)malloc(n * sizeof(int));
|
||||
// 生成数组 nums = { 1, 2, 3, ..., n }
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
nums[i] = i + 1;
|
||||
}
|
||||
// 随机打乱数组元素
|
||||
for (int i = n - 1; i > 0; i--)
|
||||
{
|
||||
for (int i = n - 1; i > 0; i--) {
|
||||
int j = rand() % (i + 1);
|
||||
int temp = nums[i];
|
||||
nums[i] = nums[j];
|
||||
|
@ -2247,31 +2221,24 @@ $$
|
|||
}
|
||||
|
||||
/* 查找数组 nums 中数字 1 所在索引 */
|
||||
int findOne(int *nums, int n)
|
||||
{
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int findOne(int *nums, int n) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (nums[i] == 1) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Driver Code */
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int main(int argc, char *argv[]) {
|
||||
// 初始化随机数种子
|
||||
srand((unsigned int)time(NULL));
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int n = 100;
|
||||
int *nums = randomNumbers(n);
|
||||
int index = findOne(nums, n);
|
||||
printf("\n数组 [ 1, 2, ..., n ] 被打乱后 = ");
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
printf("%d%s", nums[j], (j == n-1)? "" : "," );
|
||||
}
|
||||
printf("\n数字 1 的索引为 %d\n", index);
|
||||
printArray(nums, n);
|
||||
printf("数字 1 的索引为 %d\n", index);
|
||||
// 释放堆区内存
|
||||
if (nums != NULL) {
|
||||
free(nums);
|
||||
|
|
|
@ -82,10 +82,118 @@ comments: true
|
|||
|
||||
## 风格约定
|
||||
|
||||
- 文章中的重要名词会用「」符号标注,例如「数组 Array」。名词混淆会导致不必要的歧义,因此最好可以记住这类名词(包括中文和英文),以便后续阅读文献时使用。
|
||||
- 重点内容、总起句、总结句会被 **加粗** ,此类文字值得更多关注。
|
||||
- 专有名词和有特指含义的词句会使用 “ ” 标注,以避免歧义。
|
||||
- 标题后标注 * 符号的是选读章节,如果你的时间有限,可以先跳过这些章节。
|
||||
- 文章中的重要名词会用「」符号标注,例如「数组 Array」。名词混淆会导致不必要的歧义,因此最好可以记住这类名词(包括中文和英文),以便后续阅读文献时使用。
|
||||
- 重点内容、总起句、总结句会被 **加粗** ,此类文字值得特别关注。
|
||||
- 专有名词和有特指含义的词句会使用 “ ” 标注,以避免歧义。
|
||||
- 在工程应用中,每种语言都有注释规范;而本书放弃了一部分的注释规范性,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title=""
|
||||
/* 标题注释,用于标注函数、类、测试样例等 */
|
||||
|
||||
// 内容注释,用于详解代码
|
||||
|
||||
/**
|
||||
* 多行
|
||||
* 注释
|
||||
*/
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title=""
|
||||
/* 标题注释,用于标注函数、类、测试样例等 */
|
||||
|
||||
// 内容注释,用于详解代码
|
||||
|
||||
/**
|
||||
* 多行
|
||||
* 注释
|
||||
*/
|
||||
```
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title=""
|
||||
""" 标题注释,用于标注函数、类、测试样例等 """
|
||||
|
||||
# 内容注释,用于详解代码
|
||||
|
||||
"""
|
||||
多行
|
||||
注释
|
||||
"""
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title=""
|
||||
/* 标题注释,用于标注函数、类、测试样例等 */
|
||||
|
||||
// 内容注释,用于详解代码
|
||||
|
||||
/**
|
||||
* 多行
|
||||
* 注释
|
||||
*/
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js title=""
|
||||
/* 标题注释,用于标注函数、类、测试样例等 */
|
||||
|
||||
// 内容注释,用于详解代码
|
||||
|
||||
/**
|
||||
* 多行
|
||||
* 注释
|
||||
*/
|
||||
```
|
||||
|
||||
=== "TypeScript"
|
||||
|
||||
```typescript title=""
|
||||
/* 标题注释,用于标注函数、类、测试样例等 */
|
||||
|
||||
// 内容注释,用于详解代码
|
||||
|
||||
/**
|
||||
* 多行
|
||||
* 注释
|
||||
*/
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title=""
|
||||
/* 标题注释,用于标注函数、类、测试样例等 */
|
||||
|
||||
// 内容注释,用于详解代码
|
||||
|
||||
/**
|
||||
* 多行
|
||||
* 注释
|
||||
*/
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
/* 标题注释,用于标注函数、类、测试样例等 */
|
||||
|
||||
// 内容注释,用于详解代码
|
||||
|
||||
/**
|
||||
* 多行
|
||||
* 注释
|
||||
*/
|
||||
```
|
||||
|
||||
"""
|
||||
在 Java, C, C++, C#, Go, JS, TS 的代码注释中,`/* ... */` 用于注释函数、类、测试样例等标题, `// ...` 用于解释代码内容;类似地,在 Python 中,`""" ... """` 用于注释标题, `# ...` 用于解释代码。
|
||||
|
||||
## 本书特点 *
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 200 KiB |
|
@ -466,7 +466,7 @@ $$
|
|||
|
||||
**空间复杂度 $O(1)$ :** 指针 `i` , `j` 使用常数大小空间。
|
||||
|
||||
## 优缺点
|
||||
## 优点与缺点
|
||||
|
||||
二分查找效率很高,体现在:
|
||||
|
||||
|
|
|
@ -185,7 +185,7 @@ comments: true
|
|||
|
||||
**空间复杂度:** $O(n)$ ,其中 $n$ 为数组或链表长度。
|
||||
|
||||
## 优缺点
|
||||
## 优点与缺点
|
||||
|
||||
在哈希表中,**查找、插入、删除操作的平均时间复杂度都为 $O(1)$** ,这意味着无论是高频增删还是高频查找场景,哈希查找的性能表现都非常好。当然,一切的前提是保证哈希表未退化。
|
||||
|
||||
|
|
|
@ -244,7 +244,7 @@ comments: true
|
|||
|
||||
**空间复杂度 $O(1)$ :** 无需使用额外空间。
|
||||
|
||||
## 优缺点
|
||||
## 优点与缺点
|
||||
|
||||
**线性查找的通用性极佳。** 由于线性查找是依次访问元素的,即没有跳跃访问元素,因此数组或链表皆适用。
|
||||
|
||||
|
|
|
@ -328,7 +328,7 @@ comments: true
|
|||
queSize++;
|
||||
}
|
||||
/* 出队 */
|
||||
int poll() {
|
||||
void poll() {
|
||||
int num = peek();
|
||||
// 删除头结点
|
||||
ListNode *tmp = front;
|
||||
|
@ -336,7 +336,6 @@ comments: true
|
|||
// 释放内存
|
||||
delete tmp;
|
||||
queSize--;
|
||||
return num;
|
||||
}
|
||||
/* 访问队首元素 */
|
||||
int peek() {
|
||||
|
@ -719,11 +718,10 @@ comments: true
|
|||
rear = (rear + 1) % capacity();
|
||||
}
|
||||
/* 出队 */
|
||||
int poll() {
|
||||
void poll() {
|
||||
int num = peek();
|
||||
// 队头指针向后移动一位,若越过尾部则返回到数组头部
|
||||
front = (front + 1) % capacity();
|
||||
return num;
|
||||
}
|
||||
/* 访问队首元素 */
|
||||
int peek() {
|
||||
|
|
|
@ -236,7 +236,9 @@ comments: true
|
|||
|
||||
### 基于链表的实现
|
||||
|
||||
使用「链表」实现栈时,将链表的尾结点看作栈顶即可。
|
||||
使用「链表」实现栈时,将链表的头结点看作栈顶,尾结点看作栈底。
|
||||
|
||||
对于入栈操作,将元素插入到链表头部即可,这种结点添加方式被称为“头插法”。而对于出栈操作,则将头结点从链表中删除即可。
|
||||
|
||||
受益于链表的离散存储方式,栈的扩容更加灵活,删除元素的内存也会被系统自动回收;缺点是无法像数组一样高效地随机访问,并且由于链表结点需存储指针,导致单个元素占用空间更大。
|
||||
|
||||
|
@ -311,14 +313,13 @@ comments: true
|
|||
stkSize++;
|
||||
}
|
||||
/* 出栈 */
|
||||
int pop() {
|
||||
void pop() {
|
||||
int num = top();
|
||||
ListNode *tmp = stackTop;
|
||||
stackTop = stackTop->next;
|
||||
// 释放内存
|
||||
delete tmp;
|
||||
stkSize--;
|
||||
return num;
|
||||
}
|
||||
/* 访问栈顶元素 */
|
||||
int top() {
|
||||
|
@ -592,7 +593,7 @@ comments: true
|
|||
|
||||
### 基于数组的实现
|
||||
|
||||
使用「数组」实现栈时,将数组的尾部当作栈顶。准确地说,我们需要使用「列表」,因为入栈的元素可能是源源不断的,因此使用动态数组可以方便扩容。
|
||||
使用「数组」实现栈时,将数组的尾部当作栈顶,这样可以保证入栈与出栈操作的时间复杂度都为 $O(1)$ 。准确地说,由于入栈的元素可能是源源不断的,我们需要使用可以动态扩容的「列表」。
|
||||
|
||||
基于数组实现的栈,优点是支持随机访问,缺点是会造成一定的空间浪费,因为列表的容量始终 $\geq$ 元素数量。
|
||||
|
||||
|
@ -655,10 +656,9 @@ comments: true
|
|||
stack.push_back(num);
|
||||
}
|
||||
/* 出栈 */
|
||||
int pop() {
|
||||
void pop() {
|
||||
int oldTop = top();
|
||||
stack.pop_back();
|
||||
return oldTop;
|
||||
}
|
||||
/* 访问栈顶元素 */
|
||||
int top() {
|
||||
|
|
|
@ -120,7 +120,7 @@ comments: true
|
|||
|
||||
- 「根结点 Root Node」:二叉树最顶层的结点,其没有父结点;
|
||||
- 「叶结点 Leaf Node」:没有子结点的结点,其两个指针都指向 $\text{null}$ ;
|
||||
- 结点所处「层 Level」:从顶置底依次增加,根结点所处层为 1 ;
|
||||
- 结点所处「层 Level」:从顶至底依次增加,根结点所处层为 1 ;
|
||||
- 结点「度 Degree」:结点的子结点数量。二叉树中,度的范围是 0, 1, 2 ;
|
||||
- 「边 Edge」:连接两个结点的边,即结点指针;
|
||||
- 二叉树「高度」:二叉树中根结点到最远叶结点走过边的数量;
|
||||
|
|
|
@ -10,7 +10,7 @@ hide:
|
|||
![conceptual_rendering](index.assets/conceptual_rendering.png){ align=left width=350 }
|
||||
</br></br></br></br></br>
|
||||
<h1 align="center"> 《 Hello,算法 》 </h1>
|
||||
<p align="center"> 动画图解、能运行、可讨论的</br>数据结构与算法快速入门教程 </p>
|
||||
<p align="center"> 动画图解、能运行、可提问的</br>数据结构与算法快速入门教程 </p>
|
||||
<p align="center"> [![github-stars](https://img.shields.io/github/stars/krahets/hello-algo?style=social)](https://github.com/krahets/hello-algo)</p>
|
||||
<h6 align="center"> [@Krahets](https://leetcode.cn/u/jyd/) </h6>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue