算法入门<一>:C++各种排序算法详解及示例源码

news2024/12/25 9:24:40

1、排序算法

  排序算法(sorting algorithm)用于对一组数据按照特定顺序进行排列。排序算法有着广泛的应用,因为有序数据通常能够被更高效地查找、分析和处理。

1.1 评价维度

  运行效率:我们期望排序算法的时间复杂度尽量低,且总体操作数量较少(时间复杂度中的常数项变小)。对于大数据量的情况,运行效率显得尤为重要。

  就地性:顾名思义,原地排序通过在原数组上直接操作实现排序,无须借助额外的辅助数组,从而节省内存。通常情况下,原地排序的数据搬运操作较少,运行速度也更快。

  稳定性:稳定排序在完成排序后,相等元素在数组中的相对顺序不发生改变。

  稳定排序是多级排序场景的必要条件。假设我们有一个存储学生信息的表格,第 1 列和第 2 列分别是姓名和年龄。在这种情况下,非稳定排序可能导致输入数据的有序性丧失:

//输入数据是按照姓名排序好的
// (name, age)
  ('A', 19)
  ('B', 18)
  ('C', 21)
  ('D', 19)
  ('E', 23)
//假设使用非稳定排序算法按年龄排序列表,
//结果中 ('D', 19) 和 ('A', 19) 的相对位置改变,
//输入数据按姓名排序的性质丢失
  ('B', 18)
  ('D', 19)
  ('A', 19)
  ('C', 21)
  ('E', 23)

  自适应性:自适应排序的时间复杂度会受输入数据的影响,即最佳时间复杂度、最差时间复杂度、平均时间复杂度并不完全相等。

  自适应性需要根据具体情况来评估。如果最差时间复杂度差于平均时间复杂度,说明排序算法在某些数据下性能可能劣化,因此被视为负面属性;而如果最佳时间复杂度优于平均时间复杂度,则被视为正面属性。

  是否基于比较:基于比较的排序依赖比较运算符来判断元素的相对顺序,从而排序整个数组,理论最优时间复杂度为 O(nlogn) 。而非比较排序不使用比较运算符,时间复杂度可达 O(n),但其通用性相对较差。

1.2 理想排序算法

  运行快、原地、稳定、正向自适应、通用性好。显然,迄今为止尚未发现兼具以上所有特性的排序算法。因此,在选择排序算法时,需要根据具体的数据特点和问题需求来决定。接下来,我们将共同学习各种排序算法,并基于上述评价维度对各个排序算法的优缺点进行分析。

2、排序算法类别

2.1 选择排序

  选择排序(selection sort)的工作原理非常简单:开启一个循环,每轮从未排序区间选择最小的元素,将其放到已排序区间的末尾。时间复杂度为 O(n²),空间复杂度为O(1)
设数组的长度为 n,选择排序的算法流程如下所示:
1)初始状态下,所有元素未排序,即未排序(索引)区间为[0,n-1] 。
2)选取区间 [0,n-1] 中的最小元素,将其与索引 0处的元素交换。完成后,数组前 1 个元素已排序。
3)选取区间[1,n-1] 中的最小元素,将其与索引 1处的元素交换。完成后,数组前 2 个元素已排序。
4)以此类推。经过 n-1轮选择与交换后,数组前 n-1个元素已排序。
5)仅剩的一个元素必定是最大元素,无须排序,因此数组排序完成。

/* 选择排序 */
void selectionSort(vector<int> &nums) {
    int n = nums.size();
    // 外循环:未排序区间为 [i, n-1]
    for (int i = 0; i < n - 1; i++) {
        // 内循环:找到未排序区间内的最小元素
        int k = i;
        for (int j = i + 1; j < n; j++) {
            if (nums[j] < nums[k])
                k = j; // 记录最小元素的索引
        }
        // 将该最小元素与未排序区间的首个元素交换
        swap(nums[i], nums[k]);
    }
}

