From 0cb98557da9b3e0cd595822205a4ce904b5c5370 Mon Sep 17 00:00:00 2001 From: Chen Peng Date: Sun, 30 Aug 2026 22:24:45 +0800 Subject: [PATCH] Search / Sort --- Base/c_Types.h | 2 +- Foundation/c_BinarySearch.c | 1 + Foundation/c_BinarySearch.h | 66 +++++ Foundation/c_BinarySearch.t.c | 135 ++++++++++ Foundation/c_KnuthShuffle.c | 51 ++++ Foundation/c_KnuthShuffle.h | 21 ++ Foundation/c_KnuthShuffle.t.c | 112 ++++++++ Foundation/c_Random.c | 117 ++++++++ Foundation/c_Random.h | 39 +++ Foundation/c_Random.t.c | 129 +++++++++ Foundation/c_StringView.c | 4 +- Foundation/c_StringView.h | 24 +- Foundation/c_Swap.c | 5 + Foundation/c_Swap.h | 47 ++++ Search/c_BST.c | 215 +++++++++++++++ Search/c_BST.h | 51 ++++ Search/c_BST.t.c | 88 ++++++ Search/c_BinarySearchST.c | 192 +++++++++++++ Search/c_BinarySearchST.h | 43 +++ Search/c_BinarySearchST.t.c | 86 ++++++ Search/c_HashMap.c | 149 ++++++++++ Search/c_HashMap.h | 38 +++ Search/c_HashMap.t.c | 80 ++++++ Search/c_HashST.c | 240 +++++++++++++++++ Search/c_HashST.h | 67 +++++ Search/c_HashST.t.c | 216 +++++++++++++++ Search/c_RBTreeSet.c | 288 ++++++++++++++++++++ Search/c_RBTreeSet.h | 55 ++++ Search/c_RBTreeSet.t.c | 68 +++++ Search/c_RedBlackBST.c | 402 +++++++++++++++++++++++++++ Search/c_RedBlackBST.h | 54 ++++ Search/c_RedBlackBST.t.c | 129 +++++++++ Search/c_SeqSearchST.c | 149 ++++++++++ Search/c_SeqSearchST.h | 47 ++++ Search/c_SeqSearchST.t.c | 76 ++++++ Search/c_StrHashOps.c | 63 +++++ Search/c_StrHashOps.h | 23 ++ Sort/c_BinaryInsertionSort.c | 59 ++++ Sort/c_BinaryInsertionSort.h | 23 ++ Sort/c_BinaryInsertionSort.t.c | 79 ++++++ Sort/c_HeapSort.c | 105 ++++++++ Sort/c_HeapSort.h | 29 ++ Sort/c_HeapSort.t.c | 112 ++++++++ Sort/c_IndexMaxPQ.c | 205 ++++++++++++++ Sort/c_IndexMaxPQ.h | 55 ++++ Sort/c_IndexMaxPQ.t.c | 82 ++++++ Sort/c_IndexMinPQ.c | 215 +++++++++++++++ Sort/c_IndexMinPQ.h | 55 ++++ Sort/c_IndexMinPQ.t.c | 85 ++++++ Sort/c_InsertionSort.c | 33 +++ Sort/c_InsertionSort.h | 24 ++ Sort/c_InsertionSort.t.c | 102 +++++++ Sort/c_InsertionSortX.c | 75 ++++++ Sort/c_InsertionSortX.h | 14 + Sort/c_InsertionSortX.t.c | 102 +++++++ Sort/c_MaxPQ.c | 157 +++++++++++ Sort/c_MaxPQ.h | 58 ++++ Sort/c_MaxPQ.t.c | 72 +++++ Sort/c_MergeSort.c | 330 +++++++++++++++++++++++ Sort/c_MergeSort.h | 47 ++++ Sort/c_MergeSort.t.c | 243 +++++++++++++++++ Sort/c_MinPQ.c | 199 ++++++++++++++ Sort/c_MinPQ.h | 50 ++++ Sort/c_MinPQ.t.c | 80 ++++++ Sort/c_QuickSort.c | 479 +++++++++++++++++++++++++++++++++ Sort/c_QuickSort.h | 68 +++++ Sort/c_QuickSort.t.c | 381 ++++++++++++++++++++++++++ Sort/c_SelectionSort.c | 35 +++ Sort/c_SelectionSort.h | 27 ++ Sort/c_SelectionSort.t.c | 121 +++++++++ Sort/c_ShellSort.c | 55 ++++ Sort/c_ShellSort.h | 23 ++ Sort/c_ShellSort.t.c | 111 ++++++++ Sort/c_SortCompare.c | 1 + Sort/c_SortCompare.h | 14 + 75 files changed, 7570 insertions(+), 7 deletions(-) create mode 100644 Foundation/c_BinarySearch.c create mode 100644 Foundation/c_BinarySearch.h create mode 100644 Foundation/c_BinarySearch.t.c create mode 100644 Foundation/c_KnuthShuffle.c create mode 100644 Foundation/c_KnuthShuffle.h create mode 100644 Foundation/c_KnuthShuffle.t.c create mode 100644 Foundation/c_Random.c create mode 100644 Foundation/c_Random.h create mode 100644 Foundation/c_Random.t.c create mode 100644 Foundation/c_Swap.c create mode 100644 Foundation/c_Swap.h create mode 100644 Search/c_BST.c create mode 100644 Search/c_BST.h create mode 100644 Search/c_BST.t.c create mode 100644 Search/c_BinarySearchST.c create mode 100644 Search/c_BinarySearchST.h create mode 100644 Search/c_BinarySearchST.t.c create mode 100644 Search/c_HashMap.c create mode 100644 Search/c_HashMap.h create mode 100644 Search/c_HashMap.t.c create mode 100644 Search/c_HashST.c create mode 100644 Search/c_HashST.h create mode 100644 Search/c_HashST.t.c create mode 100644 Search/c_RBTreeSet.c create mode 100644 Search/c_RBTreeSet.h create mode 100644 Search/c_RBTreeSet.t.c create mode 100644 Search/c_RedBlackBST.c create mode 100644 Search/c_RedBlackBST.h create mode 100644 Search/c_RedBlackBST.t.c create mode 100644 Search/c_SeqSearchST.c create mode 100644 Search/c_SeqSearchST.h create mode 100644 Search/c_SeqSearchST.t.c create mode 100644 Search/c_StrHashOps.c create mode 100644 Search/c_StrHashOps.h create mode 100644 Sort/c_BinaryInsertionSort.c create mode 100644 Sort/c_BinaryInsertionSort.h create mode 100644 Sort/c_BinaryInsertionSort.t.c create mode 100644 Sort/c_HeapSort.c create mode 100644 Sort/c_HeapSort.h create mode 100644 Sort/c_HeapSort.t.c create mode 100644 Sort/c_IndexMaxPQ.c create mode 100644 Sort/c_IndexMaxPQ.h create mode 100644 Sort/c_IndexMaxPQ.t.c create mode 100644 Sort/c_IndexMinPQ.c create mode 100644 Sort/c_IndexMinPQ.h create mode 100644 Sort/c_IndexMinPQ.t.c create mode 100644 Sort/c_InsertionSort.c create mode 100644 Sort/c_InsertionSort.h create mode 100644 Sort/c_InsertionSort.t.c create mode 100644 Sort/c_InsertionSortX.c create mode 100644 Sort/c_InsertionSortX.h create mode 100644 Sort/c_InsertionSortX.t.c create mode 100644 Sort/c_MaxPQ.c create mode 100644 Sort/c_MaxPQ.h create mode 100644 Sort/c_MaxPQ.t.c create mode 100644 Sort/c_MergeSort.c create mode 100644 Sort/c_MergeSort.h create mode 100644 Sort/c_MergeSort.t.c create mode 100644 Sort/c_MinPQ.c create mode 100644 Sort/c_MinPQ.h create mode 100644 Sort/c_MinPQ.t.c create mode 100644 Sort/c_QuickSort.c create mode 100644 Sort/c_QuickSort.h create mode 100644 Sort/c_QuickSort.t.c create mode 100644 Sort/c_SelectionSort.c create mode 100644 Sort/c_SelectionSort.h create mode 100644 Sort/c_SelectionSort.t.c create mode 100644 Sort/c_ShellSort.c create mode 100644 Sort/c_ShellSort.h create mode 100644 Sort/c_ShellSort.t.c create mode 100644 Sort/c_SortCompare.c create mode 100644 Sort/c_SortCompare.h diff --git a/Base/c_Types.h b/Base/c_Types.h index 1c6eb30..2e40152 100644 --- a/Base/c_Types.h +++ b/Base/c_Types.h @@ -112,7 +112,7 @@ typedef int c_err_t; #define C_ERR_PARAM (-4) #define C_ERR_EMPTY (-5) #define C_ERR_FULL (-6) -#define C_ERR_ALREADY_EXISTS (-7) +#define C_ERR_EXIST (-7) #define C_SUCCESS C_ERR_OK #define C_ERR_NOTFOUND C_ERR_FAIL diff --git a/Foundation/c_BinarySearch.c b/Foundation/c_BinarySearch.c new file mode 100644 index 0000000..b3c7a34 --- /dev/null +++ b/Foundation/c_BinarySearch.c @@ -0,0 +1 @@ +#include diff --git a/Foundation/c_BinarySearch.h b/Foundation/c_BinarySearch.h new file mode 100644 index 0000000..f82fa11 --- /dev/null +++ b/Foundation/c_BinarySearch.h @@ -0,0 +1,66 @@ +#ifndef INCLUDED_C_BINARYSEARCH_H +#define INCLUDED_C_BINARYSEARCH_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 工业级泛型二分查找算法 (类似于标准库 bsearch) + * + * @param key 指向待查找目标对象的指针 + * @param base 指向已排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 比对回调函数指针 (不能为 NULL) + * @return const void* 找到时返回指向数组中匹配元素的泛型指针;未找到或参数非法时返回 NULL + */ +C_STATIC_FORCE_INLINE +const void* c_BinarySearch(const void* key, + const void* base, + c_size_t num, + c_size_t size, + int (*cmp)(const void* key, const void* elem)) +{ + // 边界与防御性校验 + if (!key || !base || num == 0 || size == 0 || !cmp) { + return NULL; + } + + const char* array_base = (const char*)base; + + // 🌟【安全加固核心】:采用 c_size_t 无符号左闭右开控制流 + c_size_t low = 0; + c_size_t high = num; // 右边界设为 num(开区间),high 永远不需要自减,物理杜绝下溢 + + while (low < high) { // 🌟 注意:开区间控制条件为 low < high,而不是 <= + // 采用防算术溢出的中间索引计算法 + c_size_t mid = low + ((high - low) >> 1); + + // 计算当前 mid 元素在扁平内存中的绝对指针位置 + const void* mid_elem = (const void*)(array_base + (mid * size)); + + // 执行用户自定义比对 + int cmp_res = cmp(key, mid_elem); + + if (cmp_res == 0) { + return mid_elem; // 精确命中,返回该元素的内存首地址 + } + else if (cmp_res < 0) { + // 🌟【核心修正】:目标在左侧低位半区,直接收缩右开边界为 mid。 + // 彻底干掉了原先“high = mid - 1”引发的 UINT64_MAX 死循环隐患! + high = mid; + } + else { + low = mid + 1; // 目标在右侧高位半区,安全单调向右靠拢 + } + } + + return NULL; // 未在已排好序的数组中检索到目标 +} + +#endif /*INCLUDED_C_BINARYSEARCH_H*/ diff --git a/Foundation/c_BinarySearch.t.c b/Foundation/c_BinarySearch.t.c new file mode 100644 index 0000000..3a8e310 --- /dev/null +++ b/Foundation/c_BinarySearch.t.c @@ -0,0 +1,135 @@ +#include "c_BinarySearch.h" +#include "c_Test.h" +#include +#include +#include "c_StringView.h" + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +// ========================================== +// 1. 测试用例伴生:基础数据类型比对器 +// ========================================== +static int compare_ints(const void* a, const void* b) { + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +// 自定义结构体与对应的比对器 +typedef struct { + uint32_t id; + uint32_t value; +} TestItem_t; + +static int compare_items_by_id(const void* key, const void* elem) { + uint32_t key_id = *(const uint32_t*)key; // 查找时通常可以直接传入 ID 变量的地址作为 key + uint32_t elem_id = ((const TestItem_t*)elem)->id; + if (key_id < elem_id) return -1; + if (key_id > elem_id) return 1; + return 0; +} + +// 适配 c_StringView_t 的比对器(对之前 Cmp 函数的外壳包裹) +static int compare_string_views(const void* key, const void* elem) { + return c_StringView_Cmp((c_StringView_t*)key, (c_StringView_t*)elem); +} + +// ========================================== +// 2. 核心测试用例 +// ========================================== + +TEST_CASE(test_c_BinarySearch_IntArray) { + int sorted_ints[] = { 2, 5, 8, 12, 16, 23, 38, 56, 72, 91 }; + c_size_t array_len = sizeof(sorted_ints) / sizeof(sorted_ints[0]); + + // 1. 查找存在的元素 + int target_exist = 23; + const int* result1 = (const int*)c_BinarySearch(&target_exist, sorted_ints, array_len, sizeof(int), compare_ints); + ASSERT_PTR_NOT_NULL(result1); + ASSERT_INT_EQ(23, *result1); + ASSERT_LL_EQ(5, result1 - sorted_ints); // 校验指针偏移,确定下标为 5 + + // 2. 查找不存在的元素 + int target_missing = 40; + const int* result2 = (const int*)c_BinarySearch(&target_missing, sorted_ints, array_len, sizeof(int), compare_ints); + ASSERT_TRUE(result2 == NULL); +} + +TEST_CASE(test_c_BinarySearch_StructArray) { + // 模拟数据库索引项或符号表,已按 ID 升序排好 + TestItem_t items[] = { + { 1001, 55 }, + { 1005, 99 }, + { 1012, 12 }, + { 1045, 78 } + }; + c_size_t array_len = sizeof(items) / sizeof(items[0]); + + // 查找 ID 为 1012 的结构体数据 + uint32_t search_id = 1012; + const TestItem_t* match = (const TestItem_t*)c_BinarySearch(&search_id, items, array_len, sizeof(TestItem_t), compare_items_by_id); + + ASSERT_PTR_NOT_NULL(match); + ASSERT_INT_EQ(1012, (int)match->id); + ASSERT_INT_EQ(12, (int)match->value); // 成功提取对应的值 + ASSERT_LL_EQ(2, match - items); // 下标确认 +} + +TEST_CASE(test_c_BinarySearch_StringViewArray) { + // 复用之前的有序 StringView 数组进行查找 + c_StringView_t sv_array[] = { + c_StringView_FromCStr("apple"), + c_StringView_FromCStr("banana"), + c_StringView_FromCStr("cherry"), + c_StringView_FromCStr("date") + }; + c_size_t array_len = sizeof(sv_array) / sizeof(sv_array[0]); + + c_StringView_t target = c_StringView_FromCStr("banana"); + const c_StringView_t* match = (const c_StringView_t*)c_BinarySearch(&target, sv_array, array_len, sizeof(c_StringView_t), compare_string_views); + + ASSERT_PTR_NOT_NULL(match); + ASSERT_INT_EQ(6, (int)match->size); + ASSERT_TRUE(memcmp(match->str, "banana", 6) == 0); + ASSERT_LL_EQ(1, match - sv_array); // 确认位于数组第 1 项 +} + +TEST_CASE(test_c_BinarySearch_EdgeAndNull) { + int single_elem[] = { 42 }; + int target = 42; + + // 1. 空参数安全防御 + ASSERT_TRUE(c_BinarySearch(NULL, single_elem, 1, sizeof(int), compare_ints) == NULL); + ASSERT_TRUE(c_BinarySearch(&target, NULL, 1, sizeof(int), compare_ints) == NULL); + ASSERT_TRUE(c_BinarySearch(&target, single_elem, 1, sizeof(int), NULL) == NULL); + ASSERT_TRUE(c_BinarySearch(&target, single_elem, 0, sizeof(int), compare_ints) == NULL); + + // 2. 单元素数组边界查找 + const int* match = (const int*)c_BinarySearch(&target, single_elem, 1, sizeof(int), compare_ints); + ASSERT_PTR_NOT_NULL(match); + ASSERT_INT_EQ(42, *match); +} + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +int main(int argc, char** argv){ + + TEST_START(Unit Tests); + + // 运行普通无环境要求的用例 + RUN_TEST(test_c_BinarySearch_IntArray); + RUN_TEST(test_c_BinarySearch_StructArray); + RUN_TEST(test_c_BinarySearch_StringViewArray); + RUN_TEST(test_c_BinarySearch_EdgeAndNull); + + // 打印最终统计报告 + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Foundation/c_KnuthShuffle.c b/Foundation/c_KnuthShuffle.c new file mode 100644 index 0000000..3b6e540 --- /dev/null +++ b/Foundation/c_KnuthShuffle.c @@ -0,0 +1,51 @@ +#include +#include "c_Random.h" + +void c_KnuthShuffle(void* base, c_size_t num, c_size_t size) { + // 边界与防御性校验:元素少于2个或大小非法时无需洗牌 + if (!base || num < 2 || size == 0) { + return; + } + + c_Random_Init(); + + char* array_base = (char*)base; + + // 采用局部栈分配缓冲区,避免堆内存分配开销,提升交换效率 + // 256字节足够容纳绝大多数基础类型与常见的结构体 + char temp_buf[256]; + + // 从后往前进行滑窗交换 + for (c_size_t i = num; i > 1; i--) { + + c_size_t current_idx = i-1; + + // 安全地在 [0, i] 闭区间内抽取一个随机目标下标 + // 依托您之前的去偏差 c_Random_RangeU64 确保概率绝对均等 + c_size_t j = (c_size_t)c_Random_RangeU64(0, current_idx); + + // 如果抽中自身,则无需原地交换 + if (current_idx == j) { + continue; + } + + // 计算需要交换的两个元素在内存中的绝对地址 + char* elem_i = array_base + (current_idx * size); + char* elem_j = array_base + (j * size); + + // 泛型就地内存交换 (In-place Swap) + c_size_t bytes_to_swap = size; + while (bytes_to_swap > 0) { + c_size_t chunk = (bytes_to_swap < sizeof(temp_buf)) ? bytes_to_swap : sizeof(temp_buf); + + memcpy(temp_buf, elem_i, chunk); + memcpy(elem_i, elem_j, chunk); + memcpy(elem_j, temp_buf, chunk); + + elem_i += chunk; + elem_j += chunk; + bytes_to_swap -= chunk; + } + } +} + diff --git a/Foundation/c_KnuthShuffle.h b/Foundation/c_KnuthShuffle.h new file mode 100644 index 0000000..77f322a --- /dev/null +++ b/Foundation/c_KnuthShuffle.h @@ -0,0 +1,21 @@ +#ifndef INCLUDED_C_KNUTHSHUFFLE_H +#define INCLUDED_C_KNUTHSHUFFLE_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 工业级泛型 Knuth (Fisher-Yates) 乱序/洗牌算法 + * + * @param base 指向待洗牌的连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + */ +void c_KnuthShuffle(void* base, c_size_t num, c_size_t size); + +#endif /*INCLUDED_C_KNUTHSHUFFLE_H*/ diff --git a/Foundation/c_KnuthShuffle.t.c b/Foundation/c_KnuthShuffle.t.c new file mode 100644 index 0000000..9fb3d7b --- /dev/null +++ b/Foundation/c_KnuthShuffle.t.c @@ -0,0 +1,112 @@ +#include "c_KnuthShuffle.h" +#include "c_Test.h" +#include +#include +#include + +TEST_CASE(test_c_KnuthShuffle_IntArray) { + // 显式播种确保单次测试序列可重现,或调用您的 c_Random_Init() + srand(54321); + + int original[] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; + int arr[] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + // 执行洗牌 + c_KnuthShuffle(arr, num, sizeof(int)); + + // 1. 验证乱序生效性:洗牌后的数组大概率不再与原数组完全相同 + int match_count = 0; + for (size_t i = 0; i < num; i++) { + if (arr[i] == original[i]) { + match_count++; + } + } + // 10个元素洗牌后,依然完全对齐原数组的概率极低 + ASSERT_TRUE(match_count < (int)num); + + // 2. 验证元素守恒性:利用计数桶确保数据没有在交换中丢失或损坏 + for (size_t i = 0; i < num; i++) { + int target = original[i]; + bool found = false; + for (size_t j = 0; j < num; j++) { + if (arr[j] == target) { + found = true; + break; + } + } + ASSERT_TRUE(found); // 每一个原数组的元素都必须在洗牌后的数组中存在 + } +} + +TEST_CASE(test_c_KnuthShuffle_StringViewArray) { + c_StringView_t original_sv[] = { + c_StringView_FromCStr("A"), + c_StringView_FromCStr("B"), + c_StringView_FromCStr("C"), + c_StringView_FromCStr("D") + }; + + c_StringView_t sv_arr[] = { + c_StringView_FromCStr("A"), + c_StringView_FromCStr("B"), + c_StringView_FromCStr("C"), + c_StringView_FromCStr("D") + }; + c_size_t num = sizeof(sv_arr) / sizeof(sv_arr[0]); + + // 对 StringView 结构体数组执行泛型洗牌 + c_KnuthShuffle(sv_arr, num, sizeof(c_StringView_t)); + + // 验证洗牌后所有的 StringView 依然完好、大小未被破坏 + for (size_t i = 0; i < num; i++) { + c_StringView_t* cur = &sv_arr[i]; + ASSERT_PTR_NOT_NULL(cur->str); + ASSERT_INT_EQ(1, (int)cur->size); // 每个视图长度原本都为 1 + + // 确保属于原集合 A, B, C, D 之一 + char c = cur->str[0]; + ASSERT_TRUE(c == 'A' || c == 'B' || c == 'C' || c == 'D'); + } +} + +TEST_CASE(test_c_KnuthShuffle_EdgeCases) { + int single_arr[] = { 42 }; + + // 1. 测试单元素或空元素数组的防御能力(应当安全返回,不崩溃) + c_KnuthShuffle(single_arr, 1, sizeof(int)); + ASSERT_INT_EQ(42, single_arr[0]); + + c_KnuthShuffle(NULL, 0, sizeof(int)); + ASSERT_TRUE(true); // 代表通过了 NULL 校验 +} + +TEST_CASE(test_c_KnuthShuffle_UnderflowDefense) { + int small_arr[] = { 1, 2 }; + + // 压测 2 个元素的无符号边界切换,确保能在一瞬间安全退出 + c_KnuthShuffle(small_arr, 2, sizeof(int)); + + // 修正:分别验证下标 0 和 下标 1 的元素有效性 + ASSERT_TRUE(small_arr[0] == 1 || small_arr[0] == 2); + ASSERT_TRUE(small_arr[1] == 1 || small_arr[1] == 2); + + // 附加校验:洗牌不会凭空克隆元素,两个位置的值必须保持不相等 + ASSERT_TRUE(small_arr[0] != small_arr[1]); +} + +int main(int argc, char** argv){ + TEST_START(Unit Tests); + // 运行普通无环境要求的用例 + RUN_TEST(test_c_KnuthShuffle_IntArray); + RUN_TEST(test_c_KnuthShuffle_StringViewArray); + RUN_TEST(test_c_KnuthShuffle_EdgeCases); + RUN_TEST(test_c_KnuthShuffle_UnderflowDefense); + + // 打印最终统计报告 + TEST_REPORT(); + + RETURN_TEST_STATUS; + + return 0; +} diff --git a/Foundation/c_Random.c b/Foundation/c_Random.c new file mode 100644 index 0000000..1288550 --- /dev/null +++ b/Foundation/c_Random.c @@ -0,0 +1,117 @@ +#include + +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) + #include +#else + #include +#endif + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +void c_Random_Init(void) { + // 1. 获取基础日历时间(秒级) + uint64_t seed_entropy = (uint64_t)time(NULL); + + // 2. 获取高精度硬件/OS级时间戳,阻断高频重启或高并发时的秒级碰撞 +#if defined(_WIN32) || defined(_WIN64) + LARGE_INTEGER perf_counter; + if (QueryPerformanceCounter(&perf_counter)) { + seed_entropy ^= (uint64_t)perf_counter.QuadPart; + } else { + seed_entropy ^= (uint64_t)GetTickCount(); + } + // 混合当前进程 ID + seed_entropy ^= ((uint64_t)GetCurrentProcessId() << 16); +#else + struct timespec ts; + // 优先采用高精度纳秒单调时钟 + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { + seed_entropy ^= ((uint64_t)ts.tv_sec << 32) ^ (uint64_t)ts.tv_nsec; + } else if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { + seed_entropy ^= ((uint64_t)ts.tv_sec << 32) ^ (uint64_t)ts.tv_nsec; + } + // 混合当前进程 ID + seed_entropy ^= ((uint64_t)getpid() << 16); +#endif + + // 3. 核心避坑:加入运行时环境的 ASLR 栈空间地址作为环境噪声 + // 局部变量在栈上的地址在每次运行时受操作系统 ASLR 影响都是随机的 + volatile int dummy_stack_var = 0; + uintptr_t stack_address = (uintptr_t)&dummy_stack_var; + seed_entropy ^= (uint64_t)stack_address; + + // 4. 将采集到的 64 位熵数据进行简单混淆(对折异或),降维匹配到标准库 srand 的参数空间 + unsigned int final_seed = (unsigned int)(seed_entropy ^ (seed_entropy >> 32)); + + // 5. 播种 + srand(final_seed); +} + + +/** + * @brief 生成指定闭区间 [min, max] 内的高质量均匀分布无符号整数 + * + * @param min 区间下界(包含) + * @param max 区间上界(包含) + * @return uint64_t 生成的随机数 + */ +uint64_t c_Random_RangeU64(uint64_t min, uint64_t max) { + if (min >= max) { + return min; + } + + uint64_t range = max - min + 1; + + // 核心:消除模数偏差 (Modulo Bias) 算法 + // 计算在 RAND_MAX(或此处为 UINT64_MAX 映射区间)内排出的残缺余数窗口 + uint64_t limit = UINT64_MAX - (UINT64_MAX % range); + uint64_t rand_val; + + do { + // 拼接两个标准的 32 位随机数以组合成高质量的 64 位宽随机数源 + // 若您的平台有硬件随机数(如 NDK 的 arc4random 或 x86 的 RDRAND),可在此处替换 + uint64_t high = (uint32_t)rand(); + uint64_t low = (uint32_t)rand(); + rand_val = (high << 32) | low; + } while (rand_val >= limit); // 落在残缺窗口内的数直接丢弃,重新抽取 + + return min + (rand_val % range); +} + +/** + * @brief 生成指定闭区间 [min, max] 内的高质量有符号整数 + */ +int64_t c_Random_RangeI64(int64_t min, int64_t max) { + if (min >= max) return min; + + // 将有符号区间平移映射至无符号空间处理,完美规避符号位溢出死锁 + uint64_t range = (uint64_t)(max - min); + uint64_t limit = UINT64_MAX - (UINT64_MAX % (range + 1)); + uint64_t rand_val; + + do { + uint64_t high = (uint32_t)rand(); + uint64_t low = (uint32_t)rand(); + rand_val = (high << 32) | low; + } while (rand_val >= limit); + + return min + (int64_t)(rand_val % (range + 1)); +} + +/** + * @brief 生成指定区间 [min, max) 内的双精度浮点数 + */ +double c_Random_RangeDouble(double min, double max) { + if (min >= max) return min; + + // 将标准生成的 64 位无符号随机数归一化映射到 [0.0, 1.0) 之间 + double scale = (double)rand() / ((double)RAND_MAX + 1.0); + + return min + scale * (max - min); +} + diff --git a/Foundation/c_Random.h b/Foundation/c_Random.h new file mode 100644 index 0000000..856d256 --- /dev/null +++ b/Foundation/c_Random.h @@ -0,0 +1,39 @@ +#ifndef INCLUDED_C_RANDOM_H +#define INCLUDED_C_RANDOM_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 初始化随机数生成引擎(自适应混合熵播种) + * + * 融合标准时间、高精度纳秒/微秒计时器、当前进程PID + * 以及局部变量在栈上的随机内存地址,提供极高的抗种子碰撞能力。 + */ +void c_Random_Init(void); + +/** + * @brief 生成指定闭区间 [min, max] 内的高质量均匀分布无符号整数 + * + * @param min 区间下界(包含) + * @param max 区间上界(包含) + * @return uint64_t 生成的随机数 + */ +uint64_t c_Random_RangeU64(uint64_t min, uint64_t max); + +/** + * @brief 生成指定闭区间 [min, max] 内的高质量有符号整数 + */ +int64_t c_Random_RangeI64(int64_t min, int64_t max); + +/** + * @brief 生成指定区间 [min, max) 内的双精度浮点数 + */ +double c_Random_RangeDouble(double min, double max); + +#endif /*INCLUDED_C_RANDOM_H*/ diff --git a/Foundation/c_Random.t.c b/Foundation/c_Random.t.c new file mode 100644 index 0000000..89d1d79 --- /dev/null +++ b/Foundation/c_Random.t.c @@ -0,0 +1,129 @@ +#include "c_Random.h" +#include +#include +#include "c_Test.h" + +TEST_CASE(test_c_Random_RangeU64_Bounds) { + // 设置随机数种子以保证单次测试的稳定性 + srand(12345); + + uint64_t min = 10; + uint64_t max = 20; + + // 1. 验证大量生成时,所有数字绝不越出 [10, 20] 边界 + for (int i = 0; i < 1000; i++) { + uint64_t val = c_Random_RangeU64(min, max); + ASSERT_TRUE(val >= min); + ASSERT_TRUE(val <= max); + } + + // 2. 逆序边界防御测试 + ASSERT_LL_EQ(50, c_Random_RangeU64(50, 10)); + ASSERT_LL_EQ(30, c_Random_RangeU64(30, 30)); +} + +TEST_CASE(test_c_Random_RangeI64_Negative) { + int64_t min = -50; + int64_t max = -10; + + // 3. 验证包含负数区间的生成矩阵安全性 + for (int i = 0; i < 1000; i++) { + int64_t val = c_Random_RangeI64(min, max); + ASSERT_TRUE(val >= min); + ASSERT_TRUE(val <= max); + } + + // 跨越 0 的区间测试 + int64_t cross_min = -5; + int64_t cross_max = 5; + bool hit_negative = false; + bool hit_positive = false; + + for (int i = 0; i < 500; i++) { + int64_t val = c_Random_RangeI64(cross_min, cross_max); + ASSERT_TRUE(val >= cross_min && val <= cross_max); + if (val < 0) hit_negative = true; + if (val > 0) hit_positive = true; + } + // 统计学判定:生成500次,正数和负数应该都有几率被命中 + ASSERT_TRUE(hit_negative && hit_positive); +} + +TEST_CASE(test_c_Random_RangeDouble_Distribution) { + double min = 1.5; + double max = 2.5; + + // 4. 验证浮点数边界安全性 + for (int i = 0; i < 1000; i++) { + double val = c_Random_RangeDouble(min, max); + ASSERT_TRUE(val >= min); + ASSERT_TRUE(val < max); // 浮点通常为左闭右开区间 + } +} + +TEST_CASE(test_c_Random_ModuloBias_Elimination) { + // 5. 验证极小范围下的去偏效果,频繁抽取测试是否出现死循环 + uint64_t small_min = 0; + uint64_t small_max = 1; // 仅生成 0 或 1 + + int count_0 = 0; + int count_1 = 0; + + for (int i = 0; i < 2000; i++) { + uint64_t val = c_Random_RangeU64(small_min, small_max); + if (val == 0) count_0++; + if (val == 1) count_1++; + } + + ASSERT_TRUE(count_0 > 0); + ASSERT_TRUE(count_1 > 0); + // 2000次抽取中,在去偏机制下,0和1的频次应当大致平分秋色(此处做宽泛判定确保随机性生效) + ASSERT_TRUE(abs(count_0 - count_1) < 400); +} + +TEST_CASE(test_c_Random_Init_EntropyBreak) { + // 1. 验证初始化函数可以正常被重复安全执行,不引发任何异常或崩溃 + c_Random_Init(); + uint64_t first_rand = c_Random_RangeU64(1, 1000000); + + // 2. 连续快速进行二次初始化(模拟极高频率的重新播种) + c_Random_Init(); + uint64_t second_rand = c_Random_RangeU64(1, 1000000); + + // 在高精度混合熵的保障下,这两次连续抽出的百万级别随机数在统计学上几乎不可能相等 + // (如果使用了差劲的普通 time(NULL) 播种,它们必然相等,因为处于同一秒内) + ASSERT_TRUE(first_rand != second_rand); +} + +TEST_CASE(test_c_Random_Init_Stability) { + // 3. 验证初始化后生成的随机数仍在预期范围内 + c_Random_Init(); + + uint64_t min_val = 5; + uint64_t max_val = 15; + + for (int i = 0; i < 50; i++) { + uint64_t val = c_Random_RangeU64(min_val, max_val); + ASSERT_TRUE(val >= min_val); + ASSERT_TRUE(val <= max_val); + } +} + + +int main(int argc, char** argv){ + + TEST_START(Unit Tests); + + // 运行普通无环境要求的用例 + RUN_TEST(test_c_Random_Init_EntropyBreak); + RUN_TEST(test_c_Random_Init_Stability); + RUN_TEST(test_c_Random_RangeU64_Bounds); + RUN_TEST(test_c_Random_RangeI64_Negative); + RUN_TEST(test_c_Random_RangeDouble_Distribution); + RUN_TEST(test_c_Random_ModuloBias_Elimination); + + // 打印最终统计报告 + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Foundation/c_StringView.c b/Foundation/c_StringView.c index 0ebd3e4..16e4629 100644 --- a/Foundation/c_StringView.c +++ b/Foundation/c_StringView.c @@ -60,7 +60,7 @@ unsigned long c_StringView_ToUL(const c_StringView_t* self, const char** endptr, // 3. 自动识别进制 (Base == 0) 或校验 16 进制前缀 if (base == 0) { - if (start + 1 < end && *start == '0' && (start[1] == 'x' || start[1] == 'X')) { + if ((c_size_t)(end - start) >= 2 && *start == '0' && (start[1] == 'x' || start[1] == 'X')) { base = 16; start += 2; } else if (start < end && *start == '0') { @@ -71,7 +71,7 @@ unsigned long c_StringView_ToUL(const c_StringView_t* self, const char** endptr, } } else if (base == 16) { // 如果显式指定了16进制,允许略过 0x/0X 前缀 - if (start + 1 < end && *start == '0' && (start[1] == 'x' || start[1] == 'X')) { + if ((c_size_t)(end - start) >= 2 && *start == '0' && (start[1] == 'x' || start[1] == 'X')) { start += 2; } } diff --git a/Foundation/c_StringView.h b/Foundation/c_StringView.h index 5aa6f98..f5a36e7 100644 --- a/Foundation/c_StringView.h +++ b/Foundation/c_StringView.h @@ -36,9 +36,11 @@ c_StringView_t c_StringView_FromParts(const char* str, c_size_t len) { C_STATIC_FORCE_INLINE c_StringView_t c_StringView_Sub(const c_StringView_t* self, c_size_t start, c_size_t len) { c_StringView_t result={0}; - if (start < self->size) { + if (self && self->str && start < self->size) { result.str = self->str + start; - result.size = ((start + len) <= self->size)?len:(self->size - start); + // 安全防御:通过减法判断可供截取的最大安全长度,防止 start + len 加法溢出 + c_size_t max_available = self->size - start; + result.size = (len <= max_available) ? len : max_available; } return result; } @@ -109,14 +111,28 @@ c_index_t c_StringView_FindChar(const c_StringView_t* self, const c_size_t start C_STATIC_FORCE_INLINE bool c_StringView_Split(c_StringView_t * self, const char delim, c_StringView_t* left, c_StringView_t* right) { + if (!self || self->size == 0 || !self->str) { + if (left) *left = (c_StringView_t){NULL, 0}; + if (right) *right = (c_StringView_t){NULL, 0}; + return false; + } const c_index_t idx = c_StringView_FindChar(self, 0, delim); if (idx == -1) { if (left) *left = *self; if (right) *right = (c_StringView_t){ NULL, 0 }; return false; } - if (left) *left = c_StringView_Sub(self, 0, idx); - if (right) *right = c_StringView_Sub(self, idx + 1, self->size - idx - 1); + if (left) *left = c_StringView_Sub(self, 0, (c_size_t)idx); + + if (right) { + c_size_t next_start = (c_size_t)idx + 1; + if (next_start < self->size) { + // 使用逆向减法,确保绝对不会发生下溢 + *right = c_StringView_Sub(self, next_start, self->size - next_start); + } else { + *right = (c_StringView_t){ NULL, 0 }; + } + } return true; } diff --git a/Foundation/c_Swap.c b/Foundation/c_Swap.c new file mode 100644 index 0000000..e0f5f5c --- /dev/null +++ b/Foundation/c_Swap.c @@ -0,0 +1,5 @@ +#include + + + + diff --git a/Foundation/c_Swap.h b/Foundation/c_Swap.h new file mode 100644 index 0000000..2e9a7af --- /dev/null +++ b/Foundation/c_Swap.h @@ -0,0 +1,47 @@ +#ifndef INCLUDED_C_SWAP_H +#define INCLUDED_C_SWAP_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 泛型就地内存块数据互换接口 + * + * @param a 指向第一个待交换对象的泛型指针 + * @param b 指向第二个待交换对象的泛型指针 + * @param size 待交换对象占用的内存字节大小 (sizeof) + */ +C_STATIC_FORCE_INLINE +void c_Swap(void* a, void* b, c_size_t size) { + // 边界与自等性防御:指针无效、长度为0、或者两个指针指向同一个内存物理地址时无需交换 + if (!a || !b || size == 0 || a == b) { + return; + } + + char* p1 = (char*)a; + char* p2 = (char*)b; + + // 在栈上开辟一个小缓冲区。256 字节足以一次性吃下绝大多数基本类型和中小型结构体 + char temp_buf[256]; + c_size_t bytes_left = size; + + // 分块拷贝机制:无视对象体量,即便面临超出 256 字节的巨型结构体也能安全互换 + while (bytes_left > 0) { + const c_size_t chunk = (bytes_left < sizeof(temp_buf)) ? bytes_left : sizeof(temp_buf); + + memcpy(temp_buf, p1, chunk); + memcpy(p1, p2, chunk); + memcpy(p2, temp_buf, chunk); + + p1 += chunk; + p2 += chunk; + bytes_left -= chunk; + } +} + +#endif /*INCLUDED_C_SWAP_H*/ diff --git a/Search/c_BST.c b/Search/c_BST.c new file mode 100644 index 0000000..81ed7b6 --- /dev/null +++ b/Search/c_BST.c @@ -0,0 +1,215 @@ +#include + +c_err_t c_BST_Init(c_BST_t* self, c_size_t key_size, c_size_t val_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator) { + if (!self || key_size == 0 || val_size == 0 || !cmp) { + return C_ERR_PARAM; + } + + // 自适应分配器降级缺省安全播种 + if (allocator) { + self->allocator = *allocator; + } else { + self->allocator = c_DefaultAllocator; + } + + self->root = NULL; + self->size = 0; + self->key_size = key_size; + self->val_size = val_size; + self->cmp = cmp; + self->args = args; + + return C_ERR_OK; +} + +/** + * @brief 根据指定的 Key 检索关联的 Value 值(平均时间复杂度 O(log N)) + */ +c_err_t c_BST_Get(const c_BST_t* self, const void* key, void* out_val) { + if (!self || !key || !out_val) return C_ERR_PARAM; + + c_BSTNode* curr = self->root; + while (curr != NULL) { + int cmp_res = self->cmp(key, curr->key, self->args); + if (cmp_res < 0) { + curr = curr->left; // 目标比当前小,滑向左子树 + } else if (cmp_res > 0) { + curr = curr->right; // 目标比当前大,滑向右子树 + } else { + // 精确命中,将内部深度存储的数据镜像复制给外部 + memcpy(out_val, curr->val, self->val_size); + return C_ERR_OK; + } + } + + return C_ERR_NOTFOUND; +} + +/** + * @brief 检查树中是否包含指定的 Key 键值 + */ +bool c_BST_Contains(const c_BST_t* self, const void* key) { + if (!self || !key) return false; + c_BSTNode* curr = self->root; + while (curr != NULL) { + int cmp_res = self->cmp(key, curr->key, self->args); + if (cmp_res < 0) curr = curr->left; + else if (cmp_res > 0) curr = curr->right; + else return true; + } + return false; +} + +/** + * @brief 内部递归插入/覆写辅助(基于二级指针代理,完美消除悬空控制流) + */ +static c_err_t c_BST_InternalPut(c_BST_t* self, c_BSTNode** node_ptr, const void* key, const void* val, bool* is_new_inserted) { + c_BSTNode* curr = *node_ptr; + + // 递归基:如果当前插槽为空,在此物理位置开辟并挂载新节点 + if (curr == NULL) { + c_BSTNode* new_node = (c_BSTNode*)c_Allocator_Alloc(&self->allocator, sizeof(c_BSTNode)); + void* new_key = c_Allocator_Alloc(&self->allocator, self->key_size); + void* new_val = c_Allocator_Alloc(&self->allocator, self->val_size); + + if (!new_node || !new_key || !new_val) { + if (new_node) c_Allocator_Free(&self->allocator, new_node); + if (new_key) c_Allocator_Free(&self->allocator, new_key); + if (new_val) c_Allocator_Free(&self->allocator, new_val); + return C_ERR_NOMEM; + } + + memcpy(new_key, key, self->key_size); + memcpy(new_val, val, self->val_size); + new_node->key = new_key; + new_node->val = new_val; + new_node->left = NULL; + new_node->right = NULL; + + *node_ptr = new_node; // 代理写入,父节点指针自动对齐 + *is_new_inserted = true; + return C_ERR_OK; + } + + int cmp_res = self->cmp(key, curr->key, self->args); + if (cmp_res < 0) { + return c_BST_InternalPut(self, &(curr->left), key, val, is_new_inserted); + } else if (cmp_res > 0) { + return c_BST_InternalPut(self, &(curr->right), key, val, is_new_inserted); + } else { + // 键已存在,执行覆写(Overwrite)语义 + memcpy(curr->val, val, self->val_size); + *is_new_inserted = false; + return C_ERR_OK; + } +} + +/** + * @brief 存入键值对。若 Key 已存在则覆写更新;若不存在则开辟新节点维护树拓扑 + */ +c_err_t c_BST_Put(c_BST_t* self, const void* key, const void* val) { + if (!self || !key || !val) return C_ERR_PARAM; + + bool is_new = false; + c_err_t err = c_BST_InternalPut(self, &(self->root), key, val, &is_new); + if (err == C_ERR_OK && is_new) { + self->size++; + } + return err; +} + +/** + * @brief 内部辅助:寻找并剥离指定子树的绝对最小值节点(用于 Hibbard 删除替换) + */ +static c_BSTNode* c_BST_DeleteMin(c_BST_t* self, c_BSTNode** node_ptr) { + c_BSTNode* curr = *node_ptr; + if (curr->left == NULL) { + // 找到最小值,将右子树代理向上对接,将当前节点断开剥离并返回 + *node_ptr = curr->right; + return curr; + } + return c_BST_DeleteMin(self, &(curr->left)); +} + +/** + * @brief 内部递归删除控制流(基于二级指针代理的 Hibbard 经典删除算法) + */ +static c_err_t c_BST_InternalDelete(c_BST_t* self, c_BSTNode** node_ptr, const void* key) { + c_BSTNode* curr = *node_ptr; + if (curr == NULL) { + return C_ERR_NOTFOUND; // 节点不存在 + } + + int cmp_res = self->cmp(key, curr->key, self->args); + if (cmp_res < 0) { + return c_BST_InternalDelete(self, &(curr->left), key); + } else if (cmp_res > 0) { + return c_BST_InternalDelete(self, &(curr->right), key); + } else { + // 精确命中当前要删除的节点 curr,开启 Hibbard 拆解合并 + c_BSTNode* old_node = curr; + + if (curr->right == NULL) { + // 情况 1:无右子树,直接将左子树整体顶替上来 + *node_ptr = curr->left; + } else if (curr->left == NULL) { + // 情况 2:无左子树,直接将右子树整体顶替上来 + *node_ptr = curr->right; + } else { + // 情况 3:左右子树均完好。寻找右子树的绝对最小值充当继承后继者 (Successor) + c_BSTNode* successor = c_BST_DeleteMin(self, &(curr->right)); + + // 后继者完美接管原节点的双向拓扑路由 + successor->left = old_node->left; + successor->right = *node_ptr; // 此时 *node_ptr 已经是处理过 deleteMin 后的右子树根 + + *node_ptr = successor; // 代理顶替 + } + + // 释放被移出树的旧节点物理内存 + c_Allocator_Free(&self->allocator, old_node->key); + c_Allocator_Free(&self->allocator, old_node->val); + c_Allocator_Free(&self->allocator, old_node); + return C_ERR_OK; + } +} + +/** + * @brief 根据指定 Key 彻底从树中移出其关联的键值对节点 + */ +c_err_t c_BST_Delete(c_BST_t* self, const void* key) { + if (!self || !key) return C_ERR_PARAM; + if (self->size == 0) return C_ERR_EMPTY; + + c_err_t err = c_BST_InternalDelete(self, &(self->root), key); + if (err == C_ERR_OK) { + self->size--; + } + return err; +} + +/** + * @brief 内部递归反初始化解构辅助 + */ +static void c_BST_InternalDeinit(c_Allocator_t* alloc, c_BSTNode* node) { + if (node == NULL) return; + + // 递归后序遍历:先解构左右子树,再回收当前节点 + c_BST_InternalDeinit(alloc, node->left); + c_BST_InternalDeinit(alloc, node->right); + + c_Allocator_Free(alloc, node->key); + c_Allocator_Free(alloc, node->val); + c_Allocator_Free(alloc, node); +} + +/** + * @brief 二叉搜索树反初始化彻底释放 + */ +void c_BST_Destroy(c_BST_t* self) { + if (self && self->root) { + c_BST_InternalDeinit(&self->allocator, self->root); + self->root = NULL; + self->size = 0; + } +} \ No newline at end of file diff --git a/Search/c_BST.h b/Search/c_BST.h new file mode 100644 index 0000000..835b1c9 --- /dev/null +++ b/Search/c_BST.h @@ -0,0 +1,51 @@ +#ifndef INCLUDED_C_BST_H +#define INCLUDED_C_BST_H + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct c_BSTNode { + void* key; // 独立分配存储的 Key 物理地址 + void* val; // 独立分配存储的 Value 物理地址 + struct c_BSTNode* left; // 左子节点指针 (指向更小键的子树) + struct c_BSTNode* right; // 右子节点指针 (指向更大键的子树) +} c_BSTNode; + +/** + * @brief 工业级泛型二叉搜索树结构体(分配器内联组合版) + */ +typedef struct { + c_BSTNode* root; // 树的根节点指针 + c_size_t size; // 当前树内有效驻留的键值对总个数 + c_size_t key_size; // 键对象占用的物理字节大小 (sizeof) + c_size_t val_size; // 值对象占用的物理字节大小 (sizeof) + c_SortCompare_t cmp; // 键对象专用的动态回调比对器 + void* args; // 自定义上下文参数指针 + c_Allocator_t allocator; // 内联组合分配器实例与默认 Fallback 缺省机制 +} c_BST_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_BST_Init(c_BST_t* self, c_size_t key_size, c_size_t val_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator); + +c_err_t c_BST_Get(const c_BST_t* self, const void* key, void* out_val); + +bool c_BST_Contains(const c_BST_t* self, const void* key); + +c_err_t c_BST_Put(c_BST_t* self, const void* key, const void* val) ; + +c_err_t c_BST_Delete(c_BST_t* self, const void* key); + +void c_BST_Destroy(c_BST_t* self); + +#endif /*INCLUDED_C_BST_H*/ diff --git a/Search/c_BST.t.c b/Search/c_BST.t.c new file mode 100644 index 0000000..04cd328 --- /dev/null +++ b/Search/c_BST.t.c @@ -0,0 +1,88 @@ +#include "c_BST.h" +#include "c_Test.h" +#include +#include + +static int bst_compare_chars(const void* a, const void* b, void* args) { + (void)args; + char char1 = *(const char*)a; + char char2 = *(const char*)b; + return char1 - char2; +} + + + +TEST_CASE(test_c_BST_Dynamic_CRUD) { + c_BST_t tree; + // 就地初始化:将 char 作为 Key,int 作为 Value + c_err_t err = c_BST_Init(&tree, sizeof(char), sizeof(int), bst_compare_chars, NULL, &c_DefaultAllocator); + ASSERT_INT_EQ(C_ERR_OK, err); + + // 故意以颠簸顺序插入,构造一个经典的非退化二叉平衡树拓扑 + char key_M = 'M'; int val_M = 40; + char key_E = 'E'; int val_E = 20; + char key_S = 'S'; int val_S = 50; + char key_A = 'A'; int val_A = 10; + char key_R = 'R'; int val_R = 45; + + ASSERT_INT_EQ(C_ERR_OK, c_BST_Put(&tree, &key_M, &val_M)); + ASSERT_INT_EQ(C_ERR_OK, c_BST_Put(&tree, &key_E, &val_E)); + ASSERT_INT_EQ(C_ERR_OK, c_BST_Put(&tree, &key_S, &val_S)); + ASSERT_INT_EQ(C_ERR_OK, c_BST_Put(&tree, &key_A, &val_A)); + ASSERT_INT_EQ(C_ERR_OK, c_BST_Put(&tree, &key_R, &val_R)); + ASSERT_INT_EQ(5, (int)tree.size); + + // 1. Get 状态命中读取验证 + int get_val = 0; + ASSERT_INT_EQ(C_ERR_OK, c_BST_Get(&tree, &key_S, &get_val)); + ASSERT_INT_EQ(50, get_val); + + ASSERT_TRUE(c_BST_Contains(&tree, &key_R)); + char key_X = 'X'; + ASSERT_TRUE(!c_BST_Contains(&tree, &key_X)); + + // 2. Overwrite 相同键覆写更新审计 + int update_val_M = 999; + ASSERT_INT_EQ(C_ERR_OK, c_BST_Put(&tree, &key_M, &update_val_M)); + ASSERT_INT_EQ(5, (int)tree.size); // 大小锁死不能变 + ASSERT_INT_EQ(C_ERR_OK, c_BST_Get(&tree, &key_M, &get_val)); + ASSERT_INT_EQ(999, get_val); + + // 3. Delete 深度极限压测:删除拥有双向子树的复杂中段根节点 'S' + // 修复后的 Hibbard 机制应当自动将 'R' 节点提升顶替上来,并原路释放 S 的内存 + ASSERT_INT_EQ(C_ERR_OK, c_BST_Delete(&tree, &key_S)); + ASSERT_INT_EQ(4, (int)tree.size); + ASSERT_INT_EQ(C_ERR_NOTFOUND, c_BST_Get(&tree, &key_S, &get_val)); + + // 确保与 S 共同历经剧变的邻居节点 R 依然在新树中完好驻留 + ASSERT_INT_EQ(C_ERR_OK, c_BST_Get(&tree, &key_R, &get_val)); + ASSERT_INT_EQ(45, get_val); + + // 深度级联注销反初始化 + c_BST_Destroy(&tree); +} + +TEST_CASE(test_c_BST_ParamConstraints) { + c_BST_t local_tree; + c_BST_Init(&local_tree, sizeof(char), sizeof(int), bst_compare_chars, NULL, NULL); // 降格 Fallback + + char k = 'Q'; + int v = 77; + // 验证各类非法调用的拦截返回 + ASSERT_INT_EQ(C_ERR_PARAM, c_BST_Init(NULL, sizeof(char), sizeof(int), bst_compare_chars, NULL, NULL)); + ASSERT_INT_EQ(C_ERR_PARAM, c_BST_Put(NULL, &k, &v)); + ASSERT_INT_EQ(C_ERR_EMPTY, c_BST_Delete(&local_tree, &k)); // 空树删除直接拦截抛出 C_ERR_EMPTY + + c_BST_Destroy(&local_tree); +} + +// ========================================== +// 5. 主集成入口 +// ========================================== +int main(void) { + TEST_START(C_BinarySearchTreeST_Isolated_TestSuite); + RUN_TEST(test_c_BST_Dynamic_CRUD); + RUN_TEST(test_c_BST_ParamConstraints); + TEST_REPORT(); + return (g_test_registry.failed_count > 0 ? 1 : 0); +} \ No newline at end of file diff --git a/Search/c_BinarySearchST.c b/Search/c_BinarySearchST.c new file mode 100644 index 0000000..53c06f3 --- /dev/null +++ b/Search/c_BinarySearchST.c @@ -0,0 +1,192 @@ +#include + + +/** + * @brief 核心内部操作:基于无符号安全的左闭右开二分 Rank 检索 + * + * @return c_size_t 返回小于指定 key 的键的总个数。该位置就是精确命中点或完美插入点 + */ +static c_size_t c_BinarySearchST_Rank(const c_BinarySearchST_t* self, const void* key) { + c_size_t low = 0; + c_size_t high = self->size; // 右边界设为 size(开区间),这样 high 永远不会因 mid-1 下溢 + + while (low < high) { // 🌟 开区间控制条件为 low < high,彻底杜绝死循环 + c_size_t mid = low + ((high - low) >> 1); + char* mid_key = self->keys + (mid * self->key_size); + + int cmp_res = self->cmp(key, mid_key, self->args); + if (cmp_res < 0) { + high = mid; // 🌟 目标比 mid 小,安全收缩右开边界,完全斩断减法下溢 + } else if (cmp_res > 0) { + low = mid + 1; // 目标比 mid 大,安全收缩左闭边界 + } else { + return mid; // 精确命中,返回对应的物理下标位置 + } + } + return low; // 未命中,返回当前最精准的插入点插槽索引 +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_BinarySearchST_Init(c_BinarySearchST_t* self, c_size_t initial_capacity, c_size_t key_size, c_size_t val_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator) { + if (!self || key_size == 0 || val_size == 0 || !cmp) { + return C_ERR_PARAM; + } + + // 自适应分配器降级缺省安全播种 + if (allocator) { + self->allocator = *allocator; + } else { + self->allocator = c_DefaultAllocator; + } + + c_size_t cap = (initial_capacity > 0) ? initial_capacity : 4; + + // 前置逆向除法溢出安全审计 + if (((c_size_t)-1) / key_size < cap) return C_ERR_NOMEM; + if (((c_size_t)-1) / val_size < cap) return C_ERR_NOMEM; + + self->keys = (char*)c_Allocator_Alloc(&self->allocator, cap * key_size); + self->vals = (char*)c_Allocator_Alloc(&self->allocator, cap * val_size); + + if (!self->keys || !self->vals) { + if (self->keys) c_Allocator_Free(&self->allocator, self->keys); + if (self->vals) c_Allocator_Free(&self->allocator, self->vals); + return C_ERR_NOMEM; + } + + self->capacity = cap; + self->size = 0; + self->key_size = key_size; + self->val_size = val_size; + self->cmp = cmp; + self->args = args; + + return C_ERR_OK; +} + + +/** + * @brief 根据指定的 Key 检索关联的 Value 值(时间复杂度 O(log N)) + */ +c_err_t c_BinarySearchST_Get(const c_BinarySearchST_t* self, const void* key, void* out_val) { + if (!self || !key || !out_val) return C_ERR_PARAM; + if (self->size == 0) return C_ERR_EMPTY; + + c_size_t i = c_BinarySearchST_Rank(self, key); + + // 如果检索出的位置合法,且对应的键与其等价,说明精确命中 + if (i < self->size && self->cmp(key, self->keys + (i * self->key_size), self->args) == 0) { + memcpy(out_val, self->vals + (i * self->val_size), self->val_size); + return C_ERR_OK; + } + + return C_ERR_NOTFOUND; +} + +/** + * @brief 检查符号表内是否包含指定的 Key 键值 + */ +bool c_BinarySearchST_Contains(const c_BinarySearchST_t* self, const void* key) { + if (!self || !key || self->size == 0) return false; + c_size_t i = c_BinarySearchST_Rank(self, key); + return (i < self->size && self->cmp(key, self->keys + (i * self->key_size), self->args) == 0); +} + +/** + * @brief 存入键值对。若已存在则覆写更新;若不存在则对后方数据滑窗后移,插入并维持有序性 + */ +c_err_t c_BinarySearchST_Put(c_BinarySearchST_t* self, const void* key, const void* val) { + if (!self || !key || !val) return C_ERR_PARAM; + + c_size_t i = c_BinarySearchST_Rank(self, key); + + // 1. 如果键值已经存在,直接更新它的值(覆写语义) + if (i < self->size && self->cmp(key, self->keys + (i * self->key_size), self->args) == 0) { + memcpy(self->vals + (i * self->val_size), val, self->val_size); + return C_ERR_OK; + } + + // 2. 键值不存在,准备执行新元素插入。前置判定自适应弹性动态扩容 + if (self->size >= self->capacity) { + c_size_t old_cap = self->capacity; + + // 算术乘法整数溢出除法拦截防护 + if (((c_size_t)-1) / self->key_size < (old_cap << 1)) return C_ERR_NOMEM; + if (((c_size_t)-1) / self->val_size < (old_cap << 1)) return C_ERR_NOMEM; + + c_size_t new_cap = old_cap << 1; + c_size_t old_key_bytes = old_cap * self->key_size; + c_size_t new_key_bytes = new_cap * self->key_size; + c_size_t old_val_bytes = old_cap * self->val_size; + c_size_t new_val_bytes = new_cap * self->val_size; + + char* new_keys = (char*)c_Allocator_Realloc(&self->allocator, self->keys, old_key_bytes, new_key_bytes); + char* new_vals = (char*)c_Allocator_Realloc(&self->allocator, self->vals, old_val_bytes, new_val_bytes); + if (!new_keys || !new_vals) { + // 部分成功安全回退 + if (new_keys) self->keys = new_keys; + if (new_vals) self->vals = new_vals; + return C_ERR_NOMEM; + } + self->keys = new_keys; + self->vals = new_vals; + self->capacity = new_cap; + } + + // 3. 核心数据平移:将区间 [i, size-1] 内的所有键值对数据,单向全部后移一位给新插入留出位置 + // 控制条件为 j > i。即便 i 为最左侧的 0,j 最小减到 1 就会强制退出,彻底封死无符号自减下溢 + for (c_size_t j = self->size; j > i; j--) { + memcpy(self->keys + (j * self->key_size), self->keys + ((j - 1) * self->key_size), self->key_size); + memcpy(self->vals + (j * self->val_size), self->vals + ((j - 1) * self->val_size), self->val_size); + } + + // 4. 将新元素深拷贝写入空出来的有序安全插槽中 + memcpy(self->keys + (i * self->key_size), key, self->key_size); + memcpy(self->vals + (i * self->val_size), val, self->val_size); + self->size++; + + return C_ERR_OK; +} + +/** + * @brief 根据指定 Key 彻底从有序表中斩断移出指定对,并对其后方数据滑窗前移覆盖 + */ +c_err_t c_BinarySearchST_Delete(c_BinarySearchST_t* self, const void* key) { + if (!self || !key) return C_ERR_PARAM; + if (self->size == 0) return C_ERR_EMPTY; + + c_size_t i = c_BinarySearchST_Rank(self, key); + + // 键值未找到拦截 + if (i >= self->size || self->cmp(key, self->keys + (i * self->key_size), self->args) != 0) { + return C_ERR_NOTFOUND; + } + + // 核心数据平移:将区间 [i+1, size-1] 内的元素统一向前推进覆盖一位 + // 纯单调递增控制,num 边界有效拦截 + c_size_t limit = self->size - 1; + for (c_size_t j = i; j < limit; j++) { + memcpy(self->keys + (j * self->key_size), self->keys + ((j + 1) * self->key_size), self->key_size); + memcpy(self->vals + (j * self->val_size), self->vals + ((j + 1) * self->val_size), self->val_size); + } + + self->size--; + return C_ERR_OK; +} + +/** + * @brief 有序表反初始化数据销毁 + */ +void c_BinarySearchST_Destroy(c_BinarySearchST_t* self) { + if (self) { + if (self->keys) c_Allocator_Free(&self->allocator, self->keys); + if (self->vals) c_Allocator_Free(&self->allocator, self->vals); + self->keys = NULL; + self->vals = NULL; + self->size = 0; + self->capacity = 0; + } +} diff --git a/Search/c_BinarySearchST.h b/Search/c_BinarySearchST.h new file mode 100644 index 0000000..0fa46c8 --- /dev/null +++ b/Search/c_BinarySearchST.h @@ -0,0 +1,43 @@ +#ifndef INCLUDED_C_BINARYSEARCHST_H +#define INCLUDED_C_BINARYSEARCHST_H + + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + char* keys; // 密集排布的有序键数组载体 (保持严格升序排列) + char* vals; // 与键数组物理下标一一对应的值数组载体 + c_size_t capacity; // 当前容器的最大可容纳插槽数 + c_size_t size; // 当前已存储的有效键值对总个数 + c_size_t key_size; // 单个键对象占用的物理字节大小 (sizeof) + c_size_t val_size; // 单个值对象占用的物理字节大小 (sizeof) + c_SortCompare_t cmp; // 键对象专用的动态回调比对器 + void* args; // 自定义上下文参数指针 + c_Allocator_t allocator;// 内联组合分配器实例与默认 Fallback 缺省机制 +} c_BinarySearchST_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_BinarySearchST_Init(c_BinarySearchST_t* self, c_size_t initial_capacity, c_size_t key_size, c_size_t val_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator); + +c_err_t c_BinarySearchST_Get(const c_BinarySearchST_t* self, const void* key, void* out_val); + +bool c_BinarySearchST_Contains(const c_BinarySearchST_t* self, const void* key); + +c_err_t c_BinarySearchST_Put(c_BinarySearchST_t* self, const void* key, const void* val); + +c_err_t c_BinarySearchST_Delete(c_BinarySearchST_t* self, const void* key); + +void c_BinarySearchST_Destroy(c_BinarySearchST_t* self); + +#endif /*INCLUDED_C_BINARYSEARCHST_H*/ diff --git a/Search/c_BinarySearchST.t.c b/Search/c_BinarySearchST.t.c new file mode 100644 index 0000000..c464c14 --- /dev/null +++ b/Search/c_BinarySearchST.t.c @@ -0,0 +1,86 @@ +#include "c_BinarySearchST.h" +#include "c_Test.h" +#include +#include + +static int bst_compare_chars(const void* a, const void* b, void* args) { + (void)args; + char char1 = *(const char*)a; + char char2 = *(const char*)b; + return char1 - char2; +} + +TEST_CASE(test_c_BinarySearchST_Sorted_CRUD) { + c_BinarySearchST_t st; + // 初始化一个仅有 2 容量的有序列,强制触发自增扩容 + c_err_t err = c_BinarySearchST_Init(&st, 2, sizeof(char), sizeof(int), bst_compare_chars, NULL, &c_DefaultAllocator); + ASSERT_INT_EQ(C_ERR_OK, err); + + char key_M = 'M'; int val_M = 400; + char key_B = 'B'; int val_B = 100; + char key_R = 'R'; int val_R = 500; + char key_G = 'G'; int val_G = 200; + + // 1. 乱序 Put 入队,内部数据平移必须能够将其安全纠正排序为严格的: B -> G -> M -> R 升序 + ASSERT_INT_EQ(C_ERR_OK, c_BinarySearchST_Put(&st, &key_M, &val_M)); + ASSERT_INT_EQ(C_ERR_OK, c_BinarySearchST_Put(&st, &key_B, &val_B)); + ASSERT_INT_EQ(C_ERR_OK, c_BinarySearchST_Put(&st, &key_R, &val_R)); + ASSERT_INT_EQ(C_ERR_OK, c_BinarySearchST_Put(&st, &key_G, &val_G)); + ASSERT_INT_EQ(4, (int)st.size); + + // 2. 验证底层有序性排列的插槽指向物理存储 + ASSERT_INT_EQ('B', st.keys[0]); + ASSERT_INT_EQ('G', st.keys[1]); + ASSERT_INT_EQ('M', st.keys[2]); + ASSERT_INT_EQ('R', st.keys[3]); + + // 3. Get 二分高速检索断言 + int get_result = 0; + ASSERT_INT_EQ(C_ERR_OK, c_BinarySearchST_Get(&st, &key_M, &get_result)); + ASSERT_INT_EQ(400, get_result); + + // 4. Overwrite 覆写测试 + int update_val_M = 8888; + ASSERT_INT_EQ(C_ERR_OK, c_BinarySearchST_Put(&st, &key_M, &update_val_M)); + ASSERT_INT_EQ(C_ERR_OK, c_BinarySearchST_Get(&st, &key_M, &get_result)); + ASSERT_INT_EQ(8888, get_result); + + // 5. 极低值溢出核验:检索一个比全队首项还小的 Key ('A' < 'B') + // 修复后的 Rank 在无符号数下应当将 high 收缩到 0 号位置而不是下溢到最大值爆发死循环 + char toxic_key = 'A'; + ASSERT_TRUE(!c_BinarySearchST_Contains(&st, &toxic_key)); + + // 6. Delete 滑窗前移覆盖移出测试 + ASSERT_INT_EQ(C_ERR_OK, c_BinarySearchST_Delete(&st, &key_G)); + ASSERT_INT_EQ(3, (int)st.size); + // 移出后原属于 G 的 下标 1 应该被后面的 M(keys='M') 顺理成章顶替 + ASSERT_INT_EQ('M', st.keys[1]); + ASSERT_INT_EQ(C_ERR_NOTFOUND, c_BinarySearchST_Get(&st, &key_G, &get_result)); + + c_BinarySearchST_Destroy(&st); +} + +TEST_CASE(test_c_BinarySearchST_EmptyAndToxicity) { + c_BinarySearchST_t local_st; + c_BinarySearchST_Init(&local_st, 4, sizeof(char), sizeof(int), bst_compare_chars, NULL, NULL); + + char k = 'X'; + int v = 99; + // 验证拦截防御 + ASSERT_INT_EQ(C_ERR_PARAM, c_BinarySearchST_Init(NULL, 4, sizeof(char), sizeof(int), bst_compare_chars, NULL, NULL)); + ASSERT_INT_EQ(C_ERR_EMPTY, c_BinarySearchST_Delete(&local_st, &k)); // 空仓删除安全抛出 C_ERR_EMPTY + ASSERT_INT_EQ(C_ERR_EMPTY, c_BinarySearchST_Get(&local_st, &k, &v)); + + c_BinarySearchST_Destroy(&local_st); +} + +// ========================================== +// 5. 主集成入口 +// ========================================== +int main(void) { + TEST_START(C_BinarySearchST_UnsignedSafe_TestSuite); + RUN_TEST(test_c_BinarySearchST_Sorted_CRUD); + RUN_TEST(test_c_BinarySearchST_EmptyAndToxicity); + TEST_REPORT(); + RETURN_TEST_STATUS; +} \ No newline at end of file diff --git a/Search/c_HashMap.c b/Search/c_HashMap.c new file mode 100644 index 0000000..91f1682 --- /dev/null +++ b/Search/c_HashMap.c @@ -0,0 +1,149 @@ +#include + +/** + * @brief 工业级无符号泛型去偏差多项式哈希映射机 + * + * 采用经典常数乘子 31 逐字节滚动翻滚,完美离散任何变长扁平结构体或内置基础类型 + */ +static c_size_t c_HashMap_HashEngine(const void* key, c_size_t key_size) { + const unsigned char* bytes = (const unsigned char*)key; + c_size_t hash = 0; + for (c_size_t i = 0; i < key_size; i++) { + hash = 31 * hash + bytes[i]; + } + return hash; +} + +/** + * @brief 内部辅助:通过哈希码安全计算对应的无符号桶插槽物理下标位置 + */ +C_STATIC_FORCE_INLINE +c_size_t c_HashMap_GetBucketSlot(const c_HashMap_t* self, const void* key) { + c_size_t code = c_HashMap_HashEngine(key, self->key_size); + // 纯无符号按位安全取模,物理屏蔽符号位回绕风险 + return code % self->m_buckets; +} + +/** + * @brief 就地初始化哈希映射表 + * + * @param initial_buckets 哈希桶(拉链容量)的总基数 M。建议传入质数(如 97, 997, 8191)以获得最佳离散度 + */ +c_err_t c_HashMap_Init(c_HashMap_t* self, c_size_t initial_buckets, c_size_t key_size, c_size_t val_size, c_SortCompare_t key_cmp, void* args, c_Allocator_t* allocator) { + if (!self || initial_buckets == 0 || key_size == 0 || val_size == 0 || !key_cmp) { + return C_ERR_PARAM; + } + + if (allocator) { + self->allocator = *allocator; + } else { + self->allocator = c_DefaultAllocator; + } + + self->m_buckets = initial_buckets; + self->size = 0; + self->key_size = key_size; + self->val_size = val_size; + self->key_cmp = key_cmp; + self->args = args; + + // 前置无符号乘法整数溢出防御审计 + if (((c_size_t)-1) / sizeof(c_SeqSearchST_t) < initial_buckets) { + return C_ERR_NOMEM; + } + + // 一次性静态分配 M 个桶的符号表控制头空间 + self->buckets = (c_SeqSearchST_t*)c_Allocator_Alloc(&self->allocator, initial_buckets * sizeof(c_SeqSearchST_t)); + if (!self->buckets) { + return C_ERR_NOMEM; + } + + // 逐个串联并初始化每个桶内部的单链表控制流,强力绑定统一的组合分配器 + for (c_size_t i = 0; i < initial_buckets; i++) { + c_SeqSearchST_Init(&(self->buckets[i]), key_size, val_size, key_cmp, args, &self->allocator); + } + + return C_ERR_OK; +} + +/** + * @brief 存入键值对(均摊常数项时间复杂度 O(1)) + * + * 如果键已存在于特定拉链桶中则覆写更新;若不存在则头插法压入新节点并刷新全局计数 + */ +c_err_t c_HashMap_Put(c_HashMap_t* self, const void* key, const void* val) { + if (!self || !key || !val) return C_ERR_PARAM; + + // 1. 利用哈希映射机瞬间定位到目标桶 + c_size_t slot = c_HashMap_GetBucketSlot(self, key); + c_SeqSearchST_t* bucket_st = &(self->buckets[slot]); + + // 2. 顺序探查该拉链。前置提取原拉链大小,用来判别本次操作是“新增”还是“修改覆写” + c_size_t old_bucket_size = bucket_st->size; + + c_err_t err = c_SeqSearchST_Put(bucket_st, key, val); + if (err == C_ERR_OK) { + // 如果引发了当前单链桶节点的空间膨胀,说明是新键插入,递增全局计数 + if (bucket_st->size > old_bucket_size) { + self->size++; + } + } + + return err; +} + +/** + * @brief 依据指定 Key 精准存取读取关联的 Value(均摊常数时间复杂度 O(1)) + */ +c_err_t c_HashMap_Get(const c_HashMap_t* self, const void* key, void* out_val) { + if (!self || !key || !out_val) return C_ERR_PARAM; + if (self->size == 0) return C_ERR_EMPTY; + + c_size_t slot = c_HashMap_GetBucketSlot(self, key); + // 穿透调用单向顺序表的 Get 接口 + return c_SeqSearchST_Get(&(self->buckets[slot]), key, out_val); +} + +/** + * @brief 检查哈希表内是否有效包含指定的 Key + */ +bool c_HashMap_Contains(const c_HashMap_t* self, const void* key) { + if (!self || !key || self->size == 0) return false; + c_size_t slot = c_HashMap_GetBucketSlot(self, key); + return c_SeqSearchST_Contains(&(self->buckets[slot]), key); +} + +/** + * @brief 从指定的哈希桶拉链中斩断并彻底移出其关联的符号对 + */ +c_err_t c_HashMap_Delete(c_HashMap_t* self, const void* key) { + if (!self || !key) return C_ERR_PARAM; + if (self->size == 0) return C_ERR_EMPTY; + + c_size_t slot = c_HashMap_GetBucketSlot(self, key); + c_SeqSearchST_t* bucket_st = &(self->buckets[slot]); + + c_err_t err = c_SeqSearchST_Delete(bucket_st, key); + if (err == C_ERR_OK) { + self->size--; // 递减总容量计数 + } + return err; +} + +/** + * @brief 反初始化:级联彻底清理销毁全部哈希桶拉链,原路逆向回收堆空间 + */ +void c_HashMap_Destroy(c_HashMap_t* self) { + if (!self || !self->buckets) return; + + // 1. 迫使每个哈希拉链桶先链式释放其内部的单链物理节点 + for (c_size_t i = 0; i < self->m_buckets; i++) { + c_SeqSearchST_Destroy(&(self->buckets[i])); + } + + // 2. 回收哈希桶外壳控制头数组本身 + c_Allocator_Free(&self->allocator, self->buckets); + self->buckets = NULL; + self->size = 0; + self->m_buckets = 0; +} \ No newline at end of file diff --git a/Search/c_HashMap.h b/Search/c_HashMap.h new file mode 100644 index 0000000..9043e8c --- /dev/null +++ b/Search/c_HashMap.h @@ -0,0 +1,38 @@ +#ifndef INCLUDED_C_HASHMAP_H +#define INCLUDED_C_HASHMAP_H + +#ifndef INCLUDED_C_SEQSEARCHST_H +#include +#endif /*INCLUDED_C_SEQSEARCHST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t m_buckets; // 哈希表内的桶(拉链数量)总基数 (M) + c_size_t size; // 当前整个哈希映射表内有效驻留的键值对总数 (N) + c_SeqSearchST_t* buckets; // 密集排布的拉链桶符号表动态数组:buckets[0 ... M-1] + c_size_t key_size; // 键对象的字节大小 (sizeof) + c_size_t val_size; // 值对象的字节大小 (sizeof) + c_SortCompare_t key_cmp; // 键对象的全等判定器 + void* args; // 自定义上下文 + c_Allocator_t allocator; // 内联组合分配器实例与自适应 Fallback 缺省 +} c_HashMap_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_HashMap_Init(c_HashMap_t* self, c_size_t initial_buckets, c_size_t key_size, c_size_t val_size, c_SortCompare_t key_cmp, void* args, c_Allocator_t* allocator); + +c_err_t c_HashMap_Put(c_HashMap_t* self, const void* key, const void* val); + +c_err_t c_HashMap_Get(const c_HashMap_t* self, const void* key, void* out_val); + +bool c_HashMap_Contains(const c_HashMap_t* self, const void* key); + +c_err_t c_HashMap_Delete(c_HashMap_t* self, const void* key); + +void c_HashMap_Destroy(c_HashMap_t* self); + +#endif /*INCLUDED_C_HASHMAP_H*/ diff --git a/Search/c_HashMap.t.c b/Search/c_HashMap.t.c new file mode 100644 index 0000000..1af5140 --- /dev/null +++ b/Search/c_HashMap.t.c @@ -0,0 +1,80 @@ +#include "c_HashMap.h" +#include "c_Test.h" + +#include +#include + +static int hash_compare_chars(const void* a, const void* b, void* args) { + (void)args; + char char1 = *(const char*)a; + char char2 = *(const char*)b; + return char1 - char2; +} + +TEST_CASE(test_c_HashMap_O1_StandardFlow) { + c_HashMap_t map; + + // 初始化具有 5 个拉链桶的泛型哈希映射表 (M=5),测试默认分配器 Fallback 降级机制 + c_err_t err = c_HashMap_Init(&map, 5, sizeof(char), sizeof(int), hash_compare_chars, NULL, NULL); + ASSERT_INT_EQ(C_ERR_OK, err); + + char key_P = 'P'; int val_P = 90; + char key_H = 'H'; int val_H = 80; + char key_Q = 'Q'; int val_Q = 70; + + // 1. Put 基础常数级高速录入验证 + ASSERT_INT_EQ(C_ERR_OK, c_HashMap_Put(&map, &key_P, &val_P)); + ASSERT_INT_EQ(C_ERR_OK, c_HashMap_Put(&map, &key_H, &val_H)); + ASSERT_INT_EQ(C_ERR_OK, c_HashMap_Put(&map, &key_Q, &val_Q)); + ASSERT_INT_EQ(3, (int)map.size); + + // 状态包含判定 + ASSERT_TRUE(c_HashMap_Contains(&map, &key_H)); + char key_NotExit = 'X'; + ASSERT_TRUE(!c_HashMap_Contains(&map, &key_NotExit)); + + // 2. Get 常数级读取断言 + int get_result = 0; + ASSERT_INT_EQ(C_ERR_OK, c_HashMap_Get(&map, &key_H, &get_result)); + ASSERT_INT_EQ(80, get_result); // 精确提取 + + // 3. Put 相同键覆写更新(Overwrite)特性核验 + int overwrite_val_H = 9999; + ASSERT_INT_EQ(C_ERR_OK, c_HashMap_Put(&map, &key_H, &overwrite_val_H)); + ASSERT_INT_EQ(3, (int)map.size); // 覆写后,全局哈希大小必须守恒,依旧为 3 + ASSERT_INT_EQ(C_ERR_OK, c_HashMap_Get(&map, &key_H, &get_result)); + ASSERT_INT_EQ(9999, get_result); + + // 4. Delete 常数级拉链断开删除测试 + ASSERT_INT_EQ(C_ERR_OK, c_HashMap_Delete(&map, &key_H)); + ASSERT_INT_EQ(2, (int)map.size); // 递减确凿 + ASSERT_INT_EQ(C_ERR_NOTFOUND, c_HashMap_Get(&map, &key_H, &get_result)); + + // 彻底级联反初始化释放桶空间 + c_HashMap_Destroy(&map); +} + +TEST_CASE(test_c_HashMap_ToxicityDefenses) { + c_HashMap_t local_map; + c_HashMap_Init(&local_map, 4, sizeof(char), sizeof(int), hash_compare_chars, NULL, &c_DefaultAllocator); + + char k = 'K'; int v = 11; + // 5. 验证关键入参异常状态码强拦截 + ASSERT_INT_EQ(C_ERR_PARAM, c_HashMap_Init(NULL, 5, sizeof(char), sizeof(int), hash_compare_chars, NULL, NULL)); + ASSERT_INT_EQ(C_ERR_PARAM, c_HashMap_Put(NULL, &k, &v)); + ASSERT_INT_EQ(C_ERR_PARAM, c_HashMap_Delete(NULL, &k)); + ASSERT_INT_EQ(C_ERR_EMPTY, c_HashMap_Delete(&local_map, &k)); // 空仓删除安全拦截抛出 C_ERR_EMPTY + + c_HashMap_Destroy(&local_map); +} + +// ========================================== +// 5. 主集成入口 +// ========================================== +int main(void) { + TEST_START(C_HashMap_Isolated_TestSuite); + RUN_TEST(test_c_HashMap_O1_StandardFlow); + RUN_TEST(test_c_HashMap_ToxicityDefenses); + TEST_REPORT(); + return (g_test_registry.failed_count > 0 ? 1 : 0); +} \ No newline at end of file diff --git a/Search/c_HashST.c b/Search/c_HashST.c new file mode 100644 index 0000000..b6fa197 --- /dev/null +++ b/Search/c_HashST.c @@ -0,0 +1,240 @@ +#include + +#define DEFAULT_INITIAL_CAPACITY (1024*8) +#define GROWTH_FACTOR 1 + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +static c_HashSTNode_t* GetNode(c_HashST_t* self, const void* key) { + c_HashSTNode_t* result = NULL; + c_HashSTNode_t* node = NULL; + c_PtrBag_t* bucket = NULL; + + uint32_t hash=0; + c_size_t bucket_idx = 0; + + hash = self->key_ops.hash(key, self->key_ops.arg); + bucket_idx = hash % self->capacity; + bucket = self->buckets[bucket_idx]; + + if (!bucket) { + return NULL; + } + + for (c_size_t i=0; isize; i++) { + node = c_PtrBag_Get(bucket, i); + if (!node) continue; + if (node->hash == hash) { + if (self->key_ops.eq(node->key, key, self->key_ops.arg)) { + result = node; + break; + } + } + } + + return result; +} + + +C_STATIC_FORCE_INLINE +c_size_t NumCol(c_PtrBag_t* self) { + if (!self) return 0; + return (self->size==0)?0:self->size-1; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_HashST_Init(c_HashST_t* self, c_size_t capacity, c_HashKeyOps_t key_ops, c_HashValOps_t val_ops, c_Allocator_t* allocator) { + if (!self ) return C_ERR_PARAM; + + self->capacity = capacity==0?DEFAULT_INITIAL_CAPACITY:capacity; + self->key_ops = key_ops; + self->val_ops = val_ops; + self->allocator = allocator!=NULL?*allocator:c_DefaultAllocator; + self->size = 0; + + // key_ops.arg = &self->allocator; + // val_ops.arg = &self->allocator; + + self->buckets = c_Allocator_Alloc(&self->allocator, self->capacity * sizeof(*(self->buckets))); + if (!self->buckets) return C_ERR_NOMEM; + + for (c_size_t i=0; icapacity; i++) { + self->buckets[i] = NULL; + } + + return C_ERR_OK; +} + +void c_HashST_Destroy(c_HashST_t* self) { + if (!self ) return; + c_PtrBag_t* bucket=0; + c_HashSTNode_t* node=0; + + for (c_size_t i=0; icapacity; i++) { + bucket = self->buckets[i]; + if (!bucket) continue; + for (c_size_t j=0; jsize; j++) { + node = c_PtrBag_Get(bucket, j); + if (!node) continue; + self->key_ops.free(node->key, self->key_ops.arg); + self->val_ops.free(node->val, self->val_ops.arg); + c_Allocator_Free(&self->allocator, node); + } + c_PtrBag_Destroy(bucket); + c_Allocator_Free(&self->allocator, bucket); + } + + c_Allocator_Free(&self->allocator, self->buckets); +} + +void* c_HashST_Get(c_HashST_t* self, const void* key) { + c_HashSTNode_t* node = GetNode(self, key); + if (!node) return NULL; + return node->val; +} + +c_err_t c_HashST_Resize(c_HashST_t* self, c_size_t new_capacity) { + if (!self || new_capacity < self->capacity) return C_ERR_PARAM; + if (new_capacity == self->capacity) { + return C_ERR_OK; + } + + c_PtrBag_t** new_buckets = c_Allocator_Alloc(&self->allocator, new_capacity * sizeof(*(self->buckets))); + if (!new_buckets) return C_ERR_NOMEM; + for (c_size_t i=0; icapacity; i++) { + c_PtrBag_t* bucket = self->buckets[i]; + if (!bucket) continue; + for (c_size_t j=0; jsize; j++) { + c_HashSTNode_t* node = c_PtrBag_Get(bucket, j); + if (!node) continue; + const c_size_t idx = node->hash % new_capacity; + if (!new_buckets[idx]) { + new_buckets[idx] = c_Allocator_Alloc(&self->allocator, sizeof(*bucket)); + if (!new_buckets[idx]) return C_ERR_NOMEM; + c_PtrBag_Init(new_buckets[idx], 0, &self->allocator); + } + c_PtrBag_Add(new_buckets[idx], node); + } + c_PtrBag_Destroy(bucket); + c_Allocator_Free(&self->allocator, bucket); + } + + self->capacity = new_capacity; + c_Allocator_Free(&self->allocator, self->buckets); + self->buckets = new_buckets; + + return C_ERR_OK; +} + +c_err_t c_HashST_Put(c_HashST_t* self, const void* key, const void* val) { + if (!self || key==NULL || val==NULL) return C_ERR_PARAM; + c_HashSTNode_t* node = GetNode(self, key); + + // 情况1: key 已经存在 + if (node!=NULL) { + self->val_ops.free(node->val, self->val_ops.arg); + node->val = val?self->val_ops.cp(val, self->val_ops.arg):NULL; + return C_ERR_OK; + } + + node = c_Allocator_Alloc(&self->allocator, sizeof(*node)); + if (!node) return C_ERR_NOMEM; + node->hash = self->key_ops.hash(key, self->key_ops.arg); + node->key = self->key_ops.cp(key, self->key_ops.arg); + node->val = self->val_ops.cp(val, self->val_ops.arg); + + const c_size_t idx = node->hash % self->capacity; + c_PtrBag_t* bucket = self->buckets[idx]; + if (bucket==NULL) { + bucket = c_Allocator_Alloc(&self->allocator, sizeof(*bucket)); + if (!bucket) return C_ERR_NOMEM; + c_PtrBag_Init(bucket, 0, &self->allocator); + self->buckets[idx] = bucket; + } + + c_PtrBag_Add(bucket, node); + + self->size++; + + // if (self->size > self->capacity * GROWTH_FACTOR) { + // return c_HashST_Resize(self, self->capacity * 2); + // } + + return C_ERR_OK; +} + + +c_err_t c_HashST_Remove(c_HashST_t* self, const void* key) { + if (!self || key==NULL) return C_ERR_PARAM; + + c_HashSTNode_t* node = NULL; + c_PtrBag_t* bucket = NULL; + + uint32_t hash=0; + c_size_t bucket_idx = 0; + + hash = self->key_ops.hash(key, self->key_ops.arg); + bucket_idx = hash % self->capacity; + bucket = self->buckets[bucket_idx]; + + if (!bucket) { + return C_ERR_NOTFOUND; + } + + for (c_size_t i=0; isize; i++) { + node = c_PtrBag_Get(bucket, i); + if (!node) continue; + if (node->hash == hash) { + if (self->key_ops.eq(node->key, key, self->key_ops.arg)) { + self->key_ops.free(node->key, self->key_ops.arg); + self->val_ops.free(node->val, self->val_ops.arg); + c_Allocator_Free(&self->allocator, node); + c_PtrBag_RemoveAt(bucket, i, 0); + self->size--; + return C_ERR_OK; + } + } + } + + return C_ERR_NOTFOUND; +} + +bool c_HashST_Contains(c_HashST_t* self, const void* key) { + if (!self || key==NULL) return false; + const c_HashSTNode_t* node = GetNode(self, key); + if (node==NULL) return false; + return true; +} + + +c_size_t c_HashST_NumCol(c_HashST_t* self) { + if (!self) return 0; + c_size_t result = 0; + for (c_size_t i=0; icapacity; i++) { + result+= NumCol(self->buckets[i]); + } + return result; +} + +void c_HashST_ForEach(c_HashST_t* self, void (*apply)(c_HashSTNode_t* node, void* args), void* args) { + if (!self || !apply) return; + if (self->size==0) return; + + for (c_size_t i=0; icapacity; i++) { + c_PtrBag_t* bucket = self->buckets[i]; + if (!bucket) continue; + for (c_size_t j=0; jsize; j++) { + c_HashSTNode_t* node = c_PtrBag_Get(bucket, j); + if (!node) continue; + apply(node, args); + } + } +} \ No newline at end of file diff --git a/Search/c_HashST.h b/Search/c_HashST.h new file mode 100644 index 0000000..b1929d4 --- /dev/null +++ b/Search/c_HashST.h @@ -0,0 +1,67 @@ +#ifndef INCLUDED_C_HASHST_H +#define INCLUDED_C_HASHST_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_C_PTRBAG_H +#include +#endif /*INCLUDED_C_PTRBAG_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +typedef struct c_HashKeyOps_t { + uint32_t (*hash)(const void *data, void *arg); + void* (*cp)(const void *data, void *arg); + void (*free)(void *data, void *arg); + bool (*eq)(const void *data1, const void *data2, void *arg); + void *arg; +} c_HashKeyOps_t; + +typedef struct c_HashValOps_t { + void* (*cp)(const void *data, void *arg); + void (*free)(void *data, void *arg); + bool (*eq)(const void *data1, const void *data2, void *arg); + void *arg; +} c_HashValOps_t; + +typedef struct c_HashSTNode_t { + uint32_t hash; + void* key; + void* val; +}c_HashSTNode_t; + +typedef struct { + c_PtrBag_t** buckets; + c_size_t capacity; + c_size_t size; + c_HashKeyOps_t key_ops; + c_HashValOps_t val_ops; + c_Allocator_t allocator; +}c_HashST_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_HashST_Init(c_HashST_t* self, c_size_t capacity, c_HashKeyOps_t key_ops, c_HashValOps_t val_ops, c_Allocator_t* allocator); + +void c_HashST_Destroy(c_HashST_t* self); + +c_err_t c_HashST_Resize(c_HashST_t* self, c_size_t new_capacity); + +c_err_t c_HashST_Put(c_HashST_t* self, const void* key, const void* val); + +void* c_HashST_Get(c_HashST_t* self, const void* key); + +c_err_t c_HashST_Remove(c_HashST_t* self, const void* key); + +bool c_HashST_Contains(c_HashST_t* self, const void* key); + +c_size_t c_HashST_NumCol(c_HashST_t* self); + +void c_HashST_ForEach(c_HashST_t* self, void (*apply)(c_HashSTNode_t* node, void* args), void* args); + +#endif /*INCLUDED_C_HASHST_H*/ diff --git a/Search/c_HashST.t.c b/Search/c_HashST.t.c new file mode 100644 index 0000000..ced09c7 --- /dev/null +++ b/Search/c_HashST.t.c @@ -0,0 +1,216 @@ +#include "c_HashST.h" +#include "c_Test.h" +#include +#include + +// ========================================== +// 1. 测试框架配套:虚操作函数模拟实现 +// ========================================== + +// --- Key 虚操作实现:以动态独立分配内存、长度未知的“深层堆字符串指针 (char**)”作为键 --- +static uint32_t test_key_string_hash(const void* data, void* arg) { + (void)arg; + const char* str = *(const char* const*)data; + uint32_t hash = 0; + while (*str) { + hash = 31 * hash + (unsigned char)(*str); + str++; + } + return hash; +} + +static void* test_key_string_cp(const void* data, void* arg) { + (void)arg; + const char* original = *(const char* const*)data; + char* clone = (char*)malloc(strlen(original) + 1); + if (clone) strcpy(clone, original); + + char** storage = (char**)malloc(sizeof(char*)); + if (storage) *storage = clone; + return (void*)storage; +} + +static void test_key_string_free(void* data, void* arg) { + (void)arg; + if (data) { + char** storage = (char**)data; + free(*storage); + free(storage); + } +} + +static bool test_key_string_eq(const void* data1, const void* data2, void* arg) { + (void)arg; + const char* s1 = *(const char* const*)data1; + const char* s2 = *(const char* const*)data2; + return strcmp(s1, s2) == 0; +} + +// --- Value 虚操作实现:以动态独立分配内存的“结构体对象指针 (Task_t**)”作为值 --- +typedef struct { + int id; + int payload; +} TestTask_t; + +static void* test_val_task_cp(const void* data, void* arg) { + (void)arg; + const TestTask_t* original = *(const TestTask_t* const*)data; + TestTask_t* clone = (TestTask_t*)malloc(sizeof(TestTask_t)); + if (clone) { + clone->id = original->id; + clone->payload = original->payload; + } + + TestTask_t** storage = (TestTask_t**)malloc(sizeof(TestTask_t*)); + if (storage) *storage = clone; + return (void*)storage; +} + +static void test_val_task_free(void* data, void* arg) { + (void)arg; + if (data) { + TestTask_t** storage = (TestTask_t**)data; + free(*storage); + free(storage); + } +} + +static bool test_val_task_eq(const void* data1, const void* data2, void* arg) { + (void)arg; + const TestTask_t* t1 = *(const TestTask_t* const*)data1; + const TestTask_t* t2 = *(const TestTask_t* const*)data2; + return (t1->id == t2->id && t1->payload == t2->payload); +} + +// ========================================== +// 2. 自动化单元测试集 +// ========================================== + +TEST_CASE(test_c_HashST_Base_CRUD_Flow) { + c_HashST_t map; + + // 1. 初始化多态虚操作集(Key 采用堆字符串,Value 采用堆任务结构体) + c_HashKeyOps_t key_ops = { test_key_string_hash, test_key_string_cp, test_key_string_free, test_key_string_eq, NULL }; + c_HashValOps_t val_ops = { test_val_task_cp, test_val_task_free, test_val_task_eq, NULL }; + + // 2. 初始化初始容量为 4 的哈希符号表 + c_err_t err = c_HashST_Init(&map, 4, key_ops, val_ops, &c_DefaultAllocator); + ASSERT_INT_EQ(C_ERR_OK, err); + + // 声明待插入的临时浅数据线 + const char* k1 = "Task_Alpha"; TestTask_t t1_raw = { 101, 555 }; const TestTask_t* v1 = &t1_raw; + const char* k2 = "Task_Beta"; TestTask_t t2_raw = { 102, 666 }; const TestTask_t* v2 = &t2_raw; + const char* k3 = "Task_Gamma"; TestTask_t t3_raw = { 103, 777 }; const TestTask_t* v3 = &t3_raw; + + // 3. Put 添加行为断言 + ASSERT_INT_EQ(C_ERR_OK, c_HashST_Put(&map, &k1, &v1)); + ASSERT_INT_EQ(C_ERR_OK, c_HashST_Put(&map, &k2, &v2)); + ASSERT_INT_EQ(C_ERR_OK, c_HashST_Put(&map, &k3, &v3)); + ASSERT_INT_EQ(3, (int)map.size); + + // 4. Contains 包含性状态断言(使用物理地址不同的副本 Key 进行全等检索) + const char* search_k1 = "Task_Alpha"; + const char* search_fake = "Task_Unknown"; + ASSERT_TRUE(c_HashST_Contains(&map, &search_k1)); + ASSERT_TRUE(!c_HashST_Contains(&map, &search_fake)); + + // 5. Get 数据读取与物理结构体字段穿透比对 + void* get_res_ptr = c_HashST_Get(&map, &search_k1); + ASSERT_TRUE(get_res_ptr != NULL); + + // 穿透二级指针,精确检验深拷贝出来的值的成员变量 + TestTask_t* matched_task = *(TestTask_t**)get_res_ptr; + ASSERT_INT_EQ(101, matched_task->id); + ASSERT_INT_EQ(555, matched_task->payload); + + // 6. Put 相同键下的覆写更新(Overwrite)语义核验 + TestTask_t t1_update_raw = { 101, 9999 }; const TestTask_t* v1_update = &t1_update_raw; + ASSERT_INT_EQ(C_ERR_OK, c_HashST_Put(&map, &k1, &v1_update)); + ASSERT_INT_EQ(3, (int)map.size); // 覆写后,全局哈希大小必须守恒 + + get_res_ptr = c_HashST_Get(&map, &search_k1); + ASSERT_TRUE(get_res_ptr != NULL); + ASSERT_INT_EQ(9999, (*(TestTask_t**)get_res_ptr)->payload); // 配额必须已被更新覆盖 + + // 7. Remove 单路摘除断言 + ASSERT_INT_EQ(C_ERR_OK, c_HashST_Remove(&map, &search_k1)); + ASSERT_INT_EQ(2, (int)map.size); // 递减正确 + ASSERT_TRUE(c_HashST_Get(&map, &search_k1) == NULL); // 再拿应该彻底变空 + + // 8. 全清理注销销毁线,多态自由解构,阻断任何残留 + c_HashST_Destroy(&map); +} + +TEST_CASE(test_c_HashST_Resize_And_Collision) { + c_HashST_t map; + c_HashKeyOps_t key_ops = { test_key_string_hash, test_key_string_cp, test_key_string_free, test_key_string_eq, NULL }; + c_HashValOps_t val_ops = { test_val_task_cp, test_val_task_free, test_val_task_eq, NULL }; + + // 1. 初始化一个极小容量的哈希表(故意设为 1,强迫后面插入的所有节点落入同一个桶中,构成 100% 碰撞拉链链条) + c_err_t err = c_HashST_Init(&map, 1, key_ops, val_ops, &c_DefaultAllocator); + ASSERT_INT_EQ(C_ERR_OK, err); + + const char* k1 = "Key_Coll_1"; TestTask_t t1 = { 1, 10 }; const TestTask_t* v1 = &t1; + const char* k2 = "Key_Coll_2"; TestTask_t t2 = { 2, 20 }; const TestTask_t* v2 = &t2; + const char* k3 = "Key_Coll_3"; TestTask_t t3 = { 3, 30 }; const TestTask_t* v3 = &t3; + + ASSERT_INT_EQ(C_ERR_OK, c_HashST_Put(&map, &k1, &v1)); + ASSERT_INT_EQ(C_ERR_OK, c_HashST_Put(&map, &k2, &v2)); + ASSERT_INT_EQ(C_ERR_OK, c_HashST_Put(&map, &k3, &v3)); + + // 2. NumCol 碰撞指标验证:在容量为 1 且塞入 3 个数据时,产生的物理碰撞线数必然为 2 次 + ASSERT_INT_EQ(2, (int)c_HashST_NumCol(&map)); + + // 3. 核心压测:触发大跨度扩容变轨(容量从 1 直接倍增重塑至 16) + // 扩容后,原先挤在 0 号桶拉链中的 3 个冲突节点必须被重新洗牌、散列分流到各个新桶中 + ASSERT_INT_EQ(C_ERR_OK, c_HashST_Resize(&map, 16)); + ASSERT_INT_EQ(16, (int)map.capacity); + ASSERT_INT_EQ(3, (int)map.size); // 全局大小守恒不变 + + // 4. 扩容分流后的碰撞指标必须自适应下降(通常随机散列到 16 个桶中后,碰撞数会锐减为 0) + ASSERT_TRUE(c_HashST_NumCol(&map) < 2); + + // 5. 扩容变轨后,再次使用 Get 穿透读取,验证多态路由未丢失 + const char* search_k3 = "Key_Coll_3"; + void* get_res_ptr = c_HashST_Get(&map, &search_k3); + ASSERT_TRUE(get_res_ptr != NULL); + ASSERT_INT_EQ(30, (*(TestTask_t**)get_res_ptr)->payload); + + c_HashST_Destroy(&map); +} + +TEST_CASE(test_c_HashST_Empty_And_Toxicity_Defenses) { + c_HashST_t map; + c_HashKeyOps_t key_ops = { test_key_string_hash, test_key_string_cp, test_key_string_free, test_key_string_eq, NULL }; + c_HashValOps_t val_ops = { test_val_task_cp, test_val_task_free, test_val_task_eq, NULL }; + + c_HashST_Init(&map, 4, key_ops, val_ops, NULL); // 降级测试 + + const char* k = "Toxic_Key"; + + // 1. 空仓返回与毒入参前置拦截防御 + ASSERT_TRUE(c_HashST_Get(&map, &k) == NULL); + ASSERT_TRUE(!c_HashST_Contains(&map, &k)); + ASSERT_INT_EQ(C_ERR_NOTFOUND, c_HashST_Remove(&map, &k)); + ASSERT_INT_EQ(0, (int)c_HashST_NumCol(&map)); + + ASSERT_INT_EQ(C_ERR_PARAM, c_HashST_Init(NULL, 4, key_ops, val_ops, NULL)); + + c_HashST_Destroy(&map); +} + +// ========================================== +// 3. 独立测试运行入口 +// ========================================== +int main(void) { + TEST_START(C_HashST_PolymorphicObject_TestSuite); + + // 顺序调度高级多态哈希表的各项极限业务场景 + RUN_TEST(test_c_HashST_Base_CRUD_Flow); + RUN_TEST(test_c_HashST_Resize_And_Collision); + RUN_TEST(test_c_HashST_Empty_And_Toxicity_Defenses); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Search/c_RBTreeSet.c b/Search/c_RBTreeSet.c new file mode 100644 index 0000000..4db4887 --- /dev/null +++ b/Search/c_RBTreeSet.c @@ -0,0 +1,288 @@ +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +bool c_RBSet_IsRed(const c_RBSetNode_t* node) { + if (node == NULL) return false; // 空链接恒为隐式黑色链接 'B' + return node->color == C_RB_RED; +} + +C_STATIC_FORCE_INLINE +void c_RBSet_RotateLeft(c_RBSetNode_t** node_ptr) { + c_RBSetNode_t* h = *node_ptr; + c_RBSetNode_t* x = h->right; + h->right = x->left; + x->left = h; + x->color = h->color; + h->color = C_RB_RED; + *node_ptr = x; +} + +C_STATIC_FORCE_INLINE +void c_RBSet_RotateRight(c_RBSetNode_t** node_ptr) { + c_RBSetNode_t* h = *node_ptr; + c_RBSetNode_t* x = h->left; + h->left = x->right; + x->right = h; + x->color = h->color; + h->color = C_RB_RED; + *node_ptr = x; +} + +C_STATIC_FORCE_INLINE +void c_RBSet_FlipColors(c_RBSetNode_t* h) { +#if 0 + h->color = (h->color == C_RB_RED) ? C_RB_BLACK : C_RB_RED; + if (h->left) h->left->color = (h->left->color == C_RB_RED) ? C_RB_BLACK : C_RB_RED; + if (h->right) h->right->color = (h->right->color == C_RB_RED) ? C_RB_BLACK : C_RB_RED; +#endif + + h->color = !h->color; + if (h->left) h->left->color = !h->left->color; + if (h->right) h->right->color = !h->right->color; +} + +static void c_RBSet_Balance(c_RBSetNode_t** node_ptr) { + if (*node_ptr == NULL) return; + if (c_RBSet_IsRed((*node_ptr)->right) && !c_RBSet_IsRed((*node_ptr)->left)) { + c_RBSet_RotateLeft(node_ptr); + } + if (c_RBSet_IsRed((*node_ptr)->left) && c_RBSet_IsRed((*node_ptr)->left->left)) { + c_RBSet_RotateRight(node_ptr); + } + if (c_RBSet_IsRed((*node_ptr)->left) && c_RBSet_IsRed((*node_ptr)->right)) { + c_RBSet_FlipColors(*node_ptr); + } +} + +static void c_RBSet_MoveRedLeft(c_RBSetNode_t** node_ptr) { + c_RBSetNode_t* h = *node_ptr; + c_RBSet_FlipColors(h); + if (c_RBSet_IsRed(h->right->left)) { + c_RBSet_RotateRight(&(h->right)); + c_RBSet_RotateLeft(node_ptr); + c_RBSet_FlipColors(*node_ptr); + } +} + +static void c_RBSet_MoveRedRight(c_RBSetNode_t** node_ptr) { + c_RBSetNode_t* h = *node_ptr; + c_RBSet_FlipColors(h); + if (c_RBSet_IsRed(h->left->left)) { + c_RBSet_RotateRight(node_ptr); + c_RBSet_FlipColors(*node_ptr); + } +} + +// ================================================================================================================== +// 核心接口层实现 +// ================================================================================================================== + +/** + * @brief 就地初始化红黑树集合 + */ +c_err_t c_RBTreeSet_Init(c_RBTreeSet_t* self, c_size_t key_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator) { + if (!self || key_size == 0 || !cmp) { + return C_ERR_PARAM; + } + + if (allocator) { + self->allocator = *allocator; + } else { + self->allocator = c_DefaultAllocator; + } + + self->root = NULL; + self->size = 0; + self->key_size = key_size; + self->cmp = cmp; + self->args = args; + + return C_ERR_OK; +} + +/** + * @brief 内部递归插入自平衡状态机(内置去重机制) + */ +static c_err_t c_RBTreeSet_InternalAdd(c_RBTreeSet_t* self, c_RBSetNode_t** node_ptr, const void* key, bool* is_new_inserted) { + c_RBSetNode_t* curr = *node_ptr; + + if (curr == NULL) { + c_RBSetNode_t* new_node = (c_RBSetNode_t*)c_Allocator_Alloc(&self->allocator, sizeof(c_RBSetNode_t)); + void* new_key = c_Allocator_Alloc(&self->allocator, self->key_size); + + if (!new_node || !new_key) { + if (new_node) c_Allocator_Free(&self->allocator, new_node); + if (new_key) c_Allocator_Free(&self->allocator, new_key); + return C_ERR_NOMEM; + } + + memcpy(new_key, key, self->key_size); + new_node->key = new_key; + new_node->left = NULL; + new_node->right = NULL; + new_node->color = C_RB_RED; // 新生成的链接赋红 + + *node_ptr = new_node; + *is_new_inserted = true; + return C_ERR_OK; + } + + int cmp_res = self->cmp(key, curr->key, self->args); + c_err_t err = C_ERR_OK; + + if (cmp_res < 0) { + err = c_RBTreeSet_InternalAdd(self, &(curr->left), key, is_new_inserted); + } else if (cmp_res > 0) { + err = c_RBTreeSet_InternalAdd(self, &(curr->right), key, is_new_inserted); + } else { + // 🌟【集合核心去重约束】:若元素已在树中存在,果断拦截,抛出 C_ERR_EXIST 阻止其向下流转 + *is_new_inserted = false; + return C_ERR_EXIST; + } + + if (err != C_ERR_OK && err != C_ERR_EXIST) return err; + + // 自底向上 Sedgewick 三部曲修复 + c_RBSet_Balance(node_ptr); + return err; +} + +/** + * @brief 向集合中注入添加一个元素(自动去重,时间复杂度 O(log N)) + */ +c_err_t c_RBTreeSet_Add(c_RBTreeSet_t* self, const void* key) { + if (!self || !key) return C_ERR_PARAM; + + bool is_new = false; + c_err_t err = c_RBTreeSet_InternalAdd(self, &(self->root), key, &is_new); + if (err == C_ERR_OK && is_new) { + self->size++; + self->root->color = C_RB_BLACK; // 根节点链接刷黑 + } + return err; +} + +/** + * @brief 包含性检索判定(时间复杂度稳定的 O(log N)) + */ +bool c_RBTreeSet_Contains(const c_RBTreeSet_t* self, const void* key) { + if (!self || !key) return false; + c_RBSetNode_t* curr = self->root; + while (curr != NULL) { + int cmp_res = self->cmp(key, curr->key, self->args); + if (cmp_res < 0) curr = curr->left; + else if (cmp_res > 0) curr = curr->right; + else return true; + } + return false; +} + +/** + * @brief 内部辅助:寻找并断开右子树绝对最小值节点 + */ +static c_RBSetNode_t* c_RBTreeSet_InternalDeleteMin(c_RBTreeSet_t* self, c_RBSetNode_t** node_ptr) { + c_RBSetNode_t* curr = *node_ptr; + if (curr->left == NULL) { + *node_ptr = NULL; + return curr; + } + if (!c_RBSet_IsRed(curr->left) && !c_RBSet_IsRed(curr->left->left)) { + c_RBSet_MoveRedLeft(node_ptr); + } + c_RBSetNode_t* min_node = c_RBTreeSet_InternalDeleteMin(self, &((*node_ptr)->left)); + c_RBSet_Balance(node_ptr); + return min_node; +} + +/** + * @brief 内部递归自适应删除控制流(Hibbard 替换策略) + */ +static c_err_t c_RBTreeSet_InternalDelete(c_RBTreeSet_t* self, c_RBSetNode_t** node_ptr, const void* key) { + c_RBSetNode_t* curr = *node_ptr; + if (curr == NULL) return C_ERR_NOTFOUND; + + if (self->cmp(key, curr->key, self->args) < 0) { + if (!c_RBSet_IsRed(curr->left) && !c_RBSet_IsRed(curr->left->left)) { + c_RBSet_MoveRedLeft(node_ptr); + } + c_err_t err = c_RBTreeSet_InternalDelete(self, &((*node_ptr)->left), key); + c_RBSet_Balance(node_ptr); + return err; + } + else { + if (c_RBSet_IsRed(curr->left)) { + c_RBSet_RotateRight(node_ptr); + curr = *node_ptr; + } + + if (self->cmp(key, curr->key, self->args) == 0 && (curr->right == NULL)) { + c_RBSetNode_t* old_node = curr; + *node_ptr = curr->left; + c_Allocator_Free(&self->allocator, old_node->key); + c_Allocator_Free(&self->allocator, old_node); + return C_ERR_OK; + } + + if (!c_RBSet_IsRed(curr->right) && !c_RBSet_IsRed(curr->right->left)) { + c_RBSet_MoveRedRight(node_ptr); + curr = *node_ptr; + } + + if (self->cmp(key, curr->key, self->args) == 0) { + c_RBSetNode_t* old_node = curr; + c_RBSetNode_t* successor = c_RBTreeSet_InternalDeleteMin(self, &(curr->right)); + + successor->left = old_node->left; + successor->right = (*node_ptr)->right; + successor->color = old_node->color; + *node_ptr = successor; + + c_Allocator_Free(&self->allocator, old_node->key); + c_Allocator_Free(&self->allocator, old_node); + c_RBSet_Balance(node_ptr); + return C_ERR_OK; + } + else { + c_err_t err = c_RBTreeSet_InternalDelete(self, &((*node_ptr)->right), key); + c_RBSet_Balance(node_ptr); + return err; + } + } +} + +/** + * @brief 从有序集合中精准剔除一个指定元素 + */ +c_err_t c_RBTreeSet_Remove(c_RBTreeSet_t* self, const void* key) { + if (!self || !key) return C_ERR_PARAM; + if (self->size == 0 || self->root == NULL) return C_ERR_EMPTY; + + c_err_t err = c_RBTreeSet_InternalDelete(self, &(self->root), key); + if (err == C_ERR_OK) { + self->size--; + if (self->root != NULL) self->root->color = C_RB_BLACK; + } + return err; +} + +static void c_RBTreeSet_InternalDeinit(c_Allocator_t* alloc, c_RBSetNode_t* node) { + if (node == NULL) return; + c_RBTreeSet_InternalDeinit(alloc, node->left); + c_RBTreeSet_InternalDeinit(alloc, node->right); + c_Allocator_Free(alloc, node->key); + c_Allocator_Free(alloc, node); +} + +/** + * @brief 集合彻底反初始化销毁释放 + */ +void c_RBTreeSet_Destroy(c_RBTreeSet_t* self) { + if (self && self->root) { + c_RBTreeSet_InternalDeinit(&self->allocator, self->root); + self->root = NULL; + self->size = 0; + } +} \ No newline at end of file diff --git a/Search/c_RBTreeSet.h b/Search/c_RBTreeSet.h new file mode 100644 index 0000000..e30d06b --- /dev/null +++ b/Search/c_RBTreeSet.h @@ -0,0 +1,55 @@ +#ifndef INCLUDED_C_RBTREESET_H +#define INCLUDED_C_RBTREESET_H + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#define C_RB_RED true +#define C_RB_BLACK false + +/** + * @brief 红黑树集合内部单节点物理封装 + */ +typedef struct c_RBSetNode_t { + void* key; // 独立分配存储的元素键物理地址 + struct c_RBSetNode_t* left; // 左子节点指针 + struct c_RBSetNode_t* right; // 右子节点指针 + bool color; // 指向该节点的父链接颜色 (R为红,B为黑) +} c_RBSetNode_t; + +/** + * @brief 工业级泛型红黑树集合结构体(分配器内联组合版) + */ +typedef struct { + c_RBSetNode_t* root; // 集合树根节点指针 + c_size_t size; // 当前集合内有效驻留的唯一元素总个数 + c_size_t key_size; // 元素键占用的物理字节大小 (sizeof) + c_SortCompare_t cmp; // 元素专用动态回调比对器 + void* args; // 自定义上下文参数指针 + c_Allocator_t allocator; // 内联组合分配器实例与自适应 Fallback 缺省 +} c_RBTreeSet_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_RBTreeSet_Init(c_RBTreeSet_t* self, c_size_t key_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator); + +c_err_t c_RBTreeSet_Add(c_RBTreeSet_t* self, const void* key); + +bool c_RBTreeSet_Contains(const c_RBTreeSet_t* self, const void* key); + +c_err_t c_RBTreeSet_Remove(c_RBTreeSet_t* self, const void* key); + +void c_RBTreeSet_Destroy(c_RBTreeSet_t* self); + +#endif /*INCLUDED_C_RBTREESET_H*/ diff --git a/Search/c_RBTreeSet.t.c b/Search/c_RBTreeSet.t.c new file mode 100644 index 0000000..e084e0f --- /dev/null +++ b/Search/c_RBTreeSet.t.c @@ -0,0 +1,68 @@ +#include "c_RBTreeSet.h" +#include "c_Test.h" +#include +#include + +static int rbset_compare_ints(const void* a, const void* b, void* args) { + (void)args; + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + + +TEST_CASE(test_c_RBTreeSet_Unique_CRUD) { + c_RBTreeSet_t set; + c_err_t err = c_RBTreeSet_Init(&set, sizeof(int), rbset_compare_ints, NULL, &c_DefaultAllocator); + ASSERT_INT_EQ(C_ERR_OK, err); + + int n1 = 50; int n2 = 20; int n3 = 80; + + // 1. 基础 Add 压入与 Contains 检测断言 + ASSERT_INT_EQ(C_ERR_OK, c_RBTreeSet_Add(&set, &n1)); + ASSERT_INT_EQ(C_ERR_OK, c_RBTreeSet_Add(&set, &n2)); + ASSERT_INT_EQ(C_ERR_OK, c_RBTreeSet_Add(&set, &n3)); + ASSERT_INT_EQ(3, (int)set.size); + ASSERT_TRUE(c_RBTreeSet_Contains(&set, &n2)); + + // 2. 🌟【绝杀点 1:去重防御】:再次尝试注入相同的数字 20 + // 修复后的代码必须果断返回 C_ERR_EXIST 报错拦截,且全局大小坚守为 3 守恒! + ASSERT_INT_EQ(C_ERR_EXIST, c_RBTreeSet_Add(&set, &n2)); + ASSERT_INT_EQ(3, (int)set.size); + + // 3. 🌟【绝杀点 2:平衡级联删除】:移出处于中段的核心枢纽元素 50 + ASSERT_INT_EQ(C_ERR_OK, c_RBTreeSet_Remove(&set, &n1)); + ASSERT_INT_EQ(2, (int)set.size); + ASSERT_TRUE(!c_RBTreeSet_Contains(&set, &n1)); + + // 确保大震荡洗牌后,树根的字符颜色依然被恢复洗回黑链接 'B' + ASSERT_INT_EQ(C_RB_BLACK, (int)set.root->color); + + c_RBTreeSet_Destroy(&set); +} + +TEST_CASE(test_c_RBTreeSet_EmptyDefenses) { + c_RBTreeSet_t local_set; + c_RBTreeSet_Init(&local_set, sizeof(int), rbset_compare_ints, NULL, NULL); + + int item = 777; + // 4. 空仓及入参不合法拦截校验 + ASSERT_INT_EQ(C_ERR_PARAM, c_RBTreeSet_Init(NULL, sizeof(int), rbset_compare_ints, NULL, NULL)); + ASSERT_INT_EQ(C_ERR_EMPTY, c_RBTreeSet_Remove(&local_set, &item)); // 空仓删除安全返回 C_ERR_EMPTY + + c_RBTreeSet_Destroy(&local_set); +} + +// ========================================== +// 5. 主集成入口 +// ========================================== +int main(void) { + TEST_START(C_RBTreeSet_UniqueUnsigned_TestSuite); + RUN_TEST(test_c_RBTreeSet_Unique_CRUD); + RUN_TEST(test_c_RBTreeSet_EmptyDefenses); + TEST_REPORT(); + return (g_test_registry.failed_count > 0 ? 1 : 0); +} + diff --git a/Search/c_RedBlackBST.c b/Search/c_RedBlackBST.c new file mode 100644 index 0000000..db37a2a --- /dev/null +++ b/Search/c_RedBlackBST.c @@ -0,0 +1,402 @@ +#include + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/** + * @brief 内部辅助:安全检测某个节点的父链接是否为红 + */ +C_STATIC_FORCE_INLINE +bool c_RBBST_IsRed(const c_RBBSTNode_t* node) { + if (node == NULL) return C_RB_BLACK; // 空链接恒为黑色链接 + return node->color == C_RB_RED; +} + +/** + * @brief 内部平衡机制 1:左旋操作 + * + * 将任意临时的右倾红链接,通过局部拓扑对调,安全置换为标准合规的左倾红链接 + */ +C_STATIC_FORCE_INLINE +void c_RBBST_RotateLeft(c_RBBSTNode_t** node_ptr) { + c_RBBSTNode_t* h = *node_ptr; + c_RBBSTNode_t* x = h->right; + + h->right = x->left; + x->left = h; + + x->color = h->color; + h->color = C_RB_RED; + + *node_ptr = x; // 代理写回父节点 +} + +/** + * @brief 内部平衡机制 2:右旋操作 + * + * 临时放宽左侧的连续红链接,为后续的 4-node 拆分做前置拓扑对调准备 + */ +C_STATIC_FORCE_INLINE +void c_RBBST_RotateRight(c_RBBSTNode_t** node_ptr) { + c_RBBSTNode_t* h = *node_ptr; + c_RBBSTNode_t* x = h->left; + + h->left = x->right; + x->right = h; + + x->color = h->color; + h->color = C_RB_RED; + + *node_ptr = x; +} + + +/** + * @brief 内部平衡机制 3:颜色翻转(分解临时的 4-node 节点) + */ +C_STATIC_FORCE_INLINE +void c_RBBST_FlipColors(c_RBBSTNode_t* h) { + h->color = !h->color; + if (h->left) h->left->color = !h->left->color; + if (h->right) h->right->color = !h->right->color; +} + +/** + * @brief 内部递归插入与自适应动态平衡状态机 + */ +static c_err_t c_RedBlackBST_InternalPut(c_RedBlackBST_t* self, c_RBBSTNode_t** node_ptr, const void* key, const void* val, bool* is_new_inserted) { + c_RBBSTNode_t* curr = *node_ptr; + + // 递归基:开辟挂载新节点,新生成的链接默认为极其活跃的【红链接】 + if (curr == NULL) { + c_RBBSTNode_t* new_node = (c_RBBSTNode_t*)c_Allocator_Alloc(&self->allocator, sizeof(c_RBBSTNode_t)); + void* new_key = c_Allocator_Alloc(&self->allocator, self->key_size); + void* new_val = c_Allocator_Alloc(&self->allocator, self->val_size); + + if (!new_node || !new_key || !new_val) { + if (new_node) c_Allocator_Free(&self->allocator, new_node); + if (new_key) c_Allocator_Free(&self->allocator, new_key); + if (new_val) c_Allocator_Free(&self->allocator, new_val); + return C_ERR_NOMEM; + } + + memcpy(new_key, key, self->key_size); + memcpy(new_val, val, self->val_size); + new_node->key = new_key; + new_node->val = new_val; + new_node->left = NULL; + new_node->right = NULL; + new_node->color = C_RB_RED; // 🌟 核心:新节点一律为红链接 + + *node_ptr = new_node; + *is_new_inserted = true; + return C_ERR_OK; + } + + int cmp_res = self->cmp(key, curr->key, self->args); + c_err_t err = C_ERR_OK; + + if (cmp_res < 0) { + err = c_RedBlackBST_InternalPut(self, &(curr->left), key, val, is_new_inserted); + } else if (cmp_res > 0) { + err = c_RedBlackBST_InternalPut(self, &(curr->right), key, val, is_new_inserted); + } else { + // 键已存在,执行覆写 + memcpy(curr->val, val, self->val_size); + *is_new_inserted = false; + return C_ERR_OK; + } + + if (err != C_ERR_OK) return err; + + // 🌟🌟🌟【左倾红黑树自适应自平衡标准控制链(Sedgewick 经典三部曲)】🌟🌟🌟 + // 指针可能随着旋转被改写,故直接对当前二级指针接管的实体执行自底向上回溯刷新 + + // 步骤 1:若右链接为红且左链接为黑,强制执行左旋使其左倾 + if (c_RBBST_IsRed((*node_ptr)->right) && !c_RBBST_IsRed((*node_ptr)->left)) { + c_RBBST_RotateLeft(node_ptr); + } + // 步骤 2:若左链接为红,且左子节点的左链接也是红(连续两条红链接出现),强制执行右旋平衡 + if (c_RBBST_IsRed((*node_ptr)->left) && c_RBBST_IsRed((*node_ptr)->left->left)) { + c_RBBST_RotateRight(node_ptr); + } + // 步骤 3:若左右两条子链接同为红,强制翻转颜色,将红链接推向更高的父层级 + if (c_RBBST_IsRed((*node_ptr)->left) && c_RBBST_IsRed((*node_ptr)->right)) { + c_RBBST_FlipColors(*node_ptr); + } + + return C_ERR_OK; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_RedBlackBST_Init(c_RedBlackBST_t* self, c_size_t key_size, c_size_t val_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator) { + if (!self || key_size == 0 || val_size == 0 || !cmp) { + return C_ERR_PARAM; + } + + if (allocator) { + self->allocator = *allocator; + } else { + self->allocator = c_DefaultAllocator; + } + + self->root = NULL; + self->size = 0; + self->key_size = key_size; + self->val_size = val_size; + self->cmp = cmp; + self->args = args; + + return C_ERR_OK; +} + + +/** + * @brief 存入键值对(始终维持树的对数级绝对平衡,时间复杂度 O(log N)) + */ +c_err_t c_RedBlackBST_Put(c_RedBlackBST_t* self, const void* key, const void* val) { + if (!self || !key || !val) return C_ERR_PARAM; + + bool is_new = false; + c_err_t err = c_RedBlackBST_InternalPut(self, &(self->root), key, val, &is_new); + if (err == C_ERR_OK) { + if (is_new) self->size++; + // 根节点的父链接在扩容和旋转后必须强制恢复回稳定严肃的【黑链接】 + self->root->color = C_RB_BLACK; + } + return err; +} + +/** + * @brief 精准二叉有序检索(时间复杂度稳定为 O(log N)) + */ +c_err_t c_RedBlackBST_Get(const c_RedBlackBST_t* self, const void* key, void* out_val) { + if (!self || !key || !out_val) return C_ERR_PARAM; + + c_RBBSTNode_t* curr = self->root; + while (curr != NULL) { + int cmp_res = self->cmp(key, curr->key, self->args); + if (cmp_res < 0) curr = curr->left; + else if (cmp_res > 0) curr = curr->right; + else { + memcpy(out_val, curr->val, self->val_size); + return C_ERR_OK; + } + } + return C_ERR_NOTFOUND; +} + +/** + * @brief 检查树中是否有效包含指定的 Key + */ +bool c_RedBlackBST_Contains(const c_RedBlackBST_t* self, const void* key) { + if (!self || !key) return false; + c_RBBSTNode_t* curr = self->root; + while (curr != NULL) { + int cmp_res = self->cmp(key, curr->key, self->args); + if (cmp_res < 0) curr = curr->left; + else if (cmp_res > 0) curr = curr->right; + else return true; + } + return false; +} + +static void c_RedBlackBST_InternalDeinit(c_Allocator_t* alloc, c_RBBSTNode_t* node) { + if (node == NULL) return; + c_RedBlackBST_InternalDeinit(alloc, node->left); + c_RedBlackBST_InternalDeinit(alloc, node->right); + c_Allocator_Free(alloc, node->key); + c_Allocator_Free(alloc, node->val); + c_Allocator_Free(alloc, node); +} + +/** + * @brief 彻底销毁红黑树并逆向释放全部堆资源 + */ +void c_RedBlackBST_Destroy(c_RedBlackBST_t* self) { + if (self && self->root) { + c_RedBlackBST_InternalDeinit(&self->allocator, self->root); + self->root = NULL; + self->size = 0; + } +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/** + * @brief 自底向上沿途修复平衡(Fix-up) + */ +static void c_RBBST_Balance(c_RBBSTNode_t** node_ptr) { + if (*node_ptr == NULL) return; + + // 1. 纠正右倾红链接 + if (c_RBBST_IsRed((*node_ptr)->right) && !c_RBBST_IsRed((*node_ptr)->left)) { + c_RBBST_RotateLeft(node_ptr); + } + // 2. 纠正连续红链接 + if (c_RBBST_IsRed((*node_ptr)->left) && c_RBBST_IsRed((*node_ptr)->left->left)) { + c_RBBST_RotateRight(node_ptr); + } + // 3. 分解临时的 4-node + if (c_RBBST_IsRed((*node_ptr)->left) && c_RBBST_IsRed((*node_ptr)->right)) { + c_RBBST_FlipColors(*node_ptr); + } +} + +/** + * @brief 假设当前节点 h 为红且 h->left 和 h->left->left 都为黑,将红链接强制向左移动 + */ +static void c_RBBST_MoveRedLeft(c_RBBSTNode_t** node_ptr) { + c_RBBSTNode_t* h = *node_ptr; + c_RBBST_FlipColors(h); + + // 如果亲兄弟节点的左子节点是红链接,说明可以向右边“借”一个红链接过来 + if (c_RBBST_IsRed(h->right->left)) { + c_RBBST_RotateRight(&(h->right)); + c_RBBST_RotateLeft(node_ptr); + // 旋转后由于指针载体换成新根,同步对其刷新颜色翻转 + c_RBBST_FlipColors(*node_ptr); + } +} + +/** + * @brief 假设当前节点 h 为红且 h->right 和 h->right->left 都为黑,将红链接强制向右移动 + */ +static void c_RBBST_MoveRedRight(c_RBBSTNode_t** node_ptr) { + c_RBBSTNode_t* h = *node_ptr; + c_RBBST_FlipColors(h); + + // 如果亲兄弟节点的左子节点是红链接,说明可以向左边“借”一个红链接过来 + if (c_RBBST_IsRed(h->left->left)) { + c_RBBST_RotateRight(node_ptr); + c_RBBST_FlipColors(*node_ptr); + } +} + +/** + * @brief 内部辅助:寻找指定子树的绝对最小值节点(内部删除并解绑提取) + */ +static c_RBBSTNode_t* c_RedBlackBST_InternalDeleteMin(c_RedBlackBST_t* self, c_RBBSTNode_t** node_ptr) { + c_RBBSTNode_t* curr = *node_ptr; + + if (curr->left == NULL) { + *node_ptr = NULL; // 断开断裂 + return curr; + } + + // 核心推进:如果当前左子链和左子的左子都是黑色链接,为了防止删除 2-node 崩溃,强行将红链接左推 + if (!c_RBBST_IsRed(curr->left) && !c_RBBST_IsRed(curr->left->left)) { + c_RBBST_MoveRedLeft(node_ptr); + } + + c_RBBSTNode_t* min_node = c_RedBlackBST_InternalDeleteMin(self, &((*node_ptr)->left)); + + // 自底向上逐级退栈平衡修复 + c_RBBST_Balance(node_ptr); + return min_node; +} + +/** + * @brief 内部递归自适应删除核心逻辑 + */ +static c_err_t c_RedBlackBST_InternalDelete(c_RedBlackBST_t* self, c_RBBSTNode_t** node_ptr, const void* key) { + c_RBBSTNode_t* curr = *node_ptr; + if (curr == NULL) { + return C_ERR_NOTFOUND; + } + + // 1. 如果目标键小于当前节点,向左子树探查 + if (self->cmp(key, curr->key, self->args) < 0) { + // 如果左边深度不足,前置借调红链接向左推 + if (!c_RBBST_IsRed(curr->left) && !c_RBBST_IsRed(curr->left->left)) { + c_RBBST_MoveRedLeft(node_ptr); + } + c_err_t err = c_RedBlackBST_InternalDelete(self, &((*node_ptr)->left), key); + c_RBBST_Balance(node_ptr); // 退栈修复 + return err; + } + else { + // 2. 如果当前左子链接是红的,强制右旋。 + // 这可以让待比较的较大或相等的元素被顺利挪动并暴露到右侧 + if (c_RBBST_IsRed(curr->left)) { + c_RBBST_RotateRight(node_ptr); + curr = *node_ptr; // 刷新本地缓存指针 + } + + // 3. 精确命中检查条件:如果完全相等,且已经走到了树的叶子底部(无右子树) + // 此时由于自顶向下红链接移位的保护,curr 必然含有红链接,可以直接抹除并释放 + if (self->cmp(key, curr->key, self->args) == 0 && (curr->right == NULL)) { + c_RBBSTNode_t* old_node = curr; + *node_ptr = curr->left; // 让左子树托管 + + c_Allocator_Free(&self->allocator, old_node->key); + c_Allocator_Free(&self->allocator, old_node->val); + c_Allocator_Free(&self->allocator, old_node); + return C_ERR_OK; + } + + // 4. 继续向右子树探查(目标比当前大,或者相等但处于中间层级) + if (!c_RBBST_IsRed(curr->right) && !c_RBBST_IsRed(curr->right->left)) { + c_RBBST_MoveRedRight(node_ptr); + curr = *node_ptr; + } + + // 5. 中间层级精确命中:执行 Hibbard 后继者替换策略 + if (self->cmp(key, curr->key, self->args) == 0) { + c_RBBSTNode_t* old_node = curr; + + // 剥离并索取右子树的绝对最小值节点作为继承人 + c_RBBSTNode_t* successor = c_RedBlackBST_InternalDeleteMin(self, &(curr->right)); + + // 继承人完美承接原有乱序节点的所有双向指针拓扑以及红黑颜色 + successor->left = old_node->left; + successor->right = (*node_ptr)->right; + successor->color = old_node->color; + + *node_ptr = successor; // 代理顶替 + + // 彻底释放旧节点占用的内存 + c_Allocator_Free(&self->allocator, old_node->key); + c_Allocator_Free(&self->allocator, old_node->val); + c_Allocator_Free(&self->allocator, old_node); + + c_RBBST_Balance(node_ptr); + return C_ERR_OK; + } + else { + // 只是普通的向右边路探查 + c_err_t err = c_RedBlackBST_InternalDelete(self, &((*node_ptr)->right), key); + c_RBBST_Balance(node_ptr); + return err; + } + } +} + +/** + * @brief 根据指定 Key 彻底从左倾红黑树中斩断删除该节点(时间复杂度卡死在完美的 O(log N) 上限) + */ +c_err_t c_RedBlackBST_Delete(c_RedBlackBST_t* self, const void* key) { + if (!self || !key) { + return C_ERR_PARAM; + } + // 状态码规范链条:空仓异常前置拦截 + if (self->size == 0 || self->root == NULL) { + return C_ERR_EMPTY; + } + + c_err_t err = c_RedBlackBST_InternalDelete(self, &(self->root), key); + if (err == C_ERR_OK) { + self->size--; + // 如果树还没被删空,必须对重组后的最终新树根强制刷新并恢复黑高性质 `'B'` + if (self->root != NULL) { + self->root->color = C_RB_BLACK; + } + } + return err; +} diff --git a/Search/c_RedBlackBST.h b/Search/c_RedBlackBST.h new file mode 100644 index 0000000..53f5142 --- /dev/null +++ b/Search/c_RedBlackBST.h @@ -0,0 +1,54 @@ +#ifndef INCLUDED_C_REDBLACKBST_H +#define INCLUDED_C_REDBLACKBST_H + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +#define C_RB_RED true +#define C_RB_BLACK false + +typedef struct c_RBBSTNode_t { + void* key; // 独立分配存储的 Key 物理地址 + void* val; // 独立分配存储的 Value 物理地址 + struct c_RBBSTNode_t* left; // 左子节点指针 + struct c_RBBSTNode_t* right; // 右子节点指针 + bool color; // 指向该节点的父链接颜色 (R 为红链接,B 为黑链接) +} c_RBBSTNode_t; + +typedef struct { + c_RBBSTNode_t* root; // 树根节点指针 + c_size_t size; // 当前有效键值对总个数 + c_size_t key_size; // 键对象占用的物理字节大小 (sizeof) + c_size_t val_size; // 值对象占用的物理字节大小 (sizeof) + c_SortCompare_t cmp; // 键专用的动态回调比对器 + void* args; // 自定义上下文参数指针 + c_Allocator_t allocator; // 内联组合分配器实例与默认 Fallback 缺省机制 +} c_RedBlackBST_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_RedBlackBST_Init(c_RedBlackBST_t* self, c_size_t key_size, c_size_t val_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator); + +c_err_t c_RedBlackBST_Put(c_RedBlackBST_t* self, const void* key, const void* val); + +c_err_t c_RedBlackBST_Get(const c_RedBlackBST_t* self, const void* key, void* out_val); + +bool c_RedBlackBST_Contains(const c_RedBlackBST_t* self, const void* key); + +c_err_t c_RedBlackBST_Delete(c_RedBlackBST_t* self, const void* key); + +void c_RedBlackBST_Destroy(c_RedBlackBST_t* self); + +#endif /*INCLUDED_C_REDBLACKBST_H*/ diff --git a/Search/c_RedBlackBST.t.c b/Search/c_RedBlackBST.t.c new file mode 100644 index 0000000..7db02e6 --- /dev/null +++ b/Search/c_RedBlackBST.t.c @@ -0,0 +1,129 @@ +#include "c_RedBlackBST.h" +#include "c_Test.h" +#include +#include + +static int rbbst_compare_chars(const void* a, const void* b, void* args) { + (void)args; + char char1 = *(const char*)a; + char char2 = *(const char*)b; + return char1 - char2; +} + +TEST_CASE(test_c_RedBlackBST_CharColorFlow) { + c_RedBlackBST_t tree; + c_err_t err = c_RedBlackBST_Init(&tree, sizeof(char), sizeof(int), rbbst_compare_chars, NULL, &c_DefaultAllocator); + ASSERT_INT_EQ(C_ERR_OK, err); + + // 🌟【高强度测试】:连续输入偏斜升序数据 + char keys[] = { 'A', 'B', 'C', 'D', 'E' }; + int vals[] = { 10, 20, 30, 40, 50 }; + + for (int i = 0; i < 5; i++) { + ASSERT_INT_EQ(C_ERR_OK, c_RedBlackBST_Put(&tree, &keys[i], &vals[i])); + } + ASSERT_INT_EQ(5, (int)tree.size); + + // 验证经过旋转后的树根节点确实符合 2-3 树的中位数上提,而不是退化链表 + char root_key = *(char*)(tree.root->key); + ASSERT_TRUE(root_key != 'A'); + + // 🌟 核心断言:根据 LLRB 契约,最终留在树最顶端的根节点的父链接颜色必须为黑色字符 'B'! + ASSERT_INT_EQ(C_RB_BLACK, tree.root->color); + + // 验证 Get 检索的完好命中 + int extracted_val = 0; + char target_key_D = 'D'; + ASSERT_INT_EQ(C_ERR_OK, c_RedBlackBST_Get(&tree, &target_key_D, &extracted_val)); + ASSERT_INT_EQ(40, extracted_val); + + ASSERT_TRUE(c_RedBlackBST_Contains(&tree, &target_key_D)); + + c_RedBlackBST_Destroy(&tree); +} + +TEST_CASE(test_c_RedBlackBST_EdgeToxicity) { + c_RedBlackBST_t local_tree; + c_RedBlackBST_Init(&local_tree, sizeof(char), sizeof(int), rbbst_compare_chars, NULL, NULL); + + char k = 'X'; int v = 99; + ASSERT_INT_EQ(C_ERR_PARAM, c_RedBlackBST_Init(NULL, sizeof(char), sizeof(int), rbbst_compare_chars, NULL, NULL)); + ASSERT_INT_EQ(C_ERR_PARAM, c_RedBlackBST_Put(NULL, &k, &v)); + + c_RedBlackBST_Destroy(&local_tree); +} + +TEST_CASE(test_c_RedBlackBST_CascadeDeleteFlow) { + c_RedBlackBST_t tree; + c_err_t err = c_RedBlackBST_Init(&tree, sizeof(char), sizeof(int), rbbst_compare_chars, NULL, &c_DefaultAllocator); + ASSERT_INT_EQ(C_ERR_OK, err); + + // 1. 先填充空表,验证初次空表下的 Delete 状态码契约是否为标准的 C_ERR_EMPTY + char key_X = 'X'; + ASSERT_INT_EQ(C_ERR_EMPTY, c_RedBlackBST_Delete(&tree, &key_X)); + + // 2. 密集录入数据,触发多层级左旋、右旋自平衡,构建标准 2-3 树拓扑 + char keys[] = { 'M', 'E', 'S', 'A', 'R', 'C', 'W' }; + int vals[] = { 10, 20, 30, 40, 50, 60, 70 }; + c_size_t num = sizeof(keys) / sizeof(keys[0]); + + for (c_size_t i = 0; i < num; i++) { + ASSERT_INT_EQ(C_ERR_OK, c_RedBlackBST_Put(&tree, &keys[i], &vals[i])); + } + ASSERT_INT_EQ(7, (int)tree.size); + + // 3. 【第一轮绝杀】:删除处于树底部的叶子边缘节点 'A' + char key_A = 'A'; + ASSERT_INT_EQ(C_ERR_OK, c_RedBlackBST_Delete(&tree, &key_A)); + ASSERT_INT_EQ(6, (int)tree.size); // 有效递减 + int get_verify = 0; + ASSERT_INT_EQ(C_ERR_NOTFOUND, c_RedBlackBST_Get(&tree, &key_A, &get_verify)); + + // 4. 【第二轮绝杀】:删除拥有双向完好子树、处于核心中间分水岭的枢纽根节点 'M' + // 修复后的控制流应当极其完美地调用 InternalDeleteMin 从右子树剥离后继者,无伤缝合红黑黑高 + char key_M = 'M'; + ASSERT_INT_EQ(C_ERR_OK, c_RedBlackBST_Delete(&tree, &key_M)); + ASSERT_INT_EQ(5, (int)tree.size); + ASSERT_INT_EQ(C_ERR_NOTFOUND, c_RedBlackBST_Get(&tree, &key_M, &get_verify)); + + // 5. 验证大动荡过后,其余未被删除的邻居兄弟节点依旧稳固且可被 O(log N) 正常 Get + char key_C = 'C'; + ASSERT_INT_EQ(C_ERR_OK, c_RedBlackBST_Get(&tree, &key_C, &get_verify)); + ASSERT_INT_EQ(60, get_verify); + + char key_W = 'W'; + ASSERT_INT_EQ(C_ERR_OK, c_RedBlackBST_Get(&tree, &key_W, &get_verify)); + ASSERT_INT_EQ(70, get_verify); + + // 🌟【终极颜色完整性断言】:数据大洗牌后,驻留在最顶层的新树根节点颜色字符,必须依然被强行洗回完美的黑色 `'B'`! + ASSERT_INT_EQ(C_RB_BLACK, (int)tree.root->color); + + c_RedBlackBST_Destroy(&tree); +} + +TEST_CASE(test_c_RedBlackBST_DeleteDefenses) { + c_RedBlackBST_t local_tree; + c_RedBlackBST_Init(&local_tree, sizeof(char), sizeof(int), rbbst_compare_chars, NULL, NULL); + + char k = 'Q'; + // 6. 验证入参非法的强参数过滤 + ASSERT_INT_EQ(C_ERR_PARAM, c_RedBlackBST_Delete(NULL, &k)); + ASSERT_INT_EQ(C_ERR_PARAM, c_RedBlackBST_Delete(&local_tree, NULL)); + + c_RedBlackBST_Destroy(&local_tree); +} + +// ========================================== +// 5. 主集成入口 +// ========================================== +int main(void) { + TEST_START(C_RedBlackBST_CharColor_TestSuite); + RUN_TEST(test_c_RedBlackBST_CharColorFlow); + RUN_TEST(test_c_RedBlackBST_EdgeToxicity); + + RUN_TEST(test_c_RedBlackBST_CascadeDeleteFlow); + RUN_TEST(test_c_RedBlackBST_DeleteDefenses); + + TEST_REPORT(); + return (g_test_registry.failed_count > 0 ? 1 : 0); +} \ No newline at end of file diff --git a/Search/c_SeqSearchST.c b/Search/c_SeqSearchST.c new file mode 100644 index 0000000..c20cea3 --- /dev/null +++ b/Search/c_SeqSearchST.c @@ -0,0 +1,149 @@ +#include + + +c_err_t c_SeqSearchST_Init(c_SeqSearchST_t* self, c_size_t key_size, c_size_t val_size, c_SortCompare_t key_cmp, void* args, c_Allocator_t* allocator) { + if (!self || key_size == 0 || val_size == 0 || !key_cmp) { + return C_ERR_PARAM; + } + + // 自适应分配器降级缺省安全播种 + if (allocator) { + self->allocator = *allocator; + } else { + self->allocator = c_DefaultAllocator; + } + + self->first = NULL; + self->size = 0; + self->key_size = key_size; + self->val_size = val_size; + self->key_cmp = key_cmp; + self->args = args; + + return C_ERR_OK; +} + +/** + * @brief 存入键值对。若 Key 已存在则覆写更新其 Value;若不存在则使用头插法压入新节点 + */ +c_err_t c_SeqSearchST_Put(c_SeqSearchST_t* self, const void* key, const void* val) { + if (!self || !key || !val) return C_ERR_PARAM; + + // 1. 前置顺序走查:如果键值已经存在,直接更新它的值(覆写语义) + for (c_SeqSearchSTNode* x = self->first; x != NULL; x = x->next) { + if (self->key_cmp(key, x->key, self->args) == 0) { + memcpy(x->val, val, self->val_size); + return C_ERR_OK; + } + } + + // 2. 键值不存在,启动托管分配:开辟新节点以及键和值的独立存储块 + c_SeqSearchSTNode* new_node = (c_SeqSearchSTNode*)c_Allocator_Alloc(&self->allocator, sizeof(c_SeqSearchSTNode)); + void* new_key = c_Allocator_Alloc(&self->allocator, self->key_size); + void* new_val = c_Allocator_Alloc(&self->allocator, self->val_size); + + if (!new_node || !new_key || !new_val) { + // 部分失败安全回收拦截,杜绝内存泄漏 + if (new_node) c_Allocator_Free(&self->allocator, new_node); + if (new_key) c_Allocator_Free(&self->allocator, new_key); + if (new_val) c_Allocator_Free(&self->allocator, new_val); + return C_ERR_NOMEM; + } + + // 执行内存数据安全转储 + memcpy(new_key, key, self->key_size); + memcpy(new_val, val, self->val_size); + + new_node->key = new_key; + new_node->val = new_val; + + // 3. 头插法挂载新节点 + new_node->next = self->first; + self->first = new_node; + self->size++; + + return C_ERR_OK; +} + +/** + * @brief 根据指定的 Key 检索关联的 Value 值 + */ +c_err_t c_SeqSearchST_Get(const c_SeqSearchST_t* self, const void* key, void* out_val) { + if (!self || !key || !out_val) return C_ERR_PARAM; + + for (const c_SeqSearchSTNode* x = self->first; x != NULL; x = x->next) { + if (self->key_cmp(key, x->key, self->args) == 0) { + memcpy(out_val, x->val, self->val_size); + return C_ERR_OK; + } + } + + return C_ERR_NOTFOUND; +} + +/** + * @brief 检查符号表内是否包含指定的 Key 键值 + */ +bool c_SeqSearchST_Contains(const c_SeqSearchST_t* self, const void* key) { + if (!self || !key) return false; + for (const c_SeqSearchSTNode* x = self->first; x != NULL; x = x->next) { + if (self->key_cmp(key, x->key, self->args) == 0) { + return true; + } + } + return false; +} + +/** + * @brief 根据指定 Key 彻底从符号表中移出其关联的键值对节点 + */ +c_err_t c_SeqSearchST_Delete(c_SeqSearchST_t* self, const void* key) { + if (!self || !key) return C_ERR_PARAM; + if (self->size == 0) return C_ERR_EMPTY; + + c_SeqSearchSTNode* prev = NULL; + c_SeqSearchSTNode* curr = self->first; + + while (curr != NULL) { + if (self->key_cmp(key, curr->key, self->args) == 0) { + if (prev == NULL) { + self->first = curr->next; + } else { + prev->next = curr->next; + } + + // 原路回收释放单节点下深开辟的所有子内存块 + c_Allocator_Free(&self->allocator, curr->key); + c_Allocator_Free(&self->allocator, curr->val); + c_Allocator_Free(&self->allocator, curr); + + self->size--; + return C_ERR_OK; + } + prev = curr; + curr = curr->next; + } + + return C_ERR_NOTFOUND; +} + +/** + * @brief 反初始化:深度级联释放符号表内的所有节点、键和值的全部堆空间 + */ +void c_SeqSearchST_Destroy(c_SeqSearchST_t* self) { + if (!self) return; + + c_SeqSearchSTNode* curr = self->first; + while (curr != NULL) { + c_SeqSearchSTNode* next_tmp = curr->next; + + c_Allocator_Free(&self->allocator, curr->key); + c_Allocator_Free(&self->allocator, curr->val); + c_Allocator_Free(&self->allocator, curr); + + curr = next_tmp; + } + + self->first = NULL; + self->size = 0; +} diff --git a/Search/c_SeqSearchST.h b/Search/c_SeqSearchST.h new file mode 100644 index 0000000..918026f --- /dev/null +++ b/Search/c_SeqSearchST.h @@ -0,0 +1,47 @@ +#ifndef INCLUDED_C_SEQSEARCHST_H +#define INCLUDED_C_SEQSEARCHST_H + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct c_SeqSearchSTNode { + void* key; // 独立分配存储的 Key 物理地址 + void* val; // 独立分配存储的 Value 物理地址 + struct c_SeqSearchSTNode* next; // 指向下一个符号节点的指针 +} c_SeqSearchSTNode; + +typedef struct { + c_SeqSearchSTNode* first; // 链表头节点指针 + c_size_t size; // 当前符号表内有效驻留的键值对总个数 + c_size_t key_size; // 键对象占用的物理字节大小 (sizeof) + c_size_t val_size; // 值对象占用的物理字节大小 (sizeof) + c_SortCompare_t key_cmp; // 键值专用的动态回调比对器 + void* args; // 自定义上下文参数指针 + c_Allocator_t allocator; // 内联组合分配器实例与默认 Fallback 缺省机制 +} c_SeqSearchST_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_SeqSearchST_Init(c_SeqSearchST_t* self, c_size_t key_size, c_size_t val_size, c_SortCompare_t key_cmp, void* args, c_Allocator_t* allocator); + +c_err_t c_SeqSearchST_Put(c_SeqSearchST_t* self, const void* key, const void* val); + +c_err_t c_SeqSearchST_Get(const c_SeqSearchST_t* self, const void* key, void* out_val); + +bool c_SeqSearchST_Contains(const c_SeqSearchST_t* self, const void* key); + +c_err_t c_SeqSearchST_Delete(c_SeqSearchST_t* self, const void* key); + +void c_SeqSearchST_Destroy(c_SeqSearchST_t* self); + +#endif /*INCLUDED_C_SEQSEARCHST_H*/ diff --git a/Search/c_SeqSearchST.t.c b/Search/c_SeqSearchST.t.c new file mode 100644 index 0000000..31ddb88 --- /dev/null +++ b/Search/c_SeqSearchST.t.c @@ -0,0 +1,76 @@ +#include "c_SeqSearchST.h" +#include +#include +#include "c_Test.h" + +static int st_compare_chars(const void* a, const void* b, void* args) { + (void)args; + char char1 = *(const char*)a; + char char2 = *(const char*)b; + return char1 - char2; +} + +TEST_CASE(test_c_SeqSearchST_CRUD_Flow) { + c_SeqSearchST_t st; + c_err_t err = c_SeqSearchST_Init(&st, sizeof(char), sizeof(int), st_compare_chars, NULL, &c_DefaultAllocator); + ASSERT_INT_EQ(C_ERR_OK, err); + + char key_S = 'S'; int val_S = 100; + char key_E = 'E'; int val_E = 200; + char key_A = 'A'; int val_A = 300; + + // 1. Put 添加行为断言 + ASSERT_INT_EQ(C_ERR_OK, c_SeqSearchST_Put(&st, &key_S, &val_S)); + ASSERT_INT_EQ(C_ERR_OK, c_SeqSearchST_Put(&st, &key_E, &val_E)); + ASSERT_INT_EQ(C_ERR_OK, c_SeqSearchST_Put(&st, &key_A, &val_A)); + ASSERT_INT_EQ(3, (int)st.size); + + // 包含性状态核验 + ASSERT_TRUE(c_SeqSearchST_Contains(&st, &key_E)); + char key_X = 'X'; + ASSERT_TRUE(!c_SeqSearchST_Contains(&st, &key_X)); + + // 2. Get 读取断言 + int get_result = 0; + ASSERT_INT_EQ(C_ERR_OK, c_SeqSearchST_Get(&st, &key_E, &get_result)); + ASSERT_INT_EQ(200, get_result); + + // 3. Put 覆写更新(Overwrite)断言 + int update_val_E = 999; + ASSERT_INT_EQ(C_ERR_OK, c_SeqSearchST_Put(&st, &key_E, &update_val_E)); + ASSERT_INT_EQ(3, (int)st.size); + ASSERT_INT_EQ(C_ERR_OK, c_SeqSearchST_Get(&st, &key_E, &get_result)); + ASSERT_INT_EQ(999, get_result); + + // 4. Delete 节点移出断言 + ASSERT_INT_EQ(C_ERR_OK, c_SeqSearchST_Delete(&st, &key_E)); + ASSERT_INT_EQ(2, (int)st.size); + ASSERT_INT_EQ(C_ERR_NOTFOUND, c_SeqSearchST_Get(&st, &key_E, &get_result)); + + c_SeqSearchST_Destroy(&st); +} + +TEST_CASE(test_c_SeqSearchST_ParamDefenses) { + c_SeqSearchST_t local_st; + c_SeqSearchST_Init(&local_st, sizeof(char), sizeof(int), st_compare_chars, NULL, NULL); + + char k = 'W'; + int v = 88; + // 5. 验证拦截线 + ASSERT_INT_EQ(C_ERR_PARAM, c_SeqSearchST_Init(NULL, sizeof(char), sizeof(int), st_compare_chars, NULL, NULL)); + ASSERT_INT_EQ(C_ERR_PARAM, c_SeqSearchST_Put(NULL, &k, &v)); + ASSERT_INT_EQ(C_ERR_EMPTY, c_SeqSearchST_Delete(&local_st, &k)); + + c_SeqSearchST_Destroy(&local_st); +} + +// ========================================== +// 5. 主集成入口 +// ========================================== +int main(void) { + TEST_START(C_SeqSearchST_Isolated_TestSuite); + RUN_TEST(test_c_SeqSearchST_CRUD_Flow); + RUN_TEST(test_c_SeqSearchST_ParamDefenses); + TEST_REPORT(); + RETURN_TEST_STATUS; +} \ No newline at end of file diff --git a/Search/c_StrHashOps.c b/Search/c_StrHashOps.c new file mode 100644 index 0000000..dd94276 --- /dev/null +++ b/Search/c_StrHashOps.c @@ -0,0 +1,63 @@ +#include + +#include "c_Allocator.h" + +C_STATIC_FORCE_INLINE +uint32_t hash_fmix32(uint32_t h) { + h ^= h >> 16; + h *= 0x3243f6a9U; + h ^= h >> 16; + return h; +} + +// Hashing: Using djb2 for string data +uint32_t c_StrHashOps_Hash(const void *data, void *arg) { + uint32_t hash = 5381; + const char *str = (const char*) data; + char c; + while((c = *str++)) { + hash = ((hash << 5) + hash) + c; // hash * 33 + c + } + return hash_fmix32(hash); +} + + +// Deep Copy: Duplicating the string in memory +void* c_StrHashOps_Cp(const void *data, void *arg) { + if (!data || !arg) { + return NULL; + } + c_Allocator_t* allocator = arg; + const char *input = (const char*) data; + c_size_t len = strlen(input); + c_size_t size = len + 1; + char *result = c_Allocator_Alloc(allocator, size); + if (!result) { + return NULL; + } + memcpy(result, input, len); + result[len] = '\0'; + + return result; +} + +// Equality: Comparing string contents +bool c_StrHashOps_Eq(const void *data1, const void *data2, void *arg) { + return strcmp((const char*)data1, (const char*)data2) == 0; +} + +// Memory Cleanup +void c_StrHashOps_Free(void *data, void *arg) { + if (!data || !arg) { + return; + } + c_Allocator_t* allocator = arg; + c_Allocator_Free(allocator, data); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_HashKeyOps_t c_StrKeyOps={.hash = c_StrHashOps_Hash, .cp = c_StrHashOps_Cp, .free = c_StrHashOps_Free, .eq = c_StrHashOps_Eq, .arg = &c_DefaultAllocator}; + +c_HashValOps_t c_StrValOps={.cp = c_StrHashOps_Cp, .free = c_StrHashOps_Free, .eq = c_StrHashOps_Eq, .arg = &c_DefaultAllocator}; diff --git a/Search/c_StrHashOps.h b/Search/c_StrHashOps.h new file mode 100644 index 0000000..0c1b980 --- /dev/null +++ b/Search/c_StrHashOps.h @@ -0,0 +1,23 @@ +#ifndef INCLUDED_C_STRHASHOPS_H +#define INCLUDED_C_STRHASHOPS_H + +#ifndef INCLUDED_C_HASHST_H +#include +#endif /*INCLUDED_C_HASHST_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +uint32_t c_StrHashOps_Hash(const void *data, void *arg); + +void* c_StrHashOps_Cp(const void *data, void *arg); + +bool c_StrHashOps_Eq(const void *data1, const void *data2, void *arg); + +void c_StrHashOps_Free(void *data, void *arg); + +extern c_HashKeyOps_t c_StrKeyOps; +extern c_HashValOps_t c_StrValOps; + +#endif /*INCLUDED_C_STRHASHOPS_H*/ diff --git a/Sort/c_BinaryInsertionSort.c b/Sort/c_BinaryInsertionSort.c new file mode 100644 index 0000000..e149a83 --- /dev/null +++ b/Sort/c_BinaryInsertionSort.c @@ -0,0 +1,59 @@ +#include + +void c_BinaryInsertionSort(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + // 边界与防御性校验:元素少于 2 个或参数非法时无需排序 + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + + char* array_base = (char*)base; + + // 栈上开辟局部单元素缓冲区,用于暂存待插入的临时变量 v,规避 malloc 堆碎片 + char v_buf[size]; + + // 外层循环:从第二个元素开始(无符号安全递增) + for (c_size_t i = 1; i < num; i++) { + const char* item_i = array_base + (i * size); + + // 暂存当前需要插入的元素:v = a[i] + memcpy(v_buf, item_i, size); + + // ----------------------------------------------------------------- + // 核心:在已排好序的左侧区间 [0, i-1] 内执行二分查找,锁定 v 应该插入的绝对位置 + // ----------------------------------------------------------------- + c_size_t low = 0; + c_size_t high = i; // 右边界设为 i(开区间),这样 high 永远不会递减到负数 + + while (low < high) { // 注意:开区间控制条件为 low < high,而不是 <= + c_size_t mid = low + ((high - low) >> 1); + char* mid_elem = array_base + (mid * size); + + // 执行您带 args 的自定义比对器 + int cmp_res = cmp(v_buf, mid_elem, args); + + if (cmp_res < 0) { + high = mid; // 目标值比 mid 小,直接收缩右开边界为 mid。彻底规避了 mid - 1 导致的下溢! + } else { + low = mid + 1; // 目标值 >= mid,向右半区逼近(保持稳定性) + } + } + + // 此时 low == high,这个值就是新元素应该插入的精准无符号下标位置 + c_size_t insert_pos = low; + + // ----------------------------------------------------------------- + // 数据搬运:将区间 [insert_pos, i-1] 内的元素统一单向往后平移一位 + // 为确保绝对不发生 c_size_t 下溢,我们采用从后往前的减法前置拦截法 + // ----------------------------------------------------------------- + for (c_size_t j = i; j > insert_pos; j--) { + char* current = array_base + (j * size); + char* previous = array_base + ((j - 1) * size); + memcpy(current, previous, size); // a[j] = a[j-1] + } + + // 将暂存的元素 v 写入最终空出来的安全插槽:a[insert_pos] = v + char* item_target = array_base + (insert_pos * size); + memcpy(item_target, v_buf, size); + } +} + diff --git a/Sort/c_BinaryInsertionSort.h b/Sort/c_BinaryInsertionSort.h new file mode 100644 index 0000000..8b4332c --- /dev/null +++ b/Sort/c_BinaryInsertionSort.h @@ -0,0 +1,23 @@ +#ifndef INCLUDED_C_BINARYINSERTIONSORT_H +#define INCLUDED_C_BINARYINSERTIONSORT_H + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 泛型二分插入排序(大幅降低比对次数,完全规避无符号整数下溢) + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 比对回调函数指针 (不能为 NULL) + */ + +void c_BinaryInsertionSort(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + +#endif /*INCLUDED_C_BINARYINSERTIONSORT_H*/ diff --git a/Sort/c_BinaryInsertionSort.t.c b/Sort/c_BinaryInsertionSort.t.c new file mode 100644 index 0000000..5f9c9fd --- /dev/null +++ b/Sort/c_BinaryInsertionSort.t.c @@ -0,0 +1,79 @@ +#include "c_BinaryInsertionSort.h" +#include "c_Test.h" +#include +#include + +static int sort_compare_ints(const void* a, const void* b, void* args) { + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + + +TEST_CASE(test_c_BinaryInsertionSort_IntArray) { + // 1. 测试基础整型数据的二分插入重排 + int arr[] = { 34, 12, 5, 56, 12, 1, 90 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_BinaryInsertionSort(arr, num, sizeof(int), sort_compare_ints, 0); + + // 验证全区间是否呈绝对严格递增排列 + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] <= arr[i + 1]); + } + ASSERT_INT_EQ(1, arr[0]); // 最小值 + ASSERT_INT_EQ(12, arr[2]); // 保持稳定性 + ASSERT_INT_EQ(12, arr[3]); + ASSERT_INT_EQ(90, arr[6]); // 最大值 +} + +TEST_CASE(test_c_BinaryInsertionSort_ReverseAndEdge) { + // 3. 压测完全倒序的极值数组,确保 low/high 边界收缩时无溢出 + int rev_arr[] = { 5, 4, 3, 2, 1 }; + c_BinaryInsertionSort(rev_arr, 5, sizeof(int), sort_compare_ints, 0); + + for (c_size_t i = 0; i < 4; i++) { + ASSERT_TRUE(rev_arr[i] < rev_arr[i + 1]); + } + + // 4. 空指针与单元素数组的安全防御 + int single_arr[] = { 77 }; + c_BinaryInsertionSort(single_arr, 1, sizeof(int), sort_compare_ints, 0); + c_BinaryInsertionSort(NULL, 0, sizeof(int), sort_compare_ints, 0); + ASSERT_INT_EQ(77, single_arr[0]); +} + +TEST_CASE(test_c_BinaryInsertionSort_Underflow) { + // 准备一个完全倒序的数组。 + // 在旧代码中,由于新元素每次都比前面的小,会导致 while 内部频繁引发 high = 0 - 1 = UINT64_MAX 从而死循环卡死。 + int toxic_arr[] = { 5, 4, 3, 2, 1 }; + c_size_t num = sizeof(toxic_arr) / sizeof(toxic_arr[0]); + + // 运行安全修改后的无符号排序 + c_BinaryInsertionSort(toxic_arr, num, sizeof(int), sort_compare_ints, NULL); + + // 1. 验证没有发生卡死或崩溃,且全区间呈绝对严格递增排列 + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(toxic_arr[i] < toxic_arr[i + 1]); + } + + // 2. 精准校验边界项 + ASSERT_INT_EQ(1, toxic_arr[0]); + ASSERT_INT_EQ(5, toxic_arr[4]); +} + +int main(int argc, char** argv){ + TEST_START(Unit Tests); + + // 运行普通无环境要求的用例 + RUN_TEST(test_c_BinaryInsertionSort_IntArray); + RUN_TEST(test_c_BinaryInsertionSort_ReverseAndEdge); + RUN_TEST(test_c_BinaryInsertionSort_Underflow); + + // 打印最终统计报告 + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Sort/c_HeapSort.c b/Sort/c_HeapSort.c new file mode 100644 index 0000000..8096b87 --- /dev/null +++ b/Sort/c_HeapSort.c @@ -0,0 +1,105 @@ +#include + +/** + * @brief 内部原子接口:泛型就地物理内存块高效率互换(复用项目基础组件思想) + */ +C_STATIC_FORCE_INLINE +void c_HS_InternalSwap(void* a, void* b, c_size_t size) { + if (a == b) return; + char* p1 = (char*)a; + char* p2 = (char*)b; + char temp_buf[256]; + c_size_t bytes_left = size; + while (bytes_left > 0) { + c_size_t chunk = (bytes_left < sizeof(temp_buf)) ? bytes_left : sizeof(temp_buf); + memcpy(temp_buf, p1, chunk); + memcpy(p1, p2, chunk); + memcpy(p2, temp_buf, chunk); + p1 += chunk; + p2 += chunk; + bytes_left -= chunk; + } +} + +/** + * @brief 堆排序核心下沉微调状态机(完全基于零基插槽的 c_size_t 安全加固) + * + * @param array_base 数组物理起始点 + * @param parent 当前待下沉调整的父节点无符号物理下标位置 + * @param num 当前有效堆数据边界(小于此边界的元素参与调整) + */ +static void c_HeapSort_SiftDown(char* array_base, c_size_t parent, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + while (1) { + // 计算左子节点在零基插槽中的无符号索引:left = 2 * parent + 1 + c_size_t left_child = (parent << 1) + 1; + + // 🌟 前置上限拦截:若左子节点已经超越或等于当前堆边界,说明已到达叶子节点,物理隔绝后续加法溢出 + if (left_child >= num) { + break; + } + + c_size_t larger_child = left_child; + c_size_t right_child = left_child + 1; + + // 如果存在右子节点,且右子节点在您带 args 的自定义比对器中大于左子节点,则锁定右半区 + if (right_child < num) { + char* p_left = array_base + (left_child * size); + char* p_right = array_base + (right_child * size); + if (cmp(p_right, p_left, args) > 0) { + larger_child = right_child; + } + } + + char* p_parent = array_base + (parent * size); + char* p_target = array_base + (larger_child * size); + + // 如果最大子节点大于父节点,则执行泛型就地对调,并向下追踪迭代 + if (cmp(p_target, p_parent, args) > 0) { + c_HS_InternalSwap(p_parent, p_target, size); + parent = larger_child; + } else { + break; // 局部大顶堆序完全达成,提前破出 + } + } +} + +/** + * @brief 工业级泛型就地堆排序(Floyd 线性建堆 + 常数级空间控制) + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_HeapSort(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + // 边界与防御性校验:元素少于 2 个或参数非法时无需排序 + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + + char* array_base = (char*)base; + + // ================================================================= + // 阶段 1:Floyd 自底向上线性建堆(Heapify,时间复杂度为完美的 O(N)) + // 从最后一个非叶子节点开始倒序执行下沉。最后一个非叶子节点无符号下标为 (num / 2) - 1 + // 为了防止 (num / 2) - 1 在极端时引发下溢,我们采用无符号安全的向前递减边界拦截法 + // ================================================================= + c_size_t i = num >> 1; + while (i > 0) { + i--; // 此时 i 从 (num/2)-1 平滑递减到 0,彻底封死下溢到最大无符号数的苗头 + c_HeapSort_SiftDown(array_base, i, num, size, cmp, args); + } + + // ================================================================= + // 阶段 2:迭代销毁堆并就地重排(时间复杂度 O(N log N)) + // 每次将大顶堆的堆顶(最大值)同当前的有效堆尾元素对调,随后缩小堆边界并下沉堆顶 + // ================================================================= + for (c_size_t j = num - 1; j > 0; j--) { + // 交换当前最大的堆顶 array_base[0] 到合法的排序落脚点 array_base[j] 处 + c_HS_InternalSwap(array_base, array_base + (j * size), size); + + // 重新对缩小后的有效堆域区间 [0, j-1] 执行自适应堆顶下沉重建 + c_HeapSort_SiftDown(array_base, 0, j, size, cmp, args); + } +} diff --git a/Sort/c_HeapSort.h b/Sort/c_HeapSort.h new file mode 100644 index 0000000..c4a1aab --- /dev/null +++ b/Sort/c_HeapSort.h @@ -0,0 +1,29 @@ +#ifndef INCLUDED_C_HEAPSORT_H +#define INCLUDED_C_HEAPSORT_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/** + * @brief 工业级泛型就地堆排序(Floyd 线性建堆 + 常数级空间控制) + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_HeapSort(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + +#endif /*INCLUDED_C_HEAPSORT_H*/ diff --git a/Sort/c_HeapSort.t.c b/Sort/c_HeapSort.t.c new file mode 100644 index 0000000..c26f567 --- /dev/null +++ b/Sort/c_HeapSort.t.c @@ -0,0 +1,112 @@ +#include "c_HeapSort.h" +#include "c_Test.h" +#include +#include + +static int sort_compare_ints_with_args(const void* a, const void* b, void* args) { + (void)args; + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +typedef struct { + char label; + int primary; + int secondary; +} LogMeta_t; + +// 复合结构体双级比对器:优先按 primary 升序,相同时按 secondary 降序 +static int sort_compare_logs(const void* a, const void* b, void* args) { + (void)args; + const LogMeta_t* l1 = (const LogMeta_t*)a; + const LogMeta_t* l2 = (const LogMeta_t*)b; + if (l1->primary != l2->primary) { + return l1->primary - l2->primary; + } + return l2->secondary - l1->secondary; +} + +TEST_CASE(test_c_HeapSort_BasicInts) { + // 准备一组带有高频大量重复项的恶劣随机分布整型集合,验证单调非减 + int arr[] = { 45, 12, 85, 45, 5, 67, 12, 90, 45, 1 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_HeapSort(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + // 验证全区间无损单调递增性 + for (c_size_t i = 0; i < num - 1; i++) { + if (arr[i] > arr[i + 1]) { + // 利用您已有的精确断言对数值冲突点实施高精确报错打印 + ASSERT_INT_EQ(arr[i + 1], arr[i]); + return; + } + } + ASSERT_INT_EQ(1, arr[0]); + ASSERT_INT_EQ(5, arr[1]); + ASSERT_INT_EQ(45, arr[4]); // 重复项完美挤压归位 + ASSERT_INT_EQ(90, arr[num - 1]); // 尾部必须收拢为最大值 90 +} + +TEST_CASE(test_c_HeapSort_StructArray) { + // 复杂业务多主键对象的就地堆排序搬运测试 + LogMeta_t logs[] = { + { 'A', 50, 100 }, + { 'B', 20, 300 }, + { 'C', 50, 400 }, // primary 键同为 50,但 secondary 次键 400 应当在 100 前面(降序) + { 'D', 10, 200 } + }; + c_size_t num = sizeof(logs) / sizeof(logs[0]); + + c_HeapSort(logs, num, sizeof(LogMeta_t), sort_compare_logs, NULL); + + // 预期就地堆排序后的精确物理排布顺序: D(10/200) -> B(20/300) -> C(50/400) -> A(50/100) + // 严格引入了正确的数组下标位置,杜绝了前几轮的数组名称未加下标引用笔误 + ASSERT_INT_EQ(10, logs[0].primary); + ASSERT_TRUE(logs[0].label == 'D'); + + ASSERT_INT_EQ(20, logs[1].primary); + ASSERT_TRUE(logs[1].label == 'B'); + + ASSERT_INT_EQ(50, logs[2].primary); + ASSERT_INT_EQ(400, logs[2].secondary); // 降序次键优先被堆顶下沉挪移到左侧插槽 + ASSERT_TRUE(logs[2].label == 'C'); + + ASSERT_INT_EQ(50, logs[3].primary); + ASSERT_INT_EQ(100, logs[3].secondary); + ASSERT_TRUE(logs[3].label == 'A'); +} + +TEST_CASE(test_c_HeapSort_ExtremeEdges) { + // 压测完全有序和完全逆序序列,全方位高强度检验 `num >> 1` 自底向上建堆边界的拦截安全性 + int rev_arr[] = { 5, 4, 3, 2, 1 }; + c_size_t num = sizeof(rev_arr) / sizeof(rev_arr[0]); + + c_HeapSort(rev_arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(rev_arr[i] < rev_arr[i + 1]); + } + + // 极端输入空边界及单元素优雅退出拦截断言 + int single_arr[] = { 66666 }; + c_HeapSort(single_arr, 1, sizeof(int), sort_compare_ints_with_args, NULL); + c_HeapSort(NULL, 0, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(66666, single_arr[0]); +} + +// ========================================== +// 5. 独立集成主入口点 +// ========================================== +int main(void) { + TEST_START(C_HeapSort_Isolated_TestSuite); + + // 顺序触发堆排序专线的全景自动化验证 + RUN_TEST(test_c_HeapSort_BasicInts); + RUN_TEST(test_c_HeapSort_StructArray); + RUN_TEST(test_c_HeapSort_ExtremeEdges); + + TEST_REPORT(); + RETURN_TEST_STATUS; +} \ No newline at end of file diff --git a/Sort/c_IndexMaxPQ.c b/Sort/c_IndexMaxPQ.c new file mode 100644 index 0000000..246948c --- /dev/null +++ b/Sort/c_IndexMaxPQ.c @@ -0,0 +1,205 @@ +#include + +/** + * @brief 内部静态原子操作:双向路由置换 + */ +C_STATIC_FORCE_INLINE +void c_IMPQ_Swap(c_IndexMaxPQ_t* pq, c_size_t i, c_size_t j) { + c_size_t swap_index_i = pq->pq[i]; + c_size_t swap_index_j = pq->pq[j]; + + pq->pq[i] = swap_index_j; + pq->pq[j] = swap_index_i; + + pq->qp[swap_index_i] = j; + pq->qp[swap_index_j] = i; +} + +/** + * @brief 内部静态辅助:根据堆物理位置提取真实 keys 空间对应数据的物理指针 + */ +C_STATIC_FORCE_INLINE +char* c_IMPQ_GetKeyByHeapPos(const c_IndexMaxPQ_t* pq, c_size_t heap_pos) { + c_size_t user_idx = pq->pq[heap_pos]; + return pq->keys + (user_idx * pq->elem_size); +} + + +static void c_IndexMaxPQ_SiftUp(c_IndexMaxPQ_t* pq, c_size_t child) { + // 堆物理下标从 1 开始,子父级自减收缩终止位置是 1。在无符号数(child > 1)下绝对安全 + while (child > 1) { + c_size_t parent = child >> 1; + // 🌟【索引最大堆定义】:若子节点的值“大于”父节点的值,向上置换顶推 + if (pq->cmp(c_IMPQ_GetKeyByHeapPos(pq, child), c_IMPQ_GetKeyByHeapPos(pq, parent), pq->args) > 0) { + c_IMPQ_Swap(pq, child, parent); + child = parent; + } else { + break; + } + } +} + +static void c_IndexMaxPQ_SiftDown(c_IndexMaxPQ_t* pq, c_size_t parent) { + c_size_t num = pq->size; + while (1) { + c_size_t left_child = parent << 1; + if (left_child > num) break; // 越界前置拦截,防止后面加法导致的整数溢出 + + c_size_t larger_child = left_child; + c_size_t right_child = left_child + 1; + + if (right_child <= num) { + // 🌟【索引最大堆定义】:若右子节点的值比左子节点还“大”,切换最大目标到右半区 + if (pq->cmp(c_IMPQ_GetKeyByHeapPos(pq, right_child), c_IMPQ_GetKeyByHeapPos(pq, left_child), pq->args) > 0) { + larger_child = right_child; + } + } + + // 🌟【索引最大堆定义】:若最大子节点依然“大于”当前的父节点,下沉对调 + if (pq->cmp(c_IMPQ_GetKeyByHeapPos(pq, larger_child), c_IMPQ_GetKeyByHeapPos(pq, parent), pq->args) > 0) { + c_IMPQ_Swap(pq, parent, larger_child); + parent = larger_child; + } else { + break; + } + } +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 就地初始化索引最大堆优先队列 + * + * @param max_elements 允许传入的最大唯一索引值。这会一次性静态开辟好所需的双向路由映射槽 + */ +c_err_t c_IndexMaxPQ_Init(c_IndexMaxPQ_t* self, c_size_t max_elements, c_size_t elem_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator) { + if (!self || max_elements == 0 || elem_size == 0 || !cmp) { + return C_ERR_PARAM; + } + + if (allocator) { + self->allocator = *allocator; + } else { + self->allocator = c_DefaultAllocator; + } + + self->max_elements = max_elements; + self->size = 0; + self->elem_size = elem_size; + self->cmp = cmp; + self->args = args; + + // 前置逆向除法溢出安全审计:确保三组大空间开辟时不会发生乘法溢出 + if (((c_size_t)-1) / sizeof(c_size_t) < (max_elements + 1)) return C_ERR_NOMEM; + if (((c_size_t)-1) / elem_size < max_elements) return C_ERR_NOMEM; + + // 分配堆数组 pq(堆下标从 1 开始使用到 max_elements,方便进行二叉父子节点位移运算) + self->pq = (c_size_t*)c_Allocator_Alloc(&self->allocator, (max_elements + 1) * sizeof(c_size_t)); + // 分配逆向映射表 qp + self->qp = (c_size_t*)c_Allocator_Alloc(&self->allocator, max_elements * sizeof(c_size_t)); + // 分配密集优先级数据存储块 + self->keys = (char*)c_Allocator_Alloc(&self->allocator, max_elements * elem_size); + + if (!self->pq || !self->qp || !self->keys) { + if (self->pq) c_Allocator_Free(&self->allocator, self->pq); + if (self->qp) c_Allocator_Free(&self->allocator, self->qp); + if (self->keys) c_Allocator_Free(&self->allocator, self->keys); + self->pq = NULL; self->qp = NULL; self->keys = NULL; + return C_ERR_NOMEM; + } + + // 将所有 qp 映射槽初始化填充为 (c_size_t)-1(代表未入队列),完美消除下溢舒适区 + memset(self->qp, 0xFF, max_elements * sizeof(c_size_t)); + return C_ERR_OK; +} + +/** + * @brief 检查某个唯一用户索引当前是否在队列中有效存在 + */ +bool c_IndexMaxPQ_Contains(const c_IndexMaxPQ_t* pq, c_size_t index) { + if (!pq || index >= pq->max_elements) return false; + return pq->qp[index] != (c_size_t)-1; +} + + +/** + * @brief 绑定一个唯一的唯一 index 压入优先级关联数据 + */ +c_err_t c_IndexMaxPQ_Push(c_IndexMaxPQ_t* pq, c_size_t index, const void* item) { + if (!pq || !item) return C_ERR_PARAM; + if (index >= pq->max_elements) return C_ERR_PARAM; + if (c_IndexMaxPQ_Contains(pq, index)) return C_ERR_EXIST; + + pq->size++; + pq->pq[pq->size] = index; + pq->qp[index] = pq->size; + + memcpy(pq->keys + (index * pq->elem_size), item, pq->elem_size); + + c_IndexMaxPQ_SiftUp(pq, pq->size); + return C_ERR_OK; +} + +/** + * @brief 弹出当前的绝对最大值元素(堆顶),并将其关联的唯一用户索引(index)通过 out_index 吐出 + */ +c_err_t c_IndexMaxPQ_Pop(c_IndexMaxPQ_t* pq, c_size_t* out_index) { + if (!pq) return C_ERR_PARAM; + if (pq->size == 0) return C_ERR_EMPTY; + + c_size_t max_idx_result = pq->pq[1]; + if (out_index) { + *out_index = max_idx_result; + } + + c_IMPQ_Swap(pq, 1, pq->size); + pq->size--; + + if (pq->size > 0) { + c_IndexMaxPQ_SiftDown(pq, 1); + } + + pq->qp[max_idx_result] = (c_size_t)-1; // 恢复注销状态 + return C_ERR_OK; +} + +/** + * @brief 在常数级时间内寻找并修改某个特定 index 的优先级数据 + */ +c_err_t c_IndexMaxPQ_Change(c_IndexMaxPQ_t* pq, c_size_t index, const void* new_item) { + if (!pq || !new_item || index >= pq->max_elements) return C_ERR_PARAM; + if (!c_IndexMaxPQ_Contains(pq, index)) return C_ERR_EMPTY; + + memcpy(pq->keys + (index * pq->elem_size), new_item, pq->elem_size); + + c_size_t heap_pos = pq->qp[index]; + + c_IndexMaxPQ_SiftUp(pq, heap_pos); + c_IndexMaxPQ_SiftDown(pq, heap_pos); + + return C_ERR_OK; +} + +/** + * @brief 观察读取但不弹出最大索引堆的堆顶最大关联索引(Index) + */ +c_err_t c_IndexMaxPQ_Peek(const c_IndexMaxPQ_t* pq, c_size_t* out_index) { + if (!pq || !out_index) return C_ERR_PARAM; + if (pq->size == 0) return C_ERR_EMPTY; + *out_index = pq->pq[1]; + return C_ERR_OK; +} + +/** + * @brief 反初始化并释放内存矩阵 + */ +void c_IndexMaxPQ_Destroy(c_IndexMaxPQ_t* pq) { + if (pq) { + if (pq->pq) c_Allocator_Free(&pq->allocator, pq->pq); + if (pq->qp) c_Allocator_Free(&pq->allocator, pq->qp); + if (pq->keys) c_Allocator_Free(&pq->allocator, pq->keys); + pq->pq = NULL; pq->qp = NULL; pq->keys = NULL; + pq->size = 0; pq->max_elements = 0; + } +} diff --git a/Sort/c_IndexMaxPQ.h b/Sort/c_IndexMaxPQ.h new file mode 100644 index 0000000..8c9edf8 --- /dev/null +++ b/Sort/c_IndexMaxPQ.h @@ -0,0 +1,55 @@ +#ifndef INCLUDED_C_INDEXMAXPQ_H +#define INCLUDED_C_INDEXMAXPQ_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +typedef struct { + c_size_t max_elements; // 允许传入的外部全局唯一索引上限边界 + c_size_t size; // 当前容器内有效驻留的索引总个数 + c_size_t elem_size; // 单个关联数据元素占用的物理字节大小 + + c_size_t* pq; // 物理堆数组:pq[i] 代表处于堆物理位置 i 处的用户唯一索引 (index) + c_size_t* qp; // 逆向映射路由:qp[index] 代表外部唯一索引在堆数组中的物理物理下标 (Heap Position) + // 特殊约束:若某个索引未入队,其 qp[index] 的值恒被安全填充为 (c_size_t)-1 + + char* keys; // 密集关联数据存储块:keys[index * elem_size] 存储该索引对应的真实优先级数据 + + c_SortCompare_t cmp; // 动态比对器 + void* args; // 自定义上下文 + c_Allocator_t allocator; // 内联组合分配器实体,支持自适应Fallback缺省 +} c_IndexMaxPQ_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_IndexMaxPQ_Init(c_IndexMaxPQ_t* self, c_size_t max_elements, c_size_t elem_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator); + +bool c_IndexMaxPQ_Contains(const c_IndexMaxPQ_t* pq, c_size_t index); + +c_err_t c_IndexMaxPQ_Push(c_IndexMaxPQ_t* pq, c_size_t index, const void* item); + +c_err_t c_IndexMaxPQ_Pop(c_IndexMaxPQ_t* pq, c_size_t* out_index); + +c_err_t c_IndexMaxPQ_Change(c_IndexMaxPQ_t* pq, c_size_t index, const void* new_item); + +c_err_t c_IndexMaxPQ_Peek(const c_IndexMaxPQ_t* pq, c_size_t* out_index); + +void c_IndexMaxPQ_Destroy(c_IndexMaxPQ_t* pq); + + +#endif /*INCLUDED_C_INDEXMAXPQ_H*/ diff --git a/Sort/c_IndexMaxPQ.t.c b/Sort/c_IndexMaxPQ.t.c new file mode 100644 index 0000000..de2ee30 --- /dev/null +++ b/Sort/c_IndexMaxPQ.t.c @@ -0,0 +1,82 @@ +#include "c_IndexMaxPQ.h" +#include "c_Test.h" + +#include +#include + +static int index_pq_compare_ints(const void* a, const void* b, void* args) { + (void)args; + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +TEST_CASE(test_c_IndexMaxPQ_IncreaseKeyFlow) { + c_IndexMaxPQ_t ipq; + // 允许最大的顶点唯一标识范围为 0 到 9 (共10个元素上界) + c_err_t err = c_IndexMaxPQ_Init(&ipq, 10, sizeof(int), index_pq_compare_ints, NULL, &c_DefaultAllocator); + ASSERT_INT_EQ(C_ERR_OK, err); + + // 模拟多路连接池的最大带宽配额管理。唯一ID为索引,当前流量分数为键值 + int score_node_0 = 50; + int score_node_1 = 12; // 此时节点 1 权重最小 (12) + int score_node_2 = 85; // 此时节点 2 权重最大 (85) + + ASSERT_INT_EQ(C_ERR_OK, c_IndexMaxPQ_Push(&ipq, 0, &score_node_0)); + ASSERT_INT_EQ(C_ERR_OK, c_IndexMaxPQ_Push(&ipq, 1, &score_node_1)); + ASSERT_INT_EQ(C_ERR_OK, c_IndexMaxPQ_Push(&ipq, 2, &score_node_2)); + + // 全局状态路由包含性验证 + ASSERT_TRUE(c_IndexMaxPQ_Contains(&ipq, 0)); + ASSERT_TRUE(c_IndexMaxPQ_Contains(&ipq, 1)); + ASSERT_TRUE(c_IndexMaxPQ_Contains(&ipq, 2)); + ASSERT_TRUE(!c_IndexMaxPQ_Contains(&ipq, 8)); + + // 🌟【核心中途剧变测试】:节点 1 发生突发流量暴涨,其权重从 12 直接被中途修改飙升到 200! + // 此时节点 1 (键值 200) 顺理成章地超越原霸主节点 2,逆袭变成本索引最大堆的绝对最高峰! + int bursting_score_node_1 = 200; + ASSERT_INT_EQ(C_ERR_OK, c_IndexMaxPQ_Change(&ipq, 1, &bursting_score_node_1)); + + c_size_t popped_index = 999; + + // 第一次 Pop 弹出的绝对必须是飙升后全局权重最大、优先级最高的节点 1,而不是原本的节点 2! + ASSERT_INT_EQ(C_ERR_OK, c_IndexMaxPQ_Pop(&ipq, &popped_index)); + ASSERT_INT_EQ(1, (int)popped_index); + ASSERT_TRUE(!c_IndexMaxPQ_Contains(&ipq, 1)); // 弹出状态平滑注销 + + // 第二次 Pop 弹出的应该是次高位的原节点 2 (键值 85) + ASSERT_INT_EQ(C_ERR_OK, c_IndexMaxPQ_Pop(&ipq, &popped_index)); + ASSERT_INT_EQ(2, (int)popped_index); + + // 最后弹出节点 0 (键值 50) + ASSERT_INT_EQ(C_ERR_OK, c_IndexMaxPQ_Pop(&ipq, &popped_index)); + ASSERT_INT_EQ(0, (int)popped_index); + + // 终极空仓防御校验 + ASSERT_INT_EQ(C_ERR_EMPTY, c_IndexMaxPQ_Pop(&ipq, &popped_index)); + + c_IndexMaxPQ_Destroy(&ipq); +} + +TEST_CASE(test_c_IndexMaxPQ_Constraints) { + c_IndexMaxPQ_t local_ipq; + int dummy = 42; + // 验证越界输入或空参数的前置状态码强拦截线 + ASSERT_INT_EQ(C_ERR_PARAM, c_IndexMaxPQ_Init(NULL, 10, sizeof(int), index_pq_compare_ints, NULL, NULL)); + ASSERT_INT_EQ(C_ERR_OK, c_IndexMaxPQ_Init(&local_ipq, 10, sizeof(int), index_pq_compare_ints, NULL, NULL)); + ASSERT_INT_EQ(C_ERR_PARAM, c_IndexMaxPQ_Push(&local_ipq, 9999, &dummy)); // 9999 溢出上界 + c_IndexMaxPQ_Destroy(&local_ipq); +} + +// ========================================== +// 5. 主集成入口 +// ========================================== +int main(void) { + TEST_START(C_IndexMaxPQ_Isolated_TestSuite); + RUN_TEST(test_c_IndexMaxPQ_IncreaseKeyFlow); + RUN_TEST(test_c_IndexMaxPQ_Constraints); + TEST_REPORT(); + return (g_test_registry.failed_count > 0 ? 1 : 0); +} \ No newline at end of file diff --git a/Sort/c_IndexMinPQ.c b/Sort/c_IndexMinPQ.c new file mode 100644 index 0000000..c914dac --- /dev/null +++ b/Sort/c_IndexMinPQ.c @@ -0,0 +1,215 @@ +#include + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/** + * @brief 双向路由置换原子操作(索引优先队列的灵魂核心) + * + * 交换堆物理下标 i 和 j 位置的数据时,不仅对调 pq,更要反向刷新两个用户索引在 qp 中的物理坐标指向。 + */ +C_STATIC_FORCE_INLINE +void c_IPQ_Swap(c_IndexMinPQ_t* pq, c_size_t i, c_size_t j) { + c_size_t swap_index_i = pq->pq[i]; + c_size_t swap_index_j = pq->pq[j]; + + pq->pq[i] = swap_index_j; + pq->pq[j] = swap_index_i; + + pq->qp[swap_index_i] = j; + pq->qp[swap_index_j] = i; +} + +/** + * @brief 根据堆物理位置,提取真实 keys 空间对应数据的安全辅助内联 + */ +C_STATIC_FORCE_INLINE +char* c_IPQ_GetKeyByHeapPos(const c_IndexMinPQ_t* pq, c_size_t heap_pos) { + c_size_t user_idx = pq->pq[heap_pos]; + return pq->keys + (user_idx * pq->elem_size); +} + +static void c_IndexMinPQ_SiftUp(c_IndexMinPQ_t* pq, c_size_t child) { + // 因为堆物理下标从 1 开始,子父级关系的自减收缩终止位置是 1。 + // 这在无符号数(child > 1)下是天然绝对安全的,不会爆发 0 - 1 的下溢。 + while (child > 1) { + c_size_t parent = child >> 1; + if (pq->cmp(c_IPQ_GetKeyByHeapPos(pq, child), c_IPQ_GetKeyByHeapPos(pq, parent), pq->args) < 0) { + c_IPQ_Swap(pq, child, parent); + child = parent; + } else { + break; + } + } +} + +static void c_IndexMinPQ_SiftDown(c_IndexMinPQ_t* pq, c_size_t parent) { + c_size_t num = pq->size; + while (1) { + c_size_t left_child = parent << 1; + if (left_child > num) break; + + c_size_t smaller_child = left_child; + c_size_t right_child = left_child + 1; + + if (right_child <= num) { + if (pq->cmp(c_IPQ_GetKeyByHeapPos(pq, right_child), c_IPQ_GetKeyByHeapPos(pq, left_child), pq->args) < 0) { + smaller_child = right_child; + } + } + + if (pq->cmp(c_IPQ_GetKeyByHeapPos(pq, smaller_child), c_IPQ_GetKeyByHeapPos(pq, parent), pq->args) < 0) { + c_IPQ_Swap(pq, parent, smaller_child); + parent = smaller_child; + } else { + break; + } + } +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_IndexMinPQ_Init(c_IndexMinPQ_t* self, c_size_t max_elements, c_size_t elem_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator) { + if (!self || max_elements == 0 || elem_size == 0 || !cmp) { + return C_ERR_PARAM; + } + + if (allocator) { + self->allocator = *allocator; + } else { + self->allocator = c_DefaultAllocator; + } + + self->max_elements = max_elements; + self->size = 0; + self->elem_size = elem_size; + self->cmp = cmp; + self->args = args; + + // 前置逆向除法溢出安全审计:确保三组大空间开辟时不会发生乘法溢出 + if (((c_size_t)-1) / sizeof(c_size_t) < (max_elements + 1)) return C_ERR_NOMEM; + if (((c_size_t)-1) / elem_size < max_elements) return C_ERR_NOMEM; + + // 分配堆数组 pq(堆下标从 1 开始使用到 max_elements,方便进行二叉父子节点位移运算) + self->pq = (c_size_t*)c_Allocator_Alloc(&self->allocator, (max_elements + 1) * sizeof(c_size_t)); + // 分配逆向映射表 qp + self->qp = (c_size_t*)c_Allocator_Alloc(&self->allocator, max_elements * sizeof(c_size_t)); + // 分配密集优先级数据存储块 + self->keys = (char*)c_Allocator_Alloc(&self->allocator, max_elements * elem_size); + + if (!self->pq || !self->qp || !self->keys) { + // 部分失败时原路进行逆向安全解构,斩断漏存 + if (self->pq) c_Allocator_Free(&self->allocator, self->pq); + if (self->qp) c_Allocator_Free(&self->allocator, self->qp); + if (self->keys) c_Allocator_Free(&self->allocator, self->keys); + return C_ERR_NOMEM; + } + + // 🌟 核心防下溢舒适区构建:将所有 qp 映射槽初始化填充为 (c_size_t)-1(代表未入队列) + memset(self->qp, 0xFF, max_elements * sizeof(c_size_t)); + return C_ERR_OK; +} + +/** + * @brief 检查某个唯一用户索引当前是否在队列中有效存在(常数级的时间开销) + */ +bool c_IndexMinPQ_Contains(const c_IndexMinPQ_t* pq, c_size_t index) { + if (!pq || index >= pq->max_elements) return false; + return pq->qp[index] != (c_size_t)-1; +} + + +/** + * @brief 绑定一个唯一的唯一 index 压入优先级关联数据 + */ +c_err_t c_IndexMinPQ_Push(c_IndexMinPQ_t* pq, c_size_t index, const void* item) { + if (!pq || !item) return C_ERR_PARAM; + if (index >= pq->max_elements) return C_ERR_PARAM; + if (c_IndexMinPQ_Contains(pq, index)) return C_ERR_EXIST; // 重复索引拦截 + + pq->size++; + pq->pq[pq->size] = index; // 堆底追加映射 + pq->qp[index] = pq->size; // 注册物理位置 + + // 拷贝优先级数据 + memcpy(pq->keys + (index * pq->elem_size), item, pq->elem_size); + + // 重建索引最小堆半序 + c_IndexMinPQ_SiftUp(pq, pq->size); + return C_ERR_OK; +} + +/** + * @brief 弹出当前的绝对最小值元素,并将其关联的唯一用户索引(index)通过 out_index 吐出 + */ +c_err_t c_IndexMinPQ_Pop(c_IndexMinPQ_t* pq, c_size_t* out_index) { + if (!pq) return C_ERR_PARAM; + if (pq->size == 0) return C_ERR_EMPTY; + + // 堆顶驻留的唯一索引即为全局最小值 + c_size_t min_idx_result = pq->pq[1]; + if (out_index) { + *out_index = min_idx_result; + } + + // 交换堆顶与堆底 + c_IPQ_Swap(pq, 1, pq->size); + pq->size--; + + // 触发下沉 + if (pq->size > 0) { + c_IndexMinPQ_SiftDown(pq, 1); + } + + // 将弹出的用户唯一索引解绑销毁注销状态(填充为 -1) + pq->qp[min_idx_result] = (c_size_t)-1; + return C_ERR_OK; +} + +/** + * @brief 【图算法核能优化原子操作】:在常数级时间内寻找并修改某个特定 index 的优先级数据 + * + * 时间复杂度为完美的 O(log N),而在普通优先队列中为退化的 O(N)! + */ +c_err_t c_IndexMinPQ_Change(c_IndexMinPQ_t* pq, c_size_t index, const void* new_item) { + if (!pq || !new_item || index >= pq->max_elements) return C_ERR_PARAM; + if (!c_IndexMinPQ_Contains(pq, index)) return C_ERR_EMPTY; + + // 1. 物理覆写数据 + memcpy(pq->keys + (index * pq->elem_size), new_item, pq->elem_size); + + // 2. 提取该索引对应的真实物理堆位置(Heap Position) + c_size_t heap_pos = pq->qp[index]; + + // 3. 联动调整:自适应向上上浮或向下下沉重建单调,保证其瞬间归位 + c_IndexMinPQ_SiftUp(pq, heap_pos); + c_IndexMinPQ_SiftDown(pq, heap_pos); + + return C_ERR_OK; +} + +/** + * @brief 观察读取但不弹出最小索引堆的堆顶最小关联索引(Index) + */ +c_err_t c_IndexMinPQ_Peek(const c_IndexMinPQ_t* pq, c_size_t* out_index) { + if (!pq || !out_index) return C_ERR_PARAM; + if (pq->size == 0) return C_ERR_EMPTY; + *out_index = pq->pq[1]; + return C_ERR_OK; +} + +/** + * @brief 反初始化并销毁全部路由内存矩阵 + */ +void c_IndexMinPQ_Destroy(c_IndexMinPQ_t* pq) { + if (pq) { + if (pq->pq) c_Allocator_Free(&pq->allocator, pq->pq); + if (pq->qp) c_Allocator_Free(&pq->allocator, pq->qp); + if (pq->keys) c_Allocator_Free(&pq->allocator, pq->keys); + pq->pq = NULL; pq->qp = NULL; pq->keys = NULL; + pq->size = 0; pq->max_elements = 0; + } +} \ No newline at end of file diff --git a/Sort/c_IndexMinPQ.h b/Sort/c_IndexMinPQ.h new file mode 100644 index 0000000..2922253 --- /dev/null +++ b/Sort/c_IndexMinPQ.h @@ -0,0 +1,55 @@ +#ifndef INCLUDED_C_INDEXMINPQ_H +#define INCLUDED_C_INDEXMINPQ_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_size_t max_elements; // 迭代器或外部图允许的最大唯一索引上限 (Index Max Limit) + c_size_t size; // 当前优先队列内有效驻留的索引总个数 + c_size_t elem_size; // 单个关联数据元素占用的物理字节大小 (sizeof) + + c_size_t* pq; // 物理堆数组:pq[i] 代表处于堆物理位置 i 处的用户唯一索引 (index) + c_size_t* qp; // 逆向映射映射表:qp[index] 代表用户唯一索引在堆数组中的物理物理下标位置 (Heap Position) + // 特殊约束:若某个索引未入队,其 qp[index] 的值恒被安全填充为 (c_size_t)-1 + + char* keys; // 密集关联数据存储块:keys[index * elem_size] 存储该索引对应的真实优先级数据 + + c_SortCompare_t cmp; // 动态比对器 + void* args; // 自定义上下文 + c_Allocator_t allocator; // 组合分配器实体,实现自适应Fallback缺省 +} c_IndexMinPQ_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_IndexMinPQ_Init(c_IndexMinPQ_t* self, c_size_t max_elements, c_size_t elem_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator); + +bool c_IndexMinPQ_Contains(const c_IndexMinPQ_t* pq, c_size_t index); + +c_err_t c_IndexMinPQ_Push(c_IndexMinPQ_t* pq, c_size_t index, const void* item); + +c_err_t c_IndexMinPQ_Pop(c_IndexMinPQ_t* pq, c_size_t* out_index); + +c_err_t c_IndexMinPQ_Change(c_IndexMinPQ_t* pq, c_size_t index, const void* new_item); + +c_err_t c_IndexMinPQ_Peek(const c_IndexMinPQ_t* pq, c_size_t* out_index); + +void c_IndexMinPQ_Destroy(c_IndexMinPQ_t* pq); + +#endif /*INCLUDED_C_INDEXMINPQ_H*/ diff --git a/Sort/c_IndexMinPQ.t.c b/Sort/c_IndexMinPQ.t.c new file mode 100644 index 0000000..0deebd0 --- /dev/null +++ b/Sort/c_IndexMinPQ.t.c @@ -0,0 +1,85 @@ +#include "c_IndexMinPQ.h" +#include "c_Test.h" + +#include +#include + +static int index_pq_compare_ints(const void* a, const void* b, void* args) { + (void)args; + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +TEST_CASE(test_c_IndexMinPQ_DecreaseKeyDijkstraFlow) { + c_IndexMinPQ_t ipq; + // 允许最大的顶点唯一标识范围为 0 到 9 (共10个元素上界) + c_err_t err = c_IndexMinPQ_Init(&ipq, 10, sizeof(int), index_pq_compare_ints, NULL, &c_DefaultAllocator); + ASSERT_INT_EQ(C_ERR_OK, err); + + // 模拟 Dijkstra 算法节点入队。唯一顶点ID为索引,其当前的极值距离为键值 + int dist_vertex_0 = 45; + int dist_vertex_1 = 88; + int dist_vertex_2 = 15; // 此时顶点 2 距离最小 (15) + + ASSERT_INT_EQ(C_ERR_OK, c_IndexMinPQ_Push(&ipq, 0, &dist_vertex_0)); + ASSERT_INT_EQ(C_ERR_OK, c_IndexMinPQ_Push(&ipq, 1, &dist_vertex_1)); + ASSERT_INT_EQ(C_ERR_OK, c_IndexMinPQ_Push(&ipq, 2, &dist_vertex_2)); + + // 全局唯一状态包含校验 + ASSERT_TRUE(c_IndexMinPQ_Contains(&ipq, 0)); + ASSERT_TRUE(c_IndexMinPQ_Contains(&ipq, 1)); + ASSERT_TRUE(c_IndexMinPQ_Contains(&ipq, 2)); + ASSERT_TRUE(!c_IndexMinPQ_Contains(&ipq, 5)); // 不包含未插入的 5 + + // 🌟【核心剧变测试】:在图算法流转中,顶点 1 经过松弛(Relaxation),其距离突然被更新缩短到 4 + // 此时顶点 1 (键值 4) 瞬间越级打破局面,逆袭变成本索引堆的绝对最小值! + int relax_dist_vertex_1 = 4; + ASSERT_INT_EQ(C_ERR_OK, c_IndexMinPQ_Change(&ipq, 1, &relax_dist_vertex_1)); + + c_size_t popped_index = 999; + + // 第一次 Pop 弹出的绝对必须是更新后全局距离最短的顶点 1,而不是原先的顶点 2! + ASSERT_INT_EQ(C_ERR_OK, c_IndexMinPQ_Pop(&ipq, &popped_index)); + ASSERT_INT_EQ(1, (int)popped_index); + ASSERT_TRUE(!c_IndexMinPQ_Contains(&ipq, 1)); // 弹出后状态注销 + + // 第二次 Pop 弹出的应该是次短的原顶点 2 + ASSERT_INT_EQ(C_ERR_OK, c_IndexMinPQ_Pop(&ipq, &popped_index)); + ASSERT_INT_EQ(2, (int)popped_index); + + // 最后弹出顶点 0 + ASSERT_INT_EQ(C_ERR_OK, c_IndexMinPQ_Pop(&ipq, &popped_index)); + ASSERT_INT_EQ(0, (int)popped_index); + + // 终极空堆校验 + ASSERT_INT_EQ(C_ERR_EMPTY, c_IndexMinPQ_Pop(&ipq, &popped_index)); + + c_IndexMinPQ_Destroy(&ipq); +} + +TEST_CASE(test_c_IndexMinPQ_ParamConstraints) { + c_IndexMinPQ_t local_ipq; + int dummy_val = 100; + + // 验证越界输入或空参数的前置状态码拦截线 + ASSERT_INT_EQ(C_ERR_PARAM, c_IndexMinPQ_Init(NULL, 10, sizeof(int), index_pq_compare_ints, NULL, NULL)); + + ASSERT_INT_EQ(C_ERR_OK, c_IndexMinPQ_Init(&local_ipq, 10, sizeof(int), index_pq_compare_ints, NULL, NULL)); + ASSERT_INT_EQ(C_ERR_PARAM, c_IndexMinPQ_Push(&local_ipq, 999, &dummy_val)); // 999 远超 max_elements(10) + + c_IndexMinPQ_Destroy(&local_ipq); +} + +// ========================================== +// 5. 主集成入口 +// ========================================== +int main(void) { + TEST_START(C_IndexMinPQ_Isolated_TestSuite); + RUN_TEST(test_c_IndexMinPQ_DecreaseKeyDijkstraFlow); + RUN_TEST(test_c_IndexMinPQ_ParamConstraints); + TEST_REPORT(); + return (g_test_registry.failed_count > 0 ? 1 : 0); +} \ No newline at end of file diff --git a/Sort/c_InsertionSort.c b/Sort/c_InsertionSort.c new file mode 100644 index 0000000..975dbcf --- /dev/null +++ b/Sort/c_InsertionSort.c @@ -0,0 +1,33 @@ +#include + +#include "c_Swap.h" + +void c_InsertionSort(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args){ + // 边界与防御性校验:元素少于 2 个或参数非法时无需排序 + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + + char* array_base = (char*)base; + + // 外层循环:从第二个元素开始(无符号安全递增) + for (c_size_t i = 1; i < num; i++) { + c_size_t j = i; + + // 内层循环:通过向前对比滑窗(j > 0)来规避无符号整数的下溢发生 + while (j > 0) { + char* current = array_base + (j * size); + char* previous = array_base + ((j - 1) * size); + + // 如果前一个元素大于当前元素,则说明顺序颠倒,需要向前执行数据挪动/交换 + if (cmp(previous, current, args) > 0) { + // 泛型就地内存块交换 (In-place Swap) + c_Swap(previous, current, size); + j--; + } else { + // 已经到达正确的插入位置,直接阻断内层滑窗 + break; + } + } + } +} diff --git a/Sort/c_InsertionSort.h b/Sort/c_InsertionSort.h new file mode 100644 index 0000000..ac1bdb7 --- /dev/null +++ b/Sort/c_InsertionSort.h @@ -0,0 +1,24 @@ +#ifndef INCLUDED_C_INSERTIONSORT_H +#define INCLUDED_C_INSERTIONSORT_H + + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 工业级泛型插入排序(稳定排序,完全规避无符号整数下溢) + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 比对回调函数指针 (不能为 NULL) + * @param args 比对时的参数 + */ +void c_InsertionSort(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + +#endif /*INCLUDED_C_INSERTIONSORT_H*/ diff --git a/Sort/c_InsertionSort.t.c b/Sort/c_InsertionSort.t.c new file mode 100644 index 0000000..8c07aed --- /dev/null +++ b/Sort/c_InsertionSort.t.c @@ -0,0 +1,102 @@ +#include "c_InsertionSort.h" +#include "c_Test.h" +#include +#include +#include "c_StringView.h" + +// ========================================== +// 测试用例伴生比对器 +// ========================================== +static int sort_compare_ints(const void* a, const void* b, void* args) { + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +static int sort_compare_string_views(const void* key, const void* elem, void* args) { + return c_StringView_Cmp((c_StringView_t*)key, (c_StringView_t*)elem); +} + +// ========================================== +// 核心测试用例 +// ========================================== + +TEST_CASE(test_c_InsertionSort_IntArray) { + // 准备一个乱序的 int 数组 + int arr[] = { 42, 12, 88, 5, 23, 12, 64 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + // 执行泛型插入排序 + c_InsertionSort(arr, num, sizeof(int), sort_compare_ints, 0); + + // 1. 验证排序后的数组必须呈绝对递增排列 + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] <= arr[i + 1]); + } + + // 2. 检查特定关键点的值,确保排序精度无误 + ASSERT_INT_EQ(5, arr[0]); // 最小值 + ASSERT_INT_EQ(12, arr[1]); // 相同元素的稳定性保留 + ASSERT_INT_EQ(12, arr[2]); + ASSERT_INT_EQ(88, arr[6]); // 最大值 +} + +TEST_CASE(test_c_InsertionSort_StringViewArray) { + // 3. 核心测试:对抽取自长文本、无 \0 结束符的 StringView 数组进行排序(深度边界防护) + const char* raw_pool = "banana,apple,date,cherry"; + + // 手工切片构建一个乱序的无拷贝视图数组 + c_StringView_t sv_arr[4]; + sv_arr[0] = c_StringView_FromParts(raw_pool, 6); // "banana" + sv_arr[1] = c_StringView_FromParts(raw_pool + 7, 5); // "apple" + sv_arr[2] = c_StringView_FromParts(raw_pool + 13, 4); // "date" + sv_arr[3] = c_StringView_FromParts(raw_pool + 18, 6); // "cherry" + + // 针对复杂结构体数组执行泛型插入排序 + c_InsertionSort(sv_arr, 4, sizeof(c_StringView_t), sort_compare_string_views, 0); + + // 4. 验证排序后的字典序状态 + // 正确顺序应当为: "apple" (size 5) -> "banana" (size 6) -> "cherry" (size 6) -> "date" (size 4) + ASSERT_INT_EQ(5, (int)sv_arr[0].size); + ASSERT_TRUE(memcmp(sv_arr[0].str, "apple", 5) == 0); + + ASSERT_INT_EQ(6, (int)sv_arr[1].size); + ASSERT_TRUE(memcmp(sv_arr[1].str, "banana", 6) == 0); + + ASSERT_INT_EQ(6, (int)sv_arr[2].size); + ASSERT_TRUE(memcmp(sv_arr[2].str, "cherry", 6) == 0); + + ASSERT_INT_EQ(4, (int)sv_arr[3].size); + ASSERT_TRUE(memcmp(sv_arr[3].str, "date", 4) == 0); +} + +TEST_CASE(test_c_InsertionSort_EdgeCases) { + int single_arr[] = { 99 }; + + // 1. 空参数及单元素安全防御,确保不发生无符号整数溢出和死循环 + c_InsertionSort(NULL, 1, sizeof(int), sort_compare_ints, 0); + c_InsertionSort(single_arr, 1, sizeof(int), sort_compare_ints, 0); + c_InsertionSort(single_arr, 0, sizeof(int), sort_compare_ints, 0); + c_InsertionSort(single_arr, 1, sizeof(int), NULL, 0); + + ASSERT_INT_EQ(99, single_arr[0]); +} + + + +int main(int argc, char** argv){ + + TEST_START(Unit Tests); + + // 运行普通无环境要求的用例 + RUN_TEST(test_c_InsertionSort_IntArray); + RUN_TEST(test_c_InsertionSort_StringViewArray); + RUN_TEST(test_c_InsertionSort_EdgeCases); + + // 打印最终统计报告 + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Sort/c_InsertionSortX.c b/Sort/c_InsertionSortX.c new file mode 100644 index 0000000..02cf366 --- /dev/null +++ b/Sort/c_InsertionSortX.c @@ -0,0 +1,75 @@ +#include +#include + +void c_InsertionSortX(void* array, c_size_t array_size, c_size_t item_size, c_SortCompare_t cmp, void* args) { + // 边界与防御性校验:元素少于 2 个或参数非法时无需排序 + if (!array || array_size < 2 || item_size == 0 || !cmp) { + return; + } + + char* array_base = (char*)array; + + // 栈上开辟 256 字节的局部单元素缓冲区,用于半交换中暂存临时变量 v + // 这避免了 malloc 带来的堆内存分配,同时保证线程安全 + char v_buf[item_size]; + + // ================================================================= + // 阶段 1:倒序滑窗,寻找绝对最小值推至首位(充当左边界哨兵) + // 为了彻底杜绝无符号整数自减至 0 导致的下溢(Underflow),此处采用向前平移法 + // ================================================================= + c_size_t exchanges = 0; + for (c_size_t i = array_size; i > 1; i--) { + const c_size_t current_idx = i - 1; + char* current = array_base + (current_idx * item_size); + char* previous = array_base + ((current_idx - 1) * item_size); + + // 如果后面的元素比前面小,执行泛型交换(复用您的 c_Swap 思想) + if (cmp(current, previous, args) < 0) { + c_Swap(current, previous, item_size); + exchanges++; + } + } + + // 统计学优化:如果第一轮扫描发现本来就是绝对递增的(exchanges == 0),说明完全有序,直接退出 + if (exchanges == 0) { + return; + } + + // ================================================================= + // 阶段 2:带有半交换(单向平移)的插入排序核心 + // 由于阶段 1 已经把全局最小值放到了 array_base[0],后续天然具备了边界防护 + // ================================================================= + for (c_size_t i = 2; i < array_size; i++) { + const char* item_i = array_base + (i * item_size); + + // 暂存当前需要插入的元素:v = a[i] + memcpy(v_buf, item_i, item_size); + + c_size_t j = i; + while (1) { + // 哨兵边界强御:因为 j 是无符号数,在此处增加前置保护 + // 实际上由于 array_base[0] 是最小值,理论上 cmp(v, previous) 永远不可能在 j==1 时成立 + // 但为了防御恶意外部比对器损坏,加入 j > 0 联锁,确保绝不发生无符号下溢 + if (j == 0) { + break; + } + + char* current = array_base + (j * item_size); + char* previous = array_base + ((j - 1) * item_size); + + // 比较暂存的 v 和前一个元素 previous。如果 v 更小,说明前面的元素需要后移 + if (cmp(v_buf, previous, args) < 0) { + // 半交换核心:单向覆盖 a[j] = a[j-1] + memcpy(current, previous, item_size); + j--; + } else { + // 顺序正确,找到了插入位置,直接退出平移滑窗 + break; + } + } + + // 将暂存的元素写入最终位置:a[j] = v + char* item_j = array_base + (j * item_size); + memcpy(item_j, v_buf, item_size); + } +} diff --git a/Sort/c_InsertionSortX.h b/Sort/c_InsertionSortX.h new file mode 100644 index 0000000..f599508 --- /dev/null +++ b/Sort/c_InsertionSortX.h @@ -0,0 +1,14 @@ +#ifndef INCLUDED_C_INSERTIONSORTX_H +#define INCLUDED_C_INSERTIONSORTX_H + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +void c_InsertionSortX(void* array, c_size_t array_size, c_size_t item_size, c_SortCompare_t cmp, void* args); + +#endif /*INCLUDED_C_INSERTIONSORTX_H*/ diff --git a/Sort/c_InsertionSortX.t.c b/Sort/c_InsertionSortX.t.c new file mode 100644 index 0000000..0b03c5b --- /dev/null +++ b/Sort/c_InsertionSortX.t.c @@ -0,0 +1,102 @@ +#include "c_InsertionSortX.h" +#include "c_Test.h" +#include +#include +#include "c_StringView.h" + +// ========================================== +// 测试用例伴生比对器 +// ========================================== +static int sort_compare_ints(const void* a, const void* b, void* args) { + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +static int sort_compare_string_views(const void* key, const void* elem, void* args) { + return c_StringView_Cmp((c_StringView_t*)key, (c_StringView_t*)elem); +} + +// ========================================== +// 核心测试用例 +// ========================================== + +TEST_CASE(test_c_InsertionSortX_IntArray) { + // 准备一个乱序的 int 数组 + int arr[] = { 42, 12, 88, 5, 23, 12, 64 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + // 执行泛型插入排序 + c_InsertionSortX(arr, num, sizeof(int), sort_compare_ints, 0); + + // 1. 验证排序后的数组必须呈绝对递增排列 + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] <= arr[i + 1]); + } + + // 2. 检查特定关键点的值,确保排序精度无误 + ASSERT_INT_EQ(5, arr[0]); // 最小值 + ASSERT_INT_EQ(12, arr[1]); // 相同元素的稳定性保留 + ASSERT_INT_EQ(12, arr[2]); + ASSERT_INT_EQ(88, arr[6]); // 最大值 +} + +TEST_CASE(test_c_InsertionSortX_StringViewArray) { + // 3. 核心测试:对抽取自长文本、无 \0 结束符的 StringView 数组进行排序(深度边界防护) + const char* raw_pool = "banana,apple,date,cherry"; + + // 手工切片构建一个乱序的无拷贝视图数组 + c_StringView_t sv_arr[4]; + sv_arr[0] = c_StringView_FromParts(raw_pool, 6); // "banana" + sv_arr[1] = c_StringView_FromParts(raw_pool + 7, 5); // "apple" + sv_arr[2] = c_StringView_FromParts(raw_pool + 13, 4); // "date" + sv_arr[3] = c_StringView_FromParts(raw_pool + 18, 6); // "cherry" + + // 针对复杂结构体数组执行泛型插入排序 + c_InsertionSortX(sv_arr, 4, sizeof(c_StringView_t), sort_compare_string_views, 0); + + // 4. 验证排序后的字典序状态 + // 正确顺序应当为: "apple" (size 5) -> "banana" (size 6) -> "cherry" (size 6) -> "date" (size 4) + ASSERT_INT_EQ(5, (int)sv_arr[0].size); + ASSERT_TRUE(memcmp(sv_arr[0].str, "apple", 5) == 0); + + ASSERT_INT_EQ(6, (int)sv_arr[1].size); + ASSERT_TRUE(memcmp(sv_arr[1].str, "banana", 6) == 0); + + ASSERT_INT_EQ(6, (int)sv_arr[2].size); + ASSERT_TRUE(memcmp(sv_arr[2].str, "cherry", 6) == 0); + + ASSERT_INT_EQ(4, (int)sv_arr[3].size); + ASSERT_TRUE(memcmp(sv_arr[3].str, "date", 4) == 0); +} + +TEST_CASE(test_c_InsertionSortX_EdgeCases) { + int single_arr[] = { 99 }; + + // 1. 空参数及单元素安全防御,确保不发生无符号整数溢出和死循环 + c_InsertionSortX(NULL, 1, sizeof(int), sort_compare_ints, 0); + c_InsertionSortX(single_arr, 1, sizeof(int), sort_compare_ints, 0); + c_InsertionSortX(single_arr, 0, sizeof(int), sort_compare_ints, 0); + c_InsertionSortX(single_arr, 1, sizeof(int), NULL, 0); + + ASSERT_INT_EQ(99, single_arr[0]); +} + + + +int main(int argc, char** argv){ + + TEST_START(Unit Tests); + + // 运行普通无环境要求的用例 + RUN_TEST(test_c_InsertionSortX_IntArray); + RUN_TEST(test_c_InsertionSortX_StringViewArray); + RUN_TEST(test_c_InsertionSortX_EdgeCases); + + // 打印最终统计报告 + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Sort/c_MaxPQ.c b/Sort/c_MaxPQ.c new file mode 100644 index 0000000..e1c752f --- /dev/null +++ b/Sort/c_MaxPQ.c @@ -0,0 +1,157 @@ +#include + +/** + * @brief 内部原子操作:泛型就地物理内存块互换 + */ +C_STATIC_FORCE_INLINE +void c_MaxPQ_InternalSwap(void* a, void* b, c_size_t size) { + if (a == b) return; + char* p1 = (char*)a; + char* p2 = (char*)b; + char temp_buf[256]; + c_size_t bytes_left = size; + while (bytes_left > 0) { + c_size_t chunk = (bytes_left < sizeof(temp_buf)) ? bytes_left : sizeof(temp_buf); + memcpy(temp_buf, p1, chunk); + memcpy(p1, p2, chunk); + memcpy(p2, temp_buf, chunk); + p1 += chunk; + p2 += chunk; + bytes_left -= chunk; + } +} + + +C_STATIC_FORCE_INLINE +void c_MaxPQ_SiftUp(c_MaxPQ_t* pq, c_size_t child) { + char* array = pq->data; + c_size_t es = pq->elem_size; + while (child > 0) { + c_size_t parent = (child - 1) >> 1; + char* p_child = array + (child * es); + char* p_parent = array + (parent * es); + if (pq->cmp(p_child, p_parent, pq->args) > 0) { + c_MaxPQ_InternalSwap(p_child, p_parent, es); + child = parent; + } else { + break; + } + } +} + + +static void c_MaxPQ_SiftDown(c_MaxPQ_t* pq, c_size_t parent) { + char* array = pq->data; + c_size_t es = pq->elem_size; + c_size_t num = pq->size; + while (1) { + c_size_t left_child = (parent << 1) + 1; + if (left_child >= num) break; + + c_size_t larger_child = left_child; + c_size_t right_child = left_child + 1; + if (right_child < num) { + char* p_left = array + (left_child * es); + char* p_right = array + (right_child * es); + if (pq->cmp(p_right, p_left, pq->args) > 0) { + larger_child = right_child; + } + } + char* p_parent = array + (parent * es); + char* p_target = array + (larger_child * es); + if (pq->cmp(p_target, p_parent, pq->args) > 0) { + c_MaxPQ_InternalSwap(p_parent, p_target, es); + parent = larger_child; + } else { + break; + } + } +} + +/** + * @brief 优先队列初始化开辟 (通过用户传入的分配器接管堆空间) + * + * @param allocator 用户自制的分配器指针(不能为 NULL) + */ +c_err_t c_MaxPQ_Init(c_MaxPQ_t* self, c_size_t initial_capacity, c_size_t elem_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator) { + if (!self || elem_size == 0 || !cmp ) return C_ERR_PARAM; + + c_size_t cap = (initial_capacity > 0) ? initial_capacity : 4; + self->allocator = (allocator!=NULL)?*allocator:c_DefaultAllocator; + + // 2. 利用分配器分配底层数据承载连续数组 + self->data = (char*)c_Allocator_Alloc(&self->allocator, cap * elem_size); + if (!self->data) { + return C_ERR_NOMEM; + } + + self->capacity = cap; + self->size = 0; + self->elem_size = elem_size; + self->cmp = cmp; + self->args = args; + + return C_ERR_OK; +} + + +/** + * @brief 向优先队列中压入一个元素(通过 c_Allocator_Realloc 自适应动态翻倍扩容) + */ +c_err_t c_MaxPQ_Push(c_MaxPQ_t* pq, const void* item) { + if (!pq || !item) return C_ERR_PARAM; + + if (pq->size >= pq->capacity) { + c_size_t old_cap = pq->capacity; + c_size_t new_cap = old_cap << 1; + + // 核心加固:改用分配器的托管 Realloc 代替原生 realloc + c_size_t old_bytes = old_cap * pq->elem_size; + c_size_t new_bytes = new_cap * pq->elem_size; + char* new_data = (char*)c_Allocator_Realloc(&pq->allocator, pq->data, old_bytes, new_bytes); + if (!new_data) return C_ERR_NOMEM; // 扩容失败保护 + + pq->data = new_data; + pq->capacity = new_cap; + } + + char* target_slot = pq->data + (pq->size * pq->elem_size); + memcpy(target_slot, item, pq->elem_size); + + c_MaxPQ_SiftUp(pq, pq->size); + pq->size++; + return C_ERR_OK; +} + +c_err_t c_MaxPQ_Pop(c_MaxPQ_t* pq, void* out_item) { + if (!pq || pq->size == 0) return C_ERR_PARAM; + char* array = pq->data; + c_size_t es = pq->elem_size; + if (out_item) { + memcpy(out_item, array, es); + } + pq->size--; + if (pq->size > 0) { + memcpy(array, array + (pq->size * es), es); + c_MaxPQ_SiftDown(pq, 0); + } + return C_ERR_OK; +} + +c_err_t c_MaxPQ_Peek(const c_MaxPQ_t* pq, void* out_item) { + if (!pq || pq->size == 0 || !out_item) return C_ERR_PARAM; + memcpy(out_item, pq->data, pq->elem_size); + return C_ERR_OK; +} + +/** + * @brief 优先队列彻底解构销毁 (通过当时绑定的分配器实例,原路进行内存逆向回收) + */ +void c_MaxPQ_Destroy(c_MaxPQ_t* pq) { + if (pq && pq->data) { + c_Allocator_Free(&pq->allocator, pq->data); + pq->data = NULL; + } +} + + diff --git a/Sort/c_MaxPQ.h b/Sort/c_MaxPQ.h new file mode 100644 index 0000000..f5ccf3e --- /dev/null +++ b/Sort/c_MaxPQ.h @@ -0,0 +1,58 @@ +#ifndef INCLUDED_C_MAXPQ_H +#define INCLUDED_C_MAXPQ_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + char* data; // 底层动态连续字节流载体 + c_size_t capacity; // 当前容器的最大可容纳插槽数 + c_size_t size; // 当前已存储的有效元素总数 + c_size_t elem_size; // 单个数据元素占用的字节大小 (sizeof) + c_SortCompare_t cmp; // 动态回调比对器 + void* args; // 自定义上下文参数指针 + c_Allocator_t allocator; +} c_MaxPQ_t; + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_MaxPQ_Init(c_MaxPQ_t* self, c_size_t initial_capacity, c_size_t elem_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator); + +c_err_t c_MaxPQ_Push(c_MaxPQ_t* pq, const void* item); + +c_err_t c_MaxPQ_Pop(c_MaxPQ_t* pq, void* out_item); + +c_err_t c_MaxPQ_Peek(const c_MaxPQ_t* pq, void* out_item); + +void c_MaxPQ_Destroy(c_MaxPQ_t* pq); + +C_STATIC_FORCE_INLINE +c_size_t c_MaxPQ_Size(c_MaxPQ_t* pq) { + if (!pq) return 0; + return pq->size; +} + +C_STATIC_FORCE_INLINE +bool c_MaxPQ_IsEmpty(c_MaxPQ_t* pq) { + if (!pq) return true; + return pq->size == 0; +} + +#endif /*INCLUDED_C_MAXPQ_H*/ diff --git a/Sort/c_MaxPQ.t.c b/Sort/c_MaxPQ.t.c new file mode 100644 index 0000000..6b17aa2 --- /dev/null +++ b/Sort/c_MaxPQ.t.c @@ -0,0 +1,72 @@ +#include "c_MaxPQ.h" +#include "c_Test.h" + +#include +#include + +// ========================================== +// 测试辅助:比对器 +// ========================================== +static int max_pq_compare_ints(const void* a, const void* b, void* args) { + (void)args; + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + + +TEST_CASE(test_c_MaxPQ_AllocatorFallbackFlow) { + c_MaxPQ_t fallback_pq; + + // 🌟 核心投毒测试:最后一个参数故意硬性传入 NULL 分配器! + // 修复后:内部应当优雅放行、完美降级对接 c_DefaultAllocator 并开辟数据成功 + c_err_t err = c_MaxPQ_Init(&fallback_pq, 2, sizeof(int), max_pq_compare_ints, NULL, NULL); + ASSERT_INT_EQ(C_ERR_OK, err); + + int data[] = { 19, 88, 45, 6, 73 }; + c_size_t num = sizeof(data) / sizeof(data[0]); + + for (c_size_t i = 0; i < num; i++) { + ASSERT_TRUE(c_MaxPQ_Push(&fallback_pq, &data[i])==C_ERR_OK); + } + ASSERT_INT_EQ((int)num, (int)fallback_pq.size); + + int previous_extracted = 999999; + int current_extracted = 0; + + // 逐级提取验证大顶堆单调性 + while (fallback_pq.size > 0) { + ASSERT_TRUE(c_MaxPQ_Peek(&fallback_pq, ¤t_extracted)==C_ERR_OK); + + int pop_verify = 0; + ASSERT_TRUE(c_MaxPQ_Pop(&fallback_pq, &pop_verify)==C_ERR_OK); + ASSERT_INT_EQ(current_extracted, pop_verify); + + ASSERT_TRUE(previous_extracted >= current_extracted); + previous_extracted = current_extracted; + } + + ASSERT_INT_EQ(0, (int)fallback_pq.size); + + // 彻底解构反初始化 + c_MaxPQ_Destroy(&fallback_pq); +} + +TEST_CASE(test_c_MaxPQ_ParamConstraints) { + c_MaxPQ_t local_pq; + // 验证关键参数如 self 或 elem_size 缺失时的拦截线依然坚固 + ASSERT_INT_EQ(C_ERR_PARAM, c_MaxPQ_Init(NULL, 4, sizeof(int), max_pq_compare_ints, NULL, NULL)); + ASSERT_INT_EQ(C_ERR_PARAM, c_MaxPQ_Init(&local_pq, 4, 0, max_pq_compare_ints, NULL, NULL)); + ASSERT_INT_EQ(C_ERR_PARAM, c_MaxPQ_Init(&local_pq, 4, sizeof(int), NULL, NULL, NULL)); +} + + +int main(int argc, char** argv){ + TEST_START(C_MaxPQ_AllocatorFallback_TestSuite); + RUN_TEST(test_c_MaxPQ_AllocatorFallbackFlow); + RUN_TEST(test_c_MaxPQ_ParamConstraints); + TEST_REPORT(); + return (g_test_registry.failed_count > 0 ? 1 : 0); +} diff --git a/Sort/c_MergeSort.c b/Sort/c_MergeSort.c new file mode 100644 index 0000000..3fc6eef --- /dev/null +++ b/Sort/c_MergeSort.c @@ -0,0 +1,330 @@ +#include +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/** + * @brief 自顶向下归并排序核心合并内部函数(完全规避 c_size_t 下溢) + * + * 采用左闭右开区间映射,或精准的边界条件限制,确保所有的索引自加和自减都不触碰回绕风险。 + */ +static void c_MergeInternal(char* array_base, c_size_t low, c_size_t mid, c_size_t high, + char* aux_buf, c_size_t size, c_SortCompare_t cmp, void* args) +{ + // 将待合并的两个有序子区间 [low, mid] 和 [mid + 1, high] 的数据完整复制到辅助缓冲区 + // 拷贝的总数据量为从 low 到 high(含)的闭区间元素总和 + c_size_t total_elements = high - low + 1; + memcpy(aux_buf + (low * size), array_base + (low * size), total_elements * size); + + c_size_t i = low; // 左半边子区间的游标起始点 + c_size_t j = mid + 1; // 右半边子区间的游标起始点 + + // 双指针滑窗合并回原数组 + for (c_size_t k = low; k <= high; k++) { + if (i > mid) { + // 左半边已经全部用尽,直接无脑拷贝右半边剩余元素 + memcpy(array_base + (k * size), aux_buf + (j * size), size); + j++; + } + else if (j > high) { + // 右半边已经全部用尽,直接无脑拷贝左半边剩余元素 + memcpy(array_base + (k * size), aux_buf + (i * size), size); + i++; + } + else { + // 核心双指针比对 + char* elem_i = aux_buf + (i * size); + char* elem_j = aux_buf + (j * size); + + // 使用 <= 0 判定。如果左边的值小于或等于右边,优先选用左边。 + // 这正是归并排序能够完美保留相同元素原始先后相对位置(保持 Stable)的关键所在! + if (cmp(elem_i, elem_j, args) <= 0) { + memcpy(array_base + (k * size), elem_i, size); + i++; + } else { + memcpy(array_base + (k * size), elem_j, size); + j++; + } + } + } +} + +/** + * @brief 递归驱动函数 + */ +static void c_MergeSortRecursive(char* array_base, c_size_t low, c_size_t high, + char* aux_buf, c_size_t size, c_SortCompare_t cmp, void* args) +{ + // 终止递归条件:当区间收缩到只包含 1 个元素时自然返回 + if (low >= high) { + return; + } + + // 防整数溢出的中间索引计算法 + c_size_t mid = low + ((high - low) >> 1); + + // 分治法阶段 1:左半区递归 + c_MergeSortRecursive(array_base, low, mid, aux_buf, size, cmp, args); + + // 分治法阶段 2:右半区递归(由于 mid + 1 永远大于 mid,不可能在此处发生无符号数反向绕回) + c_MergeSortRecursive(array_base, mid + 1, high, aux_buf, size, cmp, args); + + // 分治法阶段 3:就地执行双向有序序列的高效合并 + c_MergeInternal(array_base, low, mid, high, aux_buf, size, cmp, args); +} + +/** + * @brief 工业级泛型自顶向下归并排序入口 + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_MergeSort(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + + char* array_base = (char*)base; + + // 归并排序无法实现真正的泛型就地常数级存储,必须在堆或外部开辟一块大小为 N 的临时辅助阴影缓冲区。 + // 这里采用标准 malloc 动态分配。如果在嵌入式环境中,可以将其修改为用户传入或静态内存池分配。 + char* aux_buf = (char*)malloc(num * size); + if (!aux_buf) { + return; // 分配失败防御 + } + + // 触发递归控制流:闭区间范围为 0 到 num - 1 + // 由于 num >= 2,num - 1 经过了前置拦截,绝对不会引发 0 - 1 的无符号数最大值回绕灾难 + c_MergeSortRecursive(array_base, 0, num - 1, aux_buf, size, cmp, args); + + // 严密清理辅助缓冲区,斩断内存泄漏 + free(aux_buf); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 内部辅助内联函数:获取两个无符号数中的较小值(防止宏重复计算) + */ +C_STATIC_FORCE_INLINE +c_size_t c_InternalMin(c_size_t a, c_size_t b) { + return (a < b) ? a : b; +} + +/** + * @brief 归并排序核心合并内部函数(复用双指针滑窗逻辑) + */ +C_STATIC_FORCE_INLINE +void c_BU_MergeInternal(char* array_base, c_size_t low, c_size_t mid, c_size_t high, + char* aux_buf, c_size_t size, c_SortCompare_t cmp, void* args){ + c_size_t total_elements = high - low + 1; + memcpy(aux_buf + (low * size), array_base + (low * size), total_elements * size); + + c_size_t i = low; + c_size_t j = mid + 1; + + for (c_size_t k = low; k <= high; k++) { + if (i > mid) { + memcpy(array_base + (k * size), aux_buf + (j * size), size); + j++; + } + else if (j > high) { + memcpy(array_base + (k * size), aux_buf + (i * size), size); + i++; + } + else { + char* elem_i = aux_buf + (i * size); + char* elem_j = aux_buf + (j * size); + + if (cmp(elem_i, elem_j, args) <= 0) { + memcpy(array_base + (k * size), elem_i, size); + i++; + } else { + memcpy(array_base + (k * size), elem_j, size); + j++; + } + } + } +} + +/** + * @brief 工业级泛型自底向上归并排序(纯迭代非递归,完全规避栈溢出风险) + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_MergeSort_BottomUp(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + + char* array_base = (char*)base; + + // 分配 N 大小的临时阴影缓冲区 + char* aux_buf = (char*)malloc(num * size); + if (!aux_buf) { + return; + } + + // 外层循环:控制当前合并的子序列步长 sz (以 1, 2, 4, 8, ... 指数级翻倍递增) + // 限制条件:sz < num,由于通过前置校验限制 num >= 2,因此循环至少执行一轮 + for (c_size_t sz = 1; sz < num; sz = sz + sz) { + + // 内层循环:按当前子序列步长,成对进行两个子区间的有序合并 + // 控制条件:low < num - sz。通过逆向加法或前置限制,确保 (num - sz) 不会发生无符号下溢 + c_size_t limit = num - sz; + for (c_size_t low = 0; low < limit; low += sz + sz) { + + // 计算当前左半边有序区间的终点 mid + c_size_t mid = low + sz - 1; + + // 计算当前右半边有序区间的终点 high。 + // 核心安全优化:若右半区不满足整步长大小,直接利用 c_InternalMin 将其安全截断到真实的最后一位 (num - 1) + // 由于通过前置校验,num - 1 绝对不会产生无符号回绕 + c_size_t high = c_InternalMin(low + sz + sz - 1, num - 1); + + // 就地执行数据合并 + c_BU_MergeInternal(array_base, low, mid, high, aux_buf, size, cmp, args); + } + } + + // 清理堆空间 + free(aux_buf); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +// 设置小数组自适应截断阈值,通常在 12 - 16 之间性能达到极值 +#define COMPONENT_MERGE_CUTOFF 15 + +/** + * @brief 局部内联优化的泛型插入排序(供归并排序降级使用,规避下溢风险) + */ +C_STATIC_FORCE_INLINE +void c_Internal_InsertionSort(char* base, c_size_t low, c_size_t high, c_size_t size, c_SortCompare_t cmp, void* args) { + for (c_size_t i = low + 1; i <= high; i++) { + c_size_t j = i; + char* item_i = base + (i * size); + + // 栈上开辟单元素缓冲区,实现半交换单向平移优化 + char v_buf[256]; + memcpy(v_buf, item_i, size); + + while (j > low) { + char* current = base + (j * size); + char* previous = base + ((j - 1) * size); + + if (cmp(v_buf, previous, args) < 0) { + memcpy(current, previous, size); + j--; + } else { + break; + } + } + memcpy(base + (j * size), v_buf, size); + } +} + +/** + * @brief 核心合并逻辑:直接从 src 合并到 dst,消除往返拷贝开销 + */ +C_STATIC_FORCE_INLINE +void c_OptMergeInternal(char* src, char* dst, c_size_t low, c_size_t mid, c_size_t high, + c_size_t size, c_SortCompare_t cmp, void* args) +{ + c_size_t i = low; + c_size_t j = mid + 1; + + for (c_size_t k = low; k <= high; k++) { + if (i > mid) { + memcpy(dst + (k * size), src + (j * size), size); + j++; + } + else if (j > high) { + memcpy(dst + (k * size), src + (i * size), size); + i++; + } + else { + char* elem_i = src + (i * size); + char* elem_j = src + (j * size); + + if (cmp(elem_i, elem_j, args) <= 0) { + memcpy(dst + (k * size), elem_i, size); + i++; + } else { + memcpy(dst + (k * size), elem_j, size); + j++; + } + } + } +} + +/** + * @brief 优化版交替控制流递归核心 + * + * 注意:这里的 src 和 dst 在每一层递归中都会发生调换,使得合并直接把数据推向正确的上一层载体。 + */ +static void c_OptMergeRecursive(char* src, char* dst, c_size_t low, c_size_t high, + c_size_t size, c_SortCompare_t cmp, void* args) +{ + // 优化点 1:小数组截断,当区间长度小于等于阈值时直接调用插入排序 + if (high - low < COMPONENT_MERGE_CUTOFF) { + c_Internal_InsertionSort(dst, low, high, size, cmp, args); + return; + } + + c_size_t mid = low + ((high - low) >> 1); + + // 优化点 3:通过互换 src 与 dst 指针角色,使得子层计算出来的有序序列直接存储在 src 中 + c_OptMergeRecursive(dst, src, low, mid, size, cmp, args); + c_OptMergeRecursive(dst, src, mid + 1, high, size, cmp, args); + + // 优化点 2:有序性前置检查。 + // 如果左半区间的最大元素已经小于或等于右半区的最小元素,说明当前整体已经完全有序 + // 此时只需直接从 src 拷贝到 dst,完全免去滑窗合并在指令和缓存上的无效消耗 + char* left_max = src + (mid * size); + char* right_min = src + ((mid + 1) * size); + if (cmp(left_max, right_min, args) <= 0) { + memcpy(dst + (low * size), src + (low * size), (high - low + 1) * size); + return; + } + + // 正常合并:将两个子序列安全推入 dst 目标 + c_OptMergeInternal(src, dst, low, mid, high, size, cmp, args); +} + +/** + * @brief 工业级泛型优化版归并排序(哨兵阻断、小数组截断、零往返拷贝) + */ +void c_MergeSort_Optimized(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + + char* array_base = (char*)base; + + // 分配 N 大小的辅助阴影缓冲区 + char* aux_buf = (char*)malloc(num * size); + if (!aux_buf) { + return; + } + + // 初始化时将原始数据完整同步到辅助缓冲区中,以支持首次角色分调 + memcpy(aux_buf, array_base, num * size); + + // 触发安全递归:此时传入的两个缓冲区分别为 aux_buf 和 array_base。 + // 最终排好序的元素会完美收拢落回到原数组 array_base 中。 + c_OptMergeRecursive(aux_buf, array_base, 0, num - 1, size, cmp, args); + + free(aux_buf); +} diff --git a/Sort/c_MergeSort.h b/Sort/c_MergeSort.h new file mode 100644 index 0000000..6a018f5 --- /dev/null +++ b/Sort/c_MergeSort.h @@ -0,0 +1,47 @@ +#ifndef INCLUDED_C_MERGESORT_H +#define INCLUDED_C_MERGESORT_H + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/** + * @brief 工业级泛型自顶向下归并排序入口 + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_MergeSort(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + +/** + * @brief 工业级泛型自底向上归并排序(纯迭代非递归,完全规避栈溢出风险) + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_MergeSort_BottomUp(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + +/** + * @brief 工业级泛型优化版归并排序(哨兵阻断、小数组截断、零往返拷贝) + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_MergeSort_Optimized(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + + +#endif /*INCLUDED_C_MERGESORT_H*/ diff --git a/Sort/c_MergeSort.t.c b/Sort/c_MergeSort.t.c new file mode 100644 index 0000000..e7f4ad9 --- /dev/null +++ b/Sort/c_MergeSort.t.c @@ -0,0 +1,243 @@ +#include "c_MergeSort.h" +#include "c_Test.h" + +// ========================================== +// 1. 测试用例伴生:通用比对器与稳定性校验结构体 +// ========================================== +static int sort_compare_ints_with_args(const void* a, const void* b, void* args) { + (void)args; + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +typedef struct { + int key; // 核心排序键值 + int origin_order; // 记录元素的原始相对生成次序(用于硬性稳定性验证) +} StableElement_t; + +// 仅根据 key 键值进行比对的复合比较器 +static int sort_compare_stable_keys(const void* a, const void* b, void* args) { + (void)args; + const StableElement_t* e1 = (const StableElement_t*)a; + const StableElement_t* e2 = (const StableElement_t*)b; + if (e1->key < e2->key) return -1; + if (e1->key > e2->key) return 1; + return 0; +} + +typedef struct { + int id; + int weight; +} Package_t; + +static int sort_compare_packages(const void* a, const void* b, void* args) { + (void)args; + const Package_t* p1 = (const Package_t*)a; + // 【已修正】:彻底将原先笔误残留下来的 Task_t 符号更正为合法的 Package_t + const Package_t* p2 = (const Package_t*)b; + if (p1->weight < p2->weight) return -1; + if (p1->weight > p2->weight) return 1; + return 0; +} + +// ========================================== +// 2. 自动化测试用例集 +// ========================================== +TEST_CASE(test_c_MergeSort_IntArray) { + int arr[] = { 45, 12, 85, 32, 85, 5, 67, 19, 90, 32 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_MergeSort(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + // 1. 验证全区间是否严格单调不减排列 + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] <= arr[i + 1]); + } + ASSERT_INT_EQ(5, arr[0]); // 检查最小值 + ASSERT_INT_EQ(90, arr[num - 1]); // 检查最大值 +} + +TEST_CASE(test_c_MergeSort_StabilityCheck) { + // 2. 核心压测:验证归并排序的“稳定性(Stable)”。 + // 构建多组拥有完全相同键值(key = 40)、但原始序号不同的无序序列 + StableElement_t items[] = { + { 40, 1 }, // 第一个40 + { 10, 0 }, + { 40, 2 }, // 第二个40 + { 25, 0 }, + { 40, 3 } // 第三个40 + }; + c_size_t num = sizeof(items) / sizeof(items[0]); + + c_MergeSort(items, num, sizeof(StableElement_t), sort_compare_stable_keys, NULL); + + // 验证键值递增 + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(items[i].key <= items[i + 1].key); + } + + // 核心断言:排序后,key同样为 40 的三个元素,其原始顺序必须依然是 1 -> 2 -> 3 + // 修正了上一轮由于漏写数组索引 [idx] 导致的引用错误 + ASSERT_INT_EQ(40, items[2].key); ASSERT_INT_EQ(1, items[2].origin_order); + ASSERT_INT_EQ(40, items[3].key); ASSERT_INT_EQ(2, items[3].origin_order); + ASSERT_INT_EQ(40, items[4].key); ASSERT_INT_EQ(3, items[4].origin_order); +} + +TEST_CASE(test_c_MergeSort_EdgesAndNull) { + int ordered_arr[] = { 1, 2, 3, 4 }; + c_size_t num = sizeof(ordered_arr) / sizeof(ordered_arr[0]); + + // 3. 完全升序数组的无阻碍测试 + c_MergeSort(ordered_arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(1, ordered_arr[0]); + ASSERT_INT_EQ(4, ordered_arr[num - 1]); + + // 4. 空参数以及单元素数组的拦截压测,彻底杜绝 num-1 引发的无符号下溢崩溃 + int single_arr[] = { 555 }; + c_MergeSort(single_arr, 1, sizeof(int), sort_compare_ints_with_args, NULL); + c_MergeSort(NULL, 0, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(555, single_arr[0]); +} + +TEST_CASE(test_c_MergeSort_BottomUp_IntArray) { + // 故意使用一个长度不等于 2 的幂次方的数组 (长度为 11),强迫触发高频边界不规则裁切路径 + int arr[] = { 64, 34, 25, 12, 22, 11, 90, 45, 12, 88, 3 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_MergeSort_BottomUp(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + // 验证区间整体是否呈现绝对严格升序排列 + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] <= arr[i + 1]); + } + ASSERT_INT_EQ(3, arr[0]); // 检查最小值 + ASSERT_INT_EQ(90, arr[num - 1]); // 检查最大值 +} + +TEST_CASE(test_c_MergeSort_BottomUp_StabilityCheck) { + // 验证非递归自底向上模型的排序“稳定性”。 + StableElement_t items[] = { + { 50, 1 }, // 第一个 50 + { 20, 0 }, + { 50, 2 }, // 第二个 50 + { 10, 0 }, + { 50, 3 } // 第三个 50 + }; + c_size_t num = sizeof(items) / sizeof(items[0]); + + c_MergeSort_BottomUp(items, num, sizeof(StableElement_t), sort_compare_stable_keys, NULL); + + // 验证键值递增 + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(items[i].key <= items[i + 1].key); + } + + // 核心断言:排序后,拥有相同键值的项,其内部存储顺序必须依然是 1 -> 2 -> 3 + ASSERT_INT_EQ(50, items[2].key); ASSERT_INT_EQ(1, items[2].origin_order); + ASSERT_INT_EQ(50, items[3].key); ASSERT_INT_EQ(2, items[3].origin_order); + ASSERT_INT_EQ(50, items[4].key); ASSERT_INT_EQ(3, items[4].origin_order); +} + +TEST_CASE(test_c_MergeSort_BottomUp_EdgesAndNull) { + int ordered_arr[] = { 10, 20, 30 }; + c_size_t num = sizeof(ordered_arr) / sizeof(ordered_arr[0]); + + // 完全有序状态测试 + c_MergeSort_BottomUp(ordered_arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(10, ordered_arr[0]); + ASSERT_INT_EQ(30, ordered_arr[num - 1]); + + // 极端输入安全防御测试,防止无符号整数最大值溢出 + int single_arr[] = { 999 }; + c_MergeSort_BottomUp(single_arr, 1, sizeof(int), sort_compare_ints_with_args, NULL); + c_MergeSort_BottomUp(NULL, 0, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(999, single_arr[0]); +} + +TEST_CASE(test_c_MergeSort_Optimized_LargeAndCutoff) { + // 构建一个超越截断阈值(> 15)的长乱序整型数组 + int arr[] = { 89, 45, 68, 90, 23, 12, 57, 46, 35, 78, 24, 11, 5, 99, 41, 102, 33, 1 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_MergeSort_Optimized(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + // 1. 验证大区间在历经自适应截断重组后呈现绝对升序状态 + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] <= arr[i + 1]); + } + ASSERT_INT_EQ(1, arr[0]); + ASSERT_INT_EQ(102, arr[num - 1]); +} + +TEST_CASE(test_c_MergeSort_Optimized_PreSortedCheck) { + // 2. 压测完全有序的输入,强制激活极其高效的有序性前置快速阻断路径 + int ordered_arr[] = { 10, 20, 30, 40, 50, 60, 70, 80, 90 }; + c_size_t num = sizeof(ordered_arr) / sizeof(ordered_arr[0]); + + c_MergeSort_Optimized(ordered_arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(ordered_arr[i] < ordered_arr[i + 1]); + } + ASSERT_INT_EQ(10, ordered_arr[0]); + ASSERT_INT_EQ(90, ordered_arr[num - 1]); +} + +TEST_CASE(test_c_MergeSort_Optimized_Stability) { + // 3. 验证交替角色互换后的稳定排序特性 + Package_t pkgs[] = { + { 101, 50 }, // 相同重量 50 + { 102, 20 }, + { 103, 50 }, // 相同重量 50 + { 104, 10 } + }; + c_size_t num = sizeof(pkgs) / sizeof(pkgs[0]); + + c_MergeSort_Optimized(pkgs, num, sizeof(Package_t), sort_compare_packages, NULL); + + // 【已修正】:彻底补上数组定位下标 [idx],解决前一轮缺失索引引起的编译错误 + ASSERT_INT_EQ(10, pkgs[0].weight); + ASSERT_INT_EQ(20, pkgs[1].weight); + + // 核心断言:相同键值的项(weight = 50),其原始相对先后生成顺序(id)绝对不能被调换 + ASSERT_INT_EQ(50, pkgs[2].weight); ASSERT_INT_EQ(101, pkgs[2].id); + ASSERT_INT_EQ(50, pkgs[3].weight); ASSERT_INT_EQ(103, pkgs[3].id); +} + +TEST_CASE(test_c_MergeSort_Optimized_Edges) { + int single_arr[] = { 42 }; + + // 4. 空参数以及单元素安全边界防御 + c_MergeSort_Optimized(single_arr, 1, sizeof(int), sort_compare_ints_with_args, NULL); + c_MergeSort_Optimized(NULL, 0, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(42, single_arr[0]); +} + +// ========================================== +// 3. 独立测试运行入口 +// ========================================== +int main(void) { + TEST_START(C_TopDownMergeSort_Isolated_TestSuite); + + // 运行专项排序测试集 + RUN_TEST(test_c_MergeSort_IntArray); + RUN_TEST(test_c_MergeSort_StabilityCheck); + RUN_TEST(test_c_MergeSort_EdgesAndNull); + + RUN_TEST(test_c_MergeSort_BottomUp_IntArray); + RUN_TEST(test_c_MergeSort_BottomUp_StabilityCheck); + RUN_TEST(test_c_MergeSort_BottomUp_EdgesAndNull); + + RUN_TEST(test_c_MergeSort_Optimized_LargeAndCutoff); + RUN_TEST(test_c_MergeSort_Optimized_PreSortedCheck); + RUN_TEST(test_c_MergeSort_Optimized_Stability); + RUN_TEST(test_c_MergeSort_Optimized_Edges); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} + diff --git a/Sort/c_MinPQ.c b/Sort/c_MinPQ.c new file mode 100644 index 0000000..9903d99 --- /dev/null +++ b/Sort/c_MinPQ.c @@ -0,0 +1,199 @@ +#include + +/** + * @brief 内部原子操作:泛型就地物理内存块高效率互换 + */ +C_STATIC_FORCE_INLINE +void c_MinPQ_InternalSwap(void* a, void* b, c_size_t size) { + if (a == b) return; + char* p1 = (char*)a; + char* p2 = (char*)b; + char temp_buf[256]; + c_size_t bytes_left = size; + while (bytes_left > 0) { + c_size_t chunk = (bytes_left < sizeof(temp_buf)) ? bytes_left : sizeof(temp_buf); + memcpy(temp_buf, p1, chunk); + memcpy(p1, p2, chunk); + memcpy(p2, temp_buf, chunk); + p1 += chunk; + p2 += chunk; + bytes_left -= chunk; + } +} + +/** + * @brief 核心堆序自适应上浮调整(Sift Up) + */ +static void c_MinPQ_SiftUp(c_MinPQ_t* pq, c_size_t child) { + char* array = pq->data; + c_size_t es = pq->elem_size; + + // 严格限制 child > 0 边界,斩断减法下溢 + while (child > 0) { + c_size_t parent = (child - 1) >> 1; + + char* p_child = array + (child * es); + char* p_parent = array + (parent * es); + + // 🌟【最小堆核心调整】:若子节点的值“小于”父节点的值,向上置换顶推 + if (pq->cmp(p_child, p_parent, pq->args) < 0) { + c_MinPQ_InternalSwap(p_child, p_parent, es); + child = parent; + } else { + break; + } + } +} + + +/** + * @brief 核心堆序自适应下沉调整(Sift Down) + */ +static void c_MinPQ_SiftDown(c_MinPQ_t* pq, c_size_t parent) { + char* array = pq->data; + c_size_t es = pq->elem_size; + c_size_t num = pq->size; + + while (1) { + c_size_t left_child = (parent << 1) + 1; + + if (left_child >= num) { + break; // 越界前置拦截,防止后面加法导致的整数溢出 + } + + c_size_t smaller_child = left_child; + c_size_t right_child = left_child + 1; + + if (right_child < num) { + char* p_left = array + (left_child * es); + char* p_right = array + (right_child * es); + // 🌟【最小堆核心调整】:若右子节点的值比左子节点还“小”,切换最小目标到右半区 + if (pq->cmp(p_right, p_left, pq->args) < 0) { + smaller_child = right_child; + } + } + + char* p_parent = array + (parent * es); + char* p_target = array + (smaller_child * es); + + // 🌟【最小堆核心调整】:若最小子节点依然“小于”当前的父节点,下沉对调 + if (pq->cmp(p_target, p_parent, pq->args) < 0) { + c_MinPQ_InternalSwap(p_parent, p_target, es); + parent = smaller_child; + } else { + break; + } + } +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/** + * @brief 就地初始化最小堆优先队列 + */ +c_err_t c_MinPQ_Init(c_MinPQ_t* self, c_size_t initial_capacity, c_size_t elem_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator) { + if (!self || elem_size == 0 || !cmp) { + return C_ERR_PARAM; + } + + c_size_t cap = (initial_capacity > 0) ? initial_capacity : 4; + + // 自适应缺省降级安全播种 + if (allocator) { + self->allocator = *allocator; + } else { + self->allocator = c_DefaultAllocator; + } + + self->data = (char*)c_Allocator_Alloc(&self->allocator, cap * elem_size); + if (!self->data) { + return C_ERR_NOMEM; + } + + self->capacity = cap; + self->size = 0; + self->elem_size = elem_size; + self->cmp = cmp; + self->args = args; + + return C_ERR_OK; +} + + +/** + * @brief 向最小堆中压入一个新元素(自适应双倍托管 Realloc 动态扩容) + */ +c_err_t c_MinPQ_Push(c_MinPQ_t* pq, const void* item) { + if (!pq || !item) return C_ERR_PARAM; + + if (pq->size >= pq->capacity) { + c_size_t old_cap = pq->capacity; + c_size_t new_cap = old_cap << 1; + + c_size_t old_bytes = old_cap * pq->elem_size; + c_size_t new_bytes = new_cap * pq->elem_size; + + char* new_data = (char*)c_Allocator_Realloc(&pq->allocator, pq->data, old_bytes, new_bytes); + if (!new_data) return C_ERR_NOMEM; + + pq->data = new_data; + pq->capacity = new_cap; + } + + char* target_slot = pq->data + (pq->size * pq->elem_size); + memcpy(target_slot, item, pq->elem_size); + + c_MinPQ_SiftUp(pq, pq->size); + pq->size++; + return C_ERR_OK; +} + +/** + * @brief 弹出并捕获当前最小堆中的绝对最小值元素(堆顶) + */ +c_err_t c_MinPQ_Pop(c_MinPQ_t* pq, void* out_item) { + if (!pq ) return C_ERR_PARAM; + if (pq->size == 0) { + return C_ERR_EMPTY; + } + + char* array = pq->data; + c_size_t es = pq->elem_size; + + if (out_item) { + memcpy(out_item, array, es); + } + + pq->size--; + + if (pq->size > 0) { + memcpy(array, array + (pq->size * es), es); + c_MinPQ_SiftDown(pq, 0); + } + + return C_ERR_OK; +} + +/** + * @brief 观察读取但不弹出最小堆堆顶绝对最小值元素 + */ +c_err_t c_MinPQ_Peek(const c_MinPQ_t* pq, void* out_item) { + if (!pq || !out_item) return C_ERR_PARAM; + if (pq->size == 0) return C_ERR_EMPTY; + memcpy(out_item, pq->data, pq->elem_size); + return C_ERR_OK; +} + +/** + * @brief 资源回收反初始化 + */ +void c_MinPQ_Destroy(c_MinPQ_t* pq) { + if (pq && pq->data) { + c_Allocator_Free(&pq->allocator, pq->data); + pq->data = NULL; + pq->size = 0; + pq->capacity = 0; + } +} \ No newline at end of file diff --git a/Sort/c_MinPQ.h b/Sort/c_MinPQ.h new file mode 100644 index 0000000..1abe225 --- /dev/null +++ b/Sort/c_MinPQ.h @@ -0,0 +1,50 @@ +#ifndef INCLUDED_C_MINPQ_H +#define INCLUDED_C_MINPQ_H + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + char* data; // 底层动态连续字节流载体 + c_size_t capacity; // 当前容器的最大可容纳插槽数 + c_size_t size; // 当前已存储的有效元素总数 + c_size_t elem_size; // 单个数据元素占用的字节大小 (sizeof) + c_SortCompare_t cmp; // 动态回调比对器 + void* args; // 自定义上下文参数指针 + c_Allocator_t allocator; // 完全继承非指针内联组合定义与默认缺省机制 +} c_MinPQ_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_MinPQ_Init(c_MinPQ_t* self, c_size_t initial_capacity, c_size_t elem_size, c_SortCompare_t cmp, void* args, c_Allocator_t* allocator); + +c_err_t c_MinPQ_Push(c_MinPQ_t* pq, const void* item); + +c_err_t c_MinPQ_Pop(c_MinPQ_t* pq, void* out_item); + +c_err_t c_MinPQ_Peek(const c_MinPQ_t* pq, void* out_item); + +void c_MinPQ_Destroy(c_MinPQ_t* pq); + +C_STATIC_FORCE_INLINE +c_size_t c_MinPQ_Size(c_MinPQ_t* pq) { + if (!pq) return 0; + return pq->size; +} + +C_STATIC_FORCE_INLINE +bool c_MinPQ_IsEmpty(c_MinPQ_t* pq) { + if (!pq) return true; + return pq->size == 0; +} + +#endif /*INCLUDED_C_MINPQ_H*/ diff --git a/Sort/c_MinPQ.t.c b/Sort/c_MinPQ.t.c new file mode 100644 index 0000000..2d96b53 --- /dev/null +++ b/Sort/c_MinPQ.t.c @@ -0,0 +1,80 @@ +#include "c_MinPQ.h" +#include "c_Test.h" + +#include +#include + +static int min_pq_compare_ints(const void* a, const void* b, void* args) { + (void)args; + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +TEST_CASE(test_c_MinPQ_FullChainStatusCodes) { + c_MinPQ_t pq; + // 就地初始化 + c_err_t err = c_MinPQ_Init(&pq, 2, sizeof(int), min_pq_compare_ints, NULL, &c_DefaultAllocator); + ASSERT_INT_EQ(C_ERR_OK, err); + + // 1. 验证初次空堆下的 Pop 和 Peek 是否完美返回 C_ERR_EMPTY + int dummy = 0; + ASSERT_INT_EQ(C_ERR_EMPTY, c_MinPQ_Pop(&pq, &dummy)); + ASSERT_INT_EQ(C_ERR_EMPTY, c_MinPQ_Peek(&pq, &dummy)); + + int data[] = { 45, 12, 85, 4, 67 }; + c_size_t num = sizeof(data) / sizeof(data[0]); + + for (c_size_t i = 0; i < num; i++) { + // 2. 验证规范后的 Push 返回状态码 + ASSERT_INT_EQ(C_ERR_OK, c_MinPQ_Push(&pq, &data[i])); + } + ASSERT_INT_EQ((int)num, (int)pq.size); + + int previous_extracted = -999999; + int current_extracted = 0; + + // 3. 验证连续循环弹出状态链 + while (pq.size > 0) { + ASSERT_INT_EQ(C_ERR_OK, c_MinPQ_Peek(&pq, ¤t_extracted)); + + int pop_verify = 0; + ASSERT_INT_EQ(C_ERR_OK, c_MinPQ_Pop(&pq, &pop_verify)); + ASSERT_INT_EQ(current_extracted, pop_verify); + + // 验证最小堆单调递增性 + ASSERT_TRUE(previous_extracted <= current_extracted); + previous_extracted = current_extracted; + } + + // 4. 彻底清空后,再次断言状态码归回 C_ERR_EMPTY + ASSERT_INT_EQ(C_ERR_EMPTY, c_MinPQ_Pop(&pq, &dummy)); + + c_MinPQ_Destroy(&pq); +} + +TEST_CASE(test_c_MinPQ_ParamDefenses) { + c_MinPQ_t local_pq; + c_MinPQ_Init(&local_pq, 4, sizeof(int), min_pq_compare_ints, NULL, NULL); + + int item = 42; + // 5. 验证各底层核心函数对非法入参的 C_ERR_PARAM 拦截线 + ASSERT_INT_EQ(C_ERR_PARAM, c_MinPQ_Init(NULL, 4, sizeof(int), min_pq_compare_ints, NULL, NULL)); + ASSERT_INT_EQ(C_ERR_PARAM, c_MinPQ_Push(NULL, &item)); + ASSERT_INT_EQ(C_ERR_PARAM, c_MinPQ_Pop(NULL, &item)); + ASSERT_INT_EQ(C_ERR_PARAM, c_MinPQ_Peek(NULL, &item)); + ASSERT_INT_EQ(C_ERR_PARAM, c_MinPQ_Peek(&local_pq, NULL)); + + c_MinPQ_Destroy(&local_pq); +} + +int main(int argc, char** argv){ + + TEST_START(C_MinPQ_FullChainStatus_TestSuite); + RUN_TEST(test_c_MinPQ_FullChainStatusCodes); + RUN_TEST(test_c_MinPQ_ParamDefenses); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Sort/c_QuickSort.c b/Sort/c_QuickSort.c new file mode 100644 index 0000000..64e1d39 --- /dev/null +++ b/Sort/c_QuickSort.c @@ -0,0 +1,479 @@ +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#include + + +/** + * @brief 原子操作:泛型就地物理内存块交换 + */ +/** + * @brief 彻底加固的泛型物理内存块互换接口(256字节自适应滑窗,绝不踩踏) + */ +C_STATIC_FORCE_INLINE +void c_QS_Swap(void* a, void* b, c_size_t size) { + if (a == b || size == 0) return; + char* p1 = (char*)a; + char* p2 = (char*)b; + char temp_buf[256]; + c_size_t bytes_left = size; + while (bytes_left > 0) { + c_size_t chunk = (bytes_left < sizeof(temp_buf)) ? bytes_left : sizeof(temp_buf); + memcpy(temp_buf, p1, chunk); + memcpy(p1, p2, chunk); + memcpy(p2, temp_buf, chunk); + p1 += chunk; + p2 += chunk; + bytes_left -= chunk; + } +} + +/** + * @brief 最直观的 Lomuto 单路划分递归核心(完全规避 c_size_t 下溢) + */ +static void c_QuickSortRecursive(char* array_base, c_size_t low, c_size_t high, c_size_t size, c_SortCompare_t cmp, void* args) { + // 递归终止:区间无元素或仅剩 1 个元素时安全返回 + if (low >= high) { + return; + } + + // 1. Lomuto 划分核心:选取当前区间的最后一个元素作为基准点 (Pivot) + char* pivot = array_base + (high * size); + + // i 记录的是“小于基准点的元素”的右边界 + c_size_t i = low; + + // j 游标从 low 开始单调递增扫描到 high - 1 + for (c_size_t j = low; j < high; j++) { + // 如果当前元素比基准点小,就把她跟 i 位置的数据互换,并将 i 向右推一步 + if (cmp(array_base + (j * size), pivot, args) < 0) { + c_QS_Swap(array_base + (i * size), array_base + (j * size), size); + i++; + } + } + + // 最后,将处于 high 位置的基准点本身,交换到边界 i 的地方归位 + c_QS_Swap(array_base + (i * size), pivot, size); + + // 此时绝对位置 i 处的元素已经各就各位。接下来两侧递归分治: + // 左半区:[low, i-1] | 右半区:[i+1, high] + + // 核心安全防护:只有 i > 0 且 i - 1 > low 时才执行左半区减法,彻底封死无符号下溢 + if (i > 0 && (i - 1) > low) { + c_QuickSortRecursive(array_base, low, i - 1, size, cmp, args); + } + + // 只有 i + 1 < high 时才执行右半区加法,防止整数溢出 + if (i < high) { + c_QuickSortRecursive(array_base, i + 1, high, size, cmp, args); + } +} + +/** + * @brief 迭代版本泛型快速排序标准入口 + */ +void c_QuickSort_Recursive(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + + char* array_base = (char*)base; + + // 闭区间从 0 开始到 num - 1。经过前置 num >= 2 的拦截,num - 1 绝对不会下溢 + c_QuickSortRecursive(array_base, 0, num - 1, size, cmp, args); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + + +/** + * @brief Dijkstra 三路划分标准安全控制流 + */ +static void c_QuickSort3WayRecursive(char* array_base, c_size_t low, c_size_t high, c_size_t size, c_SortCompare_t cmp, void* args) { + if (low >= high) { + return; + } + + // 🌟【终极加固核心】:在局部栈上开辟只读缓冲区,将原始枢轴的内容完整镜像拷贝出来! + // 256字节支持绝大多数基础数据类型与中小型业务结构体 + char pivot_buf[size]; + memcpy(pivot_buf, array_base + (low * size), size); + + c_size_t lt = low; // lt 维护小于 pivot 区间的右边界 + c_size_t i = low + 1; // i 为当前单调递增扫描指针 + c_size_t gt = high; // gt 维护大于 pivot 区间的左边界 + + while (i <= gt) { + char* item_i = array_base + (i * size); + + // 🌟【核心修正】:后续所有的比对,全部投喂只读的临时影子副本 pivot_buf, + // 彻底绝缘由于首位数据被物理 Swap 覆盖导致的基准值污染! + int cmp_res = cmp(item_i, pivot_buf, args); + + if (cmp_res < 0) { + c_QS_Swap(array_base + (lt * size), item_i, size); + lt++; + i++; + } + else if (cmp_res > 0) { + c_QS_Swap(item_i, array_base + (gt * size), size); + if (gt == i) { + break; + } + gt--; + } + else { + i++; + } + } + + // ----------------------------------------------------------------- + // 纯单调递增分治:隔离无符号整数下溢 + // ----------------------------------------------------------------- + if (lt > low) { + c_size_t left_high = lt - 1; + if (low < left_high) { + c_QuickSort3WayRecursive(array_base, low, left_high, size, cmp, args); + } + } + + if (gt < high) { + c_QuickSort3WayRecursive(array_base, gt + 1, high, size, cmp, args); + } +} + +/** + * @brief 对外标准标准入口 + */ +void c_QuickSort_3Way(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + char* array_base = (char*)base; + c_QuickSort3WayRecursive(array_base, 0, num - 1, size, cmp, args); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#define COMPONENT_QS_CUTOFF 15 + +/** + * @brief 局部内联优化的泛型插入排序(供快排小区间降级调用) + */ +static void c_OptQS_InsertionSort(char* base, c_size_t low, c_size_t high, c_size_t size, c_SortCompare_t cmp, void* args) { + for (c_size_t i = low + 1; i <= high; i++) { + c_size_t j = i; + char* item_i = base + (i * size); + char v_buf[256]; + memcpy(v_buf, item_i, size); + + while (j > low) { + char* current = base + (j * size); + char* previous = base + ((j - 1) * size); + if (cmp(v_buf, previous, args) < 0) { + memcpy(current, previous, size); + j--; + } else { + break; + } + } + memcpy(base + (j * size), v_buf, size); + } +} + +/** + * @brief 三数取中(Median-of-Three)预处理逻辑 + * + * 对首、中、尾三处元素进行就地排序,并将中位数物理挪动到区间最左侧(low 位置)充当主轴 + */ +static void c_OptQS_MedianOfThree(char* base, c_size_t low, c_size_t high, c_size_t size, c_SortCompare_t cmp, void* args) { + c_size_t mid = low + ((high - low) >> 1); + char* E_low = base + (low * size); + char* E_mid = base + (mid * size); + char* E_high = base + (high * size); + + if (cmp(E_mid, E_low, args) < 0) c_QS_Swap(E_mid, E_low, size); + if (cmp(E_high, E_low, args) < 0) c_QS_Swap(E_high, E_low, size); + if (cmp(E_mid, E_high, args) < 0) c_QS_Swap(E_mid, E_high, size); + + // 此时首尾中三点已升序,将作为中位数的 E_high 与 E_low 交换,确保枢轴位于起点 + c_QS_Swap(E_low, E_high, size); +} + +/** + * @brief 经典 Hoare 双路划分与递归核心(纯加法单调驱动,规避下溢) + */ +static void c_OptQuickSortRecursive(char* array_base, c_size_t low, c_size_t high, c_size_t size, c_SortCompare_t cmp, void* args) { + // 优化点 1:小数组自适应截断降级 + if (high - low < COMPONENT_QS_CUTOFF) { + c_OptQS_InsertionSort(array_base, low, high, size, cmp, args); + return; + } + + // 优化点 2:激活三数取中,粉碎倒序或已排序序列的退化隐患 + c_OptQS_MedianOfThree(array_base, low, high, size, cmp, args); + + // 🌟 影子副本加固:将选取出的中位数枢轴数据复制到只读栈空间,绝缘交换别名污染! + char pivot_buf[size]; + memcpy(pivot_buf, array_base + (low * size), size); + + // Hoare 划分指针双向逼近控制 + c_size_t i = low; + c_size_t j = high + 1; // 开区间初始边界 + + while (1) { + // 左指针 i 向右单调逼近,直到遇到大于或等于枢轴的元素才停止 + do { + i++; + } while (i <= high && cmp(array_base + (i * size), pivot_buf, args) < 0); + + // 右指针 j 向左单调收缩,直到遇到小于或等于枢轴的元素才停止 + do { + j--; + } while (j >= low && cmp(array_base + (j * size), pivot_buf, args) > 0); + + // 如果双向指针交叉,说明本次划分区域完全合拢 + if (i >= j) { + break; + } + + // 强行物理对调 i 和 j 处的元素(把大数顶到右边,小数挪到左边) + c_QS_Swap(array_base + (i * size), array_base + (j * size), size); + } + + // 将驻留在 low 位置的枢轴元素与分界点 j 的元素进行对调归位 + c_QS_Swap(array_base + (low * size), array_base + (j * size), size); + + // ----------------------------------------------------------------- + // 优化点 3:纯加法约束的分治控制,阻断无符号整数减法下溢 + // ----------------------------------------------------------------- + if (j > low) { + c_size_t left_high = j - 1; + if (low < left_high) { + c_OptQuickSortRecursive(array_base, low, left_high, size, cmp, args); + } + } + + if (j < high) { + c_OptQuickSortRecursive(array_base, j + 1, high, size, cmp, args); + } +} + +/** + * @brief 工业级三层优化双路快速排序标准对外入口 + */ +void c_QuickSort_2Way_Optimized(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + char* array_base = (char*)base; + c_OptQuickSortRecursive(array_base, 0, num - 1, size, cmp, args); +} + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 内部辅助:获取两个无符号数中的较小值 + */ +C_STATIC_FORCE_INLINE +c_size_t c_BM_Min(c_size_t a, c_size_t b) { + return (a < b) ? a : b; +} + +/** + * @brief Bentley-McIlroy 三路划分与递归控制核心(完全规避 c_size_t 下溢) + */ +static void c_QuickBentleyMcIlroyRecursive(char* array_base, c_size_t low, c_size_t high, c_size_t size, c_SortCompare_t cmp, void* args) { + if (low >= high) { + return; + } + + // 🌟 影子副本加固:将当前首位元素拷贝到局部栈缓冲区,绝缘数据别名覆盖污染 + char pivot_buf[size]; + memcpy(pivot_buf, array_base + (low * size), size); + + // 声明游标与边界控制(采用开区间或前置递增边界控制) + c_size_t i = low; + c_size_t j = high + 1; + + c_size_t p = low; // p 维护左侧等于区的右边界 + c_size_t q = high + 1; // q 维护右侧等于区的左边界 + + while (1) { + // 1. 左游标 i 向右单调扫描,遇到大于或等于枢轴的元素时停止 + do { + i++; + } while (i <= high && cmp(array_base + (i * size), pivot_buf, args) < 0); + + // 2. 右游标 j 向左单调收缩,遇到小于或等于枢轴的元素时停止 + do { + j--; + } while (j >= low && cmp(array_base + (j * size), pivot_buf, args) > 0); + + // 如果指针发生重合或交叉,说明中间两路扫描完全汇合 + if (i >= j) { + break; + } + + // 3. 交换 i 和 j 处的乱序元素(基础双路快排动作) + c_QS_Swap(array_base + (i * size), array_base + (j * size), size); + + // 4. 【Bentley-McIlroy 核心拓展】: + // 如果换到左边的新元素恰好等于枢轴,将其物理顶推暂存到数组“最左端(p位置)” + if (cmp(array_base + (i * size), pivot_buf, args) == 0) { + p++; + c_QS_Swap(array_base + (p * size), array_base + (i * size), size); + } + // 如果换到右边的新元素恰好等于枢轴,将其物理顶推暂存到数组“最右端(--q位置)” + if (cmp(array_base + (j * size), pivot_buf, args) == 0) { + q--; + c_QS_Swap(array_base + (q * size), array_base + (j * size), size); + } + } + + // 5. 特殊边界重对齐:如果相遇在等于区元素上,使 i 跨过分界点 + if (i == j && cmp(array_base + (i * size), pivot_buf, args) == 0) { + i++; + if (j > 0) j--; + } + + // 6. 【数据归位阶段】:将两端暂存的所有等于元素整体交换回正中央 + + // 把左端 [low, p] 的等于元素完美换回当前分界点 j 的左侧 + c_size_t left_len = p - low + 1; + c_size_t left_move = (j >= low) ? (j - low + 1) : 0; + c_size_t num_to_swap_left = c_BM_Min(left_len, left_move); + for (c_size_t k = 0; k < num_to_swap_left; k++) { + c_QS_Swap(array_base + ((low + k) * size), array_base + ((j - k) * size), size); + } + + // 把右端 [q, high] 的等于元素完美换回当前分界点 i 的右侧 + c_size_t right_len = high - q + 1; + c_size_t right_move = (high >= i) ? (high - i + 1) : 0; + c_size_t num_to_swap_right = c_BM_Min(right_len, right_move); + for (c_size_t k = 0; k < num_to_swap_right; k++) { + c_QS_Swap(array_base + ((high - k) * size), array_base + ((i + k) * size), size); + } + + // ----------------------------------------------------------------- + // 7. 纯加法单调控制分治,隔离无符号整数下溢 + // 此时中央区全部由等于元素接管,我们需要向外侧递归两端的小于区与大于区: + // 小于区:[low, j - num_to_swap_left] | 大于区:[i + num_to_swap_right, high] + // ----------------------------------------------------------------- + if (j >= low && j >= num_to_swap_left) { + c_size_t left_high = j - num_to_swap_left; + if (low < left_high && left_high != (c_size_t)-1) { + c_QuickBentleyMcIlroyRecursive(array_base, low, left_high, size, cmp, args); + } + } + + if (i + num_to_swap_right < high) { + c_QuickBentleyMcIlroyRecursive(array_base, i + num_to_swap_right, high, size, cmp, args); + } +} + +/** + * @brief 工业级泛型自适应 Bentley-McIlroy 三路划分快速排序标准对外入口 + */ +void c_QuickSort_BentleyMcIlroy(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + char* array_base = (char*)base; + c_QuickBentleyMcIlroyRecursive(array_base, 0, num - 1, size, cmp, args); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/** + * @brief 经典 Lomuto 划分函数(改自最简单快排,影子副本加固,安全防护) + */ +static c_size_t c_IterQS_Partition(char* array_base, c_size_t low, c_size_t high, c_size_t size, c_SortCompare_t cmp, void* args) { + // 影子副本加固:完整镜像拷贝主轴,彻底粉碎泛型对调下的地址克隆别名污染 + char pivot_buf[size]; + memcpy(pivot_buf, array_base + (high * size), size); + + c_size_t i = low; + for (c_size_t j = low; j < high; j++) { + if (cmp(array_base + (j * size), pivot_buf, args) < 0) { + c_QS_Swap(array_base + (i * size), array_base + (j * size), size); + i++; + } + } + c_QS_Swap(array_base + (i * size), array_base + (high * size), size); + return i; +} + +/** + * @brief 工业级泛型非递归快速排序(显式模拟栈控制,彻底免疫 Stack Overflow) + */ +void c_QuickSort_Iterative(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + + char* array_base = (char*)base; + + // 🌟 核心安全优化:对于 64 位系统,递归最大深度恒小于 64,开辟 128 长度的栈空间绝对可以闭环全球所有大数据 + c_size_t stack[128]; + long long top = -1; // 栈顶游标,使用有符号长整型防止自减溢出 + + // 将初始整轨区间的 [low, high] 闭区间边界压入显式模拟栈中 + stack[++top] = 0; + stack[++top] = num - 1; + + // 进入纯迭代控制状态机 + while (top >= 0) { + // 出栈当前分治区间的右、左边界 + c_size_t high = stack[top--]; + c_size_t low = stack[top--]; + + // 1. 就地执行单路高抗性划分,锁定当前基准点的绝对无符号下标 pivot_idx + c_size_t pivot_idx = c_IterQS_Partition(array_base, low, high, size, cmp, args); + + // 2. 模拟左分治管线:如果当前枢轴左侧还有可排空间 + // 核心无符号防御:利用加法逆向控制 (low + 1 < pivot_idx) 代替传统减法,彻底切断下溢可能 + bool has_left = (pivot_idx > 0 && low < pivot_idx - 1); + + // 3. 模拟右分治管线:如果当前枢轴右侧还有可排空间 + bool has_right = (pivot_idx < high); + + // 🌟 工业级核心策略:优先将“大区间”压入栈顶,优先弹出来处理“小区间”! + // 这倒逼模拟栈的使用率呈对数律 $\mathcal{O}(\log N)$ 极限收敛,最大限度压缩运行时内存消耗 + if (has_left && has_right) { + c_size_t left_len = (pivot_idx - 1) - low; + c_size_t right_len = high - (pivot_idx + 1); + + if (left_len > right_len) { + // 左侧大,左侧优先压栈,右侧留着优先出栈处理 + stack[++top] = low; + stack[++top] = pivot_idx - 1; + stack[++top] = pivot_idx + 1; + stack[++top] = high; + } else { + // 右侧大,右侧优先压栈 + stack[++top] = pivot_idx + 1; + stack[++top] = high; + stack[++top] = low; + stack[++top] = pivot_idx - 1; + } + } + else if (has_left) { + stack[++top] = low; + stack[++top] = pivot_idx - 1; + } + else if (has_right) { + stack[++top] = pivot_idx + 1; + stack[++top] = high; + } + } +} diff --git a/Sort/c_QuickSort.h b/Sort/c_QuickSort.h new file mode 100644 index 0000000..d4bd45b --- /dev/null +++ b/Sort/c_QuickSort.h @@ -0,0 +1,68 @@ +#ifndef INCLUDED_C_QUICKSORT_H +#define INCLUDED_C_QUICKSORT_H + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 迭代版本泛型快速排序标准入口 + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_QuickSort_Recursive(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + +/** + * @brief 工业级泛型自适应三路划分快速排序入口 + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_QuickSort_3Way(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + +/** + * @brief 工业级三层优化双路快速排序标准对外入口 + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_QuickSort_2Way_Optimized(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + + +/** + * @brief 工业级泛型自适应 Bentley-McIlroy 三路划分快速排序标准对外入口 + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_QuickSort_BentleyMcIlroy(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + + +/** + * @brief 工业级泛型非递归快速排序(显式模拟栈控制,彻底免疫 Stack Overflow) + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_QuickSort_Iterative(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + +#endif /*INCLUDED_C_QUICKSORT_H*/ diff --git a/Sort/c_QuickSort.t.c b/Sort/c_QuickSort.t.c new file mode 100644 index 0000000..e47c442 --- /dev/null +++ b/Sort/c_QuickSort.t.c @@ -0,0 +1,381 @@ +#include "c_QuickSort.h" +#include +#include +#include "c_Test.h" + +static int sort_compare_ints_with_args(const void* a, const void* b, void* args) { + (void)args; + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +typedef struct { + char id; + int rank; +} UserNode_t; + +static int sort_compare_users(const void* a, const void* b, void* args) { + (void)args; + const UserNode_t* u1 = (const UserNode_t*)a; + const UserNode_t* u2 = (const UserNode_t*)b; + return u1->rank - u2->rank; // 简单按 rank 升序比对 +} + +// ========================================== +// 4. 自动化测试用例集 +// ========================================== +TEST_CASE(test_c_QuickSort_Recursive_IntArray) { + int arr[] = { 24, 9, 85, 32, 75, 5, 66 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_QuickSort_Recursive(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + // 验证严格递增状态 + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] <= arr[i + 1]); + } + ASSERT_INT_EQ(5, arr[0]); + ASSERT_INT_EQ(85, arr[num - 1]); +} + +TEST_CASE(test_c_QuickSort_Recursive_StructArray) { + UserNode_t users[] = { + { 'M', 90 }, + { 'N', 10 }, + { 'O', 45 } + }; + c_size_t num = sizeof(users) / sizeof(users[0]); + + c_QuickSort_Recursive(users, num, sizeof(UserNode_t), sort_compare_users, NULL); + + // 预期重排顺序: N(10) -> O(45) -> M(90) + ASSERT_INT_EQ(10, users[0].rank); + ASSERT_TRUE(users[0].id == 'N'); + + ASSERT_INT_EQ(45, users[1].rank); + ASSERT_TRUE(users[1].id == 'O'); + + ASSERT_INT_EQ(90, users[2].rank); + ASSERT_TRUE(users[2].id == 'M'); +} + +TEST_CASE(test_c_QuickSort_Recursive_Edges) { + // 压测完全倒序的极值输入,检查安全拦截 + int rev_arr[] = { 4, 3, 2, 1 }; + c_size_t num = sizeof(rev_arr) / sizeof(rev_arr[0]); + + c_QuickSort_Recursive(rev_arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(rev_arr[i] < rev_arr[i + 1]); + } + + // 空边界拦截测试 + int single_arr[] = { 567 }; + c_QuickSort_Recursive(single_arr, 1, sizeof(int), sort_compare_ints_with_args, NULL); + c_QuickSort_Recursive(NULL, 0, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(567, single_arr[0]); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + char label; + int major; + int minor; +} NodeItem_t; + +// 复合多级比对器:优先按 major 升序,相同时按 minor 降序 +static int sort_compare_nodes(const void* a, const void* b, void* args) { + (void)args; + const NodeItem_t* n1 = (const NodeItem_t*)a; + const NodeItem_t* n2 = (const NodeItem_t*)b; + if (n1->major != n2->major) { + return n1->major - n2->major; + } + return n2->minor - n1->minor; +} + +// ========================================== +// 4. 自动化测试用例集 +// ========================================== +TEST_CASE(test_c_QuickSort_3Way_MassiveDuplicates) { + int arr[] = { 9, 3, 9, 9, 7, 3, 9, 1, 9, 7, 9, 9, 3, 9 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_QuickSort_3Way(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] <= arr[i + 1]); + } + ASSERT_INT_EQ(1, arr[0]); + ASSERT_INT_EQ(3, arr[1]); + ASSERT_INT_EQ(3, arr[2]); + ASSERT_INT_EQ(9, arr[num - 1]); +} + +TEST_CASE(test_c_QuickSort_3Way_StructArray) { + // 2. 复杂多关键字结构体数组精确下标引用校验 + NodeItem_t nodes[] = { + { 'A', 500, 20 }, + { 'B', 300, 60 }, + { 'C', 500, 80 }, // major 同为 500,但 minor 80 应该排在 20 的前面 + { 'D', 400, 40 } + }; + c_size_t num = sizeof(nodes) / sizeof(nodes[0]); + + c_QuickSort_3Way(nodes, num, sizeof(NodeItem_t), sort_compare_nodes, NULL); + + // 预期重排顺序: B(300/60) -> D(400/40) -> C(500/80) -> A(500/20) + ASSERT_INT_EQ(300, nodes[0].major); + ASSERT_TRUE(nodes[0].label == 'B'); + + ASSERT_INT_EQ(400, nodes[1].major); + ASSERT_TRUE(nodes[1].label == 'D'); + + ASSERT_INT_EQ(500, nodes[2].major); + ASSERT_INT_EQ(80, nodes[2].minor); // 降序次键优先被推出 + ASSERT_TRUE(nodes[2].label == 'C'); + + ASSERT_INT_EQ(500, nodes[3].major); + ASSERT_INT_EQ(20, nodes[3].minor); + ASSERT_TRUE(nodes[3].label == 'A'); +} + +TEST_CASE(test_c_QuickSort_3Way_EdgesAndReverse) { + // 3. 完全逆序极值序列压测 + int rev_arr[] = { 5, 4, 3, 2, 1 }; + c_size_t num = sizeof(rev_arr) / sizeof(rev_arr[0]); + + c_QuickSort_3Way(rev_arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(rev_arr[i] < rev_arr[i + 1]); + } + + // 4. 单元素与非法空边界拦截 + int single_arr[] = { 9999 }; + c_QuickSort_3Way(single_arr, 1, sizeof(int), sort_compare_ints_with_args, NULL); + c_QuickSort_3Way(NULL, 0, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(9999, single_arr[0]); +} + +TEST_CASE(test_c_QuickSort_2Way_MassiveAndCutoff) { + // 1. 构建一个长距离乱序、超过截断阈值(> 15)的恶劣元素阵列 + int arr[] = { 99, 4, 23, 87, 12, 56, 34, 11, 90, 8, 45, 68, 22, 1, 75, 43, 19, 105, 0, 55 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_QuickSort_2Way_Optimized(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + // 验证严格单调性 + for (c_size_t i = 0; i < num - 1; i++) { + if (arr[i] > arr[i + 1]) { + // 如果发生局部乱序,精准抛出报错位置和具体数据对对碰 + ASSERT_INT_EQ(arr[i + 1], arr[i]); + return; + } + } + ASSERT_INT_EQ(0, arr[0]); + ASSERT_INT_EQ(1, arr[1]); + ASSERT_INT_EQ(105, arr[num - 1]); +} + +TEST_CASE(test_c_QuickSort_2Way_WorstCaseRegression) { + // 2. 强推完全逆序的极值分布数组。 + // 在普通快排里这会瞬间引发 O(N²) 的最坏情况,但三数取中能直接在 $O(1)$ 时间内把枢轴拉回到最优的中位数位置 + int arr[] = { 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_QuickSort_2Way_Optimized(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] < arr[i + 1]); + } + ASSERT_INT_EQ(1, arr[0]); + ASSERT_INT_EQ(20, arr[num - 1]); +} + +TEST_CASE(test_c_QuickSort_2Way_HeavyDuplicates) { + // 3. 压测大量重复元素的恶劣数据分布(Dijkstra 3-way 测试中的那个顽固集合) + int arr[] = { 9, 3, 9, 9, 7, 3, 9, 1, 9, 7, 9, 9, 3, 9 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_QuickSort_2Way_Optimized(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] <= arr[i + 1]); + } + ASSERT_INT_EQ(1, arr[0]); + ASSERT_INT_EQ(3, arr[1]); + ASSERT_INT_EQ(7, arr[4]); + ASSERT_INT_EQ(9, arr[num - 1]); +} + +TEST_CASE(test_c_QuickSort_2Way_Edges) { + int single_arr[] = { 7777 }; + c_QuickSort_2Way_Optimized(single_arr, 1, sizeof(int), sort_compare_ints_with_args, NULL); + c_QuickSort_2Way_Optimized(NULL, 0, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(7777, single_arr[0]); +} + +TEST_CASE(test_c_QuickSort_BM_MassiveDuplicates) { + // 投入那个之前极易引发枢轴内存掉包和无符号数下溢的经典恶劣重复数据集合 + int arr[] = { 9, 3, 9, 9, 7, 3, 9, 1, 9, 7, 9, 9, 3, 9 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_QuickSort_BentleyMcIlroy(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + // 1. 验证单调非减 + for (c_size_t i = 0; i < num - 1; i++) { + if (arr[i] > arr[i + 1]) { + ASSERT_INT_EQ(arr[i + 1], arr[i]); + return; + } + } + + // 2. 深度精确校验中央和两端的全部插槽值 + ASSERT_INT_EQ(1, arr[0]); + ASSERT_INT_EQ(3, arr[1]); + ASSERT_INT_EQ(3, arr[2]); + ASSERT_INT_EQ(3, arr[3]); + ASSERT_INT_EQ(7, arr[4]); + ASSERT_INT_EQ(7, arr[5]); + ASSERT_INT_EQ(9, arr[6]); + ASSERT_INT_EQ(9, arr[7]); + ASSERT_INT_EQ(9, arr[num - 1]); +} + +TEST_CASE(test_c_QuickSort_BM_ComplexPermutation) { + // 常规混合高度无序无重复梯度大样本压测,验证 Bentley-McIlroy 的广谱鲁棒性 + int arr[] = { 42, 12, 88, 5, 67, 1, 99, 34, 11, 73, 22, 15, 60 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_QuickSort_BentleyMcIlroy(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] < arr[i + 1]); + } + ASSERT_INT_EQ(1, arr[0]); + ASSERT_INT_EQ(99, arr[num - 1]); +} + +TEST_CASE(test_c_QuickSort_BM_Edges) { + int single_arr[] = { 55555 }; + c_QuickSort_BentleyMcIlroy(single_arr, 1, sizeof(int), sort_compare_ints_with_args, NULL); + c_QuickSort_BentleyMcIlroy(NULL, 0, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(55555, single_arr[0]); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + char token; + int weight; +} ElementNode_t; + +static int sort_compare_elements(const void* a, const void* b, void* args) { + (void)args; + const ElementNode_t* e1 = (const ElementNode_t*)a; + const ElementNode_t* e2 = (const ElementNode_t*)b; + return e1->weight - e2->weight; +} + +TEST_CASE(test_c_QuickSort_Iterative_BasicInts) { + // 1. 标准乱序混合整型数组非递归迭代排序测试 + int arr[] = { 42, 17, 85, 3, 64, 12, 99, 32, 5, 55 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_QuickSort_Iterative(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + for (c_size_t i = 0; i < num - 1; i++) { + if (arr[i] > arr[i + 1]) { + ASSERT_INT_EQ(arr[i + 1], arr[i]); + return; + } + } + ASSERT_INT_EQ(3, arr[0]); + ASSERT_INT_EQ(5, arr[1]); + ASSERT_INT_EQ(99, arr[num - 1]); +} + +TEST_CASE(test_c_QuickSort_Iterative_StructArray) { + // 2. 复杂自定义结构体对象在非递归模拟栈状态下的字段流转测试 + ElementNode_t nodes[] = { + { 'X', 800 }, + { 'Y', 200 }, + { 'Z', 500 }, + { 'W', 100 } + }; + c_size_t num = sizeof(nodes) / sizeof(nodes[0]); + + c_QuickSort_Iterative(nodes, num, sizeof(ElementNode_t), sort_compare_elements, NULL); + + // 预期升序排布顺序:W(100) -> Y(200) -> Z(500) -> X(800) + ASSERT_INT_EQ(100, nodes[0].weight); + ASSERT_TRUE(nodes[0].token == 'W'); + + ASSERT_INT_EQ(200, nodes[1].weight); + ASSERT_TRUE(nodes[1].token == 'Y'); + + ASSERT_INT_EQ(500, nodes[2].weight); + ASSERT_TRUE(nodes[2].token == 'Z'); + + ASSERT_INT_EQ(800, nodes[3].weight); + ASSERT_TRUE(nodes[3].token == 'X'); +} + +TEST_CASE(test_c_QuickSort_Iterative_ExtremeEdges) { + // 3. 压测完全有序和完全逆序序列,高强度检验模拟栈的大小和入栈逻辑安全性 + int rev_arr[] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; + c_size_t num = sizeof(rev_arr) / sizeof(rev_arr[0]); + + c_QuickSort_Iterative(rev_arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(rev_arr[i] < rev_arr[i + 1]); + } + + // 4. 空参数以及单体超边界数组拦截防御 + int single_arr[] = { 88888 }; + c_QuickSort_Iterative(single_arr, 1, sizeof(int), sort_compare_ints_with_args, NULL); + c_QuickSort_Iterative(NULL, 0, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(88888, single_arr[0]); +} + + + +// ========================================== +// 5. 独立测试运行入口 +// ========================================== +int main(void) { + TEST_START(C_SimpleQuickSort_Isolated_TestSuite); + + // 顺序触发快速排序测试 + RUN_TEST(test_c_QuickSort_Recursive_IntArray); + RUN_TEST(test_c_QuickSort_Recursive_StructArray); + RUN_TEST(test_c_QuickSort_Recursive_Edges); + + // 触发专项三路快排验证 + RUN_TEST(test_c_QuickSort_3Way_MassiveDuplicates); + RUN_TEST(test_c_QuickSort_3Way_StructArray); + RUN_TEST(test_c_QuickSort_3Way_EdgesAndReverse); + + RUN_TEST(test_c_QuickSort_2Way_MassiveAndCutoff); + RUN_TEST(test_c_QuickSort_2Way_WorstCaseRegression); + RUN_TEST(test_c_QuickSort_2Way_HeavyDuplicates); + RUN_TEST(test_c_QuickSort_2Way_Edges); + + RUN_TEST(test_c_QuickSort_BM_MassiveDuplicates); + RUN_TEST(test_c_QuickSort_BM_ComplexPermutation); + RUN_TEST(test_c_QuickSort_BM_Edges); + + RUN_TEST(test_c_QuickSort_Iterative_BasicInts); + RUN_TEST(test_c_QuickSort_Iterative_StructArray); + RUN_TEST(test_c_QuickSort_Iterative_ExtremeEdges); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Sort/c_SelectionSort.c b/Sort/c_SelectionSort.c new file mode 100644 index 0000000..a52b542 --- /dev/null +++ b/Sort/c_SelectionSort.c @@ -0,0 +1,35 @@ +#include +#include "c_Swap.h" + +void c_SelectionSort(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + // 边界与防御性校验:元素少于 2 个或参数非法时无需排序 + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + + char* array_base = (char*)base; + + // 外层循环:安全递增控制,num - 1 经过前置条件 num >= 2 拦截后,绝对不会发生减法下溢 + c_size_t limit = num - 1; + for (c_size_t i = 0; i < limit; i++) { + c_size_t min_idx = i; + char* min_elem = array_base + (min_idx * size); + + // 内层循环:在剩余未排序区间中寻找绝对最小值 + for (c_size_t j = i + 1; j < num; j++) { + char* current_elem = array_base + (j * size); + + // 执行您带 args 的自定义比对器:如果发现当前元素比已知的 min_elem 还要小 + if (cmp(current_elem, min_elem, args) < 0) { + min_idx = j; + min_elem = current_elem; // 更新当前区间的最小值指针与下标 + } + } + + // 优化策略:只有在锁定的最小值不是当前起始位 i 时,才触发物理内存交换 + // 这将内存写操作(Swap)控制在极致的最多 N-1 次 + if (min_idx != i) { + c_Swap(array_base + (i * size), array_base + (min_idx * size), size); + } + } +} diff --git a/Sort/c_SelectionSort.h b/Sort/c_SelectionSort.h new file mode 100644 index 0000000..6e97eec --- /dev/null +++ b/Sort/c_SelectionSort.h @@ -0,0 +1,27 @@ +#ifndef INCLUDED_C_SELECTIONSORT_H +#define INCLUDED_C_SELECTIONSORT_H + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/** + * @brief 工业级泛型选择排序(完全规避无符号整数下溢,最少数据交换开销) + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_SelectionSort(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + + + + +#endif /*INCLUDED_C_SELECTIONSORT_H*/ diff --git a/Sort/c_SelectionSort.t.c b/Sort/c_SelectionSort.t.c new file mode 100644 index 0000000..088141d --- /dev/null +++ b/Sort/c_SelectionSort.t.c @@ -0,0 +1,121 @@ +#include "c_SelectionSort.h" +#include "c_Test.h" +#include +#include + +// ========================================== +// 1. 测试用例伴生比对器与自定义结构体 +// ========================================== + +// 带 args 签名的标准整型比对器 +static int sort_compare_ints_with_args(const void* a, const void* b, void* args) { + (void)args; // 暂不使用参数 + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +// 供复杂结构体测试使用的对象 +typedef struct { + char key[4]; + int score; +} Student_t; + +// 针对结构体的带 args 自定义多级比对器 +static int sort_compare_students(const void* a, const void* b, void* args) { + (void)args; + const Student_t* s1 = (const Student_t*)a; + const Student_t* s2 = (const Student_t*)b; + + // 优先按照分数降序排序 + if (s1->score != s2->score) { + return s2->score - s1->score; + } + // 分数相同时,按照名字字典序升序排列 + return strcmp(s1->key, s2->key); +} + +// ========================================== +// 2. 核心测试用例 +// ========================================== + +TEST_CASE(test_c_SelectionSort_IntArray) { + // 1. 准备一个乱序、带重复项的整型数组 + int arr[] = { 29, 10, 14, 37, 14, 8, 20 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + // 运行带上下文比对的选择排序 + c_SelectionSort(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + // 2. 验证全区间是否呈绝对严格递增排列 + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] <= arr[i + 1]); + } + ASSERT_INT_EQ(8, arr[0]); // 全局最小值 + ASSERT_INT_EQ(10, arr[1]); + ASSERT_INT_EQ(14, arr[2]); // 重复项 + ASSERT_INT_EQ(14, arr[3]); + ASSERT_INT_EQ(20, arr[4]); + ASSERT_INT_EQ(29, arr[5]); + ASSERT_INT_EQ(37, arr[6]); // 全局最大值 +} + +TEST_CASE(test_c_SelectionSort_StructArray) { + // 3. 核心测试:针对复杂自定义结构体数组进行多级条件选择重排 + Student_t students[] = { + { "Bob", 85 }, + { "Amy", 95 }, + { "Doc", 85 }, + { "Eme", 70 } + }; + c_size_t num = sizeof(students) / sizeof(students[0]); + + c_SelectionSort(students, num, sizeof(Student_t), sort_compare_students, NULL); + + // 4. 验证排序结果(预期结果:Amy/95 -> Bob/85 -> Doc/85 -> Eme/70) + ASSERT_INT_EQ(95, students[0].score); + ASSERT_TRUE(strcmp(students[0].key, "Amy") == 0); + + ASSERT_INT_EQ(85, students[1].score); + ASSERT_TRUE(strcmp(students[1].key, "Bob") == 0); // 85分相同,B 在 D 前面 + + ASSERT_INT_EQ(85, students[2].score); + ASSERT_TRUE(strcmp(students[2].key, "Doc") == 0); + + ASSERT_INT_EQ(70, students[3].score); + ASSERT_TRUE(strcmp(students[3].key, "Eme") == 0); +} + +TEST_CASE(test_c_SelectionSort_EdgeAndStability) { + int ordered_arr[] = { 1, 2, 3 }; + + // 5. 压测完全有序的数组,验证内部优化机制(不产生任何不必要的物理交换) + c_SelectionSort(ordered_arr, 3, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(1, ordered_arr[0]); + ASSERT_INT_EQ(2, ordered_arr[1]); + ASSERT_INT_EQ(3, ordered_arr[2]); + + // 6. 极端空边界安全拦截,验证不发生无符号整数下溢、死循环或段错误 + int single_arr[] = { 888 }; + c_SelectionSort(single_arr, 1, sizeof(int), sort_compare_ints_with_args, NULL); + c_SelectionSort(NULL, 0, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(888, single_arr[0]); +} + + + +int main(int argc, char** argv){ + TEST_START(Starting Unit Tests); + + // 运行普通无环境要求的用例 + RUN_TEST(test_c_SelectionSort_IntArray); + RUN_TEST(test_c_SelectionSort_StructArray); + RUN_TEST(test_c_SelectionSort_EdgeAndStability); + + // 打印最终统计报告 + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Sort/c_ShellSort.c b/Sort/c_ShellSort.c new file mode 100644 index 0000000..9eed080 --- /dev/null +++ b/Sort/c_ShellSort.c @@ -0,0 +1,55 @@ +#include + + +void c_ShellSort(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args) { + // 边界与防御性校验:元素少于 2 个或参数非法时无需排序 + if (!base || num < 2 || size == 0 || !cmp) { + return; + } + + char* array_base = (char*)base; + + // 栈上开辟 256 字节单元素临时变量缓冲区,支持半交换平移,降低物理拷贝开销 + char v_buf[size]; + + // 1. 计算 Knuth 递增步长序列的最大安全上界 (1, 4, 13, 40, 121, ...) + // 步长必须严格小于 num,使用除法逆向校验,彻底防止 (3 * gap + 1) 无符号算术溢出 + c_size_t gap = 1; + while (gap <= (num - 1) / 3) { + gap = 3 * gap + 1; + } + + // 2. 核心跨步长交替排序状态机 + while (gap > 0) { + // 外层插入排序轮次:从无符号下标 gap 开始单调递增 + for (c_size_t i = gap; i < num; i++) { + char* item_i = array_base + (i * size); + + // 暂存当前待排元素:v = a[i] + memcpy(v_buf, item_i, size); + + c_size_t j = i; + + // 内层跨步长平移循环:严格控制 j >= gap,阻止无符号减法 (j - gap) 发生下溢回绕 + while (j >= gap) { + char* current = array_base + (j * size); + char* previous = array_base + ((j - gap) * size); + + // 如果暂存的 v_buf 小于前驱跨步长元素 previous,则前驱元素单向后移覆盖 + if (cmp(v_buf, previous, args) < 0) { + memcpy(current, previous, size); // a[j] = a[j-gap] + j -= gap; // 安全步进自减 + } else { + break; // 归位完成,阻断内层循环 + } + } + + // 将暂存数据写回最终安全插槽:a[j] = v + char* item_target = array_base + (j * size); + memcpy(item_target, v_buf, size); + } + + // 步长衰减 + gap /= 3; + } +} diff --git a/Sort/c_ShellSort.h b/Sort/c_ShellSort.h new file mode 100644 index 0000000..accb543 --- /dev/null +++ b/Sort/c_ShellSort.h @@ -0,0 +1,23 @@ +#ifndef INCLUDED_C_SHELLSORT_H +#define INCLUDED_C_SHELLSORT_H + +#ifndef INCLUDED_C_SORTCOMPARE_H +#include +#endif /*INCLUDED_C_SORTCOMPARE_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 工业级泛型希尔排序(基于 Knuth 步长序列:h = 3*h + 1,支持上下文参数 args) + * + * @param base 指向待排序连续数组首元素的指针 + * @param num 数组中元素的总个数 + * @param size 每个元素所占用的内存字节大小 (sizeof) + * @param cmp 带自定义上下文参数的比对回调函数指针 (不能为 NULL) + * @param args 传递给比对回调函数的自定义上下文参数指针 + */ +void c_ShellSort(void* base, c_size_t num, c_size_t size, c_SortCompare_t cmp, void* args); + +#endif /*INCLUDED_C_SHELLSORT_H*/ diff --git a/Sort/c_ShellSort.t.c b/Sort/c_ShellSort.t.c new file mode 100644 index 0000000..346d67a --- /dev/null +++ b/Sort/c_ShellSort.t.c @@ -0,0 +1,111 @@ +#include "c_ShellSort.h" +#include +#include +#include "c_Test.h" + +// ========================================== +// 1. 测试用例伴生:比对器与业务结构体定义 +// ========================================== +static int sort_compare_ints_with_args(const void* a, const void* b, void* args) { + (void)args; + int arg1 = *(const int*)a; + int arg2 = *(const int*)b; + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +typedef struct { + char name; // 任务标识符(字符型) + int age; // 任务时间周期 + int priority; // 任务优先级 +} Task_t; + +// 复合结构体多级比对器:优先比较优先级(降序),优先级相同时按年龄(升序) +static int sort_compare_tasks(const void* a, const void* b, void* args) { + (void)args; + const Task_t* t1 = (const Task_t*)a; + const Task_t* t2 = (const Task_t*)b; + + if (t1->priority != t2->priority) { + return t2->priority - t1->priority; + } + return t1->age - t2->age; +} + +// ========================================== +// 2. 自动化测试用例集 +// ========================================== +TEST_CASE(test_c_ShellSort_IntArray) { + // 大跨度无序、包含相同元素的经典整型测试集 + int arr[] = { 81, 94, 11, 96, 12, 35, 17, 95, 28, 58, 41, 75, 15 }; + c_size_t num = sizeof(arr) / sizeof(arr[0]); + + c_ShellSort(arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + + // 验证区间整体是否呈现绝对升序状态 + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(arr[i] <= arr[i + 1]); + } + ASSERT_INT_EQ(11, arr[0]); // 检查全局最小值 + ASSERT_INT_EQ(96, arr[num - 1]); // 检查全局最大值 +} + +TEST_CASE(test_c_ShellSort_StructArray) { + // 结构体多关键字字段级搬运重排测试 + Task_t tasks[] = { + { 'A', 30, 2 }, + { 'B', 25, 5 }, + { 'C', 45, 2 }, + { 'D', 18, 5 } + }; + c_size_t num = sizeof(tasks) / sizeof(tasks[0]); + + c_ShellSort(tasks, num, sizeof(Task_t), sort_compare_tasks, NULL); + + // 预期重排顺序:tasks[0] 应为 'D'(5级/18岁) -> tasks[1] 应为 'B'(5级/25岁) -> tasks[2] 应为 'A'(2级/30岁) + ASSERT_INT_EQ(5, tasks[0].priority); + ASSERT_INT_EQ(18, tasks[0].age); + ASSERT_INT_EQ('D', tasks[0].name); + + ASSERT_INT_EQ(5, tasks[1].priority); + ASSERT_INT_EQ(25, tasks[1].age); + ASSERT_INT_EQ('B', tasks[1].name); + + ASSERT_INT_EQ(2, tasks[2].priority); + ASSERT_INT_EQ(30, tasks[2].age); + ASSERT_INT_EQ('A', tasks[2].name); +} + +TEST_CASE(test_c_ShellSort_Security_And_Edges) { + // 强行推入完全逆序的极值数组,压测 `j -= gap` 的前置边界拦截逻辑 + int rev_arr[] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; + c_size_t num = sizeof(rev_arr) / sizeof(rev_arr[0]); + + c_ShellSort(rev_arr, num, sizeof(int), sort_compare_ints_with_args, NULL); + for (c_size_t i = 0; i < num - 1; i++) { + ASSERT_TRUE(rev_arr[i] < rev_arr[i + 1]); + } + + // 极端输入安全防御测试(验证单元素或非法参数下能安全防回绕并优雅退出) + int single_arr[] = { 777 }; + c_ShellSort(single_arr, 1, sizeof(int), sort_compare_ints_with_args, NULL); + c_ShellSort(NULL, 0, sizeof(int), sort_compare_ints_with_args, NULL); + ASSERT_INT_EQ(777, single_arr[0]); +} + +// ========================================== +// 3. 独立测试运行入口 +// ========================================== +int main(void) { + TEST_START(C_ShellSort_Isolated_TestSuite); + + // 顺序调度并运行专项测试集 + RUN_TEST(test_c_ShellSort_IntArray); + RUN_TEST(test_c_ShellSort_StructArray); + RUN_TEST(test_c_ShellSort_Security_And_Edges); + + TEST_REPORT(); + + RETURN_TEST_STATUS; +} diff --git a/Sort/c_SortCompare.c b/Sort/c_SortCompare.c new file mode 100644 index 0000000..2fa0ca8 --- /dev/null +++ b/Sort/c_SortCompare.c @@ -0,0 +1 @@ +#include diff --git a/Sort/c_SortCompare.h b/Sort/c_SortCompare.h new file mode 100644 index 0000000..f67dc58 --- /dev/null +++ b/Sort/c_SortCompare.h @@ -0,0 +1,14 @@ +#ifndef INCLUDED_C_SORTCOMPARE_H +#define INCLUDED_C_SORTCOMPARE_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef int (*c_SortCompare_t)(const void* a, const void* b, void* args); + +#endif /*INCLUDED_C_SORTCOMPARE_H*/