├── dsc-code ├── 05-circular_queue │ ├── generic_circular_queue.h │ ├── main.c │ └── generic_circular_queue.c ├── trie │ ├── trie.h │ ├── main.c │ └── trie.c ├── doubly │ ├── DoublyLinkedList.h │ ├── main.c │ └── DoublyLinkedList.c ├── bplus_easy │ ├── main.c │ ├── simple_bpt.h │ └── simple_bpt.c ├── doubly_circular_link │ ├── CircularDoublyLinkedList.h │ └── main.c ├── dsc-code │ ├── DynamicArray.h │ ├── main.c │ └── DynamicArray.c ├── 05-deque,double-ended-queue │ ├── generic_deque.h │ ├── main.c │ └── generic_deque.c ├── singly_linked_list │ ├── Node.h │ ├── main.c │ └── Node.c ├── avl │ ├── avl_tree.h │ └── main.c ├── hash_table │ ├── hash_table.h │ └── main.c ├── heap │ ├── heap.h │ ├── main.c │ └── heap.c ├── 05-linked-queue │ ├── generic_linked_queue.h │ ├── main.c │ └── generic_linked_queue.c ├── bst │ ├── bst.h │ └── main.c ├── doublyLinkedList │ ├── DoublyLinkedList.h │ └── main.c ├── linked_stack │ ├── linked_stack.h │ ├── linked_stack.c │ └── main.c ├── safe_arr │ ├── safe_array.h │ ├── main.c │ └── safe_array.c ├── sequential_stack │ ├── sequential_stack.h │ ├── main.c │ └── sequential_stack.c ├── shared_stack │ ├── shared_stack.h │ ├── main.c │ └── shared_stack.c ├── bplus_tree │ ├── bplus_tree.h │ └── main.c └── easy_singly │ └── main.c ├── dsc ├── test.html ├── btree_lesson_M5.json └── info.txt ├── LICENSE ├── .gitignore ├── tools └── convert_encoding.py └── docs ├── README_ZH.md ├── README.md ├── CONTRIBUTING.md └── CONTRIBUTING_EN.md /dsc-code/05-circular_queue/generic_circular_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | typedef struct CircularQueue Queue; 6 | 7 | Queue* queue_create(size_t capacity, size_t element_size); 8 | void queue_destroy(Queue** p_queue); 9 | bool queue_enqueue(Queue* queue, const void* element_data); 10 | bool queue_dequeue(Queue* queue, void* output_buffer); 11 | bool queue_peek(const Queue* queue, void* output_buffer); 12 | bool queue_is_empty(const Queue* queue); 13 | bool queue_is_full(const Queue* queue); 14 | size_t queue_get_size(const Queue* queue); 15 | size_t queue_get_capacity(const Queue* queue); -------------------------------------------------------------------------------- /dsc/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 数据结构课程 - 链表播放器 8 | 9 | 10 | 11 | 12 |
13 |

{{ message }}