2.2 冒泡排序

  冒泡排序(bubble sort)通过连续地比较与交换相邻元素实现排序。这个过程就像气泡从底部升到顶部一样,因此得名冒泡排序。时间复杂度为 O(n²),但当输入数组完全有序时,可达到最佳时间复杂度 O(n),空间复杂度为O(1)

设数组的长度为 n,冒泡排序的算法流程如下所示:
1)首先,对 n 个元素执行“冒泡”,将数组的最大元素交换至正确位置 。
2)接下来,对剩余 n-1个元素执行“冒泡”,将第二大元素交换至正确位置。
3)以此类推,经过 n-1轮“冒泡”后,前 n-1 大的元素都被交换至正确位置。
4)仅剩的一个元素必定是最小元素,无须排序,因此数组排序完成。

/* 冒泡排序(标志优化)*/
void bubbleSortWithFlag(vector<int> &nums) {
    // 外循环:未排序区间为 [0, i]
    for (int i = nums.size() - 1; i > 0; i--) {
        bool flag = false; // 初始化标志位
        // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端
        for (int j = 0; j < i; j++) {
            if (nums[j] > nums[j + 1]) {
                // 交换 nums[j] 与 nums[j + 1]
                // 这里使用了 std::swap() 函数
                swap(nums[j], nums[j + 1]);
                flag = true; // 记录交换元素
            }
        }
        if (!flag)
            break; // 此轮“冒泡”未交换任何元素,直接跳出
    }
}

2.3 插入排序

  插入排序(insertion sort)是一种简单的排序算法,它的工作原理与手动整理一副牌的过程非常相似。具体来说,我们在未排序区间选择一个基准元素,将该元素与其左侧已排序区间的元素逐一比较大小,并将该元素插入到正确的位置。时间复杂度为 O(n²),但当输入数组完全有序时,可达到最佳时间复杂度 O(n),空间复杂度为O(1)

  实际上,许多编程语言(例如 Java)的内置排序函数采用了插入排序,大致思路为:对于长数组,采用基于分治策略的排序算法,例如快速排序;对于短数组,直接使用插入排序

设数组的长度为 n,插入排序的算法流程如下所示:
1)初始状态下,数组的第 1 个元素已完成排序。
2)选取数组的第 2 个元素作为 base ,将其插入到正确位置后,数组的前 2 个元素已排序。
3)选取第 3 个元素作为 base ,将其插入到正确位置后,数组的前 3 个元素已排序。
4)以此类推,在最后一轮中,选取最后一个元素作为 base ,将其插入到正确位置后,所有元素均已排序。

/* 插入排序 */
void insertionSort(vector<int> &nums) {
    // 外循环:已排序区间为 [0, i-1]
    for (int i = 1; i < nums.size(); i++) {
        int base = nums[i], j = i - 1;
        // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置
        while (j >= 0 && nums[j] > base) {
            nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位
            j--;
        }
        nums[j + 1] = base; // 将 base 赋值到正确位置
    }
}

2.4 快速排序

  快速排序(quick sort)是一种基于分治策略的排序算法,运行高效,应用广泛。快速排序的核心操作是“哨兵划分”,其目标是:选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧。时间复杂度为 O(nlogn),空间复杂度为O(n)

  快速排序为什么快? 从名称上就能看出,快速排序在效率方面应该具有一定的优势。尽管快速排序的平均时间复杂度与“归并排序”和“堆排序”相同,但通常快速排序的效率更高,主要有以下原因。

  • 出现最差情况的概率很低:虽然快速排序的最差时间复杂度为 O(n²),没有归并排序稳定,但在绝大多数情况下,快速排序能在 O(nlogn) 的时间复杂度下运行。
  • 缓存使用效率高:在执行哨兵划分操作时,系统可将整个子数组加载到缓存,因此访问元素的效率较高。而像“堆排序”这类算法需要跳跃式访问元素,从而缺乏这一特性。
  • 复杂度的常数系数小:在上述三种算法中,快速排序的比较、赋值、交换等操作的总数量最少。这与“插入排序”比“冒泡排序”更快的原因类似。

