数据结构与算法学习(一)

文章目录

  • [一 基础知识](#一 基础知识)
    • [1 结构体struct 与类class](#1 结构体struct 与类class)
    • [2 函数与参数传递、指针与引用](#2 函数与参数传递、指针与引用)
    • [3 C++ STL](#3 C++ STL)
      • [3.1 vector](#3.1 vector)
      • [3.2 set](#3.2 set)
      • [3.3 map](#3.3 map)
      • [3.4 pair](#3.4 pair)
      • [3.5 algorithm](#3.5 algorithm)
  • [二 经典问题](#二 经典问题)
    • [1 排序、散列(hash)、二分查找](#1 排序、散列(hash)、二分查找)
      • [1.1 插入排序](#1.1 插入排序)
      • [1.2 冒泡排序](#1.2 冒泡排序)
      • [1.3 选择排序](#1.3 选择排序)
      • [1.4 希尔排序](#1.4 希尔排序)
      • [1.5 归并排序](#1.5 归并排序)
        • [1.5.1 基本概念](#1.5.1 基本概念)
      • [1.6 快速排序](#1.6 快速排序)
      • [1.7 堆排序](#1.7 堆排序)
        • [1.7.1 基本概念](#1.7.1 基本概念)
        • [1.7.2 核心数据结构 堆(Heap)](#1.7.2 核心数据结构 堆(Heap))
        • [1.7.3 算法原理](#1.7.3 算法原理)
        • [1.7.4 详细步骤演示](#1.7.4 详细步骤演示)
      • [1.8 桶排序](#1.8 桶排序)
        • [1.8.1 核心思想](#1.8.1 核心思想)
        • [1.8.2 C++实现实例](#1.8.2 C++实现实例)
      • [1.9 基数排序(Radix Sort)](#1.9 基数排序(Radix Sort))
        • [1.9.1 核心思想](#1.9.1 核心思想)
        • [1.9.2 LSD 基数排序算法步骤](#1.9.2 LSD 基数排序算法步骤)
    • [2 双指针(Two Pointers)](#2 双指针(Two Pointers))
      • [2.1 核心思想](#2.1 核心思想)
      • [2.2 常见模式分类](#2.2 常见模式分类)
      • [2.3 经典应用场景与代码示例](#2.3 经典应用场景与代码示例)
      • [2.4 综合应用与复杂场景](#2.4 综合应用与复杂场景)
      • [2.5 算法模板总结](#2.5 算法模板总结)
      • [2.6 常见陷阱与技巧](#2.6 常见陷阱与技巧)

一 基础知识

1 结构体struct 与类class

  • struct 和 class 中都可以声明或定义属性和方法
  • 默认权限不同(struct public/ class private)
  • 是否可用于声明模板(struct 不可以)

2 函数与参数传递、指针与引用

  • 数组作为参数时,第一维不需要填写长度,第二维需要;
  • 应用并没有对变量地址取副本,而是对原变量取了一个别名,对引用变量得操作即对原变量的操作;
  • 应用并不是取地址的意思,常量不可以使用引用。
cpp 复制代码
void swap(int* &p1, int* &p2)
// 指针变量的引用 int* p1 = &a;
// p1 是指针变量, &a 是地址常量(无符号整数)
// swap(p1, p2) 正确
// swap(a, b) 错误

3 C++ STL

3.1 vector

3.2 set

  • 特点:内部有序(自动递增)、元素不重复

3.3 map

  • 特点:映射,以键从小到大排序,键和值唯一
  • 常用于对字符串进行排序或判断字符串是否已经出现过

3.4 pair

  • 特点:二元结构体,方便比较(先比较第一个,再比较第二个)

3.5 algorithm

cpp 复制代码
max(x, max(y, z));
abs(x);
fabs(x);
swap(x, y);
reverse(it, it2); //数组元素反转
fill(a, a+5, 123);

二 经典问题

排序,将一组数按照从小到大排列

1 排序、散列(hash)、二分查找

1.1 插入排序

从第2个元素开始,将该元素和它前面额元素做大小比较,将该元素插入到合适的位置

1.2 冒泡排序

每一次比较相邻的两个元素,如果前面的数大于后面的数,就将两个数交换,第一轮先找到最大的,并放在最后,下一轮不需要找最后一个元素,直到完成整个数组的排序。

1.3 选择排序

第一轮从所有数中找到最小的,和第一个元素交换位置,下一轮从第二个元素开始,将剩下的所有元素中最小的和第二个元素交换位置,直到最后两个元素。

1.4 希尔排序

  • 希尔排序是插入排序的一种高效改进版本,也称为缩小增量排序。它通过将原始数组分割成多个子序列分别进行插入排序,然后逐步缩小增量,最终完成整个数组的排序。
  • 算法原理:
    1. 选择增量序列: 确定一个递减的增量序列
    2. 按增量分组: 根据当前增量将数组分成若干子序列
    3. 插入排序: 对每个子序列进行插入排序
    4. 减小增量: 重复上述过程,直至增量为1
cpp 复制代码
void shellSort(vector<int>& arr) {
    int n = arr.size();
    
    // 使用希尔增量序列:n/2, n/4, n/8, ..., 1
    for (int gap = n / 2; gap > 0; gap /= 2) {
        // 对每个子数组进行插入排序
        for (int i = gap; i < n; i++) {
            int temp = arr[i];
            int j;
            
            // 对子数组进行插入排序
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                arr[j] = arr[j - gap];
            }
            arr[j] = temp;
        }
    }
}

1.5 归并排序

1.5.1 基本概念
  • 归并排序(Merge Sort)是一种分治算法。核心思想是将大问题分解成小问题,解决小问题后再合并结果。
  • 主要特点:
    • 稳定排序:相等元素的相对顺序保持不变
    • 时间复杂度:始终为O(nlogn)
    • 空间复杂度:O(n) 需要额外的存储空间
    • 适合场景:大数据排序、链表排序、外部排序
  • 算法原理:
    1. 分解: 将数组递归地分成两半,直到每个子数组只有一个元素
    2. 解决: 单个元素的数据自然是有序的
    3. 合并: 将两个有序数组合并成一个有序数组。
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

// 合并两个有序数组
void merge(vector<int>& arr, int left, int mid, int right) {
    int n1 = mid - left + 1;  // 左半部分长度
    int n2 = right - mid;     // 右半部分长度
    
    // 创建临时数组
    vector<int> leftArr(n1);
    vector<int> rightArr(n2);
    
    // 拷贝数据到临时数组
    for (int i = 0; i < n1; i++)
        leftArr[i] = arr[left + i];
    for (int j = 0; j < n2; j++)
        rightArr[j] = arr[mid + 1 + j];
    
    // 合并临时数组到原数组
    int i = 0, j = 0, k = left;
    
    while (i < n1 && j < n2) {
        if (leftArr[i] <= rightArr[j]) {
            arr[k] = leftArr[i];
            i++;
        } else {
            arr[k] = rightArr[j];
            j++;
        }
        k++;
    }
    
    // 拷贝剩余元素
    while (i < n1) {
        arr[k] = leftArr[i];
        i++;
        k++;
    }
    
    while (j < n2) {
        arr[k] = rightArr[j];
        j++;
        k++;
    }
}

// 递归归并排序
void mergeSort(vector<int>& arr, int left, int right) {
    if (left >= right) return;  // 递归终止条件
    
    int mid = left + (right - left) / 2;  // 防止溢出
    
    // 递归分解
    mergeSort(arr, left, mid);      // 排序左半部分
    mergeSort(arr, mid + 1, right); // 排序右半部分
    
    // 合并结果
    merge(arr, left, mid, right);
}

// 封装函数
void mergeSort(vector<int>& arr) {
    if (arr.size() <= 1) return;
    mergeSort(arr, 0, arr.size() - 1);
}

// 测试
int main() {
    vector<int> arr = {12, 11, 13, 5, 6, 7, 9, 2, 8};
    
    cout << "原始数组: ";
    for (int num : arr) cout << num << " ";
    cout << endl;
    
    mergeSort(arr);
    
    cout << "排序后数组: ";
    for (int num : arr) cout << num << " ";
    cout << endl;
    
    return 0;
}
  • 适合使用归并排序的情况
    1. 需要稳定排序: 保持相等元素的相对顺序
    2. 大数据排序: 时间复杂稳定度为O(nlogn)
    3. 外部排序: 数据太大无法全部装入内存
    4. 链表排序: 不需要额外空间合并
    5. 并行计算: 容易实现并行化
  • 核心原理
    • 合并过程就像两个人在两个队列中挑选按:
      1. 总是比较两个数组当前的"最小元素"
      2. 选择更小的那个放入结果
      3. 移动指针继续比较

1.6 快速排序

  • 实际上就是选取一个pivot,将比pivot小的元素放在它的左侧,比它大的元素放在它的右侧;然后对左右侧子序列递归地进行上述过程。
  • 适合使用快速排序
    1. 通用排序: 大多数情况下最快
    2. 内存受限: 原地排序,空间复杂度低
    3. 大数据集: 平均性能优秀
    4. 随机数据: 对随机数据性能极佳
  • 不适合的情况
    1. 需要稳定排序: 快速排序不稳定
    2. 几乎有序的数据: 可能退化为O(n2)
  • 下面例子得partition函数中得i、j还用到了双指针中的快慢指针概念
cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// Lomuto分区方案
int partition(vector<int>& arr, int low, int high) {
    int pivot = arr[high];  // 选择最后一个元素作为基准
    int i = low - 1;        // 小于基准元素的边界
    
    for (int j = low; j < high; j++) {
        // 如果当前元素小于或等于基准
        if (arr[j] <= pivot) {
            i++;
            swap(arr[i], arr[j]);
        }
    }
    
    // 将基准放到正确位置
    swap(arr[i + 1], arr[high]);
    return i + 1;  // 返回基准的最终位置
}

// 快速排序主函数
void quickSort(vector<int>& arr, int low, int high) {
    if (low < high) {
        // pi是分区索引,arr[pi]现在在正确位置
        int pi = partition(arr, low, high);
        
        // 递归排序分区前后的元素
        quickSort(arr, low, pi - 1);   // 排序左半部分
        quickSort(arr, pi + 1, high);  // 排序右半部分
    }
}

// 包装函数
void quickSort(vector<int>& arr) {
    if (arr.size() <= 1) return;
    quickSort(arr, 0, arr.size() - 1);
}

// 测试
int main() {
    vector<int> arr = {10, 7, 8, 9, 1, 5, 3, 6, 2, 4};
    
    cout << "原始数组: ";
    for (int num : arr) cout << num << " ";
    cout << endl;
    
    quickSort(arr);
    
    cout << "排序后数组: ";
    for (int num : arr) cout << num << " ";
    cout << endl;
    
    return 0;
}

1.7 堆排序

1.7.1 基本概念
  • 堆排序(Heap Sort)是一种基于二叉堆数据结构的比较排序算法。它结合了插入排序和归并排序的有点,具有原地排序和时间复杂度稳定的特点。
  • 主要特点
    • 时间复杂度:O(nlogn) 最好、最坏、平均都是
    • 空间复杂度:O(1)原地排序
    • 不稳定排序:相等元素的相对顺序可能改变
    • 不需要递归:适合大数据排序
1.7.2 核心数据结构 堆(Heap)
  • 堆是一种完全二叉树,有两种类型:
  1. 大堆顶 Max Heap

    • 父节点的值 >= 子节点的值

    • 根节点是最大值

    • 用于升序排序

      复制代码
        100
       /   \

      19 36
      / \ /
      17 3 25

  2. 小堆顶 Min Heap

    • 父节点的值 <= 子节点的值
    • 根节点是最小值
    • 用于降序排序
1.7.3 算法原理

堆排序分为两个主要阶段

  1. 建堆(Build Heap)
    • 将无序数组构成一个大顶堆(小顶堆)
  2. 排序(Sort)
    • 将堆顶(最大值,如果是升序)与最后一个元素交换
    • 堆大小减一,对新的堆顶进行"堆化"(heapify)
    • 重复上述过程直到堆大小为1
1.7.4 详细步骤演示

以数组[4, 10, 3, 5, 1]为例

  1. 步骤1 构建大顶堆
cpp 复制代码
原始数组:[4, 10, 3, 5, 1]
表示为完全二叉树:
        4
       / \
      10  3
     / \
    5   1

建堆过程:
1. 最后一个非叶子节点是10(索引1)
2. 堆化:10 > 5 且 10 > 1,不需要交换

        4
       / \
      10  3
     / \
    5   1

3. 处理节点4(索引0)
   - 比较:4 < 10(左子节点)
   - 交换4和10:
    
        10
       / \
      4   3
     / \
    5   1

4. 递归堆化子树(节点4)
   - 比较:4 < 5(左子节点)
   - 交换4和5:
    
        10
       / \
      5   3
     / \
    4   1

最终大顶堆:[10, 5, 3, 4, 1]
  1. 步骤2 排序阶段
cpp 复制代码
堆数组:[10, 5, 3, 4, 1]  堆大小=5

第1轮:
- 交换堆顶(10)和末尾(1):[1, 5, 3, 4, 10]
- 堆化根节点(1):
  1 < 5,交换1和5:[5, 1, 3, 4, 10]
  1 < 4,交换1和4:[5, 4, 3, 1, 10]
  堆大小减1:4

第2轮:
- 交换堆顶(5)和末尾(1):[1, 4, 3, 5, 10]
- 堆化根节点(1):
  1 < 4,交换1和4:[4, 1, 3, 5, 10]
  堆大小减1:3

第3轮:
- 交换堆顶(4)和末尾(3):[3, 1, 4, 5, 10]
- 堆化根节点(3):
  3 > 1 且 3 > ? (右子节点不存在)
  堆大小减1:2

第4轮:
- 交换堆顶(3)和末尾(1):[1, 3, 4, 5, 10]
- 堆大小减1:1

最终排序结果:[1, 3, 4, 5, 10]

1.8 桶排序

  • **桶排序(Bucket Sort)是一种分布式排序算法,它将待排序元素分配到有限数量的"桶"中,然后对每个桶单独排序,最后按顺序合并所有桶。
1.8.1 核心思想
  • 桶排序基于这样一个假设:输入数据均匀分布在一个区间内。它将这个范围划分为若干个大小相等的子区间(桶),然后将元素分配到对应的桶中。
  • 基本步骤:
    1. 分桶: 创建空桶,将元素分配到对应的桶中。
    2. 桶内排序: 对每个非空桶内的元素进行排序
    3. 合并: 按顺序将各个桶中的元素合并起来
1.8.2 C++实现实例
  • 处理[0,1)之间的小数
cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

// 桶排序函数,假设输入在[0,1)范围内
void bucketSort(std::vector<float>& arr) {
    int n = arr.size();
    
    // 1. 创建n个空桶
    std::vector<std::vector<float>> buckets(n);
    
    // 2. 将元素分配到桶中
    for (int i = 0; i < n; i++) {
        int bucketIndex = n * arr[i]; // 计算桶索引
        buckets[bucketIndex].push_back(arr[i]);
    }
    
    // 3. 对每个桶排序(使用插入排序或其他稳定排序)
    for (int i = 0; i < n; i++) {
        std::sort(buckets[i].begin(), buckets[i].end());
    }
    
    // 4. 合并桶
    int index = 0;
    for (int i = 0; i < n; i++) {
        for (float num : buckets[i]) {
            arr[index++] = num;
        }
    }
}

// 测试
int main() {
    std::vector<float> arr = {0.78, 0.17, 0.39, 0.26, 0.72, 0.94, 0.21, 0.12, 0.23, 0.68};
    
    std::cout << "排序前: ";
    for (float num : arr) std::cout << num << " ";
    std::cout << std::endl;
    
    bucketSort(arr);
    
    std::cout << "排序后: ";
    for (float num : arr) std::cout << num << " ";
    std::cout << std::endl;
    
    return 0;
}

1.9 基数排序(Radix Sort)

  • 基数排序是一种非比较型的整数排序算法,它按照数字的每一位(从低位到高位或从高位到低位)进行排序。
1.9.1 核心思想
  • 基本原理:
    1. 按位排序:从最低位(LSD)或最高位(MSD)开始,对每一位进行排序
    2. 稳定性:每一轮的排序必须是稳定的(保持相对元素的相对顺序)
    3. 多趟排序:需要多趟排序,每趟处理一位数字
  • 主要方法:
    • LSD(Least Significant Digit) : 从最低位开始排序
    • MSD(Most Significant Dight) :从最高位开始排序(类似字典序)
1.9.2 LSD 基数排序算法步骤
复制代码
原始数组:[170, 45, 75, 90, 802, 24, 2, 66]

第1趟(个位):
  桶0: 170, 90
  桶2: 802, 2
  桶4: 24
  桶5: 45, 75
  桶6: 66
  合并:170, 90, 802, 2, 24, 45, 75, 66

第2趟(十位):
  桶0: 802, 2
  桶2: 24
  桶4: 45
  桶6: 66
  桶7: 170, 75
  桶9: 90
  合并:802, 2, 24, 45, 66, 170, 75, 90

第3趟(百位):
  桶0: 2, 24, 45, 66, 75, 90
  桶1: 170
  桶8: 802
  合并:2, 24, 45, 66, 75, 90, 170, 802

最终排序完成!

2 双指针(Two Pointers)

  • 双指针是一种在数组、链表或字符串中通过使用两个指针协同工作来解决问题的算法技巧

2.1 核心思想

  • 使用两个指针(索引)以不同的速度、方向或条件在数据结构中移动,从而:
    1. 减少时间复杂度(常从O(n2)降低到O(n))
    2. 减少空间复杂度(常从O(n)降低到O(1))
    3. 简化问题逻辑

2.2 常见模式分类

  1. 同向双指针(快慢指针)
  2. 对撞指针(相向指针)
  3. 滑动窗口

2.3 经典应用场景与代码示例

  • 同向双指针(快慢指针)
    1. 移除有序数组中的重复元素
    2. 移除元素(指定值)
    3. 判断链表是否有环
    4. 找到链表的中间节点
  • 对撞指针
    1. 两数之和(有序数组)
    2. 三数之和
cpp 复制代码
// LeetCode 15: 三数之和
vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> result;
    sort(nums.begin(), nums.end());  // 先排序
    
    for (int i = 0; i < nums.size(); i++) {
        // 跳过重复元素
        if (i > 0 && nums[i] == nums[i - 1]) continue;
        
        int target = -nums[i];
        int left = i + 1, right = nums.size() - 1;
        
        while (left < right) {
            int sum = nums[left] + nums[right];
            
            if (sum == target) {
                result.push_back({nums[i], nums[left], nums[right]});
                
                // 跳过重复元素
                while (left < right && nums[left] == nums[left + 1]) left++;
                while (left < right && nums[right] == nums[right - 1]) right--;
                
                left++;
                right--;
            } else if (sum < target) {
                left++;
            } else {
                right--;
            }
        }
    }
    
    return result;
}
复制代码
3. 验证回文串
cpp 复制代码
// LeetCode 125: 验证回文串
bool isPalindrome(string s) {
    int left = 0, right = s.length() - 1;
    
    while (left < right) {
        // 跳过非字母数字字符
        while (left < right && !isalnum(s[left])) left++;
        while (left < right && !isalnum(s[right])) right--;
        
        // 比较(忽略大小写)
        if (tolower(s[left]) != tolower(s[right])) {
            return false;
        }
        
        left++;
        right--;
    }
    
    return true;
}
复制代码
4. 盛最多水的容器
cpp 复制代码
    // LeetCode 11: 盛最多水的容器
int maxArea(vector<int>& height) {
    int left = 0, right = height.size() - 1;
    int maxWater = 0;
    
    while (left < right) {
        int width = right - left;
        int h = min(height[left], height[right]);
        maxWater = max(maxWater, width * h);
        
        // 移动较矮的那边(因为高度受限于较矮的)
        if (height[left] < height[right]) {
            left++;
        } else {
            right--;
        }
    }
    
    return maxWater;
}

// 示例:
// 输入: height = [1,8,6,2,5,4,8,3,7]
// 输出: 49
// 解释: 第2根(8)和第9根(7)之间:宽度7,高度7,面积49
  • 滑动窗口
    1. 无重复字符的最长子串
cpp 复制代码
// LeetCode 3: 无重复字符的最长子串
int lengthOfLongestSubstring(string s) {
    unordered_set<char> window;
    int left = 0, maxLen = 0;
    
    for (int right = 0; right < s.length(); right++) {
        // 如果字符重复,移动左指针直到不重复
        while (window.count(s[right])) {
            window.erase(s[left]);
            left++;
        }
        
        window.insert(s[right]);
        maxLen = max(maxLen, right - left + 1);
    }
    
    return maxLen;
}

2.4 综合应用与复杂场景

  1. 合并两个有序数组
  2. 接雨水
cpp 复制代码
// LeetCode 42: 接雨水
int trap(vector<int>& height) {
    if (height.empty()) return 0;
    
    int left = 0, right = height.size() - 1;
    int leftMax = 0, rightMax = 0;
    int water = 0;
    
    while (left < right) {
        if (height[left] < height[right]) {
            if (height[left] >= leftMax) {
                leftMax = height[left];
            } else {
                water += leftMax - height[left];
            }
            left++;
        } else {
            if (height[right] >= rightMax) {
                rightMax = height[right];
            } else {
                water += rightMax - height[right];
            }
            right--;
        }
    }
    
    return water;
}
  1. 颜色分类(芬兰国旗问题)
cpp 复制代码
// LeetCode 75: 颜色分类
void sortColors(vector<int>& nums) {
    int low = 0, mid = 0, high = nums.size() - 1;
    
    // 三指针法
    while (mid <= high) {
        if (nums[mid] == 0) {
            swap(nums[low], nums[mid]);
            low++;
            mid++;
        } else if (nums[mid] == 1) {
            mid++;
        } else {  // nums[mid] == 2
            swap(nums[mid], nums[high]);
            high--;
            // 注意:这里不移动mid,因为交换过来的元素还未检查
        }
    }
}

2.5 算法模板总结

  • 快慢指针(数组去重)
cpp 复制代码
int slow = 0;
for (int fast = 0; fast < n; fast++) {
    if (满足条件) {
        nums[slow] = nums[fast];
        slow++;
    }
}
return slow;  // 新长度
  • 对撞指针(有序数组)
cpp 复制代码
int left = 0, right = n - 1;
while (left < right) {
    if (条件满足) {
        // 处理结果
        left++;
        right--;
    } else if (条件1) {
        left++;
    } else {
        right--;
    }
}
  • 滑动窗口(子串问题)
cpp 复制代码
int left = 0, right = 0;
while (right < n) {
    // 扩大窗口
    window.add(s[right]);
    right++;
    
    while (窗口需要收缩) {
        // 收缩窗口
        window.remove(s[left]);
        left++;
    }
}

2.6 常见陷阱与技巧

  1. 指针移动条件
cpp 复制代码
// 错误示例:容易造成死循环
while (left < right) {
    if (条件) {
        left++;  // 可能永远不移动right
    }
}

// 正确:确保至少有一个指针移动
while (left < right) {
    if (条件1) {
        left++;
    } else if (条件2) {
        right--;
    } else {
        // 至少移动一个
        left++;  
    }
}
  1. 边界条件处理
cpp 复制代码
// 空数组/链表
if (nums.empty()) return 0;

// 单个元素
if (nums.size() == 1) return 1;

// 链表判空
if (!head || !head->next) return nullptr;
  1. 去重技巧
cpp 复制代码
// 在结果集中去重
result.push_back({a, b, c});
while (left < right && nums[left] == nums[left + 1]) left++;
while (left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
  • 双指针是面试和算法竞赛中的高频考点,掌握这三种模式及其变体,能解决大量数组和链表相关的问题。关键是多练习,培养指针移动的直觉。
相关推荐
CodeOfCC2 小时前
C++ 基于kmp解析nalu
c++·音视频·实时音视频·h.265·h.264
Sheep Shaun2 小时前
STL中的map和set:红黑树的优雅应用
开发语言·数据结构·c++·后端·c#
week_泽2 小时前
OCR学习笔记,调用免费百度api
笔记·学习·ocr
叫我莫言鸭3 小时前
关于word生成报告的POI学习2循环标题内容
java·学习·word
1001101_QIA3 小时前
【C++笔试题】递归判断数组是否是递增数组
开发语言·c++
秦明月133 小时前
EPLAN电气设计:图层导入与导出操作指南
数据库·经验分享·学习·学习方法·设计规范
小粉粉hhh4 小时前
记录前端菜鸟的日常——实现类似学习通的答题界面
学习
qq_401700414 小时前
C/C++中的signed char和unsigned char详解
c语言·c++·算法
无限进步_4 小时前
【C语言】循环队列的两种实现:数组与链表的对比分析
c语言·开发语言·数据结构·c++·leetcode·链表·visual studio