14 | 15 |
16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Frank-Code-Show 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # --- 忽略所有无后缀文件 --- 2 | # 1. 忽略所有内容 3 | * 4 | 5 | # 2. 但是,不要忽略目录 6 | !*/ 7 | 8 | # 3. 并且,不要忽略任何带有后缀名的文件 9 | !*.* 10 | 11 | # --- 例外:以下无后缀文件需要被追踪 --- 12 | # 不要忽略 Makefile 13 | !Makefile 14 | 15 | # 不要忽略 Dockerfile 16 | !Dockerfile 17 | 18 | # 不要忽略 LICENSE 文件 19 | !LICENSE 20 | 21 | # 用户特定的文件 22 | *.suo 23 | *.user 24 | *.userosscache 25 | *.sln.docstates 26 | 27 | # Visual Studio 自动生成的文件 28 | *.ncb 29 | *.sdf 30 | *.cache 31 | *.dbmdl 32 | *.opendb 33 | *.pdb 34 | *.psess 35 | *.vsp 36 | *.vsps 37 | *.vssscc 38 | *.vsix 39 | *.vsixmanifest 40 | *.vcxproj 41 | *.filters 42 | *.DB 43 | *.ipch 44 | *.vsidx 45 | *.sln 46 | *.code-workspace 47 | 48 | # 构建结果 49 | [Dd]ebug/ 50 | [Dd]ebugPublic/ 51 | [Rr]elease/ 52 | [Rr]eleases/ 53 | [Oo]utput/ 54 | [Ll]og/ 55 | [Ll]ogs/ 56 | bin/ 57 | obj/ 58 | 59 | # NuGet 相关文件 60 | .nuget/ 61 | packages/ 62 | *.nupkg 63 | *.snupkg 64 | project.lock.json 65 | project.fragment.lock.json 66 | *.csproj.user 67 | 68 | # Visual Studio Code & Rider 特定文件 69 | .vscode/ 70 | .idea/ 71 | 72 | # 备份和临时文件 73 | *~ 74 | *.bak 75 | *.tmp 76 | 77 | # ASP.NET Scaffolding 78 | ScaffoldingReadMe.txt 79 | 80 | # Tye (可选) 81 | .tye/ 82 | -------------------------------------------------------------------------------- /dsc-code/trie/trie.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct Trie Trie; 6 | 7 | // 定义一个函数指针类型,用于销毁用户自定义的值 8 | // 当Trie被销毁时,如果节点上存有值,Trie会调用这个函数来释放值的内存 9 | typedef void (*ValueDestroyer)(void* value); 10 | 11 | /** 12 | * @brief 创建一个新的Trie实例。 13 | * @param destroyer 一个函数指针,用于释放存储在Trie中的值。如果值为简单类型 14 | * 或由外部管理内存,可以传入NULL。 15 | * @return 成功时返回指向新Trie的指针;失败返回NULL。 16 | */ 17 | Trie* Trie_Create(ValueDestroyer destroyer); 18 | 19 | void Trie_Destroy(Trie* trie); 20 | 21 | /** 22 | * @brief 向Trie中插入一个键值对。 23 | * @param trie 指向Trie实例的指针。 24 | * @param key 要插入的键(单词)。 25 | * @param value 指向要存储的值的指针。如果一个键已存在,其旧值将被覆盖。 26 | * (注意:库不会自动释放旧值,需要用户自己管理)。 27 | * @return 如果插入成功,返回true;否则返回false。 28 | */ 29 | bool Trie_Insert(Trie* trie, const char* key, void* value); 30 | 31 | /** 32 | * @brief 在Trie中搜索一个键,并返回其关联的值。 33 | * @param trie 指向Trie实例的指针。 34 | * @param key 要搜索的键。 35 | * @return 如果找到键,返回关联的值的指针;否则返回NULL。 36 | */ 37 | void* Trie_Search(const Trie* trie, const char* key); 38 | 39 | bool Trie_StartsWith(const Trie* trie, const char* prefix); 40 | 41 | void Trie_Delete(Trie* trie, const char* key); 42 | 43 | -------------------------------------------------------------------------------- /dsc-code/doubly/DoublyLinkedList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | // 模拟节点存储数据 5 | typedef struct { 6 | // char title[100]; const 一旦定义,就不能更改,一直都是100 7 | // 这里使用动态内存分配来存储字符串 8 | // 指向一个指针 9 | // 这样可以在运行时动态分配内存 10 | char* title; 11 | char* artist; 12 | int duration; // 单位为秒 13 | } Song; 14 | 15 | 16 | // 双向链表 17 | typedef struct Node { 18 | Song data; 19 | struct Node* next; 20 | struct Node* prev; 21 | } Node; 22 | 23 | 24 | // 定义一个管理器 25 | typedef struct { 26 | Node* head; 27 | Node* tail; 28 | int size; 29 | } DoublyLinkedList; 30 | 31 | DoublyLinkedList* createList(); 32 | 33 | void freeList(DoublyLinkedList* list); 34 | 35 | bool append(DoublyLinkedList* list, Song songData); 36 | 37 | bool prepend(DoublyLinkedList* list, Song songData); 38 | 39 | bool insertAfter(DoublyLinkedList* list, Node* targetNode, Song songData); 40 | 41 | // list 指向链表的指针, nodeToDelete是指向要删除的节点的指针 42 | bool deleteNode(DoublyLinkedList* list, Node* nodeToDelete); 43 | 44 | Node* findByTitle(const DoublyLinkedList* list, const char* title); 45 | 46 | void printListForward(const DoublyLinkedList* list); 47 | 48 | void printListBackward(const DoublyLinkedList* list); -------------------------------------------------------------------------------- /dsc-code/bplus_easy/main.c: -------------------------------------------------------------------------------- 1 | // main.c - Test driver for the simple B+ Tree. 2 | #include "simple_bpt.h" 3 | #include 4 | 5 | int main() { 6 | printf("Creating a B+ Tree with degree t=3 (max keys=5, min keys=2).\n"); 7 | // t=3 -> max keys = 2*3 - 1 = 5. 8 | // Our implementation uses max keys = 2*t-1, so degree is really 't'. 9 | BPlusTree* tree = create_bplus_tree(3); 10 | 11 | int keys_to_insert[] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 15, 25, 35, 45, 5 }; 12 | int num_keys = sizeof(keys_to_insert) / sizeof(keys_to_insert[0]); 13 | 14 | for (int i = 0; i < num_keys; i++) { 15 | printf("Inserting %d...\n", keys_to_insert[i]); 16 | insert(tree, keys_to_insert[i], keys_to_insert[i] * 10); 17 | print_tree(tree); 18 | } 19 | 20 | printf("\n--- Search Tests ---\n"); 21 | int search_keys[] = { 50, 15, 99, 100 }; 22 | for (int i = 0; i < 4; i++) { 23 | int key = search_keys[i]; 24 | int* value_ptr = search(tree, key); 25 | if (value_ptr) { 26 | printf("Found key %d, value is %d.\n", key, *value_ptr); 27 | } 28 | else { 29 | printf("Key %d not found.\n", key); 30 | } 31 | } 32 | 33 | destroy_bplus_tree(tree); 34 | printf("\nTree destroyed.\n"); 35 | 36 | return 0; 37 | } -------------------------------------------------------------------------------- /dsc-code/doubly_circular_link/CircularDoublyLinkedList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | // Internal data structure for the doubly linked list 9 | typedef struct CircularDoublyLinkedList CircularDoublyLinkedList; 10 | typedef struct CDListNode CDListNode; 11 | 12 | typedef void (*FreeFunc)(void*); 13 | typedef int (*CompareFunc)(const void*, const void*); 14 | typedef void (*ActionFunc)(void*, void*); 15 | 16 | 17 | CDListNode* List_GetHeadNode(CircularDoublyLinkedList* list); 18 | 19 | CircularDoublyLinkedList* List_Create(size_t initial_capacity, FreeFunc free_func); 20 | void List_Destroy(CircularDoublyLinkedList** list_ptr); 21 | bool List_Append(CircularDoublyLinkedList* list, void* data); 22 | bool List_Prepend(CircularDoublyLinkedList* list, void* data); 23 | void List_DeleteNode(CircularDoublyLinkedList* list, CDListNode* node); 24 | CDListNode* List_Find(CircularDoublyLinkedList* list, const void* data_to_find, CompareFunc compare_func); 25 | void List_RotateForward(CircularDoublyLinkedList* list); 26 | void List_RotateBackward(CircularDoublyLinkedList* list); 27 | size_t List_GetSize(const CircularDoublyLinkedList* list); 28 | void List_ForEach(CircularDoublyLinkedList* list, ActionFunc action_func, void* context); 29 | void* CDListNode_GetData(const CDListNode* node); 30 | -------------------------------------------------------------------------------- /dsc-code/dsc-code/DynamicArray.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | typedef struct { 5 | int id; 6 | char name[50]; 7 | } Student; 8 | 9 | 10 | typedef Student Data; 11 | 12 | // typedef是给一个已经存在的数据类型起一个新的名字(别名) 13 | 14 | // 定义我们动态数组的结构体 15 | // 16 | 17 | //[{stu1.id =1, stu1.name='Frank'}, {},....] 18 | 19 | 20 | typedef struct { 21 | Data* data; // 指向存储数据的连续内存块; 指向一个数组的首地址 22 | // int* data; // 说白了,我们可以放很多int类型的数据,因为指向的是一个连续不断的地址空间 23 | 24 | size_t size; 25 | 26 | size_t capacity; 27 | } DynamicArray; 28 | 29 | // 公共接口函数声明 30 | 31 | // 创建并初始化一个动态数组 32 | DynamicArray* create_array(size_t initial_capcity); 33 | 34 | // 销毁数组,释放内存 35 | void destroy_array(DynamicArray* arr); 36 | 37 | // 在数组末尾追加元素 Amortized O(1) 38 | void array_append(DynamicArray* arr, Data value); 39 | 40 | // 读取指定的索引元素 41 | // 返回一个指针,以便能够检查是否成功,如果索引无效,返回NULL 42 | Data* array_read(DynamicArray* arr, size_t index); 43 | 44 | // 更新指定索引的元素 45 | // 返回0表示成功,返回-1表示失败 46 | int array_update(DynamicArray* arr, size_t index, Data value); 47 | 48 | int array_insert(DynamicArray* arr, size_t index, Data value); 49 | 50 | // 删除指定索引的元素 51 | int array_delete(DynamicArray* arr, size_t index); 52 | 53 | void print_array(const DynamicArray* arr, void (*print_func)(const void* data)); 54 | 55 | -------------------------------------------------------------------------------- /dsc-code/bplus_easy/simple_bpt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // --- Structures --- 6 | 7 | // Represents a node in the B+ Tree. 8 | typedef struct Node { 9 | bool is_leaf; 10 | int num_keys; 11 | int* keys; 12 | 13 | // Simplified: Pointers for both children and values exist, 14 | // but only one is used depending on is_leaf. 15 | struct Node** children; // Used by internal nodes 16 | int* values; // Used by leaf nodes 17 | 18 | struct Node* next; // Used by leaf nodes for linked list 19 | struct Node* parent; // Pointer to parent node 20 | } Node; 21 | 22 | // Represents the entire B+ Tree. 23 | typedef struct BPlusTree { 24 | Node* root; 25 | int degree; // This is 't', the minimum degree. 26 | } BPlusTree; 27 | 28 | // --- Public API --- 29 | 30 | // Creates a new B+ Tree with a given minimum degree 't'. 31 | BPlusTree* create_bplus_tree(int degree); 32 | 33 | // Destroys the tree and frees all associated memory. 34 | void destroy_bplus_tree(BPlusTree* tree); 35 | 36 | // Inserts a key-value pair into the tree. 37 | void insert(BPlusTree* tree, int key, int value); 38 | 39 | // Searches for a key and returns a pointer to its value. 40 | // Returns NULL if the key is not found. 41 | int* search(BPlusTree* tree, int key); 42 | 43 | // Prints the tree structure for debugging and visualization. 44 | void print_tree(BPlusTree* tree); 45 | -------------------------------------------------------------------------------- /dsc-code/05-deque,double-ended-queue/generic_deque.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | // --- Opaque Pointer Declaration --- 6 | typedef struct Deque Deque; 7 | 8 | // --- Public API Prototypes --- 9 | 10 | /** 11 | * @brief 创建一个新的泛型双端队列(基于循环数组)。 12 | * 13 | * @param capacity 队列的最大容量。 14 | * @param element_size 每个元素的大小(字节)。 15 | * @return 成功时返回指向新队列的指针,失败返回 NULL。 16 | */ 17 | Deque* deque_create(size_t capacity, size_t element_size); 18 | 19 | /** 20 | * @brief 销毁双端队列并释放所有内存。 21 | */ 22 | void deque_destroy(Deque** p_deque); 23 | 24 | /** 25 | * @brief 在队头添加一个元素。 26 | */ 27 | bool deque_push_front(Deque* dq, const void* element_data); 28 | 29 | /** 30 | * @brief 在队尾添加一个元素。 31 | */ 32 | bool deque_push_back(Deque* dq, const void* element_data); 33 | 34 | /** 35 | * @brief 从队头移除一个元素。 36 | */ 37 | bool deque_pop_front(Deque* dq, void* output_buffer); 38 | 39 | /** 40 | * @brief 从队尾移除一个元素。 41 | */ 42 | bool deque_pop_back(Deque* dq, void* output_buffer); 43 | 44 | /** 45 | * @brief 查看队头元素。 46 | */ 47 | bool deque_peek_front(const Deque* dq, void* output_buffer); 48 | 49 | /** 50 | * @brief 查看队尾元素。 51 | */ 52 | bool deque_peek_back(const Deque* dq, void* output_buffer); 53 | 54 | bool deque_is_empty(const Deque* dq); 55 | bool deque_is_full(const Deque* dq); 56 | size_t deque_get_size(const Deque* dq); -------------------------------------------------------------------------------- /dsc-code/dsc-code/main.c: -------------------------------------------------------------------------------- 1 | #define _CRT_SECURE_NO_WARNINGS 2 | #include 3 | #include 4 | #include // 需要包含 string.h 来使用 strcpy 5 | 6 | #include "DynamicArray.h" 7 | 8 | // 定义我们自己的回调函数 9 | // 1. 这是一个知道如何打印Student的函数 10 | // 它的函数签名必须和函数指针的定义完全匹配 11 | void print_student(const void* data) { 12 | // 接收到的data是一个void*指针,我们需要将它转换为我们真正的类型 13 | const Student* s_ptr = (const Student*)data; 14 | printf("Student: {id: %d, name: \"%s\"}", s_ptr->id, s_ptr->name); 15 | } 16 | 17 | 18 | int main() { 19 | printf("--- 测试 Student 动态数组 ---\n"); 20 | DynamicArray* student_list = create_array(2); 21 | 22 | Student s1 = { 101, "Alice" }; 23 | Student s2 = { 102, "Bob" }; 24 | array_append(student_list, s1); 25 | array_append(student_list, s2); 26 | 27 | // 调用通用的 print_array,并把“如何打印学生”的函数传给它! 28 | printf("打印学生名单:\n"); 29 | print_array(student_list, &print_student); // & 是可选的,但更清晰 30 | 31 | destroy_array(student_list); 32 | 33 | printf("\n\n--- 测试 Integer 动态数组 ---\n"); 34 | // 为了运行这个测试,你需要临时将 DynamicArray.h 中的 35 | // typedef Student Data; 改回 typedef int Data; 然后重新编译 36 | /* 37 | DynamicArray* number_list = create_array(5); 38 | array_append(number_list, 10); 39 | array_append(number_list, 20); 40 | array_append(number_list, 30); 41 | 42 | // 同样调用那个万能的 print_array,但这次把“如何打印整数”的函数传给它! 43 | printf("打印数字列表:\n"); 44 | print_array(number_list, &print_int); 45 | 46 | destroy_array(number_list); 47 | */ 48 | 49 | return 0; 50 | } -------------------------------------------------------------------------------- /dsc-code/singly_linked_list/Node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | typedef struct { 5 | int id; 6 | char name[50]; 7 | int age; 8 | } Student; 9 | 10 | 11 | typedef Student Data; 12 | 13 | 14 | typedef struct { 15 | Data data; 16 | // 一定得是一个嵌套的结构体 17 | struct Node* next; 18 | } Node; 19 | 20 | Node* createNode(Data data); 21 | 22 | // appendNode,当链表为空的时候,Head 是NULL时候,appendnode需要将head指向新创建的节点。 23 | // 这涉及的是head指针本身 24 | void appendNode(Node** headRef, Data data); 25 | 26 | void prependNode(Node** headRef, Data data); 27 | 28 | void printList(Node* head, void (*print_func)(const void* data)); 29 | 30 | // 这个函数执行完毕之后,会返回一个指向Node结构体的指针。 Node* 31 | // 泛型 32 | // 第三个参数,意味着,第三个参数传递过来的是一个函数的地址,这个函数必须满足 1. 返回int类型 33 | // 2. 这个函数必须接受两个const void*类型的参数 34 | // node->data 35 | // 可变参数 printf(%D); 36 | // 调用契约 37 | // 1. 它正在遍历的是当前节点的数据 38 | // 2. 你一开始传递给它的目标数据 39 | // context pointer 上下文指针 40 | Node* findNode( 41 | Node* head, 42 | const void* target_data, 43 | int (*compare_func) (const void* a, const void* b, void* context), 44 | void* context 45 | ); 46 | 47 | void deleteNode( 48 | Node** headRef, 49 | const void* target_data, 50 | int (*compare_func) (const void* a, const void* b, void* context), 51 | void* context 52 | ); 53 | 54 | void updateNode( 55 | Node** headRef, 56 | const void* target_data, 57 | Data newData, 58 | int (*compare_func) (const void* a, const void* b, void* context), 59 | void* context 60 | ); 61 | 62 | void freeList(Node** headRef, void (*free_data_func)(void* data)); 63 | -------------------------------------------------------------------------------- /dsc-code/avl/avl_tree.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | // 前向声明 6 | typedef struct AVLNode AVLNode; 7 | typedef struct AVLTree AVLTree; 8 | 9 | // 比较函数类型:返回值 < 0 表示 a < b,= 0 表示 a = b,> 0 表示 a > b 10 | typedef int (*AVLCompareFunc)(const void* a, const void* b, void* context); 11 | 12 | // 遍历回调函数类型 13 | typedef void (*AVLTraverseFunc)(void* data, void* context); 14 | 15 | // 释放数据的回调函数类型 16 | typedef void (*AVLFreeFunc)(void* data, void* context); 17 | 18 | // 创建AVL树 19 | AVLTree* avl_create(AVLCompareFunc compare, AVLFreeFunc free_func, void* context); 20 | 21 | // 销毁AVL树 22 | void avl_destroy(AVLTree* tree); 23 | 24 | // 插入元素(如果已存在则返回false) 25 | bool avl_insert(AVLTree* tree, void* data); 26 | 27 | // 删除元素(返回是否成功删除) 28 | bool avl_delete(AVLTree* tree, const void* data); 29 | 30 | // 查找元素 31 | void* avl_find(const AVLTree* tree, const void* data); 32 | 33 | // 更新元素(先删除旧的,再插入新的) 34 | bool avl_update(AVLTree* tree, const void* old_data, void* new_data); 35 | 36 | // 获取树的大小 37 | size_t avl_size(const AVLTree* tree); 38 | 39 | // 检查树是否为空 40 | bool avl_is_empty(const AVLTree* tree); 41 | 42 | // 中序遍历 43 | void avl_traverse_inorder(const AVLTree* tree, AVLTraverseFunc func, void* context); 44 | 45 | // 前序遍历 46 | void avl_traverse_preorder(const AVLTree* tree, AVLTraverseFunc func, void* context); 47 | 48 | // 后序遍历 49 | void avl_traverse_postorder(const AVLTree* tree, AVLTraverseFunc func, void* context); 50 | 51 | // 获取树的高度 52 | int avl_height(const AVLTree* tree); 53 | 54 | // 验证AVL树的平衡性(用于调试) 55 | bool avl_validate(const AVLTree* tree); 56 | -------------------------------------------------------------------------------- /dsc-code/hash_table/hash_table.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | // --- Opaque Pointer Declaration --- 6 | // 用户只知道有 HashTable 这个类型,但无法访问其内部成员,实现了完美的封装。 7 | typedef struct HashTable HashTable; 8 | 9 | // --- Public API Prototypes --- 10 | 11 | /** 12 | * @brief 创建一个新的哈希表。 13 | * 14 | * @param capacity 哈希表的容量(桶的数量)。选择一个合适的素数通常能获得更好的分布。 15 | * @return 成功时返回指向新哈希表的指针,如果内存分配失败或容量为0则返回 NULL。 16 | */ 17 | HashTable* ht_create(size_t capacity); 18 | 19 | /** 20 | * @brief 销毁一个哈希表,并释放所有相关内存(包括所有键和值)。 21 | * 22 | * @param p_ht 指向哈希表指针的指针。函数执行后,*p_ht 将被设置为 NULL。 23 | */ 24 | void ht_destroy(HashTable** p_ht); 25 | 26 | /** 27 | * @brief 在哈希表中插入或更新一个键值对。 28 | * 29 | * 如果键已存在,其对应的值将被更新。 30 | * 如果键不存在,将创建一个新的键值对并插入。 31 | * 函数会为键和值创建独立的内存拷贝,所以调用者可以释放自己的版本。 32 | * 33 | * @param ht 指向要操作的哈希表。 34 | * @param key 要插入/更新的键(一个C字符串)。 35 | * @param value 关联的值(一个C字符串)。 36 | * @return 成功返回 true,如果参数无效或内存分配失败则返回 false。 37 | */ 38 | bool ht_set(HashTable* ht, const char* key, const char* value); 39 | 40 | /** 41 | * @brief 从哈希表中查找一个键并返回其对应的值。 42 | * 43 | * @param ht 指向要操作的哈希表。 44 | * @param key 要查找的键。 45 | * @return 如果找到,返回一个指向值的常量字符串指针。 46 | * **警告**: 返回的指针指向哈希表内部数据,不应被修改或释放。 47 | * 如果未找到,返回 NULL。 48 | */ 49 | const char* ht_get(const HashTable* ht, const char* key); 50 | 51 | /** 52 | * @brief 从哈希表中删除一个键值对。 53 | * 54 | * @param ht 指向要操作的哈希表。 55 | * @param key 要删除的键。 56 | * @return 如果找到并成功删除则返回 true,否则返回 false。 57 | */ 58 | bool ht_remove(HashTable* ht, const char* key); 59 | 60 | /** 61 | * @brief (辅助函数) 打印哈希表的内部结构,用于调试和教学。 62 | */ 63 | void ht_print(const HashTable* ht); -------------------------------------------------------------------------------- /dsc-code/heap/heap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include // for size_t 3 | 4 | // 为了代码的可读性和可维护性,定义一个 Item 类型 5 | // 如果需要存储其他类型数据,只需修改这里 6 | typedef int Item; 7 | 8 | // 堆结构体定义 9 | typedef struct { 10 | Item* data; // 指向存储堆元素的动态数组 11 | size_t size; // 堆中当前的元素数量 12 | size_t capacity; // 堆的当前容量 13 | } Heap; 14 | 15 | /** 16 | * @brief 创建一个指定初始容量的最大堆 17 | * @param initial_capacity 堆的初始容量 18 | * @return 成功则返回指向堆的指针,失败则返回 NULL 19 | */ 20 | Heap* heap_create(size_t initial_capacity); 21 | 22 | /** 23 | * @brief 销毁一个堆,释放所有相关内存 24 | * @param h 指向要销毁的堆的指针的指针 25 | */ 26 | void heap_destroy(Heap** h); 27 | 28 | /** 29 | * @brief 向堆中插入一个新元素 30 | * @param h 指向堆的指针 31 | * @param value 要插入的值 32 | * @return 成功返回 0,失败(如内存分配失败)返回 -1 33 | */ 34 | int heap_insert(Heap* h, Item value); 35 | 36 | /** 37 | * @brief 从堆中提取(并移除)最大值 38 | * @param h 指向堆的指针 39 | * @param p_max_value 指向用于存储最大值的变量的指针 40 | * @return 成功提取返回 0,如果堆为空则返回 -1 41 | */ 42 | int heap_extract_max(Heap* h, Item* p_max_value); 43 | 44 | /** 45 | * @brief 查看堆顶的最大值(不移除) 46 | * @param h 指向堆的指针 47 | * @param p_peek_value 指向用于存储最大值的变量的指针 48 | * @return 成功查看返回 0,如果堆为空则返回 -1 49 | */ 50 | int heap_peek(const Heap* h, Item* p_peek_value); 51 | 52 | /** 53 | * @brief 检查堆是否为空 54 | * @param h 指向堆的指针 55 | * @return 如果堆为空返回 1,否则返回 0 56 | */ 57 | int is_heap_empty(const Heap* h); 58 | 59 | /** 60 | * @brief 获取堆中元素的数量 61 | * @param h 指向堆的指针 62 | * @return 堆中元素的数量 63 | */ 64 | size_t heap_size(const Heap* h); 65 | 66 | /** 67 | * @brief (可选) 打印堆的内部数组表示,主要用于调试 68 | * @param h 指向堆的指针 69 | */ 70 | void heap_print_debug(const Heap* h); -------------------------------------------------------------------------------- /dsc-code/05-linked-queue/generic_linked_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // --- Opaque Pointer Declaration --- 7 | // 用户只知道有 Queue 这个类型,不知道其内部是链表还是数组。 8 | typedef struct LinkedQueue Queue; 9 | 10 | // --- Public API Prototypes --- 11 | 12 | /** 13 | * @brief 创建一个新的泛型链式队列。 14 | * 15 | * @param element_size 每个元素的大小(以字节为单位)。 16 | * @return 成功时返回指向新队列的指针,失败返回 NULL。 17 | */ 18 | Queue* queue_create(size_t element_size); 19 | 20 | /** 21 | * @brief 销毁一个队列并释放其所有节点和相关内存。 22 | * 23 | * @param p_queue 指向队列指针的指针,函数会将其置为 NULL。 24 | */ 25 | void queue_destroy(Queue** p_queue); 26 | 27 | /** 28 | * @brief 将一个元素添加到队尾(入队)。 29 | * 30 | * @param queue 指向要操作的队列。 31 | * @param element_data 指向要入队的元素数据的指针。 32 | * @return 成功返回 true,如果内存分配失败或参数无效则返回 false。 33 | */ 34 | bool queue_enqueue(Queue* queue, const void* element_data); 35 | 36 | /** 37 | * @brief 从队头移除一个元素(出队)。 38 | * 39 | * @param queue 指向要操作的队列。 40 | * @param output_buffer 指向用于接收出队元素数据的缓冲区。 41 | * @return 成功返回 true,如果队列为空或参数无效则返回 false。 42 | */ 43 | bool queue_dequeue(Queue* queue, void* output_buffer); 44 | 45 | /** 46 | * @brief 查看队头元素,但不将其移除。 47 | * 48 | * @param queue 指向要操作的队列。 49 | * @param output_buffer 指向用于接收队头元素数据的缓冲区。 50 | * @return 成功返回 true,如果队列为空或参数无效则返回 false。 51 | */ 52 | bool queue_peek(const Queue* queue, void* output_buffer); 53 | 54 | /** 55 | * @brief 检查队列是否为空。 56 | * 57 | * @param queue 指向要检查的队列。 58 | * @return 如果队列为空返回 true,否则返回 false。 59 | */ 60 | bool queue_is_empty(const Queue* queue); 61 | 62 | /** 63 | * @brief 获取队列中当前的元素数量。 64 | * 65 | * @param queue 指向要操作的队列。 66 | * @return 返回队列中的元素数量。 67 | */ 68 | size_t queue_get_size(const Queue* queue); -------------------------------------------------------------------------------- /dsc-code/bst/bst.h: -------------------------------------------------------------------------------- 1 | // binary_search_tree.h 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | // --- Opaque Pointer and Function Pointer Type Definitions --- 8 | 9 | typedef struct BST BST; 10 | 11 | /** 12 | * 比较函数指针类型。 13 | * @param a 指向第一个元素的指针。 14 | * @param b 指向第二个元素的指针。 15 | * @return > 0 如果 a > b。 16 | * @return < 0 如果 a < b。 17 | * @return 0 如果 a == b。 18 | */ 19 | typedef int (*CompareFunc)(const void* a, const void* b); 20 | 21 | /** 22 | * 访问函数指针类型,用于遍历。 23 | * @param data 指向节点数据的指针。 24 | */ 25 | typedef void (*VisitFunc)(const void* data); 26 | 27 | // 遍历顺序的枚举 28 | typedef enum { 29 | IN_ORDER, // 中序遍历 (LNR -> 结果有序) 30 | PRE_ORDER, // 前序遍历 (NLR) 31 | POST_ORDER // 后序遍历 (LRN) 32 | } TraverseOrder; 33 | 34 | 35 | // --- Public API Prototypes --- 36 | 37 | /** 38 | * @brief 创建一个新的泛型二叉搜索树。 39 | * 40 | * @param element_size 每个元素的大小(字节)。 41 | * @param compare_func 用于比较元素的函数指针,这是BST的核心。 42 | * @return 成功时返回指向新树的指针,失败返回 NULL。 43 | */ 44 | BST* bst_create(size_t element_size, CompareFunc compare_func); 45 | 46 | /** 47 | * @brief 销毁一个二叉搜索树并释放所有节点和内存。 48 | */ 49 | void bst_destroy(BST** p_bst); 50 | 51 | /** 52 | * @brief 在树中插入一个新元素。 53 | * 如果元素已存在,则不进行任何操作。 54 | */ 55 | bool bst_insert(BST* bst, const void* element_data); 56 | 57 | /** 58 | * @brief 从树中移除一个元素。 59 | */ 60 | bool bst_remove(BST* bst, const void* element_data); 61 | 62 | /** 63 | * @brief 检查一个元素是否存在于树中。 64 | */ 65 | bool bst_search(const BST* bst, const void* key); 66 | 67 | /** 68 | * @brief 按照指定的顺序遍历树,并对每个节点执行访问函数。 69 | */ 70 | void bst_traverse(const BST* bst, TraverseOrder order, VisitFunc visit_func); 71 | 72 | bool bst_is_empty(const BST* bst); 73 | size_t bst_get_size(const BST* bst); 74 | -------------------------------------------------------------------------------- /dsc-code/doublyLinkedList/DoublyLinkedList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | // --- Opaque Pointer Type --- 6 | // 用户只能看到这些类型的指针,不能直接访问其内部结构 ―― OOP封装 7 | 8 | typedef struct DoublyLinkedList DoublyLinkedList; 9 | typedef struct DListNode DListNode; 10 | 11 | // --- Callback Function Pointer Typedefs --- 12 | 13 | // 定义用户需要提供的回调函数类型,增强代码可读性 14 | 15 | typedef int (*CompareFunc)(const void* data1, const void* data2); 16 | typedef void (*PrintFunc)(const void* data); 17 | typedef void (*FreeFunc)(void* data); 18 | typedef void (*ActionFunc)(void* data, void* context); 19 | 20 | // MemoryPool 相关函数指针类型 21 | // 内存池结构。它预先分配一块内存,用于存储链表节点,减少频繁的内存分配和释放操作。 22 | // 并且将其划分为多个固定大小的块,以便快速分配和释放。内部通过一个单向链表来管理这些内存块。 23 | 24 | 25 | 26 | // API FUNCTION DECLARATIONS 27 | 28 | /** 29 | * @brief 创建一个新的双向链表 30 | * 31 | * @param initial_capacity 内存池初始容量(可用于预分配节点或内部结构) 32 | * @param free_func 用户自定义的释放数据的回调函数,链表销毁或节点删除时调用 33 | * @return DoublyLinkedList* 新创建的链表指针,失败时返回NULL 34 | * 35 | * @note 创建后需调用相应的销毁函数释放链表资源 36 | */ 37 | DoublyLinkedList* List_Create(size_t initial_capacity, FreeFunc free_func); 38 | 39 | void List_Destroy(DoublyLinkedList** list); 40 | 41 | bool List_Append(DoublyLinkedList* list, const void* data); 42 | 43 | bool List_Prepend(DoublyLinkedList* list, const void* data); 44 | 45 | bool List_InsertAfter(DoublyLinkedList* list, DListNode* node, const void* data); 46 | 47 | void List_DeleteNode(DoublyLinkedList* list, DListNode* node); 48 | 49 | DListNode* List_Find(DoublyLinkedList* list, const void* data_to_find, CompareFunc compare_func); 50 | 51 | size_t List_GetSize(const DoublyLinkedList* list); 52 | 53 | void List_ForEach(const DoublyLinkedList* list, ActionFunc action_func, void* context); 54 | 55 | void* List_GetData(const DListNode* node); -------------------------------------------------------------------------------- /dsc-code/linked_stack/linked_stack.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include // for size_t 3 | #include // for bool (C99/C11 standard) 4 | 5 | // --- Opaque Pointer Declaration --- 6 | // 用户只知道有 Stack 这个类型,但不知道其内部是链表还是数组。 7 | typedef struct LinkedStack Stack; 8 | 9 | // --- Public API Prototypes --- 10 | 11 | /** 12 | * @brief 创建一个新的通用链式栈。 13 | * 14 | * @param element_size 栈中每个元素的大小(以字节为单位),例如 sizeof(int)。 15 | * @return 成功时返回指向新栈的指针,如果 element_size 为0或内存分配失败,则返回 NULL。 16 | */ 17 | Stack* stack_create(size_t element_size); 18 | 19 | /** 20 | * @brief 销毁一个栈并释放其所有节点和相关内存。 21 | * 22 | * @param p_stack 指向栈指针的指针。函数执行后,*p_stack 将被设置为 NULL。 23 | */ 24 | void stack_destroy(Stack** p_stack); 25 | 26 | /** 27 | * @brief 将一个元素压入栈顶。 28 | * 29 | * @param stack 指向要操作的栈的指针。 30 | * @param element_data 指向要压入栈的元素数据的指针。 31 | * @return 成功压栈返回 true,如果内存分配失败或参数无效则返回 false。 32 | */ 33 | bool stack_push(Stack* stack, const void* element_data); 34 | 35 | /** 36 | * @brief 从栈顶弹出一个元素。 37 | * 38 | * @param stack 指向要操作的栈的指针。 39 | * @param output_buffer 指向一个缓冲区的指针,用于接收弹出的元素数据。 40 | * @return 成功弹栈返回 true,如果栈为空或参数无效则返回 false。 41 | */ 42 | bool stack_pop(Stack* stack, void* output_buffer); 43 | 44 | /** 45 | * @brief 查看栈顶元素,但不将其弹出。 46 | * 47 | * @param stack 指向要操作的栈的指针。 48 | * @param output_buffer 指向一个缓冲区的指针,用于接收栈顶元素的数据。 49 | * @return 成功查看返回 true,如果栈为空或参数无效则返回 false。 50 | */ 51 | bool stack_peek(const Stack* stack, void* output_buffer); 52 | 53 | /** 54 | * @brief 检查栈是否为空。 55 | * 56 | * @param stack 指向要操作的栈的指针。 57 | * @return 如果栈为空或为NULL则返回 true,否则返回 false。 58 | */ 59 | bool stack_is_empty(const Stack* stack); 60 | 61 | /** 62 | * @brief 获取栈中当前的元素数量。 63 | * 64 | * @param stack 指向要操作的栈的指针。 65 | * @return 返回栈中的元素数量。如果栈为NULL,返回0。 66 | */ 67 | size_t stack_get_size(const Stack* stack); 68 | 69 | -------------------------------------------------------------------------------- /dsc-code/hash_table/main.c: -------------------------------------------------------------------------------- 1 | // main.c 2 | 3 | #include "hash_table.h" 4 | #include 5 | 6 | int main() { 7 | printf("--- 现代哈希表 C语言实现 (拉链法) ---\n"); 8 | HashTable* ht = ht_create(10); // 创建容量为10的哈希表 9 | if (!ht) { 10 | printf("创建哈希表失败。\n"); 11 | return 1; 12 | } 13 | 14 | printf("\n1. 插入初始数据 (包括哈希冲突)...\n"); 15 | // hash("name") % 10 = (110+97+109+101)%10 = 417%10 = 7 (注:ASCII码不同机器可能微调,但冲突原理一致) 16 | // hash("mane") % 10 = (109+97+110+101)%10 = 417%10 = 7 17 | ht_set(ht, "name", "Alice"); 18 | ht_set(ht, "age", "30"); 19 | ht_set(ht, "city", "New York"); 20 | ht_set(ht, "mane", "Bob"); // <-- 这个键会与 "name" 发生哈希冲突 21 | ht_print(ht); 22 | 23 | printf("2. 查找数据...\n"); 24 | const char* name = ht_get(ht, "name"); 25 | const char* city = ht_get(ht, "city"); 26 | const char* country = ht_get(ht, "country"); // 一个不存在的键 27 | printf("Get 'name': %s\n", name ? name : "Not Found"); 28 | printf("Get 'city': %s\n", city ? city : "Not Found"); 29 | printf("Get 'country': %s\n", country ? country : "Not Found"); 30 | 31 | // 查找冲突链中的另一个键 32 | const char* mane = ht_get(ht, "mane"); 33 | printf("Get 'mane' (collision key): %s\n", mane ? mane : "Not Found"); 34 | printf("\n"); 35 | 36 | printf("3. 更新数据...\n"); 37 | printf("Updating 'name' from 'Alice' to 'Amy'...\n"); 38 | ht_set(ht, "name", "Amy"); 39 | ht_print(ht); 40 | 41 | printf("4. 删除数据...\n"); 42 | printf("Removing 'age'...\n"); 43 | ht_remove(ht, "age"); 44 | ht_print(ht); 45 | 46 | printf("Removing 'name' (a key in a collision chain)...\n"); 47 | ht_remove(ht, "name"); 48 | ht_print(ht); 49 | 50 | printf("5. 销毁哈希表...\n"); 51 | ht_destroy(&ht); 52 | printf("哈希表已销毁,指针为: %s\n", ht == NULL ? "NULL" : "OK"); 53 | 54 | return 0; 55 | } -------------------------------------------------------------------------------- /dsc-code/safe_arr/safe_array.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include // for size_t 3 | #include // for bool 4 | 5 | 6 | // 1. safety, 要求提供一组接口,确保任何对数组的访问都在其合法范围内。 7 | // 2. encapsulation, 封装性。隐藏内部的原始数组,要求用户(下属程序员)只能通过这些 8 | // 接口访问数组,而不能直接访问内部数据。 9 | // 3. usability 易用性 暴露给用户的API必须清晰直观。 10 | // interface & information hiding 11 | // --- Opaque Pointer Declaration 不透明指针 --- 12 | // “安全数组”的魔法:用户只知道有 SafeArray 这个类型, 13 | // 但完全不知道它内部是用什么实现的。他们无法直接访问内部的原始数组。 14 | // Forward Declaration of SafeArray 前向声明 15 | // 它向编译器声明 SafeArray 是一个结构体类型,但不定义它的内容。 16 | // 这样做的好处是,用户无法直接访问 SafeArray 的内部数据,因此我们可以定义指向它的指针 SafeArray* 17 | typedef struct SafeArray SafeArray; 18 | 19 | // --- Public API Prototypes --- 20 | 21 | /** 22 | * @brief 创建一个新的安全数组。 23 | * 24 | * @param capacity 数组可以容纳的元素数量。 25 | * @return 成功时返回指向新安全数组的指针,失败返回 NULL。 26 | */ 27 | SafeArray* sarray_create(size_t capacity); 28 | 29 | /** 30 | * @brief 销毁一个安全数组并释放所有内存。 31 | * 32 | * @param p_sarray 指向安全数组指针的指针,函数会将其置为 NULL。 33 | */ 34 | void sarray_destroy(SafeArray** p_sarray); 35 | 36 | /** 37 | * @brief 安全地设置指定索引位置的值。 38 | * 39 | * @param sarray 指向要操作的安全数组。 40 | * @param index 要设置的元素的索引。 41 | * @param value 要设置的整数值。 42 | * @return 成功返回 true,如果索引越界或数组为NULL则返回 false。 43 | */ 44 | bool sarray_set(SafeArray* sarray, size_t index, int value); 45 | 46 | /** 47 | * @brief 安全地获取指定索引位置的值。 48 | * 49 | * @param sarray 指向要操作的安全数组。 50 | * @param index 要获取的元素的索引。 51 | * @param out_value 指向一个整数的指针,用于接收获取到的值。out-parameter 输出参数 52 | * @return 成功返回 true,如果索引越界或参数无效则返回 false。 53 | */ 54 | bool sarray_get(const SafeArray* sarray, size_t index, int* out_value); 55 | 56 | /** 57 | * @brief 获取安全数组的容量。 58 | * 59 | * @param sarray 指向要查询的数组。 60 | * @return 返回数组的容量,如果数组为NULL则返回0。 61 | */ 62 | size_t sarray_get_capacity(const SafeArray* sarray); 63 | 64 | /** 65 | * @brief (教学用) 打印安全数组的内容。 66 | */ 67 | void sarray_print(const SafeArray* sarray); -------------------------------------------------------------------------------- /dsc-code/heap/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "heap.h" // 包含我们的堆ADT头文件 3 | 4 | int main() { 5 | printf("========== Heap ADT Demonstration ==========\n"); 6 | 7 | // 1. 创建一个初始容量为5的堆 8 | printf("\n--- 1. Creating a heap with initial capacity 5 ---\n"); 9 | Heap* my_heap = heap_create(5); 10 | if (!my_heap) { 11 | fprintf(stderr, "Failed to create heap. Exiting.\n"); 12 | return 1; 13 | } 14 | heap_print_debug(my_heap); 15 | 16 | // 2. 插入一些元素 17 | printf("\n--- 2. Inserting elements ---\n"); 18 | int elements[] = { 30, 80, 50, 20, 90, 10, 60, 40, 70, 100 }; 19 | size_t num_elements = sizeof(elements) / sizeof(elements[0]); 20 | 21 | for (size_t i = 0; i < num_elements; ++i) { 22 | printf("Inserting %d...\n", elements[i]); 23 | heap_insert(my_heap, elements[i]); 24 | heap_print_debug(my_heap); 25 | } 26 | 27 | printf("\nFinal heap size: %zu\n", heap_size(my_heap)); 28 | 29 | // 3. 查看堆顶元素 30 | printf("\n--- 3. Peeking at the maximum element ---\n"); 31 | Item max_val; 32 | if (heap_peek(my_heap, &max_val) == 0) { 33 | printf("The maximum element (peek) is: %d\n", max_val); 34 | } 35 | heap_print_debug(my_heap); // 确认堆未改变 36 | 37 | // 4. 提取所有元素(堆排序的过程) 38 | printf("\n--- 4. Extracting all elements in order ---\n"); 39 | while (!is_heap_empty(my_heap)) { 40 | Item extracted_val; 41 | if (heap_extract_max(my_heap, &extracted_val) == 0) { 42 | printf("Extracted max: %d. ", extracted_val); 43 | printf("Heap state after extraction: "); 44 | heap_print_debug(my_heap); 45 | } 46 | } 47 | 48 | printf("\nHeap is now empty.\n"); 49 | heap_print_debug(my_heap); 50 | 51 | 52 | // 5. 销毁堆 53 | printf("\n--- 5. Destroying the heap ---\n"); 54 | heap_destroy(&my_heap); 55 | if (my_heap == NULL) { 56 | printf("Heap destroyed successfully. my_heap pointer is now NULL.\n"); 57 | } 58 | 59 | printf("\n========== Demonstration Finished ==========\n"); 60 | 61 | return 0; 62 | } -------------------------------------------------------------------------------- /tools/convert_encoding.py: -------------------------------------------------------------------------------- 1 | import os 2 | import chardet # 这是一个强大的编码检测库 3 | 4 | # --- 配置区 --- 5 | # 你想要转换的文件夹路径 6 | TARGET_FOLDER = '.\\dsc-code' 7 | # 你想要转换的文件扩展名 8 | FILE_EXTENSIONS = ('.c', '.h') 9 | # 原始编码(如果 chardet 检测失败,会使用这个作为备用) 10 | FROM_ENCODING = 'gbk' 11 | # --- 配置区结束 --- 12 | 13 | def convert_files_to_utf8(root_dir): 14 | """ 15 | 递归地将指定文件夹内特定扩展名的文件从原始编码转换为 UTF-8。 16 | """ 17 | print(f"开始扫描文件夹: {os.path.abspath(root_dir)}") 18 | converted_count = 0 19 | 20 | for subdir, _, files in os.walk(root_dir): 21 | for filename in files: 22 | if filename.lower().endswith(FILE_EXTENSIONS): 23 | file_path = os.path.join(subdir, filename) 24 | 25 | try: 26 | # 以二进制模式读取文件内容 27 | with open(file_path, 'rb') as f: 28 | content_bytes = f.read() 29 | 30 | # 如果文件是空的,直接跳过 31 | if not content_bytes: 32 | continue 33 | 34 | # 使用 chardet 检测编码 35 | detected_result = chardet.detect(content_bytes) 36 | encoding = detected_result['encoding'] 37 | confidence = detected_result['confidence'] 38 | 39 | # 如果检测到的编码不是 UTF-8 且置信度较高,则进行转换 40 | # 'ascii' 编码是 UTF-8 的子集,无需转换 41 | if encoding and encoding.lower() not in ['utf-8', 'ascii']: 42 | print(f"转换文件: {file_path} (检测到编码: {encoding}, 置信度: {confidence:.0%})") 43 | 44 | # 使用检测到的编码来解码 45 | decoded_content = content_bytes.decode(encoding, errors='replace') 46 | 47 | # 以 UTF-8 编码写回文件 48 | with open(file_path, 'w', encoding='utf-8') as f: 49 | f.write(decoded_content) 50 | converted_count += 1 51 | else: 52 | print(f"跳过文件: {file_path} (已是 UTF-8 或无法确定)") 53 | 54 | except Exception as e: 55 | print(f"处理文件 {file_path} 时发生错误: {e}") 56 | 57 | print(f"\n转换完成!总共转换了 {converted_count} 个文件。") 58 | 59 | if __name__ == '__main__': 60 | if not os.path.isdir(TARGET_FOLDER): 61 | print(f"错误:目标文件夹 '{TARGET_FOLDER}' 不存在。请检查路径是否正确。") 62 | else: 63 | convert_files_to_utf8(TARGET_FOLDER) -------------------------------------------------------------------------------- /dsc-code/sequential_stack/sequential_stack.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include // for size_t 3 | #include // for bool (C99/C11 standard) 4 | 5 | // --- Opaque Pointer Declaration --- 6 | // 用户只能看到这个类型名,无法知道其内部结构,实现了完美的封装。 7 | typedef struct Stack Stack; 8 | 9 | // --- Public API Prototypes --- 10 | 11 | /** 12 | * @brief 创建一个新的通用顺序栈。 13 | * 14 | * @param capacity 栈可以容纳的最大元素数量。 15 | * @param element_size 栈中每个元素的大小(以字节为单位),例如 sizeof(int)。 16 | * @return 成功时返回指向新栈的指针,如果 capacity 或 element_size 为0, 17 | * 或内存分配失败,则返回 NULL。 18 | */ 19 | Stack* stack_create(size_t capacity, size_t element_size); 20 | 21 | /** 22 | * @brief 销毁一个栈并释放其所有相关内存。 23 | * 24 | * @param p_stack 指向栈指针的指针。函数执行后,*p_stack 将被设置为 NULL, 25 | * 以防止悬空指针。 26 | */ 27 | void stack_destroy(Stack** p_stack); 28 | 29 | /** 30 | * @brief 将一个元素压入栈顶。 31 | * 32 | * @param stack 指向要操作的栈的指针。 33 | * @param element_data 指向要压入栈的元素数据的指针。该数据将被复制到栈中。 34 | * @return 成功压栈返回 true,如果栈已满或参数无效则返回 false。 35 | */ 36 | bool stack_push(Stack* stack, const void* element_data); 37 | 38 | /** 39 | * @brief 从栈顶弹出一个元素。 40 | * 41 | * @param stack 指向要操作的栈的指针。 42 | * @param output_buffer 指向一个缓冲区的指针,用于接收弹出的元素数据。 43 | * 该缓冲区的大小必须至少为栈创建时指定的 element_size。 44 | * @return 成功弹栈返回 true,如果栈为空或参数无效则返回 false。 45 | */ 46 | bool stack_pop(Stack* stack, void* output_buffer); 47 | 48 | /** 49 | * @brief 查看栈顶元素,但不将其弹出。 50 | * 51 | * @param stack 指向要操作的栈的指针。 52 | * @param output_buffer 指向一个缓冲区的指针,用于接收栈顶元素的数据。 53 | * 该缓冲区的大小必须至少为栈创建时指定的 element_size。 54 | * @return 成功查看返回 true,如果栈为空或参数无效则返回 false。 55 | */ 56 | bool stack_peek(Stack* stack, void* output_buffer); 57 | 58 | /** 59 | * @brief 检查栈是否为空。 60 | * 61 | * @param stack 指向要操作的栈的指针。 62 | * @return 如果栈为空或为NULL则返回 true,否则返回 false。 63 | */ 64 | bool stack_is_empty(const Stack* stack); 65 | 66 | /** 67 | * @brief 检查栈是否已满。 68 | * 69 | * @param stack 指向要操作的栈的指针。 70 | * @return 如果栈已满则返回 true,否则返回 false。如果栈为NULL,也返回 false。 71 | */ 72 | bool stack_is_full(const Stack* stack); 73 | 74 | /** 75 | * @brief 获取栈中当前的元素数量。 76 | * 77 | * @param stack 指向要操作的栈的指针。 78 | * @return 返回栈中的元素数量。如果栈为NULL,返回0。 79 | */ 80 | size_t stack_get_size(const Stack* stack); 81 | 82 | /** 83 | * @brief 获取栈的总容量。 84 | * 85 | * @param stack 指向要操作的栈的指针。 86 | * @return 返回栈的总容量。如果栈为NULL,返回0。 87 | */ 88 | size_t stack_get_capacity(const Stack* stack); -------------------------------------------------------------------------------- /dsc-code/05-linked-queue/main.c: -------------------------------------------------------------------------------- 1 | // main.c 2 | 3 | #include "generic_linked_queue.h" 4 | #include 5 | 6 | // 定义一个结构体用于测试 7 | typedef struct { 8 | int id; 9 | char name[16]; 10 | } Person; 11 | 12 | void print_person(const Person* p) { 13 | if (p) printf("Person(id: %d, name: \"%s\")", p->id, p->name); 14 | } 15 | 16 | void print_queue_status(const Queue* q, const char* title) { 17 | printf("--- %s ---\n", title); 18 | if (!q) { 19 | printf("队列为 NULL\n\n"); 20 | return; 21 | } 22 | printf("大小: %zu, 空? %s\n", queue_get_size(q), queue_is_empty(q) ? "是" : "否"); 23 | 24 | Person peeked_person; 25 | if (queue_peek(q, &peeked_person)) { 26 | printf("队头是: "); 27 | print_person(&peeked_person); 28 | printf("\n"); 29 | } 30 | else { 31 | printf("队头为空。\n"); 32 | } 33 | printf("\n"); 34 | } 35 | 36 | 37 | int main() { 38 | // 1. 创建一个Person类型的链式队列 39 | Queue* q = queue_create(sizeof(Person)); 40 | print_queue_status(q, "1. 初始状态"); 41 | 42 | // 2. 入队第一个元素 43 | Person p1 = { 101, "Alice" }; 44 | printf("入队: "); print_person(&p1); printf("\n"); 45 | queue_enqueue(q, &p1); 46 | print_queue_status(q, "2. 入队第一个元素后"); 47 | 48 | // 3. 入队更多元素 49 | Person p2 = { 102, "Bob" }; 50 | Person p3 = { 103, "Charlie" }; 51 | printf("入队: "); print_person(&p2); printf("\n"); 52 | queue_enqueue(q, &p2); 53 | printf("入队: "); print_person(&p3); printf("\n"); 54 | queue_enqueue(q, &p3); 55 | print_queue_status(q, "3. 继续入队后"); 56 | 57 | // 4. 出队一个元素 58 | Person dequeued_person; 59 | if (queue_dequeue(q, &dequeued_person)) { 60 | printf("出队成功: "); 61 | print_person(&dequeued_person); 62 | printf("\n"); 63 | } 64 | print_queue_status(q, "4. 出队一个元素后"); 65 | 66 | // 5. 全部出队,测试队列变空的逻辑 67 | printf("将所有剩余元素出队...\n"); 68 | while (!queue_is_empty(q)) { 69 | if (queue_dequeue(q, &dequeued_person)) { 70 | printf("出队: "); 71 | print_person(&dequeued_person); 72 | printf("\n"); 73 | } 74 | } 75 | print_queue_status(q, "5. 全部出队后"); 76 | 77 | // 6. 尝试对空队列进行操作 78 | printf("尝试对空队列出队...\n"); 79 | if (!queue_dequeue(q, &dequeued_person)) { 80 | printf("出队失败,符合预期。\n\n"); 81 | } 82 | 83 | // 7. 销毁队列 84 | queue_destroy(&q); 85 | printf("队列已销毁,指针为: %s\n", q == NULL ? "NULL" : "Not NULL"); 86 | 87 | return 0; 88 | } -------------------------------------------------------------------------------- /dsc-code/05-deque,double-ended-queue/main.c: -------------------------------------------------------------------------------- 1 | #include "generic_deque.h" 2 | #include 3 | 4 | void test_deque_as_queue() { 5 | printf("--- 1. 使用双端队列模拟“标准队列” ---\n"); 6 | printf("(操作: push_back + pop_front)\n"); 7 | 8 | Deque* dq = deque_create(5, sizeof(int)); 9 | int val; 10 | 11 | printf("Push Back: 10, 20, 30\n"); 12 | for (val = 10; val <= 30; val += 10) deque_push_back(dq, &val); 13 | 14 | printf("当前大小: %zu\n", deque_get_size(dq)); 15 | 16 | printf("Pop Front (应按FIFO顺序):\n"); 17 | while (!deque_is_empty(dq)) { 18 | deque_pop_front(dq, &val); 19 | printf(" -> %d\n", val); 20 | } 21 | printf("\n"); 22 | deque_destroy(&dq); 23 | } 24 | 25 | void test_deque_as_stack() { 26 | printf("--- 2. 使用双端队列模拟“栈” ---\n"); 27 | printf("(操作: push_front + pop_front)\n"); 28 | 29 | Deque* dq = deque_create(5, sizeof(int)); 30 | int val; 31 | 32 | printf("Push Front: 10, 20, 30\n"); 33 | for (val = 10; val <= 30; val += 10) deque_push_front(dq, &val); 34 | 35 | printf("当前大小: %zu\n", deque_get_size(dq)); 36 | 37 | printf("Pop Front (应按LIFO顺序):\n"); 38 | while (!deque_is_empty(dq)) { 39 | deque_pop_front(dq, &val); 40 | printf(" -> %d\n", val); 41 | } 42 | printf("\n"); 43 | deque_destroy(&dq); 44 | } 45 | 46 | void test_mixed_operations() { 47 | printf("--- 3. 混合操作测试 ---\n"); 48 | Deque* dq = deque_create(5, sizeof(char)); 49 | char val; 50 | 51 | // A, B 52 | val = 'A'; deque_push_back(dq, &val); 53 | val = 'B'; deque_push_back(dq, &val); 54 | printf("Push Back 'A', 'B'. Front: 'A', Back: 'B'\n"); 55 | 56 | // X, A, B 57 | val = 'X'; deque_push_front(dq, &val); 58 | printf("Push Front 'X'. Front: 'X', Back: 'B'\n"); 59 | 60 | // X, A, B, C 61 | val = 'C'; deque_push_back(dq, &val); 62 | printf("Push Back 'C'. Front: 'X', Back: 'C'\n"); 63 | 64 | printf("当前队列大小: %zu. 内容(从头到尾): X, A, B, C\n", deque_get_size(dq)); 65 | 66 | deque_peek_front(dq, &val); 67 | printf("Peek Front: %c\n", val); 68 | deque_peek_back(dq, &val); 69 | printf("Peek Back: %c\n", val); 70 | 71 | deque_pop_back(dq, &val); 72 | printf("Pop Back: %c. 剩下: X, A, B\n", val); 73 | 74 | deque_pop_front(dq, &val); 75 | printf("Pop Front: %c. 剩下: A, B\n", val); 76 | 77 | deque_destroy(&dq); 78 | } 79 | 80 | 81 | int main(void) { 82 | 83 | test_deque_as_queue(); 84 | test_deque_as_stack(); 85 | test_mixed_operations(); 86 | return 0; 87 | } -------------------------------------------------------------------------------- /dsc/btree_lesson_M5.json: -------------------------------------------------------------------------------- 1 | { 2 | "minDegree": 3, 3 | "root": { 4 | "keys": [ 5 | 50, 6 | 100 7 | ], 8 | "isLeaf": false, 9 | "children": [ 10 | { 11 | "keys": [ 12 | 20, 13 | 35 14 | ], 15 | "isLeaf": false, 16 | "children": [ 17 | { 18 | "keys": [ 19 | 5, 20 | 10, 21 | 15 22 | ], 23 | "isLeaf": true, 24 | "children": [] 25 | }, 26 | { 27 | "keys": [ 28 | 22, 29 | 28 30 | ], 31 | "isLeaf": true, 32 | "children": [] 33 | }, 34 | { 35 | "keys": [ 36 | 40, 37 | 45 38 | ], 39 | "isLeaf": true, 40 | "children": [] 41 | } 42 | ] 43 | }, 44 | { 45 | "keys": [ 46 | 70, 47 | 85 48 | ], 49 | "isLeaf": false, 50 | "children": [ 51 | { 52 | "keys": [ 53 | 60, 54 | 65 55 | ], 56 | "isLeaf": true, 57 | "children": [] 58 | }, 59 | { 60 | "keys": [ 61 | 75, 62 | 80 63 | ], 64 | "isLeaf": true, 65 | "children": [] 66 | }, 67 | { 68 | "keys": [ 69 | 90, 70 | 95 71 | ], 72 | "isLeaf": true, 73 | "children": [] 74 | } 75 | ] 76 | }, 77 | { 78 | "keys": [ 79 | 120, 80 | 150, 81 | 180 82 | ], 83 | "isLeaf": false, 84 | "children": [ 85 | { 86 | "keys": [ 87 | 110, 88 | 115 89 | ], 90 | "isLeaf": true, 91 | "children": [] 92 | }, 93 | { 94 | "keys": [ 95 | 130, 96 | 140 97 | ], 98 | "isLeaf": true, 99 | "children": [] 100 | }, 101 | { 102 | "keys": [ 103 | 160, 104 | 170 105 | ], 106 | "isLeaf": true, 107 | "children": [] 108 | }, 109 | { 110 | "keys": [ 111 | 190, 112 | 200 113 | ], 114 | "isLeaf": true, 115 | "children": [] 116 | } 117 | ] 118 | } 119 | ] 120 | } 121 | } -------------------------------------------------------------------------------- /dsc-code/shared_stack/shared_stack.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // for size_t 4 | #include // for bool 5 | 6 | // --- Public Enumeration and Opaque Pointer --- 7 | 8 | // 使用枚举来清晰地标识要操作的栈 9 | typedef enum { 10 | STACK_ONE = 1, // 栈1,从数组头部开始 11 | STACK_TWO = 2 // 栈2,从数组尾部开始 12 | } StackNumber; 13 | 14 | // 不透明指针,用户无法看到内部结构 15 | typedef struct SharedStack Stack; 16 | 17 | // --- Public API Prototypes --- 18 | 19 | /** 20 | * @brief 创建一个新的共享栈。 21 | * 22 | * @param total_capacity 两个栈共享的总容量(元素个数)。 23 | * @param element_size 每个元素的大小(以字节为单位)。 24 | * @return 成功时返回指向新栈的指针,失败返回 NULL。 25 | */ 26 | Stack* stack_create(size_t total_capacity, size_t element_size); 27 | 28 | /** 29 | * @brief 销毁一个共享栈并释放所有内存。 30 | * 31 | * @param p_stack 指向栈指针的指针,函数会将其置为 NULL。 32 | */ 33 | void stack_destroy(Stack** p_stack); 34 | 35 | /** 36 | * @brief 将一个元素压入指定的栈。 37 | * 38 | * @param stack 指向要操作的栈。 39 | * @param num 要操作的栈编号 (STACK_ONE 或 STACK_TWO)。 40 | * @param element_data 指向要压入的元素数据的指针。 41 | * @return 成功返回 true,如果栈满或参数无效则返回 false。 42 | */ 43 | bool stack_push(Stack* stack, StackNumber num, const void* element_data); 44 | 45 | /** 46 | * @brief 从指定的栈弹出一个元素。 47 | * 48 | * @param stack 指向要操作的栈。 49 | * @param num 要操作的栈编号 (STACK_ONE 或 STACK_TWO)。 50 | * @param output_buffer 指向用于接收弹出元素数据的缓冲区。 51 | * @return 成功返回 true,如果栈空或参数无效则返回 false。 52 | */ 53 | bool stack_pop(Stack* stack, StackNumber num, void* output_buffer); 54 | 55 | /** 56 | * @brief 查看指定栈的栈顶元素,但不弹出。 57 | * 58 | * @param stack 指向要操作的栈。 59 | * @param num 要操作的栈编号 (STACK_ONE 或 STACK_TWO)。 60 | * @param output_buffer 指向用于接收栈顶元素数据的缓冲区。 61 | * @return 成功返回 true,如果栈空或参数无效则返回 false。 62 | */ 63 | bool stack_peek(const Stack* stack, StackNumber num, void* output_buffer); 64 | 65 | /** 66 | * @brief 检查共享空间是否已满。 67 | * 68 | * @param stack 指向要检查的栈。 69 | * @return 如果已满返回 true,否则返回 false。 70 | */ 71 | bool stack_is_full(const Stack* stack); 72 | 73 | /** 74 | * @brief 检查指定的栈是否为空。 75 | * 76 | * @param stack 指向要检查的栈。 77 | * @param num 要检查的栈编号 (STACK_ONE 或 STACK_TWO)。 78 | * @return 如果该栈为空返回 true,否则返回 false。 79 | */ 80 | bool stack_is_empty(const Stack* stack, StackNumber num); 81 | 82 | /** 83 | * @brief 获取指定栈的当前元素数量。 84 | * 85 | * @param stack 指向要操作的栈。 86 | * @param num 要获取大小的栈编号 (STACK_ONE 或 STACK_TWO)。 87 | * @return 返回该栈的元素数量。 88 | */ 89 | size_t stack_get_size(const Stack* stack, StackNumber num); 90 | 91 | /** 92 | * @brief 获取共享栈的总容量。 93 | * 94 | * @param stack 指向要操作的栈。 95 | * @return 返回总容量。 96 | */ 97 | size_t stack_get_total_capacity(const Stack* stack); 98 | -------------------------------------------------------------------------------- /dsc-code/safe_arr/main.c: -------------------------------------------------------------------------------- 1 | 2 | // main.c 3 | 4 | #include "safe_array.h" 5 | #include 6 | 7 | void plain_c_array_example() { 8 | printf("--- 1. 普通C数组的危险性 ---\n"); 9 | int normal_array[5] = { 10, 20, 30, 40, 50 }; 10 | 11 | printf("尝试读取越界索引 10...\n"); 12 | // 这行代码在编译时可能不会报错,但运行时会读取到未知的内存垃圾值, 13 | // 或者直接导致程序崩溃。这就是“不安全”。 14 | int garbage = normal_array[10]; 15 | printf("读取到的垃圾值: %d (这是未定义行为!)\n", garbage); 16 | 17 | // update the value at index 2 18 | normal_array[2] = 99; // 正常写入 19 | 20 | 21 | 22 | // MyIntArr* myarr = normal_array_create(5); 23 | 24 | // bool res = update_arr(myarr, 2, 99); 25 | // 封装 26 | printf("尝试写入越界索引 -5...\n"); 27 | // 这比读取更危险,它会污染程序其他部分的内存,导致难以追踪的bug。 28 | // normal_array[-5] = 999; // (取消注释这行可能会导致程序崩溃) 29 | printf("写入操作可能已破坏了内存!\n\n"); 30 | 31 | 32 | printf("普通C数组的使用是危险的,因为它没有任何安全检查。\n"); 33 | 34 | 35 | } 36 | 37 | void safe_array_example() { 38 | printf("--- 2. 使用我们的“安全数组” ---\n"); 39 | 40 | // SafeArray my_array = { .data = NULL, .capacity = 0 }; 41 | 42 | // API 函数原型 43 | // C语言函数库 C原型 44 | 45 | 46 | // 创建一个容量为5的安全数组 47 | SafeArray* sa = sarray_create(5); 48 | if (!sa) { 49 | printf("创建安全数组失败。\n"); 50 | return; 51 | } 52 | printf("安全数组创建成功。\n"); 53 | sarray_print(sa); // 初始应为0(或随机值,取决于系统) 54 | 55 | 56 | 57 | 58 | // 安全地设置值 59 | printf("\n安全地设置值...\n"); 60 | for (size_t i = 0; i < sarray_get_capacity(sa); i++) { 61 | sarray_set(sa, i, (i + 1) * 11); 62 | } 63 | sarray_print(sa); 64 | 65 | // 尝试安全地写入越界索引 66 | printf("\n尝试安全地写入越界索引 10...\n"); 67 | if (sarray_set(sa, 10, 999)) { 68 | printf("写入成功 (不应发生!)\n"); 69 | } 70 | else { 71 | printf("写入失败,因为索引越界。程序安全!\n"); 72 | } 73 | sarray_print(sa); // 数组内容没有被破坏 74 | 75 | // 安全地获取值 76 | printf("\n安全地获取值...\n"); 77 | int value; 78 | if (sarray_get(sa, 3, &value)) { 79 | printf("索引 3 的值为: %d\n", value); 80 | } 81 | 82 | // 尝试安全地读取越界索引 83 | printf("\n尝试安全地读取越界索引 5...\n"); 84 | 85 | // SafeArray* sa = sarray_get(xxx); 86 | 87 | // sa->data = "123"; 88 | 89 | 90 | if (sarray_get(sa, 5, &value)) { 91 | printf("读取到的值: %d (不应发生!)\n", value); 92 | } 93 | else { 94 | printf("读取失败,因为索引越界。程序安全!\n"); 95 | } 96 | 97 | sarray_destroy(&sa); 98 | printf("\n安全数组已销毁。\n"); 99 | } 100 | 101 | int main() { 102 | plain_c_array_example(); 103 | safe_array_example(); 104 | 105 | return 0; 106 | } -------------------------------------------------------------------------------- /dsc-code/shared_stack/main.c: -------------------------------------------------------------------------------- 1 | #include "shared_stack.h" 2 | #include 3 | 4 | void print_stack_status(const Stack* stack) { 5 | if (!stack) { 6 | printf("Stack is NULL.\n"); 7 | return; 8 | } 9 | printf("------------------------------------------\n"); 10 | printf("Total Capacity: %zu\n", stack_get_total_capacity(stack)); 11 | printf("Stack 1 Size: %zu (Empty: %s)\n", 12 | stack_get_size(stack, STACK_ONE), 13 | stack_is_empty(stack, STACK_ONE) ? "Yes" : "No"); 14 | printf("Stack 2 Size: %zu (Empty: %s)\n", 15 | stack_get_size(stack, STACK_TWO), 16 | stack_is_empty(stack, STACK_TWO) ? "Yes" : "No"); 17 | printf("Is Full: %s\n", stack_is_full(stack) ? "Yes" : "No"); 18 | printf("------------------------------------------\n\n"); 19 | } 20 | 21 | int main() { 22 | printf("--- Testing Shared Integer Stack ---\n"); 23 | // 创建一个总容量为10的共享栈 24 | Stack* ss = stack_create(10, sizeof(int)); 25 | 26 | print_stack_status(ss); 27 | 28 | // 1. 向栈1压入数据 29 | printf("Pushing 10, 20, 30 to Stack 1...\n"); 30 | for (int i = 10; i <= 30; i += 10) { 31 | stack_push(ss, STACK_ONE, &i); 32 | } 33 | print_stack_status(ss); 34 | 35 | // 2. 向栈2压入数据 36 | printf("Pushing 100, 90 to Stack 2...\n"); 37 | for (int i = 100; i >= 90; i -= 10) { 38 | stack_push(ss, STACK_TWO, &i); 39 | } 40 | print_stack_status(ss); 41 | 42 | // 3. 查看栈顶元素 43 | int peek_val; 44 | if (stack_peek(ss, STACK_ONE, &peek_val)) { 45 | printf("Peek at Stack 1 top: %d\n", peek_val); 46 | } 47 | if (stack_peek(ss, STACK_TWO, &peek_val)) { 48 | printf("Peek at Stack 2 top: %d\n", peek_val); 49 | } 50 | printf("\n"); 51 | 52 | // 4. 从栈1弹出一个元素 53 | int pop_val; 54 | printf("Popping from Stack 1...\n"); 55 | if (stack_pop(ss, STACK_ONE, &pop_val)) { 56 | printf("Popped value: %d\n", pop_val); 57 | } 58 | print_stack_status(ss); 59 | 60 | // 5. 将栈填满 61 | printf("Filling the rest of the stack from Stack 1...\n"); 62 | int val = 40; 63 | while (!stack_is_full(ss)) { 64 | printf("Pushing %d to Stack 1.\n", val); 65 | stack_push(ss, STACK_ONE, &val); 66 | val += 10; 67 | } 68 | print_stack_status(ss); 69 | 70 | // 6. 尝试在已满的栈中继续压入 71 | printf("Attempting to push 999 to Stack 2 (should fail)...\n"); 72 | int extra = 999; 73 | if (!stack_push(ss, STACK_TWO, &extra)) { 74 | printf("Push failed as expected.\n\n"); 75 | } 76 | 77 | // 7. 销毁栈 78 | printf("Destroying stack...\n"); 79 | stack_destroy(&ss); 80 | printf("Stack pointer is now: %s\n", ss == NULL ? "NULL" : "Not NULL"); 81 | 82 | return 0; 83 | } -------------------------------------------------------------------------------- /dsc-code/05-circular_queue/main.c: -------------------------------------------------------------------------------- 1 | #include "generic_circular_queue.h" 2 | #include 3 | 4 | // 辅助函数,用于打印队列的详细内部状态,便于教学 5 | // ... (此处省略与上一版完全相同的 print_queue_status 函数) ... 6 | void print_queue_status(const Queue* q, const char* title) { 7 | printf("--- %s ---\n", title); 8 | if (!q) { 9 | printf("队列为 NULL\n\n"); 10 | return; 11 | } 12 | printf("容量: %zu, 大小: %zu\n", queue_get_capacity(q), queue_get_size(q)); 13 | printf("空? %s, 满? %s\n", queue_is_empty(q) ? "是" : "否", queue_is_full(q) ? "是" : "否"); 14 | struct CircularQueue_Internal { 15 | void* data; size_t capacity; size_t element_size; size_t size; 16 | int front; int rear; 17 | }; 18 | struct CircularQueue_Internal* q_internal = (struct CircularQueue_Internal*)q; 19 | printf("内部指针: front = %d, rear = %d\n\n", q_internal->front, q_internal->rear); 20 | } 21 | 22 | 23 | int main(void) { 24 | // 1. 创建一个容量为5的整数队列 25 | Queue* q = queue_create(5, sizeof(int)); 26 | print_queue_status(q, "1. 初始状态"); 27 | 28 | // 2. 入队3个元素 29 | for (int i = 10; i <= 30; i += 10) { 30 | printf("入队: %d\n", i); 31 | queue_enqueue(q, &i); 32 | } 33 | print_queue_status(q, "2. 入队 10, 20, 30 后"); 34 | 35 | // 3. 出队2个元素 36 | int dequeued_val; 37 | for (int i = 0; i < 2; i++) { 38 | if (queue_dequeue(q, &dequeued_val)) { 39 | printf("出队: %d\n", dequeued_val); 40 | } 41 | } 42 | print_queue_status(q, "3. 出队两个元素后"); 43 | 44 | // 4. 继续入队,展示“循环”特性 45 | struct CircularQueue_Internal { 46 | void* data; size_t capacity; size_t element_size; size_t size; 47 | int front; int rear; 48 | }; 49 | printf("现在 rear=%d, front=%d。空间已在数组前方空出。\n", ((struct CircularQueue_Internal*)q)->rear, ((struct CircularQueue_Internal*)q)->front); 50 | printf("继续入队 40, 50, 60 来填满队列...\n"); 51 | for (int i = 40; i <= 60; i += 10) { 52 | printf("入队: %d\n", i); 53 | queue_enqueue(q, &i); 54 | } 55 | print_queue_status(q, "4. 入队 40, 50, 60 后 (发生循环)"); 56 | 57 | // 5. 查看队头 58 | int peek_val; 59 | if (queue_peek(q, &peek_val)) { 60 | printf("查看队头元素 (应为30): %d\n\n", peek_val); 61 | } 62 | 63 | // 6. 尝试在满队列时入队 64 | int overflow_val = 999; 65 | printf("尝试入队 %d 到满队列中...\n", overflow_val); 66 | if (!queue_enqueue(q, &overflow_val)) { 67 | printf("入队失败,符合预期。\n\n"); 68 | } 69 | 70 | // 7. 全部出队,清空队列 71 | printf("将所有元素出队:\n"); 72 | while (!queue_is_empty(q)) { 73 | queue_dequeue(q, &dequeued_val); 74 | printf("出队: %d\n", dequeued_val); 75 | } 76 | print_queue_status(q, "7. 全部出队后"); 77 | 78 | // 8. 销毁队列 79 | queue_destroy(&q); 80 | printf("队列已销毁,指针为: %s\n", q == NULL ? "NULL" : "Not NULL"); 81 | 82 | return 0; 83 | } -------------------------------------------------------------------------------- /docs/README_ZH.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [English](./README.md) | **简体中文** 4 | 5 |
6 | 7 |
8 | 9 | # 📊 数据结构项目 10 | 11 |