设数组的长度为 n,快速排序的算法流程如下所示:
1)首先,对原数组执行一次“哨兵划分”,得到未排序的左子数组和右子数组。
2)然后,对左子数组和右子数组分别递归执行“哨兵划分”。
3)持续递归,直至子数组长度为 1 时终止,从而完成整个数组的排序。

/* 选取三个候选元素的中位数 */
int medianThree(vector<int> &nums, int left, int mid, int right) {
    int l = nums[left], m = nums[mid], r = nums[right];
    if ((l <= m && m <= r) || (r <= m && m <= l))
        return mid; // m 在 l 和 r 之间
    if ((m <= l && l <= r) || (r <= l && l <= m))
        return left; // l 在 m 和 r 之间
    return right;
}

/* 哨兵划分(三数取中值) */
int partition(vector<int> &nums, int left, int right) {
    // 选取三个候选元素的中位数
    int med = medianThree(nums, left, (left + right) / 2, right);
    // 将中位数交换至数组最左端
    swap(nums, left, med);
    // 以 nums[left] 为基准数
    int i = left, j = right;
    while (i < j) {
        while (i < j && nums[j] >= nums[left])
            j--; // 从右向左找首个小于基准数的元素
        while (i < j && nums[i] <= nums[left])
            i++;          // 从左向右找首个大于基准数的元素
        swap(nums, i, j); // 交换这两个元素
    }
    swap(nums, i, left); // 将基准数交换至两子数组的分界线
    return i;            // 返回基准数的索引
}

/* 快速排序(尾递归优化) */
void quickSort(vector<int> &nums, int left, int right) {
    // 子数组长度为 1 时终止
    while (left < right) {
        // 哨兵划分操作
        int pivot = partition(nums, left, right);
        // 对两个子数组中较短的那个执行快速排序
        if (pivot - left < right - pivot) {
            quickSort(nums, left, pivot - 1); // 递归排序左子数组
            left = pivot + 1;                 // 剩余未排序区间为 [pivot + 1, right]
        } else {
            quickSort(nums, pivot + 1, right); // 递归排序右子数组
            right = pivot - 1;                 // 剩余未排序区间为 [left, pivot - 1]
        }
    }
}

2.5 归并排序

  归并排序(merge sort)是一种基于分治策略的排序算法,包含图 11-10 所示的“划分”和“合并”阶段。划分阶段:通过递归不断地将数组从中点处分开,将长数组的排序问题转换为短数组的排序问题。合并阶段:当子数组长度为 1 时终止划分,开始合并,持续地将左右两个较短的有序数组合并为一个较长的有序数组,直至结束。时间复杂度为 O(nlogn),空间复杂度为O(n)

划分阶段”从顶至底递归地将数组从中点切分为两个子数组:
1)计算数组中点 mid ,递归划分左子数组(区间[left, mid])和右子数组(区间[mid+1, right] )。
2)递归执行步骤 1. ,直至子数组区间长度为 1 时终止。
合并阶段”从底至顶地将左子数组和右子数组合并为一个有序数组。需要注意的是,从长度为 1 的子数组开始合并,合并阶段中的每个子数组都是有序的。

/* 合并左子数组和右子数组 */
void merge(vector<int> &nums, int left, int mid, int right) {
    // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right]
    // 创建一个临时数组 tmp ,用于存放合并后的结果
    vector<int> tmp(right - left + 1);
    // 初始化左子数组和右子数组的起始索引
    int i = left, j = mid + 1, k = 0;
    // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中
    while (i <= mid && j <= right) {
        if (nums[i] <= nums[j])
            tmp[k++] = nums[i++];
        else
            tmp[k++] = nums[j++];
    }
    // 将左子数组和右子数组的剩余元素复制到临时数组中
    while (i <= mid) {
        tmp[k++] = nums[i++];
    }
    while (j <= right) {
        tmp[k++] = nums[j++];
    }
    // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间
    for (k = 0; k < tmp.size(); k++) {
        nums[left + k] = tmp[k];
    }
}