12 | 直面数据结构,可视化学习之旅 13 |

14 | 15 |

16 | Language 17 | Platform 18 | License 19 | PRs Welcome 20 |

21 | 22 |

23 | 特性 • 24 | 快速开始 • 25 | 项目结构 • 26 | 贡献 • 27 | 许可证 28 |

29 | 30 |
31 | 32 | --- 33 | 34 | ## 🎯 简介 35 | 36 | 一个优雅的数据结构学习项目,配合 B站 UP主 [Frank](https://space.bilibili.com/19658621) 的《直面数据结构》课程,让复杂概念变得简单易懂。 37 | 38 | ## ✨ 特性 39 | 40 | 🎨 **可视化动画** - 直观展示数据结构操作过程 41 | 📚 **完整实现** - 涵盖链表、栈、队列、树、图等经典结构 42 | 💡 **详细注释** - 每行代码都有清晰解释 43 | 🔧 **工具集成** - 内置字符编码转换工具 44 | 🎓 **教学优先** - 专为学习者设计 45 | 46 | ## 🚀 快速开始 47 | 48 | ### 克隆项目 49 | 50 | ```bash 51 | git clone https://github.com/Frank-Code-Show/DataStructure.git 52 | cd DataStructure 53 | ``` 54 | 55 | ### 查看动画演示 56 | 57 | ```bash 58 | # 打开任意 HTML 文件 59 | open dsc/demo.html # macOS 60 | start dsc/demo.html # Windows 61 | ``` 62 | 63 | ### 编译 C 代码 64 | 65 | ```bash 66 | cd dsc-code 67 | gcc -std=c17 -o demo main.c 68 | ./demo 69 | ``` 70 | 71 | ## 📁 项目结构 72 | 73 | ``` 74 | DataStructure/ 75 | │ 76 | ├── 📂 dsc/ # 可视化动画演示 77 | │ └── *.html # HTML 动画文件 78 | │ 79 | ├── 📂 dsc-code/ # C语言实现 80 | │ ├── linkedlist.c # 链表 81 | │ ├── stack.c # 栈 82 | │ ├── queue.c # 队列 83 | │ └── ... # 更多数据结构 84 | │ 85 | └── 🔧 tools/ # 实用工具 86 | └── convert_encoding.py # 编码转换工具 87 | ``` 88 | 89 | ## 💻 代码示例 90 | 91 | ```c 92 | // 简洁优雅的链表实现 93 | typedef struct Node { 94 | int data; 95 | struct Node* next; 96 | } Node; 97 | 98 | Node* createNode(int value) { 99 | Node* newNode = (Node*)malloc(sizeof(Node)); 100 | newNode->data = value; 101 | newNode->next = NULL; 102 | return newNode; 103 | } 104 | ``` 105 | 106 | ## 🤝 贡献 107 | 108 | 我们欢迎所有形式的贡献!查看 [贡献指南](CONTRIBUTING.md) 了解如何: 109 | 110 | - 🐛 报告问题 111 | - 💡 提出新功能 112 | - 📝 改进文档 113 | - 🔧 提交代码 114 | 115 | ## 👨‍💻 作者 116 | 117 | **Frank** - *B站创作者* - [@Frank](https://space.bilibili.com/19658621) 118 | 119 | ## 📄 许可证 120 | 121 | 本项目采用 MIT 许可证 - 查看 [LICENSE](https://github.com/Frank-Code-Show/DataStructure/blob/main/LICENSE) 文件了解详情 122 | 123 | --- 124 | 125 |
126 | 127 | **[⬆ 回到顶部](#-数据结构项目)** 128 | 129 | 如果这个项目对你有帮助,请给一个 ⭐️! 130 | 131 |
132 | -------------------------------------------------------------------------------- /dsc-code/singly_linked_list/main.c: -------------------------------------------------------------------------------- 1 | #define _CRT_SECURE_NO_WARNINGS 2 | 3 | #include "Node.h" 4 | #include 5 | #include 6 | 7 | // 1. 定义“打印”回调 8 | void print_student(const void* data) { 9 | const Student* s = (const Student*)data; 10 | printf("{ID: %d, 姓名: %s, 年龄: %d}", s->id, s->name, s->age); 11 | } 12 | 13 | // 2. 定义一个简单的“比较”回调 (只按ID比较) 14 | int compare_by_id(const void* a, const void* b, void* context) { 15 | // 这个函数不需要额外的上下文,所以 context 参数被忽略 16 | // (void)context; // 明确表示我们有意不使用这个参数,避免编译器警告 17 | 18 | const Student* s_a = (const Student*)a; 19 | const Student* target_s = (const Student*)b; 20 | return s_a->id == target_s->id ? 0 : 1; // 返回0表示相等 21 | } 22 | 23 | // 3. 定义一个复杂的“比较”回调 (需要使用上下文) 24 | // 上下文结构体,用于打包额外参数 25 | typedef struct { 26 | int min_age_required; 27 | } SearchContext; 28 | 29 | int compare_by_id_and_min_age(const void* a, const void* b, void* context) { 30 | const Student* s_a = (const Student*)a; 31 | const Student* target_s = (const Student*)b; 32 | SearchContext* ctx = (SearchContext*)context; 33 | 34 | // 复杂的比较逻辑 35 | if (s_a->id == target_s->id && s_a->age >= ctx->min_age_required) { 36 | return 0; // 所有条件满足,视为相等 37 | } 38 | return 1; // 否则不相等 39 | } 40 | 41 | // 4. 定义“释放Data内部资源”的回调 (本例中为空,仅作演示) 42 | void free_student_data(void* data) { 43 | // 如果 Student 结构体内的 name 是 char* 类型且由 malloc 分配, 44 | // 此处就需要 free(((Student*)data)->name); 45 | } 46 | 47 | 48 | 49 | int main(void) { 50 | Node* head = NULL; 51 | 52 | // --- 初始化数据 --- 53 | printf("--- 1. 初始化链表 ---\n"); 54 | Student students[] = { 55 | {101, "Alice", 22}, 56 | {102, "Bob", 19}, 57 | {103, "Carol", 25}, 58 | {104, "David", 19} 59 | }; 60 | for (int i = 0; i < 4; ++i) { 61 | appendNode(&head, students[i]); 62 | } 63 | printList(head, print_student); 64 | 65 | // --- 简单查找与删除 --- 66 | printf("\n--- 2. 删除学号为103的学生(Carol) ---\n"); 67 | Student target_carol = { 103, "", 0 }; 68 | deleteNode(&head, &target_carol, compare_by_id, NULL); // 简单比较,不需要上下文 69 | printList(head, print_student); 70 | 71 | // --- 复杂查找与更新 --- 72 | printf("\n--- 3. 查找学号为104且年龄不小于20岁的学生 ---\n"); 73 | SearchContext ctx_fail = { 20 }; // 设置上下文:最小年龄20 74 | Student target_david = { 104, "", 0 }; 75 | Node* found = findNode(head, &target_david, compare_by_id_and_min_age, &ctx_fail); 76 | if (found) { 77 | printf("找到了!\n"); 78 | } 79 | else { 80 | 81 | // test 82 | printf("没找到 (因为David只有19岁)。\n"); 83 | } 84 | 85 | printf("\n--- 4. 查找学号为101且年龄不小于20岁的学生,并更新 ---\n"); 86 | SearchContext ctx_success = { 20 }; // 设置上下文:最小年龄20 87 | Student target_alice = { 101, "", 0 }; 88 | Student new_alice_data = { 101, "Alicia", 23 }; 89 | updateNode(head, &target_alice, new_alice_data, compare_by_id_and_min_age, &ctx_success); 90 | printList(head, print_student); 91 | 92 | //// --- 清理 --- 93 | //printf("\n--- 5. 释放所有内存 ---\n"); 94 | //freeList(&head, free_student_data); 95 | //printf("链表已清空。\n"); 96 | //printList(head, print_student); 97 | 98 | return 0; 99 | } -------------------------------------------------------------------------------- /dsc-code/doubly/main.c: -------------------------------------------------------------------------------- 1 | // main.c 2 | // #define _CRT_SECURE_NO_WARNINGS // 如果在 VS2022 中使用 strcpy 等函数需要这个 3 | #include 4 | #include 5 | #include "DoublyLinkedList.h" 6 | 7 | int main() { 8 | printf("=== Doubly Linked List Music Player Demo ===\n\n"); 9 | 10 | // 1. 创建一个空的播放列表 11 | printf(">> Initializing an empty playlist...\n"); 12 | DoublyLinkedList* playlist = createList(); 13 | if (!playlist) { 14 | return 1; // 内存分配失败,退出 15 | } 16 | printListForward(playlist); 17 | printf("\n"); 18 | 19 | // 2. 添加初始歌曲 (Append) 20 | printf(">> Adding initial songs to the playlist using append()...\n"); 21 | append(playlist, (Song) { "As It Was", "Harry Styles", 167 }); 22 | append(playlist, (Song) { "Levitating", "Dua Lipa", 203 }); 23 | append(playlist, (Song) { "good 4 u", "Olivia Rodrigo", 178 }); 24 | append(playlist, (Song) { "Blinding Lights", "The Weeknd", 200 }); 25 | printListForward(playlist); 26 | printf("\n"); 27 | 28 | // 3. 演示向前和向后遍历 29 | printf(">> Verifying backward traversal...\n"); 30 | printListBackward(playlist); 31 | printf("\n"); 32 | 33 | // 4. 演示插入操作 (Insert After) 34 | printf(">> Action: Inserting 'Industry Baby' after 'Levitating'.\n"); 35 | printf(" This is an O(1) operation once the target node is found.\n"); 36 | Node* levitatingNode = findByTitle(playlist, "Levitating"); 37 | if (levitatingNode) { 38 | insertAfter(playlist, levitatingNode, (Song) { "Industry Baby", "Lil Nas X", 212 }); 39 | printListForward(playlist); 40 | } 41 | else { 42 | printf(" Could not find 'Levitating'.\n"); 43 | } 44 | printf("\n"); 45 | 46 | // 5. 演示在头部插入 (Prepend) 47 | printf(">> Action: Inserting 'First Class' at the very beginning.\n"); 48 | printf(" This is an O(1) operation.\n"); 49 | prepend(playlist, (Song) { "First Class", "Jack Harlow", 174 }); 50 | printListForward(playlist); 51 | printf("\n"); 52 | 53 | // 6. 演示删除操作 (Delete) 54 | printf(">> Action: Deleting 'good 4 u' from the playlist.\n"); 55 | printf(" This is an O(1) operation once the target node is found.\n"); 56 | Node* good4uNode = findByTitle(playlist, "good 4 u"); 57 | if (good4uNode) { 58 | deleteNode(playlist, good4uNode); 59 | printListForward(playlist); 60 | } 61 | else { 62 | printf(" Could not find 'good 4 u'.\n"); 63 | } 64 | printf("\n"); 65 | 66 | // 7. 演示删除头节点 67 | printf(">> Action: Deleting the head node ('First Class').\n"); 68 | Node* headNode = playlist->head; 69 | if (headNode) { 70 | deleteNode(playlist, headNode); 71 | printListForward(playlist); 72 | } 73 | printf("\n"); 74 | 75 | // 8. 演示删除尾节点 76 | printf(">> Action: Deleting the tail node ('Blinding Lights').\n"); 77 | Node* tailNode = playlist->tail; 78 | if (tailNode) { 79 | deleteNode(playlist, tailNode); 80 | printListForward(playlist); 81 | } 82 | printf("\n"); 83 | 84 | // 9. 清理工作 85 | printf(">> Demo finished. Freeing all allocated memory...\n"); 86 | freeList(playlist); 87 | printf(" Memory has been successfully freed.\n"); 88 | 89 | return 0; 90 | } -------------------------------------------------------------------------------- /dsc-code/sequential_stack/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "sequential_stack.h" 3 | // 顺序栈 4 | 5 | // 定义一个自定义结构体,用于测试 6 | typedef struct { 7 | int id; 8 | double value; 9 | } DataPoint; 10 | 11 | // 用于打印 DataPoint 的辅助函数 12 | void print_datapoint(const DataPoint* dp) { 13 | if (dp) { 14 | printf("DataPoint(id: %d, value: %.2f)", dp->id, dp->value); 15 | } 16 | } 17 | 18 | // --- 测试函数 --- 19 | 20 | void test_int_stack() { 21 | printf("--- Testing Integer Stack ---\n"); 22 | Stack* int_stack = stack_create(5, sizeof(int)); 23 | 24 | if (!int_stack) { 25 | printf("Failed to create integer stack.\n"); 26 | return; 27 | } 28 | 29 | printf("Stack created. Capacity: %zu, Is empty? %s\n", 30 | stack_get_capacity(int_stack), stack_is_empty(int_stack) ? "Yes" : "No"); 31 | 32 | for (int i = 10; i <= 50; i += 10) { 33 | printf("Pushing %d... ", i); 34 | if (stack_push(int_stack, &i)) { 35 | printf("Success. Current size: %zu\n", stack_get_size(int_stack)); 36 | } 37 | } 38 | printf("Is stack full? %s\n", stack_is_full(int_stack) ? "Yes" : "No"); 39 | 40 | int top_val; 41 | if (stack_peek(int_stack, &top_val)) { 42 | printf("Peek at top: %d\n", top_val); 43 | } 44 | 45 | printf("Popping all elements:\n"); 46 | while (!stack_is_empty(int_stack)) { 47 | int popped_val; 48 | if (stack_pop(int_stack, &popped_val)) { 49 | printf("Popped: %d\n", popped_val); 50 | } 51 | } 52 | 53 | printf("Is stack empty now? %s\n", stack_is_empty(int_stack) ? "Yes" : "No"); 54 | 55 | // 销毁栈 56 | stack_destroy(&int_stack); 57 | printf("Stack destroyed. Pointer is now %s\n", int_stack == NULL ? "NULL" : "Not NULL"); 58 | printf("\n"); 59 | } 60 | 61 | void test_struct_stack() { 62 | printf("--- Testing Struct (DataPoint) Stack ---\n"); 63 | Stack* dp_stack = stack_create(3, sizeof(DataPoint)); 64 | 65 | DataPoint p1 = { 101, 99.5 }; 66 | DataPoint p2 = { 102, 120.75 }; 67 | DataPoint p3 = { 103, 85.0 }; 68 | 69 | printf("Pushing "); print_datapoint(&p1); printf("...\n"); 70 | stack_push(dp_stack, &p1); 71 | printf("Pushing "); print_datapoint(&p2); printf("...\n"); 72 | stack_push(dp_stack, &p2); 73 | printf("Pushing "); print_datapoint(&p3); printf("...\n"); 74 | stack_push(dp_stack, &p3); 75 | 76 | printf("Stack size: %zu, Is full? %s\n", stack_get_size(dp_stack), stack_is_full(dp_stack) ? "Yes" : "No"); 77 | 78 | DataPoint peeked_dp; 79 | if (stack_peek(dp_stack, &peeked_dp)) { 80 | printf("Peek at top: "); 81 | print_datapoint(&peeked_dp); 82 | printf("\n"); 83 | } 84 | 85 | printf("Popping all elements:\n"); 86 | while (!stack_is_empty(dp_stack)) { 87 | DataPoint popped_dp; 88 | if (stack_pop(dp_stack, &popped_dp)) { 89 | printf("Popped: "); 90 | print_datapoint(&popped_dp); 91 | printf("\n"); 92 | } 93 | } 94 | 95 | stack_destroy(&dp_stack); 96 | printf("Struct stack destroyed.\n\n"); 97 | } 98 | 99 | int main() { 100 | test_int_stack(); 101 | test_struct_stack(); 102 | 103 | return 0; 104 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | **English** | [简体中文](./README_ZH.md) 4 | 5 |
6 | 7 |
8 | 9 | # 📊 Data Structure Project 10 | 11 |

12 | Face Data Structures, A Visual Learning Journey 13 |

14 | 15 |

16 | Language 17 | Platform 18 | License 19 | PRs Welcome 20 |

21 | 22 |

23 | Features • 24 | Quick Start • 25 | Structure • 26 | Contributing • 27 | License 28 |

29 | 30 |
31 | 32 | --- 33 | 34 | ## 🎯 Introduction 35 | 36 | An elegant data structure learning project, created alongside [Frank](https://space.bilibili.com/19658621)'s "Facing Data Structures" course, making complex concepts simple and understandable. 37 | 38 | ## ✨ Features 39 | 40 | 🎨 **Visual Animations** - Intuitive demonstrations of data structure operations 41 | 📚 **Complete Implementations** - Covers linked lists, stacks, queues, trees, graphs, and more 42 | 💡 **Detailed Comments** - Clear explanations for every line of code 43 | 🔧 **Integrated Tools** - Built-in character encoding conversion utilities 44 | 🎓 **Education First** - Designed specifically for learners 45 | 46 | ## 🚀 Quick Start 47 | 48 | ### Clone the Repository 49 | 50 | ```bash 51 | git clone https://github.com/Frank-Code-Show/DataStructure.git 52 | cd DataStructure 53 | ``` 54 | 55 | ### View Animations 56 | 57 | ```bash 58 | # Open any HTML file 59 | open dsc/demo.html # macOS 60 | start dsc/demo.html # Windows 61 | ``` 62 | 63 | ### Compile C Code 64 | 65 | ```bash 66 | cd dsc-code 67 | gcc -std=c17 -o demo main.c 68 | ./demo 69 | ``` 70 | 71 | ## 📁 Project Structure 72 | 73 | ``` 74 | DataStructure/ 75 | │ 76 | ├── 📂 dsc/ # Visual animations 77 | │ └── *.html # HTML animation files 78 | │ 79 | ├── 📂 dsc-code/ # C implementations 80 | │ ├── linkedlist.c # Linked list 81 | │ ├── stack.c # Stack 82 | │ ├── queue.c # Queue 83 | │ └── ... # More structures 84 | │ 85 | └── 🔧 tools/ # Utilities 86 | └── convert_encoding.py # Encoding converter 87 | ``` 88 | 89 | ## 💻 Code Example 90 | 91 | ```c 92 | // Clean and elegant linked list implementation 93 | typedef struct Node { 94 | int data; 95 | struct Node* next; 96 | } Node; 97 | 98 | Node* createNode(int value) { 99 | Node* newNode = (Node*)malloc(sizeof(Node)); 100 | newNode->data = value; 101 | newNode->next = NULL; 102 | return newNode; 103 | } 104 | ``` 105 | 106 | ## 🤝 Contributing 107 | 108 | We welcome all forms of contributions! Check out the [Contributing Guide](CONTRIBUTING_EN.md) to learn how to: 109 | 110 | - 🐛 Report issues 111 | - 💡 Suggest new features 112 | - 📝 Improve documentation 113 | - 🔧 Submit code 114 | 115 | ## 👨‍💻 Author 116 | 117 | **Frank** - *Bilibili Creator* - [@Frank](https://space.bilibili.com/19658621) 118 | 119 | ## 📄 License 120 | 121 | This project is licensed under the MIT License - see the [LICENSE](https://github.com/Frank-Code-Show/DataStructure/blob/main/LICENSE) file for details 122 | 123 | --- 124 | 125 |
126 | 127 | **[⬆ Back to Top](#-data-structure-project)** 128 | 129 | If this project helps you, please give it a ⭐️! 130 | 131 |
132 | -------------------------------------------------------------------------------- /dsc-code/linked_stack/linked_stack.c: -------------------------------------------------------------------------------- 1 | #include "linked_stack.h" 2 | #include 3 | #include 4 | 5 | // --- Private Structure Definitions --- 6 | 7 | // 内部节点结构,对用户不可见 8 | typedef struct Node { 9 | void* data; // 指向为该节点元素分配的内存 10 | struct Node* next; // 指向栈中的下一个节点 11 | } Node; 12 | 13 | // 链式栈的实际管理结构 14 | struct LinkedStack { 15 | Node* top; // 指向栈顶的节点 16 | size_t element_size; // 每个元素的大小 17 | size_t size; // 栈中当前的元素数量 18 | }; 19 | 20 | // --- API Function Implementations --- 21 | 22 | Stack* stack_create(size_t element_size) { 23 | if (element_size == 0) { 24 | return NULL; 25 | } 26 | 27 | Stack* stack = (Stack*)malloc(sizeof(Stack)); 28 | if (stack == NULL) { 29 | return NULL; 30 | } 31 | 32 | stack->top = NULL; // 初始化时,栈顶没有节点 33 | stack->element_size = element_size; 34 | stack->size = 0; 35 | 36 | return stack; 37 | } 38 | 39 | void stack_destroy(Stack** p_stack) { 40 | if (p_stack == NULL || *p_stack == NULL) { 41 | return; 42 | } 43 | 44 | Stack* stack = *p_stack; 45 | Node* current = stack->top; 46 | 47 | // 遍历整个链表,释放每一个节点和它所包含的数据 48 | while (current != NULL) { 49 | Node* temp = current; 50 | current = current->next; // 移动到下一个节点 51 | free(temp->data); // 释放节点的数据 52 | free(temp); // 释放节点本身 53 | } 54 | 55 | free(stack); // 最后释放栈的管理结构 56 | *p_stack = NULL; // 设置外部指针为NULL 57 | } 58 | 59 | bool stack_push(Stack* stack, const void* element_data) { 60 | if (stack == NULL || element_data == NULL) { 61 | return false; 62 | } 63 | 64 | // 1. 为新节点分配内存 65 | Node* new_node = (Node*)malloc(sizeof(Node)); 66 | if (new_node == NULL) { 67 | return false; // 内存分配失败 68 | } 69 | 70 | // 2. 为新节点的数据区分配内存 71 | new_node->data = malloc(stack->element_size); 72 | if (new_node->data == NULL) { 73 | free(new_node); // 清理已分配的节点,防止泄漏 74 | return false; 75 | } 76 | 77 | // 3. 将用户数据拷贝到新节点的数据区 78 | memcpy(new_node->data, element_data, stack->element_size); 79 | 80 | // 4. 将新节点链接到栈顶 81 | new_node->next = stack->top; // 新节点的下一个是当前的栈顶 (旧栈顶) 82 | stack->top = new_node; // 更新栈顶为新节点 83 | 84 | stack->size++; 85 | return true; 86 | } 87 | 88 | bool stack_pop(Stack* stack, void* output_buffer) { 89 | if (stack_is_empty(stack) || output_buffer == NULL) { 90 | return false; 91 | } 92 | 93 | // 1. 暂存栈顶节点 94 | Node* node_to_pop = stack->top; 95 | 96 | // 2. 将数据从节点拷贝到用户的缓冲区 97 | memcpy(output_buffer, node_to_pop->data, stack->element_size); 98 | 99 | // 3. 更新栈顶指针,使其指向下一个节点 100 | stack->top = node_to_pop->next; 101 | 102 | // 4. 释放被弹出节点的所有内存 103 | free(node_to_pop->data); 104 | free(node_to_pop); 105 | 106 | stack->size--; 107 | return true; 108 | } 109 | 110 | bool stack_peek(const Stack* stack, void* output_buffer) { 111 | if (stack_is_empty(stack) || output_buffer == NULL) { 112 | return false; 113 | } 114 | 115 | // 只需拷贝数据,不修改任何指针或释放内存 116 | memcpy(output_buffer, stack->top->data, stack->element_size); 117 | return true; 118 | } 119 | 120 | bool stack_is_empty(const Stack* stack) { 121 | if (stack == NULL) { 122 | return true; 123 | } 124 | return stack->top == NULL; // 或 stack->size == 0 125 | } 126 | 127 | size_t stack_get_size(const Stack* stack) { 128 | if (stack == NULL) { 129 | return 0; 130 | } 131 | return stack->size; 132 | } -------------------------------------------------------------------------------- /dsc-code/doubly_circular_link/main.c: -------------------------------------------------------------------------------- 1 | #include "CircularDoublyLinkedList.h" 2 | #include 3 | #include 4 | #include 5 | 6 | // --- 用户自定义数据结构 --- 7 | typedef struct { 8 | char* title; 9 | char* artist; 10 | int duration_s; 11 | } PlaylistSong; 12 | 13 | // --- Helper function for cross-platform string duplication --- 14 | char* portable_strdup(const char* s) { 15 | if (!s) return NULL; 16 | size_t len = strlen(s) + 1; 17 | char* new_str = malloc(len); 18 | if (new_str) memcpy(new_str, s, len); 19 | return new_str; 20 | } 21 | 22 | // --- 用户自定义的回调函数 --- 23 | void free_song(void* data) { 24 | PlaylistSong* song = (PlaylistSong*)data; 25 | if (song) { 26 | free(song->title); 27 | free(song->artist); 28 | free(song); 29 | } 30 | } 31 | 32 | int compare_song_by_title(const void* data1, const void* data2) { 33 | const PlaylistSong* song1 = (const PlaylistSong*)data1; 34 | const PlaylistSong* song2 = (const PlaylistSong*)data2; 35 | return strcmp(song1->title, song2->title); 36 | } 37 | 38 | void print_song_action(void* data, void* context) { 39 | const PlaylistSong* song = (const PlaylistSong*)data; 40 | int* counter = (int*)context; 41 | printf(" %d. '%s' by %s (%d sec)\n", (*counter)++, song->title, song->artist, song->duration_s); 42 | } 43 | 44 | // --- Helper to create a song --- 45 | PlaylistSong* create_song(const char* title, const char* artist, int duration) { 46 | PlaylistSong* song = malloc(sizeof(PlaylistSong)); 47 | song->title = portable_strdup(title); 48 | song->artist = portable_strdup(artist); 49 | song->duration_s = duration; 50 | return song; 51 | } 52 | 53 | int main() { 54 | printf("--- Circular Doubly Linked List Demo: Music Playlist ---\n\n"); 55 | 56 | // 1. 创建播放列表 57 | printf("Step 1: Creating a playlist...\n"); 58 | CircularDoublyLinkedList* playlist = List_Create(10, free_song); 59 | 60 | // 2. 添加歌曲 61 | printf("\nStep 2: Adding songs to the playlist...\n"); 62 | List_Append(playlist, create_song("Bohemian Rhapsody", "Queen", 355)); 63 | List_Append(playlist, create_song("Stairway to Heaven", "Led Zeppelin", 482)); 64 | List_Append(playlist, create_song("Hotel California", "Eagles", 391)); 65 | List_Prepend(playlist, create_song("Imagine", "John Lennon", 183)); 66 | 67 | // 3. 打印播放列表 68 | printf("\nStep 3: Displaying the full playlist (Size: %zu)\n", List_GetSize(playlist)); 69 | int counter = 1; 70 | List_ForEach(playlist, print_song_action, &counter); 71 | 72 | // 4. 模拟播放和旋转(切换到下一首) 73 | printf("\nStep 4: Simulating playback and rotation...\n"); 74 | 75 | 76 | CDListNode* current_song_node = List_GetHeadNode(playlist); 77 | printf("Now Playing: '%s'\n", ((PlaylistSong*)CDListNode_GetData(current_song_node))->title); 78 | 79 | List_RotateForward(playlist); // 切换到下一首 80 | current_song_node = List_GetHeadNode(playlist); 81 | printf("Next Song: '%s'\n", ((PlaylistSong*)CDListNode_GetData(current_song_node))->title); 82 | 83 | List_RotateForward(playlist); // 再切换 84 | current_song_node = List_GetHeadNode(playlist); 85 | printf("Next Song: '%s'\n", ((PlaylistSong*)CDListNode_GetData(current_song_node))->title); 86 | 87 | // 5. 打印旋转后的列表顺序 88 | printf("\nStep 5: Displaying playlist after rotation...\n"); 89 | counter = 1; 90 | List_ForEach(playlist, print_song_action, &counter); 91 | 92 | // 6. 删除一首歌 93 | printf("\nStep 6: Removing 'Stairway to Heaven' from the playlist...\n"); 94 | PlaylistSong search_key = { "Stairway to Heaven", NULL, 0 }; 95 | CDListNode* node_to_delete = List_Find(playlist, &search_key, compare_song_by_title); 96 | if (node_to_delete) { 97 | List_DeleteNode(playlist, node_to_delete); 98 | printf("Song removed. New playlist size: %zu\n", List_GetSize(playlist)); 99 | } 100 | 101 | printf("\n--- Playlist after deletion ---\n"); 102 | counter = 1; 103 | List_ForEach(playlist, print_song_action, &counter); 104 | 105 | // 7. 销毁播放列表 106 | printf("\nStep 7: Shutting down and destroying the playlist...\n"); 107 | List_Destroy(&playlist); 108 | printf("Playlist destroyed. Pointer is now NULL: %s\n", playlist == NULL ? "Yes" : "No"); 109 | 110 | printf("\n--- Demo Finished ---\n"); 111 | 112 | return 0; 113 | } -------------------------------------------------------------------------------- /dsc-code/singly_linked_list/Node.c: -------------------------------------------------------------------------------- 1 | #define _CRT_SECURE_NO_WARNINGS 2 | #include "Node.h" 3 | #include 4 | #include 5 | 6 | 7 | Node* createNode(Data data) { 8 | Node* newNode = (Node*)malloc(sizeof(Node)); 9 | if (newNode == NULL) { 10 | perror("创建节点失败:内存分配错误"); 11 | return NULL; 12 | } 13 | 14 | newNode->data = data; 15 | newNode->next = NULL; 16 | return newNode; 17 | } 18 | 19 | 20 | void appendNode(Node** headRef, Data data) { 21 | Node* newNode = createNode(data); 22 | 23 | if (newNode == NULL) return; 24 | 25 | if (*headRef == NULL) { 26 | *headRef = newNode; 27 | return; 28 | } 29 | 30 | // head -> [10 | next] ---> [20 | next] ---> newNode --> [30 | next] ---> NULL 31 | // head 32 | // last newNode 33 | 34 | // newNode --> [30 | next] 35 | 36 | Node* last = *headRef; 37 | // Node* last = head; 38 | while (last->next != NULL) { 39 | last = last->next; 40 | } 41 | 42 | last->next = newNode; 43 | 44 | } 45 | 46 | void prependNode(Node** headRef, Data data) { 47 | Node* newNode = createNode(data); 48 | 49 | if (newNode == NULL) return; 50 | newNode->next = *headRef; 51 | *headRef = newNode; 52 | 53 | // head -> [10 | next] ---> [20 | next] ---> newNode --> [30 | next] ---> NULL 54 | // head 55 | // last newNode 56 | 57 | // newNode --> [5 | next] 58 | } 59 | 60 | void printList(Node* head, void (*print_func)(const void* data)) { 61 | if (print_func == NULL) { 62 | printf("错误: 未提供打印函数.\n"); 63 | return; 64 | } 65 | 66 | printf("链表内容: "); 67 | Node* current = head; 68 | while (current != NULL) { 69 | print_func(&(current->data)); 70 | printf(" -> "); 71 | current = current->next; 72 | } 73 | printf("NULL\n"); 74 | } 75 | 76 | Node* findNode( 77 | Node* head, 78 | const void* target_data, 79 | int (*compare_func) (const void* a, const void* b, void* context), 80 | void* context 81 | ) { 82 | if (compare_func == NULL) { 83 | printf("错误: 未提供比较函数.\n"); 84 | return NULL; 85 | } 86 | 87 | Node* current = head; 88 | while (current != NULL) { 89 | // 调用回调 90 | if (compare_func(&(current->data), target_data, context) == 0) { 91 | return current; 92 | } 93 | current = current->next; 94 | } 95 | 96 | return NULL; 97 | } 98 | 99 | void deleteNode( 100 | Node** headRef, 101 | const void* target_data, 102 | int (*compare_func) (const void* a, const void* b, void* context), 103 | void* context 104 | ) { 105 | if (headRef == NULL || *headRef == NULL || compare_func == NULL) { 106 | printf("错误: 无效的参数.\n"); 107 | return; 108 | } 109 | 110 | Node* temp = *headRef; 111 | Node* prev = NULL; 112 | 113 | // 检查头节点是否是目标 114 | if (compare_func(&(temp->data), target_data, context) == 0) { 115 | *headRef = temp->next; 116 | free(temp); 117 | printf("信息:头节点已经删除!\n"); 118 | return; 119 | 120 | } 121 | 122 | while (temp != NULL && compare_func(&temp->data, target_data, context) != 0) { 123 | prev = temp; 124 | temp = temp->next; 125 | } 126 | 127 | if (temp == NULL) { 128 | printf("警告:未找到目标节点,无法删除!\n"); 129 | return; 130 | } 131 | 132 | // 检查 prev 是否为 NULL 133 | if (prev != NULL) { 134 | prev->next = temp->next; 135 | } 136 | 137 | free(temp); 138 | printf("信息:节点已经删除!\n"); 139 | 140 | } 141 | 142 | void updateNode(Node** headRef, const void* target_data, Data newData, int(*compare_func)(const void* a, const void* b, void* context), void* context) 143 | { 144 | Node* nodeToUpdate = findNode(headRef, target_data, compare_func, context); 145 | if (nodeToUpdate != NULL) { 146 | nodeToUpdate->data = newData; 147 | printf("信息:节点已经成功更新!\n"); 148 | } 149 | else { 150 | printf("警告: 未找到目标节点,无法更新!\n"); 151 | } 152 | } 153 | 154 | //void updateNode( 155 | // Node* headRef, 156 | // const void* target_data, 157 | // Data newData, 158 | // int (*compare_func) (const void* a, const void* b, void* context), 159 | // void* context 160 | //) { 161 | // 162 | //} 163 | 164 | void freeList(Node** headRef, void (*free_data_func)(void* data)) { 165 | if (headRef == NULL) return; 166 | 167 | Node* current = *headRef; 168 | Node* nextNode; 169 | 170 | while (current != NULL) { 171 | nextNode = current->next; 172 | if (free_data_func != NULL) { 173 | free_data_func(¤t->data); 174 | } 175 | free(current); 176 | current = nextNode; 177 | } 178 | 179 | *headRef = NULL; 180 | } 181 | -------------------------------------------------------------------------------- /dsc-code/bplus_tree/bplus_tree.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef _MSC_VER 10 | #pragma warning(push) 11 | #pragma warning(disable: 4996) // VS2022 compatibility 12 | #endif 13 | 14 | /* ========================================================================= */ 15 | /* CONFIGURATION */ 16 | /* ========================================================================= */ 17 | 18 | #define BPLUS_MIN_DEGREE 3 // 规定B+树的最小度数t不能小于3,这里是B+。 19 | #define BPLUS_MAX_DEGREE 128 20 | #define BPLUS_DEFAULT_DEGREE 4 21 | 22 | /* ========================================================================= */ 23 | /* TYPE DEFINITIONS */ 24 | /* ========================================================================= */ 25 | 26 | /* Error codes returned by API functions */ 27 | typedef enum { 28 | BPLUS_SUCCESS = 0, 29 | BPLUS_ERROR_MEMORY = -1, 30 | BPLUS_ERROR_INVALID_PARAM = -2, 31 | BPLUS_ERROR_KEY_EXISTS = -3, 32 | BPLUS_ERROR_KEY_NOT_FOUND = -4, 33 | BPLUS_ERROR_TREE_EMPTY = -5 34 | } bplus_error_t; 35 | 36 | /* Forward declarations for opaque structures */ 37 | typedef struct bplus_node bplus_node_t; 38 | typedef struct bplus_tree bplus_tree_t; 39 | typedef struct bplus_context bplus_context_t; 40 | typedef struct bplus_iterator bplus_iterator_t; 41 | 42 | /* --- Callback Function Pointer Types for Generic Programming --- */ 43 | 44 | /* Function to compare two keys. 45 | * Should return: 46 | * < 0 if a < b 47 | * = 0 if a == b 48 | * > 0 if a > b 49 | */ 50 | typedef int (*bplus_compare_fn)(const void* a, const void* b, void* user_data); 51 | 52 | /* Memory allocation functions */ 53 | typedef void* (*bplus_alloc_fn)(size_t size, void* user_data); 54 | typedef void (*bplus_free_fn)(void* ptr, void* user_data); 55 | 56 | /* Key cloning and freeing functions */ 57 | typedef void* (*bplus_key_clone_fn)(const void* key, void* user_data); 58 | typedef void (*bplus_key_free_fn)(void* key, void* user_data); 59 | 60 | /* Value cloning and freeing functions */ 61 | typedef void* (*bplus_value_clone_fn)(const void* value, void* user_data); 62 | typedef void (*bplus_value_free_fn)(void* value, void* user_data); 63 | 64 | /* ========================================================================= */ 65 | /* PUBLIC API */ 66 | /* ========================================================================= */ 67 | 68 | /* --- Context Management --- 69 | * The context holds all configuration and callback functions for a tree. 70 | */ 71 | bplus_context_t* bplus_context_create( 72 | int degree, 73 | bplus_compare_fn compare, 74 | void* user_data 75 | ); 76 | 77 | void bplus_context_set_memory_functions( 78 | bplus_context_t* ctx, 79 | bplus_alloc_fn alloc_fn, 80 | bplus_free_fn free_fn 81 | ); 82 | 83 | void bplus_context_set_key_functions( 84 | bplus_context_t* ctx, 85 | bplus_key_clone_fn clone_fn, 86 | bplus_key_free_fn free_fn 87 | ); 88 | 89 | void bplus_context_set_value_functions( 90 | bplus_context_t* ctx, 91 | bplus_value_clone_fn clone_fn, 92 | bplus_value_free_fn free_fn 93 | ); 94 | 95 | void bplus_context_destroy(bplus_context_t* ctx); 96 | 97 | 98 | /* --- Tree Operations --- */ 99 | bplus_tree_t* bplus_tree_create(bplus_context_t* ctx); 100 | void bplus_tree_destroy(bplus_tree_t* tree); 101 | bplus_error_t bplus_tree_insert(bplus_tree_t* tree, void* key, void* value); 102 | bplus_error_t bplus_tree_delete(bplus_tree_t* tree, void* key); 103 | void* bplus_tree_search(bplus_tree_t* tree, void* key); 104 | bool bplus_tree_contains(bplus_tree_t* tree, void* key); 105 | size_t bplus_tree_size(const bplus_tree_t* tree); 106 | bool bplus_tree_empty(const bplus_tree_t* tree); 107 | 108 | 109 | /* --- Iterator Operations --- 110 | * Allows for ordered traversal of the tree. 111 | */ 112 | bplus_iterator_t* bplus_iterator_create(bplus_tree_t* tree); 113 | bool bplus_iterator_has_next(bplus_iterator_t* iter); 114 | void bplus_iterator_next(bplus_iterator_t* iter, void** key, void** value); 115 | void bplus_iterator_destroy(bplus_iterator_t* iter); 116 | 117 | 118 | /* --- Utility and Debugging Functions --- */ 119 | void bplus_tree_print(bplus_tree_t* tree, void (*print_key)(void*)); 120 | bool bplus_tree_validate(bplus_tree_t* tree); 121 | 122 | 123 | #ifdef _MSC_VER 124 | #pragma warning(pop) 125 | #endif 126 | -------------------------------------------------------------------------------- /dsc-code/bplus_tree/main.c: -------------------------------------------------------------------------------- 1 | #include "bplus_tree.h" 2 | #include 3 | #include 4 | #include 5 | 6 | /* ========================================================================= */ 7 | /* EXAMPLE HELPER FUNCTIONS */ 8 | /* ========================================================================= */ 9 | 10 | /* Integer comparison function */ 11 | static int int_compare(const void* a, const void* b, void* user_data) { 12 | (void)user_data; 13 | int va = *(int*)a; 14 | int vb = *(int*)b; 15 | return (va > vb) - (va < vb); 16 | } 17 | 18 | /* Integer key/value clone function (for deep copies) */ 19 | static void* int_clone(const void* key, void* user_data) { 20 | (void)user_data; 21 | int* new_key = malloc(sizeof(int)); 22 | if (new_key) { 23 | *new_key = *(int*)key; 24 | } 25 | return new_key; 26 | } 27 | 28 | /* Integer key/value free function (for deep copies) */ 29 | static void int_free(void* key, void* user_data) { 30 | (void)user_data; 31 | free(key); 32 | } 33 | 34 | /* Print integer key */ 35 | static void print_int(void* key) { 36 | if (key) { 37 | printf("%d", *(int*)key); 38 | } 39 | else { 40 | printf("NULL"); 41 | } 42 | } 43 | 44 | /* ========================================================================= */ 45 | /* TEST SUITE */ 46 | /* ========================================================================= */ 47 | 48 | void run_bplus_tree_tests(void) { 49 | printf("=== B+ Tree Test Suite ===\n\n"); 50 | 51 | /* Create context with degree 3 and deep-copy functions */ 52 | bplus_context_t* ctx = bplus_context_create(3, int_compare, NULL); 53 | bplus_context_set_key_functions(ctx, int_clone, int_free); 54 | bplus_context_set_value_functions(ctx, int_clone, int_free); 55 | assert(ctx != NULL); 56 | 57 | /* Create tree */ 58 | bplus_tree_t* tree = bplus_tree_create(ctx); 59 | assert(tree != NULL); 60 | 61 | printf("--- Test 1: Insertions ---\n"); 62 | int keys[] = { 10, 20, 5, 6, 12, 30, 7, 17, 3, 8, 15, 25, 35, 40, 45 }; 63 | int n = sizeof(keys) / sizeof(keys[0]); 64 | 65 | for (int i = 0; i < n; i++) { 66 | bplus_error_t err = bplus_tree_insert(tree, &keys[i], &keys[i]); 67 | printf("Inserted key=%d ... ", keys[i]); 68 | assert(err == BPLUS_SUCCESS); 69 | printf("OK\n"); 70 | } 71 | 72 | printf("\nTree size: %zu (expected: %d)\n", bplus_tree_size(tree), n); 73 | assert(bplus_tree_size(tree) == (size_t)n); 74 | 75 | printf("\nTree structure after insertions:\n"); 76 | bplus_tree_print(tree, print_int); 77 | 78 | printf("\n--- Test 2: Search for existing keys ---\n"); 79 | for (int i = 0; i < n; i++) { 80 | int* found = bplus_tree_search(tree, &keys[i]); 81 | printf("Searching for key=%d ... ", keys[i]); 82 | assert(found != NULL); 83 | assert(*found == keys[i]); 84 | printf("Found value=%d. OK\n", *found); 85 | } 86 | 87 | printf("\n--- Test 3: Search for non-existing keys ---\n"); 88 | int non_existing[] = { 1, 100, 50 }; 89 | for (int i = 0; i < 3; i++) { 90 | void* found = bplus_tree_search(tree, &non_existing[i]); 91 | printf("Searching for key=%d ... ", non_existing[i]); 92 | assert(found == NULL); 93 | printf("Not found (as expected). OK\n"); 94 | } 95 | 96 | printf("\n--- Test 4: Iterator Traversal ---\n"); 97 | bplus_iterator_t* iter = bplus_iterator_create(tree); 98 | int count = 0; 99 | int prev_key = -1; 100 | while (bplus_iterator_has_next(iter)) { 101 | int* key; 102 | int* value; 103 | bplus_iterator_next(iter, (void**)&key, (void**)&value); 104 | printf("Iterator: key=%d, value=%d\n", *key, *value); 105 | if (count > 0) { 106 | assert(*key > prev_key); // Check order 107 | } 108 | prev_key = *key; 109 | count++; 110 | } 111 | printf("Iterator traversed %d elements. OK\n", count); 112 | assert(count == n); 113 | bplus_iterator_destroy(iter); 114 | 115 | printf("\n--- Test 5: Duplicate key insertion ---\n"); 116 | int dup_key = 10; 117 | bplus_error_t err = bplus_tree_insert(tree, &dup_key, &dup_key); 118 | printf("Attempting to insert duplicate key %d ... ", dup_key); 119 | assert(err == BPLUS_ERROR_KEY_EXISTS); 120 | printf("Rejected (as expected). OK\n"); 121 | 122 | // Deletion tests would go here if implemented 123 | // ... 124 | 125 | printf("\n--- Test 6: Clean up ---\n"); 126 | bplus_tree_destroy(tree); 127 | bplus_context_destroy(ctx); 128 | printf("Tree and context destroyed successfully.\n"); 129 | 130 | printf("\n=== All tests passed! ===\n"); 131 | } 132 | 133 | int main(void) { 134 | run_bplus_tree_tests(); 135 | return 0; 136 | } -------------------------------------------------------------------------------- /dsc-code/safe_arr/safe_array.c: -------------------------------------------------------------------------------- 1 | // safe_array.c 2 | 3 | #include "safe_array.h" 4 | #include 5 | #include 6 | 7 | // --- Private Structure Definition --- 8 | // “安全数组”的内部秘密:它其实是一个结构体, 9 | // 封装了真正的原始数组指针和它的容量。 10 | struct SafeArray { 11 | int* data; // 指向动态分配的原始整数数组 12 | size_t capacity; // 数组的容量 13 | }; 14 | 15 | // ====================================================================== 16 | // ==================== 私有辅助函数 - “部门经理” ==================== 17 | // ====================================================================== 18 | // 这些函数被声明为 static,意味着它们只能在本文件(.c)内部被调用。 19 | // 它们是“内部员工”,用户(其他.c文件)永远无法直接接触到它们。 20 | // 它们假设所有检查工作已经被上级做完了,只专注于自己的核心任务。 21 | 22 | /** 23 | * @brief [私有] 真正执行设置值的操作。 24 | * @note 这个函数是“不安全”的,因为它假设索引是有效的。 25 | * 它的存在,是为了把“核心操作”和“安全检查”分离开。 26 | */ 27 | static void _set_value(SafeArray* sarray, size_t index, int value) { 28 | // 部门经理的核心工作:就这一行代码! 29 | // 他相信他的CEO上级已经检查过index的合法性了。 30 | sarray->data[index] = value; 31 | } 32 | 33 | /** 34 | * @brief [私有] 真正执行获取值的操作。 35 | */ 36 | static int _get_value(const SafeArray* sarray, size_t index) { 37 | // 部门经理的核心工作:也只有这一行! 38 | return sarray->data[index]; 39 | } 40 | 41 | // ====================================================================== 42 | // ==================== 公共API函数 - “公司CEO” ====================== 43 | // ====================================================================== 44 | 45 | SafeArray* sarray_create(size_t capacity) { 46 | // CEO的职责1:检查初始参数的合法性 47 | if (capacity == 0) return NULL; 48 | 49 | SafeArray* sa = (SafeArray*)malloc(sizeof(SafeArray)); 50 | if (!sa) return NULL; 51 | 52 | // CEO的职责2:负责资源的分配 53 | sa->data = (int*)malloc(capacity * sizeof(int)); 54 | if (!sa->data) { 55 | free(sa); 56 | return NULL; 57 | } 58 | 59 | sa->capacity = capacity; 60 | return sa; 61 | } 62 | 63 | void sarray_destroy(SafeArray** p_sarray) { 64 | // CEO的职责:负责资源的释放 65 | if (p_sarray && *p_sarray) { 66 | free((*p_sarray)->data); // 先释放内部的数组 67 | free(*p_sarray); // 再释放结构体本身 68 | *p_sarray = NULL; 69 | } 70 | } 71 | 72 | bool sarray_set(SafeArray* sarray, size_t index, int value) { 73 | /* 74 | * 函数功能: [CEO] 安全地设置一个值。 75 | * ------------------------- 教学案例 ------------------------- 76 | * 假设我们调用 sarray_set(my_array, 2, 99)。 77 | * 1. CEO (sarray_set) 接到请求。 78 | * 2. CEO 做的第一件事,就是进行严格的“安检”。 79 | * - 检查 my_array 是否存在?(sarray != NULL) 80 | * - 检查索引 2 是否在合法范围内 [0, capacity-1]? 81 | * 3. 如果安检通过,CEO 就把“将99这个值放到2号位置”这个具体的任务, 82 | * 委托给内部的“部门经理” `_set_value` 去执行。 83 | * 4. CEO 不关心经理具体是怎么写的,他只知道任务会完成。 84 | * 5. CEO 向客户报告:任务成功 (return true)。 85 | * ----------------------------------------------------------- 86 | */ 87 | 88 | // 步骤 1: CEO进行严格的“安检”。这是公共API的核心职责。 89 | // 检查数组指针是否有效,以及索引是否越界。 90 | if (!sarray || index >= sarray->capacity) { 91 | // 如果安检不通过,直接拒绝服务,报告失败。 92 | return false; 93 | } 94 | 95 | // 步骤 2: 安检通过后,将核心任务委托给“部门经理”。 96 | // 97 | // 98 | // 步骤3: 任务委托。这里体现了职责分离:CEO负责安全,经理负责执行。 99 | _set_value(sarray, index, value); 100 | 101 | 102 | // 103 | // 104 | // sarray->data[index] = value; 105 | 106 | // 职责分离 107 | // Separation of Concerns 108 | 109 | 110 | // 步骤 3: 报告任务成功。 111 | return true; 112 | } 113 | 114 | bool sarray_get(const SafeArray* sarray, size_t index, int* out_value) { 115 | /* 116 | * 函数功能: [CEO] 安全地获取一个值。 117 | * 逻辑与 sarray_set 完全相同,只是委托给了不同的“部门经理”。 118 | */ 119 | 120 | // 步骤 1: CEO进行严格的“安检”。 121 | // 检查数组指针、索引、以及用于接收结果的输出指针是否都有效。 122 | if (!sarray || index >= sarray->capacity || !out_value) { 123 | return false; 124 | } 125 | 126 | // 步骤 2: 安检通过,委托给“部门经理”`_get_value`去取回数据。 127 | int value = _get_value(sarray, index); 128 | 129 | // 步骤 3: 将经理取回的数据,通过安全的“输出参数”方式交给客户。 130 | *out_value = value; 131 | 132 | // void * 133 | // memcpy(out_value, &value, sizeof(int)); 134 | 135 | // 步骤 4: 报告任务成功。 136 | return true; 137 | } 138 | 139 | size_t sarray_get_capacity(const SafeArray* sarray) { 140 | if (!sarray) return 0; 141 | return sarray->capacity; 142 | } 143 | 144 | void sarray_print(const SafeArray* sarray) { 145 | if (!sarray) { 146 | printf("SafeArray is NULL.\n"); 147 | return; 148 | } 149 | printf("SafeArray (capacity: %zu) [ ", sarray->capacity); 150 | for (size_t i = 0; i < sarray->capacity; i++) { 151 | // 为了打印,我们在这里“作弊”直接访问了内部数据。 152 | // 在实际应用中,我们甚至可以不提供打印函数,以保证绝对的封装。 153 | printf("%d ", sarray->data[i]); 154 | } 155 | printf("]\n"); 156 | } -------------------------------------------------------------------------------- /dsc-code/easy_singly/main.c: -------------------------------------------------------------------------------- 1 | #define _CRT_SECURE_NO_WARNINGS 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | 10 | 11 | // 定义节点结构 12 | 13 | // 1. data int 14 | 15 | // 2. 指向下一个节点的指针 16 | 17 | typedef struct Node { 18 | 19 | int data; 20 | 21 | struct Node* next; 22 | 23 | } Node; 24 | 25 | 26 | 27 | Node* createNote(int data) { 28 | 29 | Node* newNode = (Node*)malloc(sizeof(Node)); 30 | 31 | 32 | 33 | if (newNode == NULL) { 34 | 35 | printf("错误:分配失败!\n"); 36 | 37 | return NULL; 38 | 39 | } 40 | 41 | 42 | 43 | newNode->data = data; 44 | 45 | newNode->next = NULL; 46 | 47 | 48 | 49 | 50 | 51 | return newNode; 52 | 53 | } 54 | 55 | 56 | 57 | // headRef是指向head指针的指针 58 | 59 | // 这样我们才能够在链表为空的时候,修改head 60 | 61 | void list_append(Node** headRef, int data) { 62 | 63 | Node* newNode = createNote(data); 64 | 65 | if (newNode == NULL) return; 66 | 67 | 68 | 69 | if (*headRef == NULL) { 70 | 71 | *headRef = newNode; 72 | 73 | return; 74 | 75 | } 76 | 77 | 78 | 79 | // head 是你家的一张便签纸,上面写着你好朋友家的地址 80 | 81 | // head 一级指针 Node* 就是是便签纸 82 | 83 | // 链表数据:你朋友家,以及他家的连着他家户口本... 84 | 85 | // Node* list_append 跑腿小哥 86 | 87 | 88 | 89 | // head NULL 90 | 91 | 92 | 93 | Node* last = *headRef; 94 | 95 | while (last->next != NULL) { 96 | 97 | last = last->next; 98 | 99 | } 100 | 101 | 102 | 103 | last->next = newNode; 104 | 105 | 106 | 107 | } 108 | 109 | 110 | 111 | 112 | 113 | void prependNode(Node** headRef, int data) { 114 | 115 | Node* newNode = createNote(data); 116 | 117 | 118 | 119 | newNode->next = *headRef; 120 | 121 | *headRef = newNode; 122 | 123 | 124 | 125 | } 126 | 127 | 128 | 129 | 130 | 131 | void printList(Node* head) { 132 | 133 | Node* current = head; 134 | 135 | 136 | 137 | printf("当前列表:"); 138 | 139 | while (current != NULL) { 140 | 141 | printf("%d => ", current->data); 142 | 143 | current = current->next; 144 | 145 | } 146 | 147 | 148 | 149 | printf("NULL\n"); 150 | 151 | 152 | 153 | } 154 | 155 | 156 | 157 | Node* findNode(Node* head, int data) { 158 | 159 | Node* current = head; 160 | 161 | while (current != NULL) { 162 | 163 | if (current->data == data) { 164 | 165 | return current; 166 | 167 | } 168 | 169 | 170 | 171 | current = current->next; 172 | 173 | } 174 | 175 | 176 | 177 | return NULL; 178 | 179 | } 180 | 181 | 182 | 183 | void updateNode(Node* head, int oldData, int newData) { 184 | 185 | Node* nodeToUpdate = findNode(head, oldData); 186 | 187 | if (nodeToUpdate != NULL) { 188 | 189 | nodeToUpdate->data = newData; 190 | 191 | printf("数据 %d 已经成功更新至 %d .\n", oldData, newData); 192 | 193 | } 194 | 195 | else { 196 | 197 | printf("未找到!\n"); 198 | 199 | } 200 | 201 | } 202 | 203 | 204 | 205 | void deleteNode(Node** headRef, int data) { 206 | 207 | Node* temp = *headRef; 208 | 209 | Node* prev = NULL; 210 | 211 | 212 | 213 | // 如果删除的是头节点 214 | 215 | if (temp != NULL && temp->data == data) { 216 | 217 | *headRef = temp->next; 218 | 219 | free(temp); 220 | 221 | return; 222 | 223 | } 224 | 225 | 226 | 227 | // head --> [10 | next] --> [20 | next] --> [30 | next] --> NULL 228 | 229 | // head 230 | 231 | // temp 232 | 233 | // prev 234 | 235 | // 如果是在尾部或者中间 236 | 237 | while (temp != NULL && temp->data != data) { 238 | 239 | prev = temp; 240 | 241 | temp = temp->next; 242 | 243 | } 244 | 245 | 246 | 247 | if (temp == NULL) { 248 | 249 | return; 250 | 251 | } 252 | 253 | 254 | 255 | prev->next = temp->next; 256 | 257 | free(temp); 258 | 259 | } 260 | 261 | 262 | 263 | void list_free(Node** headRef) { 264 | 265 | if (headRef == NULL) return; 266 | 267 | Node* current = *headRef; 268 | 269 | 270 | 271 | Node* nextNode; 272 | 273 | 274 | 275 | while (current != NULL) { 276 | 277 | nextNode = current->next; 278 | 279 | free(current); 280 | 281 | current = nextNode; 282 | 283 | } 284 | 285 | 286 | 287 | *headRef = NULL; 288 | 289 | } 290 | 291 | 292 | 293 | int main(void) { 294 | 295 | 296 | 297 | Node* head = NULL; 298 | 299 | 300 | 301 | list_append(&head, 10); 302 | 303 | list_append(&head, 20); 304 | 305 | list_append(&head, 30); 306 | 307 | 308 | 309 | printList(head); 310 | 311 | 312 | 313 | 314 | 315 | list_append(&head, 40); 316 | 317 | printList(head); 318 | 319 | 320 | 321 | 322 | 323 | prependNode(&head, 5); 324 | 325 | printList(head); 326 | 327 | 328 | 329 | list_free(&head); 330 | 331 | 332 | 333 | printList(head); 334 | 335 | 336 | 337 | 338 | 339 | return 0; 340 | 341 | } -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [English](./CONTRIBUTING_EN.md) | **简体中文** 4 | 5 |
6 | 7 | # 🤝 贡献指南 8 | 9 | 首先,感谢你考虑为 **数据结构项目** 做出贡献!正是像你这样的人让开源社区变得更加美好。 10 | 11 | ## 📋 目录 12 | 13 | - [行为准则](#行为准则) 14 | - [我该如何贡献?](#我该如何贡献) 15 | - [报告 Bug](#-报告-bug) 16 | - [建议新功能](#-建议新功能) 17 | - [提交代码](#-提交代码) 18 | - [改进文档](#-改进文档) 19 | - [开发流程](#开发流程) 20 | - [代码规范](#代码规范) 21 | - [提交信息规范](#提交信息规范) 22 | - [Pull Request 流程](#pull-request-流程) 23 | 24 | ## 行为准则 25 | 26 | 本项目采用开源社区行为准则。参与项目即表示你同意遵守其条款。请友善、包容地对待每一位贡献者。 27 | 28 | ## 我该如何贡献? 29 | 30 | ### 🐛 报告 Bug 31 | 32 | 发现 Bug 是对项目的贡献!如果你发现了问题,请: 33 | 34 | 1. **确认 Bug 尚未被报告** - 先搜索 [Issues](https://github.com/Frank-Code-Show/DataStructure/issues) 35 | 2. **创建详细的 Bug 报告**,包含: 36 | - 清晰的标题和描述 37 | - 重现步骤 38 | - 预期行为 vs 实际行为 39 | - 截图(如果适用) 40 | - 环境信息(操作系统、编译器版本等) 41 | 42 | **Bug 报告模板:** 43 | ```markdown 44 | **描述问题** 45 | 简明扼要地描述问题是什么。 46 | 47 | **重现步骤** 48 | 1. 进入 '...' 49 | 2. 点击 '....' 50 | 3. 滚动到 '....' 51 | 4. 出现错误 52 | 53 | **预期行为** 54 | 描述你期望发生什么。 55 | 56 | **截图** 57 | 如果适用,请添加截图。 58 | 59 | **环境信息:** 60 | - OS: [例如 Windows 10] 61 | - 编译器: [例如 gcc 9.3.0] 62 | - 版本: [例如 commit hash] 63 | ``` 64 | 65 | ### 💡 建议新功能 66 | 67 | 我们欢迎新想法!请通过 Issue 提交功能请求: 68 | 69 | 1. **明确的功能描述** - 这个功能是什么? 70 | 2. **使用场景** - 为什么需要这个功能? 71 | 3. **可能的实现方案** - 你认为如何实现? 72 | 4. **替代方案** - 是否考虑过其他解决方案? 73 | 74 | ### 🔧 提交代码 75 | 76 | 想要提交代码?太棒了!请遵循以下步骤: 77 | 78 | #### 1. Fork 和 Clone 79 | ```bash 80 | # Fork 项目到你的 GitHub 账号 81 | # 然后 clone 到本地 82 | git clone https://github.com/你的用户名/DataStructure.git 83 | cd DataStructure 84 | ``` 85 | 86 | #### 2. 创建分支 87 | ```bash 88 | # 基于最新的 main 分支创建新分支 89 | git checkout -b feature/你的功能名称 90 | # 或 91 | git checkout -b fix/修复的问题 92 | ``` 93 | 94 | #### 3. 进行修改 95 | - 编写清晰、可维护的代码 96 | - 添加必要的注释 97 | - 更新相关文档 98 | - 添加测试(如果适用) 99 | 100 | #### 4. 提交更改 101 | ```bash 102 | git add . 103 | git commit -m "feat: 添加某某功能" 104 | ``` 105 | 106 | #### 5. 推送到 GitHub 107 | ```bash 108 | git push origin feature/你的功能名称 109 | ``` 110 | 111 | #### 6. 创建 Pull Request 112 | 在 GitHub 上创建 PR,填写 PR 模板 113 | 114 | ### 📝 改进文档 115 | 116 | 文档同样重要!你可以: 117 | - 修正错别字或语法错误 118 | - 改进示例代码 119 | - 添加更多解释说明 120 | - 翻译文档 121 | 122 | ## 开发流程 123 | 124 | ### 项目结构 125 | ``` 126 | DataStructure/ 127 | ├── dsc/ # HTML 动画演示 128 | ├── dsc-code/ # C 语言实现 129 | │ ├── include/ # 头文件 130 | │ ├── src/ # 源代码 131 | │ └── tests/ # 测试文件 132 | └── docs/ # 文档 133 | ``` 134 | 135 | ### 编译和测试 136 | ```bash 137 | # 编译代码 138 | cd dsc-code 139 | make all 140 | 141 | # 运行测试 142 | make test 143 | 144 | # 清理编译文件 145 | make clean 146 | ``` 147 | 148 | ## 代码规范 149 | 150 | ### C 语言规范 151 | 152 | 1. **命名约定** 153 | - 函数名:`camelCase` 或 `snake_case`(保持一致) 154 | - 常量:`UPPER_SNAKE_CASE` 155 | - 结构体:`PascalCase` 156 | 157 | 2. **格式化** 158 | - 缩进:4 个空格 159 | - 最大行长:80 字符 160 | - 大括号:K&R 风格 161 | 162 | 3. **注释** 163 | ```c 164 | /* 多行注释 165 | * 用于函数说明 166 | */ 167 | 168 | // 单行注释用于代码内部说明 169 | ``` 170 | 171 | 4. **示例代码** 172 | ```c 173 | /** 174 | * 创建新节点 175 | * @param value 节点值 176 | * @return 指向新节点的指针 177 | */ 178 | Node* createNode(int value) { 179 | Node* newNode = (Node*)malloc(sizeof(Node)); 180 | if (newNode == NULL) { 181 | fprintf(stderr, "内存分配失败\n"); 182 | return NULL; 183 | } 184 | newNode->data = value; 185 | newNode->next = NULL; 186 | return newNode; 187 | } 188 | ``` 189 | 190 | ### HTML/CSS 规范 191 | 192 | - 使用语义化 HTML5 标签 193 | - CSS 采用 BEM 命名规范 194 | - 确保动画流畅(60fps) 195 | 196 | ## 提交信息规范 197 | 198 | 采用 [Conventional Commits](https://www.conventionalcommits.org/) 规范: 199 | 200 | ``` 201 | <类型>(<范围>): <主题> 202 | 203 | <正文> 204 | 205 | <页脚> 206 | ``` 207 | 208 | **类型:** 209 | - `feat`: 新功能 210 | - `fix`: 修复 Bug 211 | - `docs`: 文档更新 212 | - `style`: 代码格式(不影响功能) 213 | - `refactor`: 重构 214 | - `test`: 添加测试 215 | - `chore`: 构建过程或辅助工具的变动 216 | 217 | **示例:** 218 | ``` 219 | feat(linkedlist): 添加反转链表功能 220 | 221 | 实现了 reverseList() 函数,可以原地反转单向链表。 222 | 时间复杂度 O(n),空间复杂度 O(1)。 223 | 224 | Closes #123 225 | ``` 226 | 227 | ## Pull Request 流程 228 | 229 | 1. **PR 前检查清单** 230 | - [ ] 代码通过所有测试 231 | - [ ] 遵循代码规范 232 | - [ ] 更新了相关文档 233 | - [ ] 提交信息清晰规范 234 | - [ ] PR 描述完整 235 | 236 | 2. **PR 模板** 237 | ```markdown 238 | ## 描述 239 | 简要描述这个 PR 的改动 240 | 241 | ## 改动类型 242 | - [ ] Bug 修复 243 | - [ ] 新功能 244 | - [ ] 破坏性变更 245 | - [ ] 文档更新 246 | 247 | ## 测试 248 | 描述你如何测试这些改动 249 | 250 | ## 截图(如果适用) 251 | 添加截图帮助解释改动 252 | 253 | ## 相关 Issue 254 | Closes #(issue 编号) 255 | ``` 256 | 257 | 3. **Review 流程** 258 | - 至少需要 1 位维护者的批准 259 | - 所有 CI 检查必须通过 260 | - 解决所有 review 意见 261 | 262 | ## 🎯 小贴士 263 | 264 | - **先讨论,后编码** - 对于大的改动,先开 Issue 讨论 265 | - **保持简单** - 每个 PR 只做一件事 266 | - **写好测试** - 新功能需要配套测试 267 | - **耐心等待** - 维护者可能需要时间 review 268 | 269 | ## 📮 联系方式 270 | 271 | - **B站**: [@Frank](https://space.bilibili.com/19658621) 272 | - **GitHub Issues**: [项目 Issues](https://github.com/Frank-Code-Show/DataStructure/issues) 273 | 274 | --- 275 | 276 |
277 | 278 | 再次感谢你的贡献!🎉 279 | 280 | **[⬆ 回到顶部](#-贡献指南)** 281 | 282 |
283 | -------------------------------------------------------------------------------- /dsc-code/shared_stack/shared_stack.c: -------------------------------------------------------------------------------- 1 | #include "shared_stack.h" 2 | #include 3 | #include 4 | 5 | // --- Private Structure Definition --- 6 | struct SharedStack { 7 | void* data; // 指向共享内存区域的指针 8 | size_t capacity; // 总容量 (元素个数) 9 | size_t element_size; // 每个元素的大小 10 | int top1; // 栈1的栈顶索引 11 | int top2; // 栈2的栈顶索引 12 | }; 13 | 14 | // --- API Function Implementations --- 15 | 16 | Stack* stack_create(size_t total_capacity, size_t element_size) { 17 | if (total_capacity == 0 || element_size == 0) { 18 | return NULL; 19 | } 20 | 21 | Stack* s = (Stack*)malloc(sizeof(Stack)); 22 | if (!s) { 23 | return NULL; 24 | } 25 | 26 | s->data = malloc(total_capacity * element_size); 27 | if (!s->data) { 28 | free(s); 29 | return NULL; 30 | } 31 | 32 | s->capacity = total_capacity; 33 | s->element_size = element_size; 34 | s->top1 = -1; // 栈1从头开始,-1为空 35 | s->top2 = (int)total_capacity; // 栈2从尾开始,capacity为空 36 | 37 | return s; 38 | } 39 | 40 | void stack_destroy(Stack** p_stack) { 41 | if (p_stack && *p_stack) { 42 | free((*p_stack)->data); 43 | free(*p_stack); 44 | *p_stack = NULL; 45 | } 46 | } 47 | 48 | bool stack_is_full(const Stack* stack) { 49 | if (!stack) { 50 | return true; // 无效栈视作满 51 | } 52 | return stack->top1 + 1 == stack->top2; 53 | } 54 | 55 | bool stack_is_empty(const Stack* stack, StackNumber num) { 56 | if (!stack) { 57 | return true; // 无效栈视作空 58 | } 59 | if (num == STACK_ONE) { 60 | return stack->top1 == -1; 61 | } 62 | else { // STACK_TWO 63 | return stack->top2 == (int)stack->capacity; 64 | } 65 | } 66 | 67 | bool stack_push(Stack* stack, StackNumber num, const void* element_data) { 68 | if (!stack || !element_data || stack_is_full(stack)) { 69 | return false; 70 | } 71 | 72 | void* target_address; 73 | if (num == STACK_ONE) { 74 | stack->top1++; 75 | target_address = (char*)stack->data + (stack->top1 * stack->element_size); 76 | } 77 | else { // STACK_TWO 78 | stack->top2--; 79 | target_address = (char*)stack->data + (stack->top2 * stack->element_size); 80 | } 81 | 82 | memcpy(target_address, element_data, stack->element_size); 83 | return true; 84 | } 85 | 86 | bool stack_pop(Stack* stack, StackNumber num, void* output_buffer) { 87 | if (!stack || !output_buffer || stack_is_empty(stack, num)) { 88 | return false; 89 | } 90 | 91 | void* source_address; 92 | if (num == STACK_ONE) { 93 | source_address = (char*)stack->data + (stack->top1 * stack->element_size); 94 | memcpy(output_buffer, source_address, stack->element_size); 95 | stack->top1--; 96 | } 97 | else { // STACK_TWO 98 | source_address = (char*)stack->data + (stack->top2 * stack->element_size); 99 | memcpy(output_buffer, source_address, stack->element_size); 100 | stack->top2++; 101 | } 102 | return true; 103 | } 104 | 105 | bool stack_peek(const Stack* stack, StackNumber num, void* output_buffer) { 106 | if (!stack || !output_buffer || stack_is_empty(stack, num)) { 107 | return false; 108 | } 109 | 110 | void* source_address; 111 | if (num == STACK_ONE) { 112 | source_address = (char*)stack->data + (stack->top1 * stack->element_size); 113 | } 114 | else { // STACK_TWO 115 | source_address = (char*)stack->data + (stack->top2 * stack->element_size); 116 | } 117 | memcpy(output_buffer, source_address, stack->element_size); 118 | return true; 119 | } 120 | 121 | size_t stack_get_size(const Stack* stack, StackNumber num) { 122 | if (!stack) { 123 | return 0; 124 | } 125 | if (num == STACK_ONE) { 126 | return (size_t)(stack->top1 + 1); 127 | } 128 | else { // STACK_TWO 129 | return stack->capacity - stack->top2; 130 | 131 | // Stack* ss = stack_create(10, sizeof(int)); 132 | // ss-> capacity = 10; 133 | // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 => index 134 | // top1 = -1; // 栈1从头开始,-1为空 135 | // top2 = 10; // 栈2从尾开始,capacity为空 136 | // Index: 0 1 2 3 4 5 6 7 8 9 137 | // Array: [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ] 138 | // top2 (is at 10) 139 | // size = capacity - top2; 140 | // size = 10 - 10 = 0 141 | // size = 0 142 | // e.g 99 143 | // stack_push() 144 | // top2 10->9 top2-- 145 | // Index: 0 1 2 3 4 5 6 7 8 9 146 | // Array: [ ][ ][ ][ ][ ][ ][ ][ ][ ][99] 147 | // top2 (is at 9) 148 | 149 | // size = capacity - top2; 150 | // size = 10 - 9 = 1 151 | // size = 1 152 | 153 | } 154 | } 155 | 156 | size_t stack_get_total_capacity(const Stack* stack) { 157 | if (!stack) { 158 | return 0; 159 | } 160 | return stack->capacity; 161 | } -------------------------------------------------------------------------------- /dsc-code/doublyLinkedList/main.c: -------------------------------------------------------------------------------- 1 | #define _CRT_SECURE_NO_WARNINGS 2 | #include "DoublyLinkedList.h" 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | int id; 9 | char* name; 10 | 11 | } Employee; 12 | 13 | // Helper function to duplicate a string 14 | char* protable_strdup(const char* str) { 15 | if (!str) return NULL; 16 | size_t len = strlen(str); 17 | char* new_str = (char*)malloc(len + 1); 18 | if (new_str) { 19 | strcpy(new_str, str); 20 | } 21 | return new_str; 22 | } 23 | 24 | void free_employee(void* data) { 25 | 26 | Employee* emp = (Employee*)data; 27 | 28 | if (emp) { 29 | printf("Freeing employee ID: %d, Name: %s\n", emp->id, emp->name); 30 | free(emp->name); // Free the dynamically allocated name 31 | free(emp); // Free the employee structure itself 32 | } 33 | } 34 | 35 | int compare_employee_by_id(const void* a, const void* b) { 36 | const Employee* emp_a = (const Employee*)a; 37 | const Employee* emp_b = (const Employee*)b; 38 | return emp_a->id - emp_b->id; // Compare by ID 39 | } 40 | 41 | void print_employee_action(void* data, void* context) { 42 | const Employee* emp = (const Employee*)data; 43 | 44 | // context is not used here, but you can use it if needed 45 | // TODO 46 | 47 | if (!emp) { 48 | printf("Employee data is NULL.\n"); 49 | return; // Check if emp is NULL 50 | } 51 | printf("Employee ID: %d, Name: %s\n", emp->id, emp->name); 52 | } 53 | 54 | int main(void) { 55 | 56 | printf("--- Architecture of Doubly Linked List ---\n"); 57 | 58 | // 1. Create a new doubly linked list with a memory pool 59 | printf("Setup 1: Creating a list with a memory pool of 10 nodes...\n"); 60 | 61 | DoublyLinkedList* emp_list = List_Create(10, free_employee); 62 | 63 | if (!emp_list) { 64 | fprintf(stderr, "Failed to create the doubly linked list.\n"); 65 | return 1; // Exit if list creation failed 66 | } 67 | 68 | printf("List created successfully. Initial size: %zu\n", List_GetSize(emp_list)); 69 | 70 | // 2. Append some employees to the list 71 | 72 | printf("Setup 2: Appending employees to the list...\n"); 73 | 74 | for (int i = 0; i < 5; i++) { 75 | Employee* emp = (Employee*)malloc(sizeof(Employee)); 76 | if (emp == NULL) { 77 | fprintf(stderr, "Memory allocation failed for employee.\n"); 78 | continue; // Skip this iteration if memory allocation fails 79 | } 80 | emp->id = 101 + i; 81 | char buffer[50]; 82 | sprintf(buffer, "Employee #%d", emp->id); 83 | emp->name = protable_strdup(buffer); 84 | if (emp->name == NULL) { 85 | fprintf(stderr, "Memory allocation failed for employee name.\n"); 86 | free(emp); // Free the employee structure if name allocation fails 87 | continue; 88 | } 89 | List_Append(emp_list, emp); 90 | } 91 | 92 | // 3. Print the list of employees 93 | printf("Setup 3: Printing the list of employees...\n"); 94 | List_ForEach(emp_list, print_employee_action, NULL); 95 | printf("Current list size: %zu\n", List_GetSize(emp_list)); 96 | 97 | // 4. Find an employee by ID 98 | 99 | printf("Setup 4: Finding an employee with ID 103...\n"); 100 | 101 | Employee search_key = { 103, NULL }; // Create a search key with ID 103 102 | 103 | DListNode* found_node = List_Find(emp_list, &search_key, compare_employee_by_id); 104 | 105 | if (found_node) { 106 | Employee* found_emp = (Employee*)List_GetData(found_node); 107 | printf(": Found employee with ID %d, Name: %s\n", found_emp->id, found_emp->name); 108 | } else { 109 | printf("Employee with ID 103 not found.\n"); 110 | } 111 | 112 | // 5. Delete an employee from the list 113 | if (found_node) { 114 | printf("Setup 5: Deleting employee with ID 103...\n"); 115 | List_DeleteNode(emp_list, found_node); 116 | printf("Employee with ID 103 deleted. Current list size: %zu\n", List_GetSize(emp_list)); 117 | } 118 | 119 | printf("\n --- List fater deletion---\n"); 120 | 121 | List_ForEach(emp_list, print_employee_action, NULL); 122 | 123 | // 6. insert value -> head node 124 | 125 | printf("Setup 6: Prepending an employee to the list...\n"); 126 | Employee* ceo = (Employee*)malloc(sizeof(Employee)); 127 | 128 | // Before using the 'ceo' pointer, ensure it is not NULL 129 | if (ceo != NULL) { 130 | ceo->id = 99; 131 | ceo->name = protable_strdup("CEO"); 132 | if (ceo->name == NULL) { 133 | fprintf(stderr, "Memory allocation failed for CEO name.\n"); 134 | free(ceo); // Free the employee structure if name allocation fails 135 | } else { 136 | List_Prepend(emp_list, ceo); 137 | printf("Prepended employee with ID %d, Name: %s\n", ceo->id, ceo->name); 138 | printf("\n--- Final list contents ---\n"); 139 | List_ForEach(emp_list, print_employee_action, NULL); 140 | printf("Final list size: %zu\n", List_GetSize(emp_list)); 141 | } 142 | } else { 143 | fprintf(stderr, "Memory allocation failed for CEO.\n"); 144 | } 145 | 146 | 147 | 148 | // 7. Clean up the list 149 | printf("Cleanup: Destroying the doubly linked list...\n"); 150 | 151 | List_Destroy(&emp_list); 152 | printf("List destroyed. The pointer emp_list is now NULL: %s\n", 153 | emp_list == NULL ? "true" : "false"); 154 | 155 | printf("All resources freed successfully.\n"); 156 | 157 | return 0; 158 | } -------------------------------------------------------------------------------- /dsc-code/bst/main.c: -------------------------------------------------------------------------------- 1 | // main.c 2 | 3 | #include "bst.h" 4 | #include 5 | 6 | 7 | /* 8 | 9 | * 二叉搜索树 (BST) 模块构建 10 | 11 | ├── Ⅰ. 奠定基石:设计蓝图与公共接口 (./bst.h) 12 | │ │ 13 | │ ├── 1. 定义“客户”的工具箱:函数指针类型 14 | │ │ ├── a. CompareFunc (讲解:为什么需要它?它是BST的“灵魂”,定义了大小关系) 15 | │ │ └── b. VisitFunc (讲解:这是为了遍历时能对每个节点做操作的“钩子”) 16 | │ │ 17 | │ └── 2. 设计“不透明”的门面:结构体与API原型 18 | │ ├── a. typedef struct BST BST; (讲解:信息隐藏的魔法,用户只知其名不知其形) 19 | │ └── b. 公共API函数原型 (逐一讲解每个函数的作用,这是我们与用户的“合同”) 20 | │ ├── bst_create() 21 | │ ├── bst_destroy() 22 | │ ├── bst_insert() 23 | │ ├── bst_remove() 24 | │ ├── bst_search() 25 | │ └── bst_traverse() 26 | │ 27 | ├── Ⅱ. 搭建骨架:实现核心结构与简单公共函数 (./bst.c) 28 | │ │ 29 | │ ├── 1. 揭秘内部结构:定义私有结构体 30 | │ │ ├── a. Node (讲解:这是树的基本砖块) 31 | │ │ └── b. BST (struct) (讲解:这是树的“大脑”,管理着一切) 32 | │ │ 33 | │ ├── 2. 实现“构造”与“析构” 34 | │ │ ├── a. bst_create() (讲解:分配内存,初始化“大脑”) 35 | │ │ └── b. bst_destroy() (讲解:作为CEO,它发起销毁任务,但具体工作要“委托”给下属) 36 | │ │ └── (引出对 _destroy_recursive 的需求) 37 | │ │ 38 | │ └── 3. 实现“简单”的公共操作 39 | │ ├── a. bst_search() (讲解:这是最简单的查找,用迭代实现,逻辑直观) 40 | │ ├── b. bst_is_empty(), bst_get_size() (讲解:这些是简单的状态查询) 41 | │ └── c. bst_traverse() (讲解:作为CEO,它发起遍历任务,具体工作“委托”给下属) 42 | │ └── (引出对 _traverse_recursive 的需求) 43 | │ 44 | └── Ⅲ. 填充血肉:实现复杂的私有递归逻辑 (./bst.c 的核心) 45 | │ 46 | ├── 1. 实现“插入”的递归逻辑 (CEO -> 经理) 47 | │ ├── a. bst_insert() (复习:回顾CEO的职责) 48 | │ └── b. _insert_recursive() (深入讲解:部门经理如何递归地找到位置) 49 | │ ├── b.1. 基准情况 (Base Case): 找到空位,创建节点 50 | │ └── b.2. 递归步骤: 比较大小,决定向左走还是向右走 51 | │ 52 | ├── 2. 实现“删除”的递归逻辑 (最难的部分,分步攻克) 53 | │ ├── a. bst_remove() (复习:回顾CEO的职责) 54 | │ └── b. _remove_recursive() (深入讲解:这位经理的任务最复杂) 55 | │ ├── b.1. 查找阶段: 递归地找到要删除的节点 56 | │ ├── b.2. 删除阶段 - 情况1: 叶子节点 (最简单) 57 | │ ├── b.3. 删除阶段 - 情况2: 只有一个子节点的节点 58 | │ └── b.4. 删除阶段 - 情况3: 有两个子节点的节点 59 | │ └── (此处需要先讲解 _find_min_recursive 辅助函数) 60 | │ 61 | └── 3. 实现“遍历”与“销毁”的递归逻辑 (作为递归概念的巩固) 62 | ├── a. _traverse_recursive() (深入讲解:前、中、后序遍历的递归实现) 63 | └── b. _destroy_recursive() (深入讲解:后序遍历在销毁树时的妙用) 64 | */ 65 | 66 | // --- 用户定义的比较和访问函数 --- 67 | // 比较两个整数 68 | int compare_int(const void* a, const void* b) { 69 | int int_a = *(const int*)a; 70 | int int_b = *(const int*)b; 71 | if (int_a < int_b) return -1; 72 | if (int_a > int_b) return 1; 73 | return 0; 74 | } 75 | 76 | // 我们假如写一个比较函数,比较类型是Student的id编号排序 77 | // int compare_student_by_id(const void* a, const void* b) { 78 | // const Student* student_a = (const Student*)a; 79 | // const Student* student_b = (const Student*)b; 80 | // if (student_a->id < student_b->id) return -1; 81 | // if (student_a->id > student_b->id) return 1; 82 | // return 0; 83 | // } 84 | 85 | // 打印一个整数 86 | void visit_int(const void* data) { 87 | printf("%d ", *(const int*)data); 88 | } 89 | 90 | void print_in_order(const BST* bst) { 91 | printf("中序遍历 (有序): "); 92 | bst_traverse(bst, IN_ORDER, visit_int); 93 | printf("\n"); 94 | } 95 | 96 | int main() { 97 | printf("--- 现代C语言泛型二叉搜索树实现 ---\n"); 98 | BST* bst = bst_create(sizeof(int), compare_int); 99 | 100 | // 1. 插入节点 101 | printf("\n1. 插入节点: 20, 10, 30, 5, 15, 25, 40, 3, 7\n"); 102 | int values[] = { 20, 10, 30, 5, 15, 25, 40, 3, 7 }; 103 | for (size_t i = 0; i < sizeof(values) / sizeof(int); i++) { 104 | bst_insert(bst, &values[i]); 105 | } 106 | print_in_order(bst); 107 | printf("当前大小: %zu\n", bst_get_size(bst)); 108 | 109 | // 2. 搜索节点 110 | printf("\n2. 搜索节点...\n"); 111 | int key_to_find = 15; 112 | printf("搜索 %d: %s\n", key_to_find, bst_search(bst, &key_to_find) ? "找到" : "未找到"); 113 | key_to_find = 99; 114 | printf("搜索 %d: %s\n", key_to_find, bst_search(bst, &key_to_find) ? "找到" : "未找到"); 115 | 116 | // 3. 删除操作 - 情况1: 叶子节点 117 | printf("\n3. 删除叶子节点 (7)...\n"); 118 | int key_to_remove = 7; 119 | bst_remove(bst, &key_to_remove); 120 | print_in_order(bst); 121 | 122 | // 4. 删除操作 - 情况2: 只有一个子节点的节点 123 | // 我们先删除 3,让 5 只有一个右子节点。然后再删除 5。 124 | printf("\n4. 删除只有一个子节点的节点 (5)...\n"); 125 | key_to_remove = 3; 126 | bst_remove(bst, &key_to_remove); // 先删掉3 127 | key_to_remove = 5; 128 | bst_remove(bst, &key_to_remove); // 再删5,此时5应该只有一个孩子 129 | print_in_order(bst); 130 | 131 | // 5. 删除操作 - 情况3: 有两个子节点的节点 132 | printf("\n5. 删除有两个子节点的节点 (10)...\n"); 133 | key_to_remove = 10; 134 | bst_remove(bst, &key_to_remove); 135 | print_in_order(bst); 136 | 137 | // 6. 删除根节点 138 | printf("\n6. 删除根节点 (20)...\n"); 139 | key_to_remove = 20; 140 | bst_remove(bst, &key_to_remove); 141 | print_in_order(bst); 142 | printf("当前大小: %zu\n", bst_get_size(bst)); 143 | 144 | // 7. 销毁树 145 | printf("\n7. 销毁树...\n"); 146 | bst_destroy(&bst); 147 | printf("树已销毁,指针为: %s\n", bst == NULL ? "NULL" : "OK"); 148 | 149 | return 0; 150 | } -------------------------------------------------------------------------------- /dsc-code/heap/heap.c: -------------------------------------------------------------------------------- 1 | #include "heap.h" 2 | #include 3 | #include // For memcpy 4 | 5 | // ========== 内部辅助函数 (不对外暴露) ========== 6 | 7 | // 交换两个Item类型的值 8 | static void swap(Item* a, Item* b) { 9 | Item temp = *a; 10 | *a = *b; 11 | *b = temp; 12 | } 13 | 14 | // "上浮"操作:将指定索引的节点向上调整,以维持最大堆性质 15 | static void heapify_up(Heap* h, size_t index) { 16 | if (index == 0) return; // 根节点无法上浮 17 | 18 | size_t parent_index = (index - 1) / 2; 19 | 20 | // 对于任意索引为index的节点: 21 | // 它的左子节点索引为 2 * index + 1 22 | // 它的右子节点索引为 2 * index + 2 23 | // 如果当前节点是左子节点,则其父节点索引为 (index - 1) / 2 24 | // 如果当前节点是右子节点,则其父节点索引为 (index - 2) / 2 25 | // C语言的整数除法会自动向下取整,所以我们可以直接使用 (index - 1) / 2 来计算父节点索引。 26 | // parent_index = 3 27 | // left child_index = 2 * parent_index + 1; // 左子节点索引 7 28 | // => parent_index = (index - 1) / 2; // 父节点索引 3 29 | // right child_index = 2 * parent_index + 2; // 右子节点索引 8 30 | // => parent_index = (index - 1) / 2; // 父节点索引 3 31 | 32 | // 100 90 80 70 60 50 40 33 | // 0 1 2 3 4 5 6 34 | 35 | 36 | // 如果当前节点比父节点大,则交换并继续向上调整 37 | if (h->data[index] > h->data[parent_index]) { 38 | swap(&h->data[index], &h->data[parent_index]); 39 | heapify_up(h, parent_index); 40 | } 41 | } 42 | 43 | // "下沉"操作:将指定索引的节点向下调整,以维持最大堆性质 44 | static void heapify_down(Heap* h, size_t index) { 45 | size_t left_child_index = 2 * index + 1; 46 | size_t right_child_index = 2 * index + 2; 47 | size_t largest_index = index; 48 | 49 | // 找出当前节点、左子节点、右子节点中的最大值索引 50 | if (left_child_index < h->size && h->data[left_child_index] > h->data[largest_index]) { 51 | largest_index = left_child_index; 52 | } 53 | if (right_child_index < h->size && h->data[right_child_index] > h->data[largest_index]) { 54 | largest_index = right_child_index; 55 | } 56 | 57 | // 如果最大值不是当前节点,则交换并继续向下调整 58 | if (largest_index != index) { 59 | swap(&h->data[index], &h->data[largest_index]); 60 | heapify_down(h, largest_index); 61 | } 62 | } 63 | 64 | // 动态扩容堆的内部存储 65 | static int heap_resize(Heap* h) { 66 | size_t new_capacity = h->capacity * 2; 67 | Item* new_data = (Item*)realloc(h->data, new_capacity * sizeof(Item)); 68 | if (!new_data) { 69 | perror("Failed to resize heap"); 70 | return -1; 71 | } 72 | h->data = new_data; 73 | h->capacity = new_capacity; 74 | printf("[DEBUG] Heap resized to capacity %zu\n", new_capacity); 75 | return 0; 76 | } 77 | 78 | 79 | // ========== ADT 接口函数实现 ========== 80 | 81 | Heap* heap_create(size_t initial_capacity) { 82 | if (initial_capacity == 0) { 83 | initial_capacity = 8; // 默认一个较小的初始容量 84 | } 85 | 86 | Heap* h = (Heap*)malloc(sizeof(Heap)); 87 | if (!h) { 88 | perror("Failed to allocate memory for heap structure"); 89 | return NULL; 90 | } 91 | 92 | h->data = (Item*)malloc(initial_capacity * sizeof(Item)); 93 | if (!h->data) { 94 | perror("Failed to allocate memory for heap data"); 95 | free(h); 96 | return NULL; 97 | } 98 | 99 | h->size = 0; 100 | h->capacity = initial_capacity; 101 | 102 | return h; 103 | } 104 | 105 | void heap_destroy(Heap** h) { 106 | if (h && *h) { 107 | free((*h)->data); // 释放数据数组 108 | (*h)->data = NULL; 109 | free(*h); // 释放堆结构体 110 | *h = NULL; // 将外部指针置为NULL,防止野指针 111 | } 112 | } 113 | 114 | int heap_insert(Heap* h, Item value) { 115 | if (!h) return -1; 116 | 117 | // 如果堆满了,进行扩容 118 | if (h->size == h->capacity) { 119 | if (heap_resize(h) != 0) { 120 | return -1; // 扩容失败 121 | } 122 | } 123 | 124 | // 1. 将新元素添加到数组末尾 125 | h->data[h->size] = value; 126 | // 2. 对新元素执行 "上浮" 操作 127 | heapify_up(h, h->size); 128 | // 3. 增加堆的大小 129 | h->size++; 130 | 131 | return 0; 132 | } 133 | 134 | int heap_extract_max(Heap* h, Item* p_max_value) { 135 | if (!h || is_heap_empty(h)) { 136 | return -1; // 堆为空或无效 137 | } 138 | 139 | // 1. 堆顶元素即为最大值 140 | *p_max_value = h->data[0]; 141 | 142 | // 2. 将最后一个元素移到堆顶 143 | h->data[0] = h->data[h->size - 1]; 144 | h->size--; 145 | 146 | // 3. 对新的堆顶元素执行 "下沉" 操作 147 | if (h->size > 0) { 148 | heapify_down(h, 0); 149 | } 150 | 151 | return 0; 152 | } 153 | 154 | int heap_peek(const Heap* h, Item* p_peek_value) { 155 | if (!h || is_heap_empty(h)) { 156 | return -1; 157 | } 158 | *p_peek_value = h->data[0]; 159 | return 0; 160 | } 161 | 162 | int is_heap_empty(const Heap* h) { 163 | return (h == NULL || h->size == 0); 164 | } 165 | 166 | size_t heap_size(const Heap* h) { 167 | if (!h) return 0; 168 | return h->size; 169 | } 170 | 171 | void heap_print_debug(const Heap* h) { 172 | if (!h) { 173 | printf("Heap is NULL.\n"); 174 | return; 175 | } 176 | if (is_heap_empty(h)) { 177 | printf("Heap is empty.\n"); 178 | return; 179 | } 180 | 181 | printf("Heap (size=%zu, capacity=%zu): [ ", h->size, h->capacity); 182 | for (size_t i = 0; i < h->size; ++i) { 183 | printf("%d ", h->data[i]); 184 | } 185 | printf("]\n"); 186 | } -------------------------------------------------------------------------------- /dsc-code/sequential_stack/sequential_stack.c: -------------------------------------------------------------------------------- 1 | #include "sequential_stack.h" 2 | #include // for malloc, free 3 | #include // for memcpy 4 | #include // for assert 5 | 6 | // --- Private Structure Definition --- 7 | // 这是栈的实际内部结构,对用户不可见。 8 | struct Stack { 9 | void* data; // 指向存储数据的连续内存块 (我们的"数组") 10 | size_t capacity; // 栈的总容量(元素个数) 11 | size_t element_size; // 每个元素的大小(字节) 12 | int top; // 栈顶索引,-1 表示空栈 13 | }; 14 | 15 | // --- API Function Implementations --- 16 | 17 | Stack* stack_create(size_t capacity, size_t element_size) { 18 | if (capacity == 0 || element_size == 0) { 19 | return NULL; // 无效参数 20 | } 21 | 22 | // 1. 为栈结构体本身分配内存 23 | Stack* stack = (Stack*)malloc(sizeof(Stack)); 24 | if (stack == NULL) { 25 | return NULL; // 内存分配失败 26 | } 27 | 28 | // 2. 为存储数据的数组分配内存 29 | stack->data = malloc(capacity * element_size); 30 | if (stack->data == NULL) { 31 | free(stack); // 清理已分配的栈结构体,防止内存泄漏 32 | return NULL; // 内存分配失败 33 | } 34 | 35 | // 3. 初始化栈的属性 36 | stack->capacity = capacity; 37 | stack->element_size = element_size; 38 | stack->top = -1; // -1 代表空栈 39 | 40 | return stack; 41 | } 42 | 43 | void stack_destroy(Stack** p_stack) { 44 | if (p_stack == NULL || *p_stack == NULL) { 45 | return; // 空指针,无需操作 46 | } 47 | 48 | free((*p_stack)->data); // 1. 释放数据区内存 49 | free(*p_stack); // 2. 释放栈结构体内存 50 | *p_stack = NULL; // 3. 将外部指针置为NULL,防止野指针 51 | } 52 | 53 | bool stack_push(Stack* stack, const void* element_data) { 54 | if (stack == NULL || element_data == NULL) { 55 | return false; 56 | } 57 | if (stack_is_full(stack)) { 58 | return false; // 栈满,无法压入 59 | } 60 | 61 | stack->top++; 62 | // 计算要插入的位置的地址 63 | // 使用 (char*) 是因为对 void* 的指针算术不是标准C 64 | // char* 按字节移动,最为安全 65 | // 它的目标是计算出新元素在物理内存中应该存放的位置。 66 | void* target_address = (char*)stack->data + (stack->top * stack->element_size); 67 | 68 | // Stack* my_stack = stack_create(5, sizeof(int)); 69 | // my_stack->data 是一个指向连续内存块的指针 20 70 | // my_stack->capacity 是 5 71 | // my_stack->element_size 是 4 72 | // my_stack->top 是 -1 73 | // 74 | // stack->top++; // 先将 top 增加,指向下一个空位 75 | // top => 0 76 | // top 0 => index 77 | 78 | // stack->top * stack->element_size 计算字节偏移量 79 | // stack->element_size 是每个元素的大小 4 80 | // 0*4 => 81 | 82 | // (char*)stack->data 83 | // C语言中,指针算术是基于类型的,所以我们需要将 void* 转换为 char*, 84 | // 因为 char 是 1 字节的类型,这样可以按字节进行偏移。 85 | // Pointer arithmetic 86 | // stack->data void* , C语言里,不允许对Void* 进行指针算术操作,void* + 1 是不合法的。到底是移动多少字节? 87 | // char 1 88 | 89 | // 因此,当我们把任何指针的类型转换为 char* 时,我们就相当于告诉编译器:请现在把整个指针看看作一个 90 | // 字节数组来处理,这样就可以按字节进行偏移了。(指向一个单字节数据块的指针) 91 | // (char*)stack -> data + N : 获取stack->data 的地址,并且向后移动 N个字节 92 | // void* target_address = (char*)stack->data + (stack->top * stack->element_size); 93 | 94 | // void* target_address = (char*)stack->data + 0 95 | // 取栈数据区的起始地址 stack->data,把它当作一个字节指针,向后移动0个字节,得到新的地址 96 | // 就是我们新元素的目标存放地址。 97 | 98 | 99 | // *target_address = element_data; 100 | // 将用户数据拷贝到栈的内存中 101 | memcpy(target_address, element_data, stack->element_size); 102 | 103 | return true; 104 | } 105 | 106 | bool stack_pop(Stack* stack, void* output_buffer) { 107 | if (stack == NULL || output_buffer == NULL) { 108 | return false; 109 | } 110 | if (stack_is_empty(stack)) { 111 | return false; // 栈空,无法弹出 112 | } 113 | 114 | // 计算栈顶元素的地址 115 | void* source_address = (char*)stack->data + (stack->top * stack->element_size); 116 | // my_stack->data => 10,20,30,40,50 117 | // capacity = 5 118 | // element_size = 4 119 | // top = 4 120 | 121 | // int received_value; 122 | // bool success = stack_pop(my_stack, &received_value); 123 | 124 | 125 | 126 | // 将栈顶数据拷贝到用户的缓冲区 127 | memcpy(output_buffer, source_address, stack->element_size); 128 | 129 | stack->top--; 130 | // 逻辑删除:通过移动指针来宣告一块数据无效,而不是花费时间去清理它 131 | 132 | return true; 133 | } 134 | 135 | bool stack_peek(Stack* stack, void* output_buffer) { 136 | if (stack == NULL || output_buffer == NULL) { 137 | return false; 138 | } 139 | if (stack_is_empty(stack)) { 140 | return false; // 栈空,无法查看 141 | } 142 | 143 | void* source_address = (char*)stack->data + (stack->top * stack->element_size); 144 | memcpy(output_buffer, source_address, stack->element_size); 145 | 146 | // 与 pop 的唯一区别:不移动 top 指针 147 | return true; 148 | } 149 | 150 | bool stack_is_empty(const Stack* stack) { 151 | if (stack == NULL) { 152 | return true; // 视作空 153 | } 154 | return stack->top == -1; 155 | } 156 | 157 | bool stack_is_full(const Stack* stack) { 158 | if (stack == NULL) { 159 | return false; // NULL栈不满 160 | } 161 | // 注意类型转换,避免有符号和无符号整数比较的警告 162 | return stack->top == (int)(stack->capacity - 1); 163 | } 164 | 165 | size_t stack_get_size(const Stack* stack) { 166 | if (stack == NULL) { 167 | return 0; 168 | } 169 | return (size_t)(stack->top + 1); 170 | } 171 | 172 | size_t stack_get_capacity(const Stack* stack) { 173 | if (stack == NULL) { 174 | return 0; 175 | } 176 | return stack->capacity; 177 | } -------------------------------------------------------------------------------- /dsc-code/linked_stack/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "linked_stack.h" 3 | 4 | // 定义一个自定义结构体用于测试 5 | typedef struct { 6 | int id; 7 | char name[20]; 8 | } Record; 9 | 10 | void print_record(const Record* r) { 11 | if (r) { 12 | printf("Record(id: %d, name: \"%s\")", r->id, r->name); 13 | } 14 | } 15 | 16 | void test_record_stack() { 17 | printf("--- Testing Struct (Record) Linked Stack ---\n"); 18 | // 创建时不再需要容量 19 | Stack* record_stack = stack_create(sizeof(Record)); 20 | 21 | if (!record_stack) { 22 | printf("Failed to create record stack.\n"); 23 | return; 24 | } 25 | 26 | printf("Stack created. Is empty? %s\n", stack_is_empty(record_stack) ? "Yes" : "No"); 27 | 28 | // 创建一些数据并压栈 29 | Record r1 = { 1, "Alice" }; 30 | Record r2 = { 2, "Bob" }; 31 | Record r3 = { 3, "Charlie" }; 32 | 33 | printf("Pushing "); print_record(&r1); printf("...\n"); 34 | stack_push(record_stack, &r1); 35 | 36 | printf("Pushing "); print_record(&r2); printf("...\n"); 37 | stack_push(record_stack, &r2); 38 | 39 | printf("Pushing "); print_record(&r3); printf("...\n"); 40 | stack_push(record_stack, &r3); 41 | 42 | printf("Current stack size: %zu\n", stack_get_size(record_stack)); 43 | 44 | Record peeked_record; 45 | if (stack_peek(record_stack, &peeked_record)) { 46 | printf("Peek at top: "); 47 | print_record(&peeked_record); 48 | printf("\n"); 49 | } 50 | 51 | printf("\nPopping all elements:\n"); 52 | while (!stack_is_empty(record_stack)) { 53 | Record popped_record; 54 | if (stack_pop(record_stack, &popped_record)) { 55 | printf("Popped: "); 56 | print_record(&popped_record); 57 | printf(" | New size: %zu\n", stack_get_size(record_stack)); 58 | } 59 | } 60 | 61 | printf("\nIs stack empty now? %s\n", stack_is_empty(record_stack) ? "Yes" : "No"); 62 | 63 | // 销毁栈 64 | stack_destroy(&record_stack); 65 | printf("Stack destroyed. Pointer is now %s\n", record_stack == NULL ? "NULL" : "Not NULL"); 66 | } 67 | 68 | 69 | // ------------------------以下内容的函数是后面的课程-------------------------------------- 70 | // 检查括号是否匹配的辅助函数 71 | bool is_opener(char c) { 72 | return c == '(' || c == '{' || c == '['; 73 | } 74 | 75 | bool matches(char opener, char closer) { 76 | return (opener == '(' && closer == ')') || 77 | (opener == '{' && closer == '}') || 78 | (opener == '[' && closer == ']'); 79 | } 80 | 81 | bool check_brackets(const char* code) { 82 | 83 | Stack* stack = stack_create(sizeof(char)); 84 | 85 | if (!stack) { 86 | 87 | fprintf(stderr, "Failed to create stack for bracket checking.\n"); 88 | return false; 89 | } 90 | 91 | size_t len = strlen(code); 92 | for (size_t i = 0; i < len; i++) { 93 | char current_char = code[i]; 94 | 95 | // 如果是开括号,则压入栈 96 | if (is_opener(current_char)) { 97 | stack_push(stack, ¤t_char); 98 | 99 | // 如果是闭括号,则检查栈顶元素 100 | } 101 | else if (current_char == ')' || current_char == '}' || current_char == ']') { 102 | 103 | // 错误情况1:栈为空,闭括号多余 104 | if (stack_is_empty(stack)) { 105 | fprintf(stderr, "Unmatched closing bracket '%c' found.\n", current_char); 106 | stack_destroy(&stack); 107 | return false; // 闭括号多余 108 | } 109 | 110 | char popped_opener; 111 | // 从栈顶弹出一个开括号进行比较 112 | stack_pop(stack, &popped_opener);// 将弹出的char存入popped_opener 113 | 114 | // 错误情况2:开括号和闭括号不匹配 (类型不匹配) 115 | if (!matches(popped_opener, current_char)) { 116 | 117 | fprintf(stderr, "Mismatched brackets: '%c' does not match '%c'.\n", popped_opener, current_char); 118 | stack_destroy(&stack); 119 | return false; // 开闭括号不匹配 120 | } 121 | } 122 | } 123 | 124 | // 错误情况3:遍历结束,但是栈中仍然有未关闭的开括号 125 | if (!stack_is_empty(stack)) { 126 | 127 | char unclosed_opener; 128 | 129 | // 查看一下是哪一个开括号没有关闭 130 | stack_peek(stack, &unclosed_opener); 131 | fprintf(stderr, "Unmatched opening brackets remain in stack.\n"); 132 | stack_destroy(&stack); 133 | return false; // 有未关闭的开括号 134 | } 135 | 136 | 137 | // 销毁栈 138 | stack_destroy(&stack); 139 | return true; 140 | } 141 | 142 | void check() { 143 | const char* test_cases[] = { 144 | "int main() { int x = (1 + 2); return 0; }", // 有效 145 | "void func(int a[]);", // 有效 146 | "([{}])", // 有效 147 | "int arr[5] = {1, 2, 3};", // 有效 148 | "", // 有效 (空字符串) 149 | "abc", // 有效 (无括号) 150 | "([)]", // 无效: 交叉不匹配 151 | "((()", // 无效: 开括号未关闭 152 | "())", // 无效: 闭括号无对应 153 | "if (x > 0) { printf(\"hello\");", // 无效: } 未关闭 154 | "int y = { ( [ ] ) };" // 有效: 复杂嵌套 155 | }; 156 | 157 | int num_cases = sizeof(test_cases) / sizeof(test_cases[0]); 158 | 159 | for (int i = 0; i < num_cases; i++) { 160 | printf("正在检查: \"%s\"\n", test_cases[i]); 161 | if (check_brackets(test_cases[i])) { 162 | printf("结果: -> 有效\n\n"); 163 | } 164 | else { 165 | printf("结果: -> 无效\n\n"); 166 | } 167 | } 168 | } 169 | 170 | int main() { 171 | // ---------------测试链式栈--------------------下面的注释取消即可-------------------- 172 | // test_record_stack(); 173 | 174 | 175 | // ------------------------以下内容的函数是后面的课程-------------------------------------- 176 | 177 | // 测试括号匹配器(后面的课程) 178 | check(); 179 | 180 | return 0; 181 | } -------------------------------------------------------------------------------- /dsc-code/05-linked-queue/generic_linked_queue.c: -------------------------------------------------------------------------------- 1 | #include "generic_linked_queue.h" 2 | #include 3 | #include 4 | 5 | // --- Private Structure Definitions --- 6 | 7 | // 内部节点结构,对用户不可见 8 | typedef struct Node { 9 | void* data; // 指向为该节点元素动态分配的内存 10 | struct Node* next; // 指向队列中的下一个节点 11 | } Node; 12 | 13 | // 链式队列的实际管理结构 14 | struct LinkedQueue { 15 | Node* front; // 指向队头的节点 16 | Node* rear; // 指向队尾的节点 17 | size_t element_size; // 每个元素的数据大小 18 | size_t size; // 队列中当前的元素数量 19 | }; 20 | 21 | // --- API Function Implementations --- 22 | 23 | Queue* queue_create(size_t element_size) { 24 | if (element_size == 0) return NULL; 25 | Queue* q = (Queue*)malloc(sizeof(Queue)); 26 | if (!q) return NULL; 27 | 28 | q->front = NULL; // 初始化时,队列为空,头尾指针都为NULL 29 | q->rear = NULL; 30 | q->element_size = element_size; 31 | q->size = 0; 32 | 33 | return q; 34 | } 35 | 36 | void queue_destroy(Queue** p_queue) { 37 | if (p_queue && *p_queue) { 38 | Queue* q = *p_queue; 39 | Node* current = q->front; 40 | while (current != NULL) { 41 | Node* temp = current; 42 | current = current->next; 43 | free(temp->data); // 1. 释放节点的数据 44 | free(temp); // 2. 释放节点本身 45 | } 46 | free(q); 47 | *p_queue = NULL; 48 | } 49 | } 50 | 51 | bool queue_enqueue(Queue* queue, const void* element_data) { 52 | /* 53 | * 函数功能: [工业级] 将一个新元素添加到链表队尾。 54 | * 教学案例 1 (常规情况): 队列中已有元素 A -> B。 55 | * front -> [A] -> [B] <- rear 56 | * 我们要入队一个新的元素 'C'。 57 | * 58 | * 教学案例 2 (特殊情况): 队列为空。 59 | * front -> NULL, rear -> NULL 60 | * 我们要入队第一个元素 'A'。 61 | */ 62 | 63 | // 步骤 1: [防御性编程] 检查指针有效性。 64 | if (!queue || !element_data) { 65 | return false; 66 | } 67 | 68 | // 步骤 2: 为新节点本身分配内存。 69 | Node* new_node = (Node*)malloc(sizeof(Node)); 70 | if (!new_node) { 71 | return false; // 系统内存不足,入队失败 72 | } 73 | 74 | // 步骤 3: 为新节点的数据区分配内存。 75 | new_node->data = malloc(queue->element_size); 76 | if (!new_node->data) { 77 | free(new_node); // 如果数据区分配失败,必须清理已分配的节点,防止内存泄漏。 78 | return false; 79 | } 80 | 81 | // 步骤 4: 拷贝用户数据到新节点的数据区。 82 | memcpy(new_node->data, element_data, queue->element_size); 83 | new_node->next = NULL; // 新节点总是被加在队尾,所以它的next永远是NULL。 84 | 85 | // 步骤 5: [核心逻辑] 将新节点链接到队列尾部。 86 | if (queue_is_empty(queue)) { 87 | // --- 处理特殊情况:这是队列的第一个元素 --- 88 | // 案例 2: 队列为空,front和rear都为NULL。 89 | // front -> NULL 90 | // rear -> NULL 91 | // new_node -> [A|next=NULL] 92 | // 让 front 和 rear 同时指向这个新节点。 93 | // front -> [A] <- rear 94 | queue->front = new_node; 95 | queue->rear = new_node; 96 | } 97 | else { 98 | // --- 处理常规情况:队列非空 --- 99 | // 案例 1: 队列为 A -> B。 100 | // rear 指向节点B: [B|next=NULL] 101 | // new_node: [C|next=NULL] 102 | // 关键一步: 让当前队尾节点(B)的next指针指向新节点(C)。 103 | // 链接后: [B|next=ptr_to_C] -> [C|next=NULL] 104 | queue->rear->next = new_node; 105 | // 然后,更新rear指针,让它指向新的队尾节点(C)。 106 | // A -> B -> [C] <- rear 107 | queue->rear = new_node; 108 | } 109 | 110 | // 步骤 6: 更新队列大小。 111 | queue->size++; 112 | return true; 113 | } 114 | 115 | bool queue_dequeue(Queue* queue, void* output_buffer) { 116 | /* 117 | * 函数功能: [工业级] 从链表队头移除一个元素。 118 | * 教学案例 1 (常规情况): 队列为 A -> B -> C。 119 | * front -> [A] -> [B] -> [C] <- rear 120 | * 我们要出队元素 'A'。 121 | * 122 | * 教学案例 2 (特殊情况): 队列中只有一个元素。 123 | * front -> [A] <- rear 124 | * 我们要出队元素 'A'。 125 | */ 126 | 127 | // 步骤 1: [防御性编程] 检查指针有效性及队列是否为空。 128 | if (queue_is_empty(queue) || !output_buffer) { 129 | return false; 130 | } 131 | 132 | // 步骤 2: 创建一个临时指针,指向即将被移除的队头节点。 133 | // 案例 1 & 2: temp -> [A] 134 | Node* temp = queue->front; 135 | 136 | // 步骤 3: 拷贝队头节点的数据到用户的缓冲区。 137 | memcpy(output_buffer, temp->data, queue->element_size); 138 | 139 | // 步骤 4: [核心逻辑] 更新front指针,使其指向下一个节点。 140 | // 案例 1: 原front指向A,A的next指向B。 141 | // 新front = temp->next,所以新front指向了节点B。 142 | // 逻辑上队列变为: front -> [B] -> [C] <- rear 143 | queue->front = temp->next; 144 | 145 | // 步骤 5: [处理特殊情况] 如果更新后的front为NULL,说明队列变空了。 146 | // 案例 2: 原队列只有一个元素A,A的next是NULL。 147 | // 执行步骤4后,queue->front变为NULL。 148 | // 这表示队列中已无任何元素,此时必须同时更新rear指针也为NULL, 149 | // 以保持队列状态的一致性。 150 | if (queue->front == NULL) { 151 | queue->rear = NULL; 152 | } 153 | 154 | // 步骤 6: 释放被移除节点的内存(数据区和节点本身)。 155 | // 案例 1 & 2: 释放 temp 指向的节点A的数据区和节点A本身。 156 | free(temp->data); 157 | free(temp); 158 | 159 | // 步骤 7: 更新队列大小。 160 | queue->size--; 161 | return true; 162 | } 163 | 164 | bool queue_peek(const Queue* queue, void* output_buffer) { 165 | if (queue_is_empty(queue) || !output_buffer) { 166 | return false; 167 | } 168 | // Peek操作非常简单:只拷贝队头的数据,不进行任何修改。 169 | memcpy(output_buffer, queue->front->data, queue->element_size); 170 | return true; 171 | } 172 | 173 | bool queue_is_empty(const Queue* queue) { 174 | if (!queue) return true; 175 | return queue->size == 0; // 或者判断 queue->front == NULL 176 | } 177 | 178 | size_t queue_get_size(const Queue* queue) { 179 | if (!queue) return 0; 180 | return queue->size; 181 | } -------------------------------------------------------------------------------- /dsc-code/trie/main.c: -------------------------------------------------------------------------------- 1 | #define _CRT_SECURE_NO_WARNINGS 2 | #include 3 | #include 4 | #include 5 | #include "trie.h" 6 | 7 | // --- 自定义数据结构 --- 8 | // 为了测试泛型Trie,我们定义一个Person结构体来作为存储的值。 9 | typedef struct { 10 | char* name; 11 | int age; 12 | } Person; 13 | 14 | // --- 自定义值的创建和销毁函数 --- 15 | 16 | /** 17 | * @brief 创建一个新的Person实例(辅助函数)。 18 | */ 19 | Person* Person_Create(const char* name, int age) { 20 | Person* p = (Person*)malloc(sizeof(Person)); 21 | if (!p) return NULL; 22 | // 为名字分配内存并拷贝 23 | p->name = (char*)malloc(strlen(name) + 1); 24 | if (!p->name) { 25 | free(p); 26 | return NULL; 27 | } 28 | strcpy(p->name, name); 29 | p->age = age; 30 | return p; 31 | } 32 | 33 | /** 34 | * @brief 用于Trie的ValueDestroyer,负责释放Person结构体的内存。 35 | * @param value 一个void*指针,指向一个Person结构体。 36 | */ 37 | void Person_Destroy(void* value) { 38 | if (!value) return; 39 | Person* p = (Person*)value; 40 | printf(" (Destroying value for Person: %s)\n", p->name); 41 | free(p->name); // 释放内部的字符串 42 | free(p); // 释放结构体本身 43 | } 44 | 45 | 46 | /** 47 | * @brief 打印搜索结果的辅助函数。 48 | */ 49 | void print_search_result(const Trie* trie, const char* key) { 50 | Person* p = (Person*)Trie_Search(trie, key); 51 | if (p) { 52 | printf("Search for '%s': FOUND. Value -> { Name: %s, Age: %d }\n", key, p->name, p->age); 53 | } 54 | else { 55 | printf("Search for '%s': NOT FOUND.\n", key); 56 | } 57 | } 58 | 59 | 60 | int main(void) { 61 | printf("--- Trie Test Suite ---\n\n"); 62 | 63 | // 1. 创建Trie,并传入Person的析构函数 64 | printf("1. Creating Trie with a custom value destroyer...\n"); 65 | Trie* trie = Trie_Create(Person_Destroy); 66 | if (!trie) { 67 | fprintf(stderr, "Failed to create Trie.\n"); 68 | return 1; 69 | } 70 | printf("Trie created successfully.\n\n"); 71 | 72 | // 2. 插入数据 73 | printf("2. Testing Trie_Insert...\n"); 74 | // 注意:Trie会接管这些Person指针的内存管理,我们不需要在main中手动释放它们 75 | Trie_Insert(trie, "apple", Person_Create("iPhone", 15)); 76 | Trie_Insert(trie, "app", Person_Create("AppStore", 16)); 77 | Trie_Insert(trie, "application", Person_Create("GenericApp", 5)); 78 | Trie_Insert(trie, "banana", Person_Create("Fruit", 2)); 79 | Trie_Insert(trie, "band", Person_Create("MusicGroup", 10)); 80 | printf("Finished inserting keys: 'apple', 'app', 'application', 'banana', 'band'.\n\n"); 81 | 82 | // 3. 搜索测试 83 | printf("3. Testing Trie_Search...\n"); 84 | print_search_result(trie, "apple"); 85 | print_search_result(trie, "app"); 86 | print_search_result(trie, "application"); 87 | print_search_result(trie, "banana"); 88 | print_search_result(trie, "band"); 89 | print_search_result(trie, "ban"); // 不存在的键 90 | print_search_result(trie, "apples"); // 不存在的键 91 | printf("\n"); 92 | 93 | // 4. 前缀测试 94 | printf("4. Testing Trie_StartsWith...\n"); 95 | printf("Prefix 'app': %s\n", Trie_StartsWith(trie, "app") ? "true" : "false"); 96 | printf("Prefix 'ban': %s\n", Trie_StartsWith(trie, "ban") ? "true" : "false"); 97 | printf("Prefix 'bana': %s\n", Trie_StartsWith(trie, "bana") ? "true" : "false"); 98 | printf("Prefix 'cat': %s\n", Trie_StartsWith(trie, "cat") ? "false" : "false"); 99 | printf("Prefix 'application': %s\n", Trie_StartsWith(trie, "application") ? "true" : "false"); 100 | printf("\n"); 101 | 102 | // 5. 删除测试 103 | printf("5. Testing Trie_Delete...\n"); 104 | 105 | // 场景A: 删除一个作为其他键前缀的键 ("app") 106 | // 预期:'app'的value变为NULL,但节点保留,因为'apple'和'application'需要它。 107 | printf("\n--- Deleting 'app' (is a prefix of others) ---\n"); 108 | Trie_Delete(trie, "app"); 109 | print_search_result(trie, "app"); // 应该找不到了 110 | print_search_result(trie, "apple"); // 应该还在 111 | printf("Prefix 'app' after deleting key 'app': %s\n", Trie_StartsWith(trie, "app") ? "true" : "false"); // 前缀应该还存在 112 | 113 | // 场景B: 删除一个叶子键 ("banana") 114 | // 预期:'banana'被删除,并且'nana'路径的节点被物理移除。 115 | printf("\n--- Deleting 'banana' (is a leaf path) ---\n"); 116 | Trie_Delete(trie, "banana"); 117 | print_search_result(trie, "banana"); // 应该找不到了 118 | print_search_result(trie, "band"); // 应该还在 119 | printf("Prefix 'bana' after deleting key 'banana': %s\n", Trie_StartsWith(trie, "bana") ? "false" : "false"); // 前缀应该不存在了 120 | printf("Prefix 'ban' after deleting key 'banana': %s\n", Trie_StartsWith(trie, "ban") ? "true" : "false"); // 但'ban'前缀应因'band'而存在 121 | 122 | // 场景C: 删除一个不存在的键 123 | printf("\n--- Deleting 'zebra' (non-existent) ---\n"); 124 | Trie_Delete(trie, "zebra"); 125 | printf("Attempted to delete a non-existent key. Let's check if others are intact.\n"); 126 | print_search_result(trie, "apple"); // 应该还在 127 | print_search_result(trie, "band"); // 应该还在 128 | 129 | // 场景D: 删除最后一个依赖于某个前缀的键 130 | printf("\n--- Deleting 'application' ---\n"); 131 | Trie_Delete(trie, "application"); 132 | print_search_result(trie, "application"); 133 | printf("Prefix 'appli' after deleting key 'application': %s\n", Trie_StartsWith(trie, "appli") ? "false" : "false"); // 前缀应该不存在了 134 | 135 | printf("\n"); 136 | 137 | // 6. 销毁Trie 138 | printf("6. Testing Trie_Destroy...\n"); 139 | printf("Calling Trie_Destroy. The custom destroyer should be called for remaining values ('apple', 'band').\n"); 140 | Trie_Destroy(trie); 141 | printf("Trie destroyed.\n\n"); 142 | 143 | printf("--- Test Suite Finished ---\n"); 144 | return 0; 145 | } -------------------------------------------------------------------------------- /dsc-code/dsc-code/DynamicArray.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "DynamicArray.h" 6 | 7 | #define INITIAL_CAPACITY 10 8 | 9 | // 内部辅助函数:当容量不足时候,进行扩容 10 | 11 | static int resize_array(DynamicArray *arr, size_t new_capacity) 12 | { 13 | 14 | // realloc arr->data 老地皮的地址 15 | 16 | Data *new_data = realloc(arr->data, new_capacity * sizeof(Data)); 17 | 18 | // 1. 最佳情况,原地扩容:地址没变,原先的老地址旁边正好有连续不断的空白地址。 19 | 20 | // 2. 普遍情况,搬家扩容:地址变化,原先的数据全部搬迁到新的地址,并且,清空原先旧地址的数据 21 | 22 | if (!new_data) 23 | { 24 | 25 | // realloc失败,内存不足! 26 | 27 | return -1; 28 | 29 | // 我这里一返回,意味着,原先的那个arr->data 还在! 30 | } 31 | 32 | // realloc成功之后,才会用新的地址去更新结构体 33 | 34 | arr->data = new_data; 35 | 36 | arr->capacity = new_capacity; 37 | 38 | return 0; 39 | } 40 | 41 | // 创建并初始化一个动态数组 42 | 43 | DynamicArray *create_array(size_t initial_capcity) 44 | { 45 | 46 | if (initial_capcity == 0) 47 | 48 | { 49 | 50 | initial_capcity = INITIAL_CAPACITY; 51 | } 52 | 53 | DynamicArray *arr = (DynamicArray *)malloc(sizeof(DynamicArray)); 54 | 55 | /* 56 | 57 | typedef struct { 58 | 59 | Data* data; // 指向存储数据的连续内存块; 指向一个数组的首地址 60 | 61 | // int* data; // 说白了,我们可以放很多int类型的数据,因为指向的是一个连续不断的地址空间 62 | 63 | 64 | 65 | size_t size; 66 | 67 | 68 | 69 | size_t capacity; 70 | 71 | } DynamicArray; 72 | 73 | 74 | 75 | */ 76 | 77 | if (!arr) 78 | return NULL; 79 | 80 | arr->data = (Data *)malloc(initial_capcity * sizeof(Data)); 81 | 82 | // arr作为结构体变量,它有三个成员,这三个成员中,最重要的就是Data* data; 83 | 84 | // data指向一个新的连续不断的内存空间 85 | 86 | // 而整个空间,现在被我们指向的是一个capcity = 10的数组 87 | 88 | if (!arr->data) 89 | { 90 | 91 | free(arr); 92 | 93 | return NULL; 94 | } 95 | 96 | arr->size = 0; 97 | 98 | arr->capacity = initial_capcity; 99 | 100 | return arr; 101 | } 102 | 103 | // 销毁数组,释放内存 104 | 105 | void destroy_array(DynamicArray *arr) 106 | { 107 | 108 | if (arr) 109 | { 110 | 111 | free(arr->data); 112 | 113 | free(arr); 114 | } 115 | } 116 | 117 | // 在数组末尾追加元素 Amortized O(1) 118 | 119 | void array_append(DynamicArray *arr, Data value) 120 | { 121 | 122 | // 检查是否要扩容 123 | 124 | if (arr->size >= arr->capacity) 125 | { 126 | 127 | size_t new_capacity = arr->capacity * 2; 128 | 129 | resize_array(arr, new_capacity); 130 | } 131 | 132 | arr->data[arr->size] = value; 133 | 134 | arr->size++; 135 | } 136 | 137 | // 读取指定的索引元素 138 | 139 | // 返回一个指针,以便能够检查是否成功,如果索引无效,返回NULL 140 | 141 | Data *array_read(DynamicArray *arr, size_t index) 142 | { 143 | 144 | if (index >= arr->size) 145 | { 146 | 147 | return NULL; 148 | } 149 | 150 | // address = base_address + index * sizeof(element); 151 | 152 | return &(arr->data[index]); 153 | } 154 | 155 | // 更新指定索引的元素 156 | 157 | // 返回0表示成功,返回-1表示失败 158 | 159 | int array_update(DynamicArray *arr, size_t index, Data value) 160 | { 161 | 162 | if (index >= arr->size) 163 | { 164 | 165 | return -1; 166 | } 167 | 168 | arr->data[index] = value; 169 | 170 | return 0; 171 | } 172 | 173 | int array_insert(DynamicArray *arr, size_t index, Data value) 174 | { 175 | 176 | if (index > arr->size) 177 | { 178 | 179 | return -1; 180 | 181 | // 索引越界,允许在末尾插入, index == size 182 | } 183 | 184 | // 检查是否要扩容 185 | 186 | if (arr->size >= arr->capacity) 187 | { 188 | 189 | // 函数合约 The Function Contract 190 | 191 | if (resize_array(arr, arr->capacity * 2) != 0) 192 | { 193 | 194 | return -1; 195 | } 196 | } 197 | 198 | for (size_t i = arr->size; i > index; --i) 199 | { 200 | 201 | arr->data[i] = arr->data[i - 1]; 202 | } 203 | 204 | /* 205 | 206 | 207 | 208 | arr->data = [10, 20, 30, 40] 209 | 210 | arr->data = [10, 20, 30, 40, □] 211 | 212 | 213 | 214 | 215 | 216 | arr->data = [10, 99, 20, 30, 40, □] 217 | 218 | arr->size = 4; 219 | 220 | index = 1 目标位置 221 | 222 | capacity 8 223 | 224 | 225 | 226 | 1. 227 | 228 | i arr->size i = 4; 229 | 230 | i > index 4>1 t 231 | 232 | arr->data[4] = arr->data[3]; 233 | 234 | 把索引3的值(40) 复制到 索引4上(空白) 235 | 236 | arr->data = [10, 20, 30, □, 40] 237 | 238 | 239 | 240 | 241 | 242 | */ 243 | 244 | arr->data[index] = value; 245 | 246 | arr->size++; 247 | 248 | return 0; 249 | } 250 | 251 | // 删除指定索引的元素 252 | 253 | int array_delete(DynamicArray *arr, size_t index) 254 | { 255 | 256 | if (index >= arr->size) 257 | { 258 | 259 | return -1; 260 | } 261 | 262 | for (size_t i = index; i < arr->size - 1; ++i) 263 | { 264 | 265 | arr->data[i] = arr->data[i + 1]; 266 | } 267 | 268 | /* 269 | 270 | 271 | 272 | arr->data = [10, 20, 30, 40, 50] 273 | 274 | 275 | 276 | arr->data = [10, , 30, 40, □] 277 | 278 | 279 | 280 | 281 | 282 | */ 283 | 284 | arr->size--; 285 | 286 | // size capacity 1/4, 287 | 288 | if (arr->size > 0 && arr->size <= arr->capacity / 4 && arr->capacity > INITIAL_CAPACITY) 289 | { 290 | 291 | size_t new_capacity = arr->capacity / 2; 292 | 293 | // 保证缩容后的容量仍然能够装得下所有元素,并且不会小于初始容量 294 | 295 | if (new_capacity < arr->size) 296 | { 297 | 298 | new_capacity = arr->size; 299 | } 300 | 301 | if (new_capacity < INITIAL_CAPACITY) 302 | { 303 | 304 | new_capacity = INITIAL_CAPACITY; 305 | } 306 | 307 | printf("\n---> [缩容警告!] Size (%zu) <= Capacity/4 (%zu). 准备缩容至 %zu. \n", 308 | 309 | arr->size, arr->capacity / 4, new_capacity); 310 | 311 | resize_array(arr, new_capacity); 312 | } 313 | 314 | return 0; 315 | } 316 | 317 | // print_array第二个参数,要求传递一个指向函数的指针 318 | 319 | // 这个传递的函数必须满足条件是参数必须是const void* 320 | 321 | void print_array(const DynamicArray *arr, void (*print_func)(const void *data)) 322 | { 323 | 324 | if (!print_func) 325 | { 326 | 327 | printf("错误:未提供有效的打印函数!\n"); 328 | 329 | return; 330 | } 331 | 332 | printf("Array (Size: %zu, Capacity: %zu): [\n", arr->size, arr->capacity); 333 | 334 | for (size_t i = 0; i < arr->size; i++) 335 | 336 | { 337 | 338 | printf(" "); 339 | 340 | // 关键是,调用外部传入的函数指针,来打印每一个元素 341 | 342 | // 我们需要传递每个元素的地址 &arr->data[i] 343 | 344 | // 因为print_func接收一个void*指针 345 | 346 | print_func(&arr->data[i]); 347 | 348 | printf("\n"); 349 | } 350 | 351 | printf("]\n"); 352 | } -------------------------------------------------------------------------------- /dsc/info.txt: -------------------------------------------------------------------------------- 1 | 栈帧 stack frame 2 | 3 | 调用约定 calling convention 4 | 5 | 压入返回地址 6 | 7 | 指令指针 instruction pointer ip pc 寄存器 8 | 9 | 它的工作:IP寄存器里面 永远存储着 下一条 将要被执行的指令的内存地址 10 | 11 | 读取 addr 12 | 根据Address获取指令 13 | 执行 14 | 自动指向next指令地址 15 | 循环…… 16 | 17 | CALL 指令 18 | 1. 压入返回地址 19 | 2. 无条件跳转 20 | 21 | main -》 22 | 23 | 24 | IP->adddr 汇编指令 对应C代码 25 | 0x1000 MOV[start_num], 10 start_num=10 26 | 0x1004 CALL calc() final_result = calculate_score(start_num); 27 | 0x1008 MOV(final_result),EAX (return address ) 28 | 0x100C MOV EAX,0 return 0; 29 | 0x2000 30 | 31 | 32 | 1. 递归的步骤(Recursive Step) f(n) f(n-1) f(3) 33 | 34 | 35 | 36 | AVL 37 | 38 | BalanceFactor(node) = Height(node.left) - Heigth(node.right) 39 | 40 | 该节点的左子树的高度,减去其右子树的高度。 41 | 42 | BF(node) -1, 0, 1 43 | 44 | BF = 1 (平衡因子) 45 | BF = 0 46 | BF = -1 47 | 48 | |BF| > 1 倾斜 49 | 50 | 51 | 树的高度 Height of a tree: 根节点的高度 52 | 节点的高度 Height of Node : 从该节点到其最远叶子节点的路径上所包含的边的数量或节点的数量。 53 | 54 | 计算一个非叶子节点N的高度: 55 | 56 | Height(N) = 1 + max(height(N.leftChild, height(N.rightChild)) 57 | 一个节点的高度,等于其所有子树中最高高度加上1, max函数是取左、右子树高度的最大值。 58 | 59 | 60 | D 1 61 | 边 : 高度从0开始计算 62 | 63 | 空节点 NULL 的高度 -1 64 | 叶子节点 高度: 0 65 | 计算公式 66 | - 如果节点N为空: height(N) = -1 67 | - 否则 height(N) = 1 + max(height(N.leftChild), height(N.rightChild)) 68 | 69 | D 2 70 | 节点 71 | 72 | 高度 从1 开始计算 73 | 74 | 空节点 NULL -》 0 75 | 叶子节点 高度 1 76 | 计算公式: 77 | - 如果节点N为空: height(N) = 0 78 | - 否则,height(N) = 1 + max(height(N.leftChild), height(N.rightChild)) 79 | 非叶子节点的高度 = 1+ 其左右子树中较高的那个高度 80 | 81 | 82 | Height Depth 83 | 高度 深度 84 | 85 | 高度: 自底向上看。一个节点的高度是它到最远叶子节点的距离 86 | 深度: 自顶向下看。一个节点的深度是它到根节点的距离。 87 | 88 | 89 | 深度计算: 90 | 1. 根节点的深度永远是0 91 | 2. 任何其他节点的深度=其父节点的深度+1 92 | 93 | 节点值(Value) 高度(Height) 深度(Depth) Note 94 | 30 3 0 根节点,深度越小,高度越大 95 | 20 2 1 中间层 96 | 40 2 1 97 | 10 1 2 98 | 25 1 2 99 | 35 100 | 50 101 | 树的深度等于数的高度-1 (如果高度从1开始计数),所以这Tree的深度是2 102 | 对于任何一棵树,根节点的深度最小(为零),高度最大 103 | 叶子节点的深度最大看,高度越小(为0或者1, 取决于D) 104 | 105 | 106 | 107 | 核心概念 108 | 1. 旋转 (Rotation) 109 | 110 | 旋转是一系列针对二叉搜索树中节点指针的重新赋值操作。其目的是在保持二叉搜索树性质不变的前提下,改变特定节点在树中的深度(depth),以满足自平衡树的高度平衡标准。这是一种局部操作,仅影响少量节点及其直接子节点。 111 | 112 | 失去平衡Root 根节点: 从新插入节点向上回溯时,第一个发现其平衡因子变为 +2 或 -2 的节点。 113 | 114 | Pivot (枢轴/旋转轴心): 这通常是 Root 节点的子节点,并且位于走向新插入节点的路径上。在旋转过程中,Pivot 或其子节点将“旋转”到 Root 的位置,成为新的子树根节点。 115 | 116 | 2. 二叉搜索树性质 (Binary Search Tree Property) 117 | 118 | 旋转操作必须严格遵守的规则。对于树中的任意节点 N: 119 | 120 | 其左子树中所有节点的键值(key)都小于或等于 N 的键值。 121 | 122 | 其右子树中所有节点的键值都大于 N 的键值。 123 | 124 | 单次旋转 (Single Rotation) 125 | 126 | 单次旋转是基础的结构调整操作。 127 | 128 | 1. 左旋转 (Left Rotation) 129 | 130 | 条件:对节点 x 进行左旋转,其右子节点 y 不能为 null。 131 | 132 | 过程: 133 | 134 | 节点 y 的左子树将成为节点 x 的新右子树。具体操作是:将 y 的左子节点(设为 beta)的父指针指向 x,并将 x 的右子节点指针指向 beta。 135 | 136 | 节点 x 的父节点将成为节点 y 的父节点。具体操作是:更新 x 原父节点的子节点指针,使其指向 y。 137 | 138 | 节点 x 将成为节点 y 的左子节点。具体操作是:将 y 的左子节点指针指向 x,并将 x 的父指针指向 y。 139 | 140 | 结果:x 成为 y 的左子节点,y 取代了 x 的原始位置。x 的右子树被 y 的原左子树替换。 141 | 142 | 2. 右旋转 (Right Rotation) 143 | 144 | 条件:对节点 y 进行右旋转,其左子节点 x 不能为 null。 145 | 146 | 过程:(这是左旋转的对称操作) 147 | 148 | 节点 x 的右子树将成为节点 y 的新左子树。具体操作是:将 x 的右子节点(设为 beta)的父指针指向 y,并将 y 的左子节点指针指向 beta。 149 | 150 | 节点 y 的父节点将成为节点 x 的父节点。具体操作是:更新 y 原父节点的子节点指针,使其指向 x。 151 | 152 | 节点 y 将成为节点 x 的右子节点。具体操作是:将 x 的右子节点指针指向 y,并将 y 的父指针指向 x。 153 | 154 | 结果:y 成为 x 的右子节点,x 取代了 y 的原始位置。y 的左子树被 x 的原右子树替换。 155 | 156 | 双重旋转 (Double Rotation) 157 | 158 | 双重旋转是两次单次旋转的组合,用于处理单次旋转无法解决的特定失衡情况。 159 | 160 | 1. 左右旋转 (Left-Right Rotation) 161 | 162 | 触发条件:节点 z 出现失衡,且失衡是由向 z 的左子节点 y 的右子树中插入新节点引起的。 163 | 164 | 执行步骤: 165 | 166 | 对节点 y(z的左子节点)执行一次 左旋转。 167 | 168 | 对节点 z 本身执行一次 右旋转。 169 | 170 | 2. 右左旋转 (Right-Left Rotation) 171 | 172 | 触发条件:节点 z 出现失衡,且失衡是由向 z 的右子节点 y 的左子树中插入新节点引起的。 173 | 174 | 执行步骤: 175 | 176 | 对节点 y(z的右子节点)执行一次 右旋转。 177 | 178 | 对节点 z 本身执行一次 左旋转。 179 | 180 | 181 | 182 | 183 | NIL 节点 哨兵节点 Sentinel Node 184 | 它不是NULL 185 | 红黑树中,如果一个节点没有左孩子或右孩子,那么我们用一个指向同一个、全局的、特殊的NIL节点的指针。 186 | 187 | 哑节点 Dummy node 188 | 189 | 它是红黑树真正的“叶子” 190 | 191 | 法则5:从任一指定节点出发,到达其所有后代NIL叶子节点的每一条路径上,所包含的黑色节点数量都是相同的。 192 | 193 | 这个相同数量——》 从该节点出发的 黑高(Black-Height) 194 | 195 | 196 | 一棵M阶的B树具有以下关键特性: 197 | 198 | 1. 多路性:每个节点最多可以拥有M个子节点 199 | 2. 平衡性:所有的叶子节点都位于同一层,这保证了查找操作的稳定性。 200 | 3. 有序性: 每个节点中的关键字都按照升序排列,并且节点中的关键字将子树分隔开来。例如,一个有两个关键字(k1, k2)的节点,它 201 | 会有三个子树,左边子树的所有值都小于k1, 中间子树的所有值介于k1和k2之间,右边子树的所有值都大于k2 202 | 有序的区间划分 203 | 4. 对节点关键字数量的限制: 204 | - 根节点至少有两个子节点(除非它本身是叶子节点) 205 | - 非叶子节点(除根节点之外)至少有[M/2]个子节点。 206 | - 一个拥有K个子节点的非叶子节点包含k-1个关键字 207 | 208 | 209 | 为了维持这个特性,所有的插入和删除的操作必须要涉及到节点的分裂和合并。 210 | 211 | 212 | 计算M 213 | 214 | 阶数: 一个节点最多可以拥有的子节点数 215 | 216 | 定义:M是指一个B树节点最多可以拥有的子节点(Children)的数量 217 | 推论:B树中,任何一个节点的关键字(keys)数量总是比它的子节点数量少1 218 | 结论:一个M阶的B树节点,最多可以拥有M-1个关键字 219 | 220 | eg. 221 | M=5 222 | 一个节点最多可以拥有5个子节点 223 | 一个节点最多可以拥有5-1个关键字 224 | 225 | t(minDegree)最小度数 226 | 定义:t指的是一个非根节点最少必须拥有的子节点数量。这是为了保证B树不会因为节点内元素过少而导致树的高度过高。 227 | 计算公式:t由M计算得出,公式为:t = ⌈M/2⌉ 228 | 符号 ⌈⌉ 向上取整 ceiling function 表示将除法结果中的小数部分直接进位到下一个整数。 229 | 推论:因为关键字数量比子节点数量少1 230 | 结论:所以,一个非根节点,必须拥有t-1个关键字 231 | 232 | eg. 233 | M=5 234 | t = ⌈5/2⌉ = 3 235 | 一个非根节点最少要有3个子节点 236 | 一个非根节点最少要有3-1=2个关键字 237 | 238 | 239 | 240 | 顶点 Vertex 241 | 242 | 无向图 undirected Graph 243 | 244 | 权重 Weight 245 | 246 | 带权图 Weighted Graph 247 | 248 | 有向无环图 Directed Acyclic Graph DAG 249 | 250 | 图密度 = 实际边数 / 所有可能存在的边数 251 | 252 | 253 | 邻接矩阵 254 | 255 | 定义数据结构: 256 | 257 | 1. 一个二维数组 graph[V][V] graph[i][j] 258 | 2. key[V] 259 | 3. mstSet[V] bool 260 | 4. parent[V] 261 | 262 | 263 | 邻接表+ 最小堆 264 | 265 | {cost, Vertex} -------------------------------------------------------------------------------- /dsc-code/05-circular_queue/generic_circular_queue.c: -------------------------------------------------------------------------------- 1 | // generic_circular_queue.c 2 | 3 | #include "generic_circular_queue.h" 4 | #include 5 | #include 6 | 7 | // 内部结构定义保持不变 8 | struct CircularQueue { 9 | void* data; 10 | size_t capacity; 11 | size_t element_size; 12 | size_t size; 13 | int front; 14 | int rear; 15 | }; 16 | 17 | // --- API Function Implementations --- 18 | 19 | Queue* queue_create(size_t capacity, size_t element_size) { 20 | // 工业级检查 1: 确保容量和元素大小不为0。 21 | // 这是一个基本的不变量,创建没有容量或没有大小的队列是无意义的。 22 | if (capacity == 0 || element_size == 0) { 23 | return NULL; 24 | } 25 | 26 | // 为管理结构体分配内存 27 | Queue* q = (Queue*)malloc(sizeof(Queue)); 28 | // 工业级检查 2: 检查 malloc 是否成功。 29 | // 在内存受限的系统中,malloc 可能会失败。 30 | if (!q) { 31 | return NULL; // 如果失败,无法继续,返回 NULL 32 | } 33 | 34 | // 为实际存储数据的数组分配内存 35 | q->data = malloc(capacity * element_size); 36 | // 工业级检查 3: 检查为数据区分配内存是否成功。 37 | if (!q->data) { 38 | free(q); // 如果数据区分配失败,必须释放之前已分配的管理结构体,防止内存泄漏。 39 | return NULL; 40 | } 41 | 42 | // 初始化所有成员变量 43 | q->capacity = capacity; 44 | q->element_size = element_size; 45 | q->size = 0; 46 | q->front = 0; 47 | q->rear = 0; 48 | 49 | return q; 50 | } 51 | 52 | void queue_destroy(Queue** p_queue) { 53 | // 工业级检查: 传入的指针和指针指向的内容都必须有效。 54 | // *p_queue 检查确保我们不会对一个已经是 NULL 的指针进行解引用。 55 | if (p_queue && *p_queue) { 56 | free((*p_queue)->data); // 1. 先释放内部的数据数组 57 | free(*p_queue); // 2. 再释放队列管理结构体本身 58 | *p_queue = NULL; // 3. 将外部的指针设置为NULL,这是防止“悬空指针”的最佳实践。 59 | } 60 | } 61 | 62 | bool queue_enqueue(Queue* queue, const void* element_data) { 63 | /* 64 | * 函数功能: [工业级] 将一个新元素添加到队尾。 65 | * 教学案例: 假设我们有一个容量为4的队列,当前状态如下: 66 | * capacity = 4, size = 2, front = 2, rear = 0 67 | * 数组内容 (逻辑上): [ (空) | (空) | A | B ] 68 | * 队列中的逻辑顺序是 A -> B。 69 | * 我们现在要入队一个新的元素 'C'。 70 | */ 71 | 72 | // 步骤 1: [防御性编程] 检查所有传入的指针是否有效。 73 | // 工业代码必须假设任何传入的指针都可能是无效的(NULL)。 74 | // 如果队列不存在,或者没有提供要入队的数据,操作无法进行。 75 | if (!queue || !element_data) { 76 | return false; 77 | } 78 | 79 | // 步骤 2: 检查队列是否已满。 80 | // 案例: 当前 size=2, capacity=4,未满,检查通过。 81 | if (queue->size == queue->capacity) { 82 | return false; 83 | } 84 | 85 | // 步骤 3: 计算要插入元素的物理内存地址。 86 | // `(char*)` 类型转换是为了进行标准的字节级指针算术。 87 | // 案例: rear = 0, element_size = sizeof(char) = 1 (假设)。 88 | // target_address = (char*)queue->data + (0 * 1) = 数组的起始地址。 89 | void* target_address = (char*)queue->data + (queue->rear * queue->element_size); 90 | 91 | // 步骤 4: 使用memcpy将用户数据安全地拷贝到计算出的地址。 92 | // 案例: 将 'C' 的数据从 element_data 拷贝到数组索引0的位置。 93 | // 数组变为: [ C | (空) | A | B ] 94 | memcpy(target_address, element_data, queue->element_size); 95 | 96 | // 步骤 5: 更新队尾 rear 指针,实现“循环”。 97 | // 模运算(%)是循环队列的灵魂。它能自动处理“绕回”的情况。 98 | // 案例: rear 原本是 0。 99 | // 新 rear = (0 + 1) % 4 = 1。 100 | // rear 指针现在安全地指向了下一个空位,索引1。 101 | queue->rear = (queue->rear + 1) % queue->capacity; 102 | 103 | // 步骤 6: 更新队列的大小。 104 | // 案例: size 原本是 2,现在成功增加到 3。 105 | queue->size++; 106 | 107 | return true; // 所有步骤成功完成 108 | } 109 | 110 | bool queue_dequeue(Queue* queue, void* output_buffer) { 111 | /* 112 | * 函数功能: [工业级] 从队头取出一个元素。 113 | * 教学案例: 承接上面的例子,现在队列状态如下: 114 | * capacity = 4, size = 3, front = 2, rear = 1 115 | * 数组内容 (逻辑上): [ C | (空) | A | B ] 116 | * 队列中的逻辑顺序是 A -> B -> C。 117 | * 我们现在要出队队头元素 'A'。 118 | */ 119 | 120 | // 步骤 1: [防御性编程] 检查所有指针是否有效。 121 | // 如果队列不存在,或者用户没有提供用于接收数据的缓冲区,操作无法进行。 122 | if (!queue || !output_buffer) { 123 | return false; 124 | } 125 | 126 | // 步骤 2: 检查队列是否为空。 127 | // 案例: 当前 size=3,不为空,检查通过。 128 | if (queue->size == 0) { 129 | return false; 130 | } 131 | 132 | // 步骤 3: 计算队头元素的物理内存地址。 133 | // 案例: front = 2, element_size = 1 (假设)。 134 | // source_address = (char*)queue->data + (2 * 1) = 指向数组索引2的地址。 135 | void* source_address = (char*)queue->data + (queue->front * queue->element_size); 136 | 137 | // 步骤 4: 使用memcpy将队头数据安全地拷贝到用户的缓冲区。 138 | // 案例: 将数组索引2位置的 'A' 的数据拷贝到 output_buffer。 139 | memcpy(output_buffer, source_address, queue->element_size); 140 | 141 | // 步骤 5: 更新队头 front 指针,实现“逻辑删除”和“循环”。 142 | // 案例: front 原本是 2。 143 | // 新 front = (2 + 1) % 4 = 3。 144 | // front 指针现在指向了 'B' (索引3),'A' 在逻辑上已经被移除了。 145 | queue->front = (queue->front + 1) % queue->capacity; 146 | 147 | // 步骤 6: 更新队列的大小。 148 | // 案例: size 原本是 3,现在安全地减少到 2。 149 | queue->size--; 150 | 151 | return true; // 所有步骤成功完成 152 | } 153 | 154 | // void* queue_peek(const Queue* queue, void* output_buffer); 155 | 156 | bool queue_peek(const Queue* queue, void* output_buffer) { 157 | /* 158 | * 函数功能: [工业级] 查看队头元素,但不移动任何指针。 159 | * 教学案例: 队列状态:size = 2, front = 3, rear = 1 160 | * 数组内容 (逻辑上): [ C | (空) | A | B ] 161 | * 队列逻辑顺序: B -> C 162 | * 我们要查看队头的 'B'。 163 | */ 164 | 165 | // 步骤 1: [防御性编程] 检查所有指针是否有效。 166 | if (!queue || !output_buffer) { 167 | return false; 168 | } 169 | 170 | // 步骤 2: 检查队列是否为空。 171 | if (queue->size == 0) { 172 | return false; 173 | } 174 | 175 | // 步骤 3: 计算队头元素的地址。 176 | // 案例: front = 3。source_address 指向数组索引3的位置。 177 | void* source_address = (char*)queue->data + (queue->front * queue->element_size); 178 | 179 | // 步骤 4: 拷贝数据到用户缓冲区。 180 | // 案例: 将 'B' 的数据拷贝到 output_buffer。 181 | memcpy(output_buffer, source_address, queue->element_size); 182 | 183 | // ** Peek 操作的核心: 到此结束。不修改 queue->front, queue->rear, 或 queue->size。** 184 | 185 | return true; 186 | } 187 | 188 | // 其余的辅助函数比较简单,但同样需要进行NULL检查 189 | bool queue_is_empty(const Queue* queue) { 190 | if (!queue) return true; // 一个不存在的队列可以视为空 191 | return queue->size == 0; 192 | } 193 | 194 | bool queue_is_full(const Queue* queue) { 195 | if (!queue) return true; // 一个不存在的队列可以视作满,以防止错误地对其进行操作 196 | return queue->size == queue->capacity; 197 | } 198 | 199 | size_t queue_get_size(const Queue* queue) { 200 | if (!queue) return 0; 201 | return queue->size; 202 | } 203 | 204 | size_t queue_get_capacity(const Queue* queue) { 205 | if (!queue) return 0; 206 | return queue->capacity; 207 | } -------------------------------------------------------------------------------- /dsc-code/05-deque,double-ended-queue/generic_deque.c: -------------------------------------------------------------------------------- 1 | #include "generic_deque.h" 2 | #include 3 | #include 4 | 5 | // --- Private Structure Definition --- 6 | // 与循环队列的结构完全相同,只是对 front 和 rear 的操作方式更多样 7 | struct Deque { 8 | void* data; 9 | size_t capacity; 10 | size_t element_size; 11 | size_t size; 12 | int front; // 指向队头元素的索引 13 | int rear; // 指向队尾元素的后一个位置的索引 14 | }; 15 | 16 | // --- API Function Implementations --- 17 | 18 | Deque* deque_create(size_t capacity, size_t element_size) { 19 | if (capacity == 0 || element_size == 0) return NULL; 20 | Deque* dq = (Deque*)malloc(sizeof(Deque)); 21 | if (!dq) return NULL; 22 | dq->data = malloc(capacity * element_size); 23 | if (!dq->data) { 24 | free(dq); 25 | return NULL; 26 | } 27 | dq->capacity = capacity; 28 | dq->element_size = element_size; 29 | dq->size = 0; 30 | dq->front = 0; 31 | dq->rear = 0; 32 | return dq; 33 | } 34 | 35 | void deque_destroy(Deque** p_deque) { 36 | if (p_deque && *p_deque) { 37 | free((*p_deque)->data); 38 | free(*p_deque); 39 | *p_deque = NULL; 40 | } 41 | } 42 | 43 | bool deque_is_empty(const Deque* dq) { return !dq || dq->size == 0; } 44 | bool deque_is_full(const Deque* dq) { return !dq || dq->size == dq->capacity; } 45 | size_t deque_get_size(const Deque* dq) { return dq ? dq->size : 0; } 46 | 47 | 48 | bool deque_push_back(Deque* dq, const void* element_data) { 49 | /* 50 | * 函数功能: 在队尾添加元素 (与循环队列的 enqueue 完全相同) 51 | */ 52 | // 1. 检查 53 | if (!dq || !element_data || deque_is_full(dq)) { 54 | return false; 55 | } 56 | 57 | // 2. 拷贝数据到 rear 指向的位置 58 | // 案例: rear=4。数据 'C' 被拷贝到索引4。 59 | // 数组: [ | | A | B | C ] 60 | void* target_address = (char*)dq->data + dq->rear * dq->element_size; 61 | memcpy(target_address, element_data, dq->element_size); 62 | 63 | // 3. 更新 rear 指针 (向前移动并循环) 64 | // 案例: 新 rear = (4 + 1) % 5 = 0。rear 指针绕回到了数组开头。 65 | dq->rear = (dq->rear + 1) % dq->capacity; 66 | 67 | // 4. 更新大小 68 | dq->size++; 69 | return true; 70 | } 71 | 72 | bool deque_push_front(Deque* dq, const void* element_data) { 73 | /* 74 | * capacity 5 75 | * size 2 76 | * front 2 => 指向 A 77 | * rear 4 => 指向 队尾元素 B 的后一个位置 78 | * index: 0 1 2 3 4 79 | * Array: [ | | A | B | ] 80 | * A -> B 81 | * push_front(X) 82 | * X -> A -> B 83 | * new front index 1 => 指向 X 84 | * 85 | * 86 | */ 87 | // 1. 检查 88 | if (!dq || !element_data || deque_is_full(dq)) { 89 | return false; 90 | } 91 | 92 | /* 93 | * capacity 5 94 | * size 2 95 | * front 2 => 指向 A 96 | * rear 4 => 指向 队尾元素 B 的后一个位置 97 | * index: 0 1 2 3 4 98 | * Array: [ | | A | B | ] 99 | * A -> B 100 | * push_front(X) 101 | * X -> A -> B 102 | * new front index 1 => 指向 X 103 | * 104 | * index: 0 1 2 3 4 105 | * Array: [| | A | B | ] 106 | * 107 | * front = 2 108 | * 2 - 1 = 1 109 | * new_front = (old_front - 1 + capacity) % capacity 110 | * 111 | * dq->front = 2 112 | * dq->capacity = 5 113 | * new_front = (2 - 1 + 5) % 5 = 1 114 | * 甲鱼的臀部――龟腚 ―― 规定 115 | * 116 | */ 117 | dq->front = (dq->front - 1 + dq->capacity) % dq->capacity; 118 | // index : 0 1 2 3 4 119 | // Array: [ A | B | C | | ] 120 | // ^ ^ 121 | // front rear 122 | // new_front = (dq->front - 1) % dq->capacity 123 | 124 | // front 0 125 | // capacity 5 126 | // new_front = (0 - 1) % 5 127 | // -1 % 5 128 | // 取决于编译器 129 | // C99 , c11 ,c17 都是 -1 % 5 = -1 130 | // a % b = a 131 | // (x + N) % N and x % N 对于正数x来说是等价的,但在这里对于负数x来说,(x + N) % N能够得到一个非负结果。 132 | // 在C语言中,凡是能够涉及到可能产生负数的循环数组索引计算 都需要使用 (index - 1 + capacity) % capacity 133 | 134 | 135 | void* target_address = (char*)dq->data + dq->front * dq->element_size; 136 | memcpy(target_address, element_data, dq->element_size); 137 | 138 | // 4. 更新大小 139 | dq->size++; 140 | return true; 141 | } 142 | 143 | bool deque_pop_front(Deque* dq, void* output_buffer) { 144 | /* 145 | * 函数功能: 从队头移除元素 (与循环队列的 dequeue 完全相同)。 146 | * 教学案例: capacity=5, size=3, front=1, rear=4 147 | * 数组内容: [ | X | A | B | ] 148 | * ^ ^ 149 | * front rear 150 | * 我们要 pop_front() 151 | */ 152 | // 1. 检查 153 | if (!dq || !output_buffer || deque_is_empty(dq)) { 154 | return false; 155 | } 156 | 157 | // 2. 从 front 指向的位置拷贝数据 158 | // 案例: front=1。将 'X' 拷贝到 output_buffer。 159 | void* source_address = (char*)dq->data + dq->front * dq->element_size; 160 | memcpy(output_buffer, source_address, dq->element_size); 161 | 162 | // 3. 更新 front 指针 (向前移动并循环) 163 | // 案例: 新 front = (1 + 1) % 5 = 2。front 指针现在指向了 'A'。 164 | dq->front = (dq->front + 1) % dq->capacity; 165 | 166 | // 4. 更新大小 167 | dq->size--; 168 | return true; 169 | } 170 | 171 | bool deque_pop_back(Deque* dq, void* output_buffer) { 172 | /* 173 | * 函数功能: 从队尾移除元素 (这是双端队列的新功能)。 174 | * 教学案例: capacity=5, size=2, front=2, rear=4 175 | * 数组内容: [ | | A | B | ] 176 | * ^ ^ 177 | * front rear 178 | * 我们要 pop_back() 179 | */ 180 | // 1. 检查 181 | if (!dq || !output_buffer || deque_is_empty(dq)) { 182 | return false; 183 | } 184 | 185 | // 2. [核心逻辑] 更新 rear 指针 (向后移动并循环) 186 | // rear 指向的是队尾元素的“后一个”位置,所以真正的队尾元素在 rear-1。 187 | // 我们先将 rear 指针移回队尾元素的位置。 188 | // 案例: rear=4, capacity=5。 189 | // 新 rear = (4 - 1 + 5) % 5 = 8 % 5 = 3。 190 | dq->rear = (dq->rear - 1 + dq->capacity) % dq->capacity; 191 | 192 | // 3. 从新的 rear 指向的位置拷贝数据 193 | // 案例: 新 rear=3。将队尾元素 'B' 拷贝到 output_buffer。 194 | void* source_address = (char*)dq->data + dq->rear * dq->element_size; 195 | memcpy(output_buffer, source_address, dq->element_size); 196 | 197 | // 4. 更新大小 198 | dq->size--; 199 | return true; 200 | } 201 | 202 | bool deque_peek_front(const Deque* dq, void* output_buffer) { 203 | if (!dq || !output_buffer || deque_is_empty(dq)) return false; 204 | void* source_address = (char*)dq->data + dq->front * dq->element_size; 205 | memcpy(output_buffer, source_address, dq->element_size); 206 | return true; 207 | } 208 | 209 | bool deque_peek_back(const Deque* dq, void* output_buffer) { 210 | if (!dq || !output_buffer || deque_is_empty(dq)) return false; 211 | // 真正的队尾元素在 rear 的前一个位置 212 | int last_element_index = (dq->rear - 1 + dq->capacity) % dq->capacity; 213 | void* source_address = (char*)dq->data + last_element_index * dq->element_size; 214 | memcpy(output_buffer, source_address, dq->element_size); 215 | return true; 216 | } -------------------------------------------------------------------------------- /dsc-code/trie/trie.c: -------------------------------------------------------------------------------- 1 | // trie.c (Generic Version) 2 | 3 | #include "trie.h" 4 | #include 5 | #include 6 | #include 7 | 8 | #define ALPHABET_SIZE 26 9 | 10 | typedef struct TrieNode { 11 | // bool isEndOfWord; 12 | // 数据插槽 13 | void* value; // 用void*替换bool isEndOfWord。NULL表示非单词结尾。 14 | 15 | // 指针数组,数组的每个格子(0-25),都用来存放指向另一个TrieNode的指针。 16 | struct TrieNode* children[ALPHABET_SIZE]; 17 | } TrieNode; 18 | 19 | struct Trie { 20 | TrieNode* root; 21 | ValueDestroyer valueDestroyer; // 存储用户提供的析构函数 22 | }; 23 | 24 | // --- 私有函数原型保持不变 --- 25 | static TrieNode* TrieNode_Create(void); 26 | static void TrieNode_DestroyRecursive(TrieNode* node, ValueDestroyer destroyer); 27 | static int CharToIndex(char c); 28 | static bool TrieNode_DeleteRecursiveHelper(TrieNode** pNode, 29 | const char* key, 30 | int depth, 31 | ValueDestroyer destroyer); 32 | 33 | // --- 公共函数实现修改 --- 34 | 35 | Trie* Trie_Create(ValueDestroyer destroyer) { 36 | Trie* trie = (Trie*)malloc(sizeof(Trie)); 37 | if (!trie) return NULL; 38 | 39 | trie->root = TrieNode_Create(); 40 | if (!trie->root) { 41 | free(trie); 42 | return NULL; 43 | } 44 | trie->valueDestroyer = destroyer; // 保存析构函数 45 | return trie; 46 | } 47 | 48 | void Trie_Destroy(Trie* trie) { 49 | if (!trie) return; 50 | TrieNode_DestroyRecursive(trie->root, trie->valueDestroyer); 51 | free(trie); 52 | } 53 | 54 | bool Trie_Insert(Trie* trie, const char* key, void* value) { 55 | if (!trie || !trie->root || !key) return false; 56 | // 插入NULL值是无意义的,因为我们用NULL来标记非结尾节点 57 | assert(value != NULL && "Cannot insert a NULL value into the Trie."); 58 | 59 | 60 | // crawl指针用于遍历Trie "爬行指针" 61 | TrieNode* crawl = trie->root; 62 | int len = strlen(key); 63 | 64 | // 遍历每个字符,将其转换为索引,并在Trie中插入或查找相应的节点 65 | for (int i = 0; i < len; ++i) { 66 | int index = CharToIndex(key[i]); 67 | if (index == -1) return false; 68 | 69 | if (!crawl->children[index]) { 70 | crawl->children[index] = TrieNode_Create(); 71 | if (!crawl->children[index]) return false; 72 | } 73 | 74 | // 移动到下一个节点(无论是否是新的) 75 | crawl = crawl->children[index]; 76 | } 77 | 78 | // 注意:如果 crawl->value 之前已经有值,这里会直接覆盖。 79 | // 一个更完善的库可能会先调用destroyer释放旧值。为简化,此处由用户负责。 80 | 81 | // 将整个节点的值设置为传入的非NULL指针(value) 82 | 83 | // te 84 | 85 | // not prefix 86 | 87 | // 这里的逻辑是:如果一个节点的value不为NULL,则表示这是一个单词的结尾。 88 | // crawl->isEndOfWord = true; 89 | crawl->value = value; 90 | return true; 91 | } 92 | 93 | void* Trie_Search(const Trie* trie, const char* key) { 94 | if (!trie || !trie->root || !key) return NULL; 95 | 96 | const TrieNode* crawl = trie->root; 97 | int len = strlen(key); 98 | 99 | for (int i = 0; i < len; ++i) { 100 | int index = CharToIndex(key[i]); 101 | 102 | // 如果字符不在字母表范围内,直接返回NULL 103 | if (index == -1 || !crawl->children[index]) { 104 | return NULL; 105 | } 106 | 107 | // 前进到下一个节点 108 | crawl = crawl->children[index]; 109 | } 110 | 111 | // 返回存储的值的指针,如果不是一个结尾(即值为NULL),则返回NULL。 112 | return crawl ? crawl->value : NULL; 113 | } 114 | 115 | // StartsWith的实现基本不变 116 | bool Trie_StartsWith(const Trie* trie, const char* prefix) { 117 | if (!trie || !trie->root || !prefix) return false; 118 | const TrieNode* crawl = trie->root; 119 | int len = strlen(prefix); 120 | for (int i = 0; i < len; ++i) { 121 | int index = CharToIndex(prefix[i]); 122 | if (index == -1 || !crawl->children[index]) return false; 123 | crawl = crawl->children[index]; 124 | } 125 | return crawl != NULL; 126 | } 127 | 128 | void Trie_Delete(Trie* trie, const char* key) { 129 | if (!trie || !key || !*key) { // 检查trie, key和空字符串 130 | return; 131 | } 132 | TrieNode_DeleteRecursiveHelper(&trie->root, key, 0, trie->valueDestroyer); 133 | } 134 | 135 | /** 136 | * @brief 检查一个节点是否可以被删除。 137 | * @param node 要检查的节点。 138 | * @return 如果节点没有值并且没有子节点,返回true。 139 | */ 140 | static bool IsNodeEmpty(TrieNode* node) { 141 | if (node->value) { 142 | return false; 143 | } 144 | for (int i = 0; i < ALPHABET_SIZE; i++) { 145 | if (node->children[i]) { 146 | return false; 147 | } 148 | } 149 | return true; 150 | } 151 | 152 | 153 | 154 | // --- 私有函数实现修改 --- 155 | 156 | static TrieNode* TrieNode_Create(void) { 157 | TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode)); 158 | if (node) { 159 | node->value = NULL; // 初始化为NULL 160 | for (int i = 0; i < ALPHABET_SIZE; ++i) { 161 | node->children[i] = NULL; 162 | } 163 | } 164 | return node; 165 | } 166 | 167 | static void TrieNode_DestroyRecursive(TrieNode* node, ValueDestroyer destroyer) { 168 | if (!node) return; 169 | 170 | 171 | // 递归销毁所有子节点 172 | for (int i = 0; i < ALPHABET_SIZE; ++i) { 173 | TrieNode_DestroyRecursive(node->children[i], destroyer); 174 | } 175 | 176 | // 关键一步:在释放节点本身之前,检查是否有用户数据需要释放 177 | if (node->value && destroyer) { 178 | destroyer(node->value); 179 | } 180 | free(node); 181 | } 182 | 183 | static int CharToIndex(char c) { 184 | if (c >= 'a' && c <= 'z') return c - 'a'; 185 | return -1; 186 | } 187 | 188 | // 物理删除 189 | 190 | /** 191 | * @brief 递归地删除键。 192 | * @param pNode 指向当前节点指针的指针,允许我们修改父节点中的指针。 193 | * @param key 要删除的键。 194 | * @param depth 当前递归深度。 195 | * @param destroyer 用于释放值的函数。 196 | * @return 如果当前节点在删除后变为空,可以被父节点安全移除,则返回true。 197 | */ 198 | static bool TrieNode_DeleteRecursiveHelper(TrieNode** pNode, 199 | const char* key, 200 | int depth, 201 | ValueDestroyer destroyer) { 202 | 203 | 204 | // ** 205 | 206 | // TrieNode** pNode: &l->children['e']; 207 | // TrieNode* node = *pNode; : node 现在是'e'节点的地址 208 | 209 | 210 | // 获取当前节点 211 | TrieNode* node = *pNode; 212 | 213 | if (!node) { 214 | return false; // 键不存在 215 | } 216 | 217 | // key[5] \0 218 | // 如果到达了键的末尾 219 | if (key[depth] == '\0') { 220 | // 如果这个节点确实是一个单词的结尾 221 | if (node->value) { 222 | if (destroyer) { 223 | destroyer(node->value); 224 | } 225 | // node->isEndOfWord = false; // 不是单词结尾 226 | node->value = NULL; // 懒删除:标记为非结尾 227 | } 228 | // 检查这个节点现在是否可以被物理删除 229 | if (IsNodeEmpty(node)) { 230 | free(node); 231 | *pNode = NULL; // 将父节点指向此节点的指针设为NULL 232 | return true; 233 | } 234 | return false; 235 | } 236 | 237 | // 递归到下一个节点 238 | int index = CharToIndex(key[depth]); 239 | if (index == -1) return false; // 无效字符 240 | 241 | if (TrieNode_DeleteRecursiveHelper(&node->children[index], key, depth + 1, destroyer)) { 242 | // 如果子节点被删除了,再次检查当前节点是否也变为空了 243 | if (IsNodeEmpty(node)) { 244 | free(node); 245 | *pNode = NULL; 246 | return true; 247 | } 248 | } 249 | 250 | return false; 251 | } -------------------------------------------------------------------------------- /docs/CONTRIBUTING_EN.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | **English** | [简体中文](./CONTRIBUTING.md) 4 | 5 |
6 | 7 | # 🤝 Contributing Guide 8 | 9 | First off, thank you for considering contributing to the **Data Structure Project**! It's people like you that make the open source community such a great place. 10 | 11 | ## 📋 Table of Contents 12 | 13 | - [Code of Conduct](#code-of-conduct) 14 | - [How Can I Contribute?](#how-can-i-contribute) 15 | - [Reporting Bugs](#-reporting-bugs) 16 | - [Suggesting Features](#-suggesting-features) 17 | - [Submitting Code](#-submitting-code) 18 | - [Improving Documentation](#-improving-documentation) 19 | - [Development Process](#development-process) 20 | - [Code Standards](#code-standards) 21 | - [Commit Message Guidelines](#commit-message-guidelines) 22 | - [Pull Request Process](#pull-request-process) 23 | 24 | ## Code of Conduct 25 | 26 | This project adopts the open source community code of conduct. By participating, you are expected to uphold this code. Please treat every contributor with kindness and respect. 27 | 28 | ## How Can I Contribute? 29 | 30 | ### 🐛 Reporting Bugs 31 | 32 | Finding bugs is a contribution! If you find an issue, please: 33 | 34 | 1. **Check if the bug has been reported** - Search existing [Issues](https://github.com/Frank-Code-Show/DataStructure/issues) 35 | 2. **Create a detailed bug report** including: 36 | - Clear title and description 37 | - Steps to reproduce 38 | - Expected vs actual behavior 39 | - Screenshots (if applicable) 40 | - Environment info (OS, compiler version, etc.) 41 | 42 | **Bug Report Template:** 43 | ```markdown 44 | **Describe the bug** 45 | A clear and concise description of what the bug is. 46 | 47 | **To Reproduce** 48 | Steps to reproduce the behavior: 49 | 1. Go to '...' 50 | 2. Click on '....' 51 | 3. Scroll down to '....' 52 | 4. See error 53 | 54 | **Expected behavior** 55 | Describe what you expected to happen. 56 | 57 | **Screenshots** 58 | If applicable, add screenshots. 59 | 60 | **Environment:** 61 | - OS: [e.g. Windows 10] 62 | - Compiler: [e.g. gcc 9.3.0] 63 | - Version: [e.g. commit hash] 64 | ``` 65 | 66 | ### 💡 Suggesting Features 67 | 68 | We welcome new ideas! Please submit feature requests via Issues: 69 | 70 | 1. **Clear feature description** - What is this feature? 71 | 2. **Use case** - Why is this feature needed? 72 | 3. **Possible implementation** - How do you think it should work? 73 | 4. **Alternatives** - Have you considered other solutions? 74 | 75 | ### 🔧 Submitting Code 76 | 77 | Want to submit code? Awesome! Please follow these steps: 78 | 79 | #### 1. Fork and Clone 80 | ```bash 81 | # Fork the project to your GitHub account 82 | # Then clone locally 83 | git clone https://github.com/your-username/DataStructure.git 84 | cd DataStructure 85 | ``` 86 | 87 | #### 2. Create a Branch 88 | ```bash 89 | # Create a new branch from the latest main 90 | git checkout -b feature/your-feature-name 91 | # or 92 | git checkout -b fix/issue-to-fix 93 | ``` 94 | 95 | #### 3. Make Changes 96 | - Write clean, maintainable code 97 | - Add necessary comments 98 | - Update related documentation 99 | - Add tests (if applicable) 100 | 101 | #### 4. Commit Changes 102 | ```bash 103 | git add . 104 | git commit -m "feat: add new feature" 105 | ``` 106 | 107 | #### 5. Push to GitHub 108 | ```bash 109 | git push origin feature/your-feature-name 110 | ``` 111 | 112 | #### 6. Create Pull Request 113 | Create a PR on GitHub and fill out the PR template 114 | 115 | ### 📝 Improving Documentation 116 | 117 | Documentation is equally important! You can: 118 | - Fix typos or grammar errors 119 | - Improve code examples 120 | - Add more explanations 121 | - Translate documentation 122 | 123 | ## Development Process 124 | 125 | ### Project Structure 126 | ``` 127 | DataStructure/ 128 | ├── dsc/ # HTML animations 129 | ├── dsc-code/ # C implementations 130 | │ ├── include/ # Header files 131 | │ ├── src/ # Source code 132 | │ └── tests/ # Test files 133 | └── docs/ # Documentation 134 | ``` 135 | 136 | ### Build and Test 137 | ```bash 138 | # Build the code 139 | cd dsc-code 140 | make all 141 | 142 | # Run tests 143 | make test 144 | 145 | # Clean build files 146 | make clean 147 | ``` 148 | 149 | ## Code Standards 150 | 151 | ### C Language Standards 152 | 153 | 1. **Naming Conventions** 154 | - Functions: `camelCase` or `snake_case` (be consistent) 155 | - Constants: `UPPER_SNAKE_CASE` 156 | - Structs: `PascalCase` 157 | 158 | 2. **Formatting** 159 | - Indentation: 4 spaces 160 | - Max line length: 80 characters 161 | - Braces: K&R style 162 | 163 | 3. **Comments** 164 | ```c 165 | /* Multi-line comments 166 | * for function descriptions 167 | */ 168 | 169 | // Single-line comments for inline explanations 170 | ``` 171 | 172 | 4. **Example Code** 173 | ```c 174 | /** 175 | * Create a new node 176 | * @param value The node value 177 | * @return Pointer to the new node 178 | */ 179 | Node* createNode(int value) { 180 | Node* newNode = (Node*)malloc(sizeof(Node)); 181 | if (newNode == NULL) { 182 | fprintf(stderr, "Memory allocation failed\n"); 183 | return NULL; 184 | } 185 | newNode->data = value; 186 | newNode->next = NULL; 187 | return newNode; 188 | } 189 | ``` 190 | 191 | ### HTML/CSS Standards 192 | 193 | - Use semantic HTML5 tags 194 | - Follow BEM naming convention for CSS 195 | - Ensure smooth animations (60fps) 196 | 197 | ## Commit Message Guidelines 198 | 199 | Follow [Conventional Commits](https://www.conventionalcommits.org/): 200 | 201 | ``` 202 | (): 203 | 204 | 205 | 206 |