/* 归并排序 */
void mergeSort(vector<int> &nums, int left, int right) {
    // 终止条件
    if (left >= right)
        return; // 当子数组长度为 1 时终止递归
    // 划分阶段
    int mid = (left + right) / 2;    // 计算中点
    mergeSort(nums, left, mid);      // 递归左子数组
    mergeSort(nums, mid + 1, right); // 递归右子数组
    // 合并阶段
    merge(nums, left, mid, right);
}

2.6 堆排序

  堆排序(heap sort)是一种基于堆数据结构实现的高效排序算法。我们可以利用已经学过的“建堆操作”和“元素出堆操作”实现堆排序。时间复杂度为 O(n²),但当输入数组完全有序时,可达到最佳时间复杂度 O(n),空间复杂度为O(1)

设数组的长度为 n,堆排序的算法流程如下所示:
1)输入数组并建立大顶堆。完成后,最大元素位于堆顶。
2)将堆顶元素(第一个元素)与堆底元素(最后一个元素)交换。完成交换后,堆的长度减 1,已排序元素数量加1 。
3)从堆顶元素开始,从顶到底执行堆化操作(sift down)。完成堆化后,堆的性质得到修复。
4)循环执行第 2. 步和第 3. 步。循环 n-1轮后,即可完成数组排序。

/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */
void siftDown(vector<int> &nums, int n, int i) {
    while (true) {
        // 判断节点 i, l, r 中值最大的节点,记为 ma
        int l = 2 * i + 1;
        int r = 2 * i + 2;
        int ma = i;
        if (l < n && nums[l] > nums[ma])
            ma = l;
        if (r < n && nums[r] > nums[ma])
            ma = r;
        // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出
        if (ma == i) {
            break;
        }
        // 交换两节点
        swap(nums[i], nums[ma]);
        // 循环向下堆化
        i = ma;
    }
}

/* 堆排序 */
void heapSort(vector<int> &nums) {
    // 建堆操作:堆化除叶节点以外的其他所有节点
    for (int i = nums.size() / 2 - 1; i >= 0; --i) {
        siftDown(nums, nums.size(), i);
    }
    // 从堆中提取最大元素,循环 n-1 轮
    for (int i = nums.size() - 1; i > 0; --i) {
        // 交换根节点与最右叶节点(交换首元素与尾元素)
        swap(nums[0], nums[i]);
        // 以根节点为起点,从顶至底进行堆化
        siftDown(nums, i, 0);
    }
}

2.7 桶排序

  桶排序(bucket sort)是分治策略的一个典型应用。它通过设置一些具有大小顺序的桶,每个桶对应一个数据范围,将数据平均分配到各个桶中;然后,在每个桶内部分别执行排序;最终按照桶的顺序将所有数据合并。时间复杂度为 O(n+k),空间复杂度为O(n+k)

考虑一个长度为 n的数组,其元素是范围 [0,1)内的浮点数,桶排序的算法流程如下所示:
1)初始化 k个桶,将 n个元素分配到 k 个桶中。
2)对每个桶分别执行排序(这里采用编程语言的内置排序函数) 。
3)按照桶从小到大的顺序合并结果。

/* 桶排序 */
void bucketSort(vector<float> &nums) {
    // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素
    int k = nums.size() / 2;
    vector<vector<float>> buckets(k);
    // 1. 将数组元素分配到各个桶中
    for (float num : nums) {
        // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1]
        int i = num * k;
        // 将 num 添加进桶 bucket_idx
        buckets[i].push_back(num);
    }
    // 2. 对各个桶执行排序
    for (vector<float> &bucket : buckets) {
        // 使用内置排序函数,也可以替换成其他排序算法
        sort(bucket.begin(), bucket.end());
    }
    // 3. 遍历桶合并结果
    int i = 0;
    for (vector<float> &bucket : buckets) {
        for (float num : bucket) {
            nums[i++] = num;
        }
    }
}

2.8 计数排序

  计数排序(counting sort)通过统计元素数量来实现排序,通常应用于整数数组。时间复杂度为 O(n+m),空间复杂度为O(n+m)

给定一个长度为 n的数组 nums ,其中的元素都是“非负整数”,计数排序的算法流程如下所示:
1)遍历数组,找出其中的最大数字,记为 m,然后创建一个长度为 m+1的辅助数组counter 。
2)借助 counter 统计 nums 中各数字的出现次数,其中 counter[num] 对应数字 num 的出现次数。统计方法很简单,只需遍历 nums(设当前数字为 num),每轮将 counter[num] 增加 1
即可 。
3)由于 counter 的各个索引天然有序,因此相当于所有数字已经排序好了。接下来,我们遍历 counter ,根据各数字出现次数从小到大的顺序填入 nums 即可。

/* 计数排序 */
// 简单实现,无法用于排序对象
void countingSortNaive(vector<int> &nums) {
    // 1. 统计数组最大元素 m
    int m = 0;
    for (int num : nums) {
        m = max(m, num);
    }
    // 2. 统计各数字的出现次数
    // counter[num] 代表 num 的出现次数
    vector<int> counter(m + 1, 0);
    for (int num : nums) {
        counter[num]++;
    }
    // 3. 遍历 counter ,将各元素填入原数组 nums
    int i = 0;
    for (int num = 0; num < m + 1; num++) {
        for (int j = 0; j < counter[num]; j++, i++) {
            nums[i] = num;
        }
    }
}

  计数排序只适用于非负整数。若想将其用于其他类型的数据,需要确保这些数据可以转换为非负整数,并且在转换过程中不能改变各个元素之间的相对大小关系。例如,对于包含负数的整数数组,可以先给所有数字加上一个常数,将全部数字转化为正数,排序完成后再转换回去。

  计数排序适用于数据量大但数据范围较小的情况。比如,在上述示例中 m不能太大,否则会占用过多空间。而当 n<<m 时,计数排序使用O(m) 时间,可能比O(nlogn) 的排序算法还要慢。

2.9 基数排序

  基数排序(radix sort)的核心思想与计数排序一致,也通过统计个数来实现排序。在此基础上,基数排序利用数字各位之间的递进关系,依次对每一位进行排序,从而得到最终的排序结果。时间复杂度为 O(nk),空间复杂度为O(n+d)

以学号数据为例,假设数字的最低位是第1位,最高位是第8位,基数排序的算法流程如下所示:
1)初始化位数 k=1。
2)对学号的第k位执行“计数排序”。完成后,数据会根据第k位从小到大排序 。
3)将k增加 1,然后返回步骤 2. 继续迭代,直到所有位都排序完成后结束。

/* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */
int digit(int num, int exp) {
    // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算
    return (num / exp) % 10;
}

/* 计数排序(根据 nums 第 k 位排序) */
void countingSortDigit(vector<int> &nums, int exp) {
    // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组
    vector<int> counter(10, 0);
    int n = nums.size();
    // 统计 0~9 各数字的出现次数
    for (int i = 0; i < n; i++) {
        int d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d
        counter[d]++;                // 统计数字 d 的出现次数
    }
    // 求前缀和,将“出现个数”转换为“数组索引”
    for (int i = 1; i < 10; i++) {
        counter[i] += counter[i - 1];
    }
    // 倒序遍历,根据桶内统计结果,将各元素填入 res
    vector<int> res(n, 0);
    for (int i = n - 1; i >= 0; i--) {
        int d = digit(nums[i], exp);
        int j = counter[d] - 1; // 获取 d 在数组中的索引 j
        res[j] = nums[i];       // 将当前元素填入索引 j
        counter[d]--;           // 将 d 的数量减 1
    }
    // 使用结果覆盖原数组 nums
    for (int i = 0; i < n; i++)
        nums[i] = res[i];
}

/* 基数排序 */
void radixSort(vector<int> &nums) {
    // 获取数组的最大元素,用于判断最大位数
    int m = *max_element(nums.begin(), nums.end());
    // 按照从低位到高位的顺序遍历
    for (int exp = 1; exp <= m; exp *= 10)
        // 对数组元素的第 k 位执行计数排序
        // k = 1 -> exp = 1
        // k = 2 -> exp = 10
        // 即 exp = 10^(k-1)
        countingSortDigit(nums, exp);
}

相较于计数排序,基数排序适用于数值范围较大的情况,但前提是数据必须可以表示为固定位数的格式,且位数不能过大。例如,浮点数不适合使用基数排序,因为其位数 过大,可能导致时间复杂度大于 O(n²)

3、总结

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1643613.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言学习【C语言基本数据类型】

C语言学习【C语言基本数据类型】 整数溢出 /* 整数溢出 */ #include "stdio.h" /* Last Modified Time: 2024-05-05 17:53:49 */int main(void) {int i 2147483647;unsigned int j 4294967295;printf("%d %d %d\n", i, i1, i2);printf("%u %u %u\…

【数据结构初阶】直接插入排序

最近浅学了直接插入排序&#xff0c;写个博客做笔记&#xff01;笔记功能除外若能对读者老爷有所帮助最好不过了&#xff01; 直接插入排序是插入排序的一种&#xff0c;那么介绍直接插入排序之前先介绍一下常见的排序算法&#xff01; 目录 1.常见的排序算法 2.直接插入排…

500行代码实现贪吃蛇(1)

文章目录 目录1. Win32 API 介绍1.1 Win32 API1.2 控制台程序&#xff08;Console&#xff09;1.3 控制台屏幕上的坐标COORD1.4 [GetStdHandle](https://learn.microsoft.com/zh-cn/windows/console/getstdhandle)1.5 [GetConsoleCursorInfo](https://learn.microsoft.com/zh-c…

项目经理【人】原则

系列文章目录 【引论一】项目管理的意义 【引论二】项目管理的逻辑 【环境】概述 【环境】原则 【环境】任务 【环境】绩效 【人】概述 【人】原则 一、共创模式 1.1 共创模式 二、干系人的影响力强度和态度 2.1 干系人影响力 2.2 干系人态度 2.3 干系人管理 三、干系人权力…

Java17 --- SpringCloud之Gateway

目录 一、Gateway网关创建 1.1、创建微服务子工程9527及配置和依赖 1.1.1、pom依赖 1.1.2、yml配置 1.1.3、主启动类并测试入驻consul 二、实现路由映射 2.1、服务8001新增测试代码 2.2、修改9527服务yml配置文件 2.3、远程调用接口加gateway 2.3.1、新增80服务测…

【Android学习】简单的登录页面和业务逻辑实现

实现功能 1 登录页&#xff1a;密码登录和验证码登录 2 忘记密码页&#xff1a;修改密码 3 页面基础逻辑 java代码 基础页面 XML login_main.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.and…

C++静态数组和C语言静态数组的区别( array,int a[])

目录 一、区别 1、越界读&#xff0c;检查不出来 2、越界写&#xff0c;抽查&#xff0c;可能检查不出来&#xff0c;有局限性 二、array缺点 一、区别 C语言的静态数组int a[]; 静态数组的越界检查不稳定的&#xff1a; 1、越界读&#xff0c;检查不出来 2、越界写&#x…

开发一款简易APP

希望打开APP后,显示当前时间..可能不实用,重在体验 安装Flutter 如果在arm架构的 Mac 电脑上进行开发&#xff0c;需要安装 Rosetta 2, 因为一些辅助工具需要&#xff0c;可通过手动运行下面的命令来安装&#xff1a; sudo softwareupdate --install-rosetta --agree-to-licens…

一篇文章带你深入了解“指针”

一篇文章带你深入了解“指针” 内存和地址了解指针指针类型const修饰指针指针的运算指针与整数之间的运算指针与指针之间的运算指针的关系运算 void* 指针传值调用和传址调用数组和指针的关系野指针野指针的形成原因规避野指针 二级指针字符指针指针数组数组指针数组传参一维数…

动态炫酷的新年烟花网页代码

烟花效果的实现可以采用前端技术&#xff0c;如HTML、CSS和JavaScript。通过结合动画、粒子效果等技术手段&#xff0c;可以创建出独特而炫目的烟花效果。同时&#xff0c;考虑到性能和兼容性&#xff0c;需要确保效果在各种设备上都能够良好运行。 效果显示http://www.bokequ.…

Transformer中的数据输入构造

文章目录 1. 文本内容2. 字典构造2.1 定义一个类用于字典构造2.2 拆分文本2.3 构造结果 3. 完整代码 1. 文本内容 假如我们有如下一段文本内容&#xff1a; Optics It is the branch of physics that studies the behaviour and properties of light . Optical Science 这段…

代码随想录Day 37|Leetcode|Python|● 1049. 最后一块石头的重量 II ● 494. 目标和 ● 474.一和零

1049. 最后一块石头的重量 II 有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x < y。那么粉碎的可能结…

Java web第五次作业

1.在idea中配置好数据源 2、视频案例中只给出了查询所有结果的示例&#xff0c;请自己完成添加、删除、修改操作的代码。以下供参 考。 Delete("delete from emp where id#{id}") public void delete(Integer id); 测试代码 Test public void testDelete(){ empMa…

AI产品经理需要懂的技术全景图

AI产品经理需要懂技术&#xff0c;以便与算法工程师同频沟通&#xff0c;以及合理管控AI项目进度。 项目掌握内容掌握边界数学统计学基础概念常见概念知道、了解模型构建 模型构建流程涉及角色每个角色工作内容清楚知道每个角色该做什么&#xff0c;需要花费多少成本&#xff…

使用python开发的词云图生成器2.0

使用python开发的词云图生成器2.0 更新部分词云图主要三方库工具介绍和效果工具界面&#xff1a; 代码 更新部分 1.支持选择字体&#xff1b; 2.支持选择词云图形状 词云图 词云图啊&#xff0c;简单来说&#xff0c;它可以把文本数据中的高频关键词变成不同大小、颜色的词汇…

「C/C++ 01」scanf()与回车滞留问题

目录 〇、scanf()接收用户输入的流程 一、回车的缓冲区滞留问题是什么&#xff1f; 二、为什么&#xff1f; 三、四个解决方法&#xff1a; 1. 在前面的scanf()中加上\n 2. 在scanf("%c")中添加空格 3. 使用getchar()来吸收回车 4. 使用fflush()清空缓冲区 〇、scan…

seata容器部署nacos注册配置中心、db存储实践记录

seata容器部署nacos注册&配置中心、db存储实践记录 说明seata容器初步部署(可跳过)seata初部署获取配置文件springboot简单集成seata测试 seata使用nacos注册中心、db存储环境准备准备nacos配置中心配置准备Mysql数据库 seata配置nacos注册中心准备docker-compose.yaml文件…

stm32单片机开发四、USART“串口通信“

串口的空闲状态时高电平&#xff0c;起始位是低电平&#xff0c;来打破空闲状态的高电平 必须要有停止位&#xff0c;停止位一般为一位高电平 串口常说的数据为8N1&#xff0c;其实就是8个数据位&#xff08;固定的&#xff09;&#xff0c;N就是none&#xff0c;也就是0个校验…

mfc140.dll丢失如何修复?分享各种mfc140.dll丢失的解决方法

在Windows操作系统的世界里&#xff0c;动态链接库&#xff08;Dynamic Link Library, DLL&#xff09;扮演着举足轻重的角色&#xff0c;它们是实现程序功能共享、减少内存占用、促进模块化编程的关键组件。MFC140.dll便是众多DLL文件中的一员&#xff0c;它与微软基础类库&am…

【kettle006】kettle访问华为openGauss高斯数据库并处理数据至execl文件(已更新)

1.一直以来想写下基于kettle的系列文章&#xff0c;作为较火的数据ETL工具&#xff0c;也是日常项目开发中常用的一款工具&#xff0c;最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下华为openGauss高斯数据库相关知识体系 3.欢迎批评指正&#xff0c;跪谢…