排序算法可视化

news2025/3/12 11:52:45

前言

这两天在B站上刷到一个视频,用python把各种排序动画可视化显示了出来觉得还蛮好玩的,当即就决定用Flutter写一个玩玩,顺便复习一下排序算法,话不多说,进入正文~

效果图:

该效果图为鸡尾酒排序(双向冒泡排序)的演示

在线体验地址:https://dartpad.dev/?id=cb52d49828a2e2f879353458bbb7b80f

单击run之后等待一会~

代码下载:https://www.aliyundrive.com/s/1TcPCBhSWyW

布局绘制

想看算法实现的朋友请直接跳至排序算法实现一节

顶部

选择使用什么排序算法,通过一个PopupMenuButton弹出菜单来实现。

appBar: AppBar(
  title: Text(
    "当前选择的是:${getTitle()}",
    style: const TextStyle(fontSize: 14),
  ),
  actions: <Widget>[
    PopupMenuButton(
        initialValue: currentSort,
        itemBuilder: (ctx) {
          return const [
            PopupMenuItem(
              value: 'bubble',
              child: Text("Bubble Sort"),
            ),
          	...
          ];
        })
  ],
),
排序数组渲染

在这里使用StreamBuilder,用于实时接收到排序数组的变化。当排序算法进行中,数组发生变化时,StreamBuilder 会自动重新构建,并更新 UI。这样可以实现动态的排序过程,让我们可以看到每一步的变化。

body: StreamBuilder<Object>(
  initialData: numbers,
  stream: streamController.stream,
  builder: (context, snapshot) {
    List<int> numbers = snapshot.data as List<int>;
    int counter = 0;
    return Row(
      children: numbers.map((int num) {
      	通过更新counter,标记要渲染数组中的索引
        counter++;
        return CustomPaint(
          painter: BarPainter(
            width: MediaQuery.of(context).size.width / sampleSize,
            value: num,
            index: counter,
          ),
        );
      }).toList(),
    );
  },
),
绘制线段

通过CustomPainter对排序数组中的值进行渲染。

class BarPainter extends CustomPainter {
  //宽度
  final double width;

  //高度(数组中对应的值)
  final int value;

  //位置索引
  final int index;

  BarPainter({required this.width, required this.value, required this.index});

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint();
    if (value < 500 * .10) {
      paint.color = Colors.blue.shade100;
    } else if (value < 500 * .20) {
      paint.color = Colors.blue.shade200;
    } else if (value < 500 * .30) {
      paint.color = Colors.blue.shade300;
    } else if (value < 500 * .40) {
      paint.color = Colors.blue.shade400;
    } else if (value < 500 * .50) {
      paint.color = Colors.blue.shade500;
    } else if (value < 500 * .60) {
      paint.color = Colors.blue.shade600;
    } else if (value < 500 * .70) {
      paint.color = Colors.blue.shade700;
    } else if (value < 500 * .80) {
      paint.color = Colors.blue.shade800;
    } else if (value < 500 * .90) {
      paint.color = Colors.blue.shade900;
    } else {
      paint.color = const Color(0xFF011E51);
    }

    paint.strokeWidth = width;
    paint.strokeCap = StrokeCap.round;

  	//绘制线段
    canvas.drawLine(
        Offset(index * width, 0),
        Offset(
          index * width,
        	//将一个数字向上取整并转换为双精度浮点数
          value.ceilToDouble(),
        ),
        paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}
底部控制

借助ScaffoldbottomNavigationBar快速实现需求(偷懒下🤣)

bottomNavigationBar: BottomAppBar(
  child: Row(
    mainAxisAlignment: MainAxisAlignment.spaceAround,
    children: <Widget>[
      ElevatedButton(
          onPressed: isSorting ? null : () {}, child: const Text("重置")),
      ElevatedButton(
          onPressed: isSorting ? null : sort, child: const Text("开始排序")),
      ElevatedButton(
        onPressed: isSorting ? null : changeSpeed,
        child: Text(
          "${speed + 1}x",
          style: const TextStyle(fontSize: 20),
        ),
      ),
    ],
  ),
),
初始化数组
reset() {
	//用于判断是否排序
  isSorted = false;
  numbers = [];
	//往numbers中随机添加sampleSize个值
  for (int i = 0; i < sampleSize; ++i) {
    numbers.add(Random().nextInt(500));
  }
  streamController.add(numbers);
}

这样就实现了对界面的渲染布局~

其他关联操作

对动画时间的控制

定义一个速度等级,在定义默认的动画时间,根据动画更新的速度计算动画时间。

//排序动画更新的速度
int speed = 0;

static int duration = 1500;

///动画时间
changeSpeed() {
  	//加到4级后重置为1级(默认从1级开始)
    if (speed >= 3) {
      speed = 0;
      duration = 1500;
    } else {
      speed++;
    	//每次缩短一半时间
      duration = duration ~/ 2;
    }
    setState(() {});
}
计算算法运行时长

借助Stopwatch计时器实现。

Stopwatch stopwatch = Stopwatch()..start();
。。。开始排序

//排序结束,定时器停止
stopwatch.stop();

//打印排序时间
print("Sorting completed in ${stopwatch.elapsed.inMilliseconds} ms");

排序算法实现

冒泡排序

冒泡排序的主要思路是通过多次遍历数组,比较相邻元素的大小并交换位置,使得逐渐大的元素移动到数组的右侧。每一轮循环都会确定一个数字的最终位置,因为较大的元素会逐渐“冒泡”到数组的末尾。

///冒泡排序
bubbleSort() async {
  //控制需要进行排序的次数。每一轮循环都会确定一个数字的最终位置。
  for (int i = 0; i < numbers.length; ++i) {
    //遍历当前未排序的元素,通过相邻的元素比较并交换位置来完成排序。
    for (int j = 0; j < numbers.length - i - 1; ++j) {
      //如果 _numbers[j] 大于 _numbers[j + 1],则交换它们的位置,确保较大的元素移到右边。
      if (numbers[j] > numbers[j + 1]) {
        int temp = numbers[j];
        numbers[j] = numbers[j + 1];
        numbers[j + 1] = temp;
      }
      //实现一个延迟,以便在ui上展示排序的动画效果
      await Future.delayed(getDuration(), () {});
      streamController.add(numbers);
    }
  }
}
鸡尾酒排序(双向冒泡排序)

该排序算法其主要思想是在排序过程中,首先从左往右逐个比较并交换相邻元素,直到最后一个元素。然后再从右往左逐个比较并交换相邻元素,直到第一个元素。这样可以确保每一轮排序后都能找到当前未排序部分的最大值和最小值。

///鸡尾酒排序(双向冒泡排序)
cocktailSort() async {
  bool swapped = true; // 表示是否进行了交换
  int start = 0; // 当前未排序部分的起始位置
  int end = numbers.length; // 当前未排序部分的结束位置

  // 开始排序循环,只有当没有进行交换时才会退出循环
  while (swapped == true) {
    swapped = false;

    // 从左往右遍历需要排序的部分
    for (int i = start; i < end - 1; ++i) {
      // 对每两个相邻元素进行比较
      if (numbers[i] > numbers[i + 1]) {
        // 如果前面的元素大于后面的元素,则交换它们的位置
        int temp = numbers[i];
        numbers[i] = numbers[i + 1];
        numbers[i + 1] = temp;
        swapped = true; // 进行了交换
      }

      await Future.delayed(getDuration());
      streamController.add(numbers);
    }

    // 如果没有进行交换,则说明已经排好序,退出循环
    if (swapped == false) break;
    // 重设为false,准备进行下一轮排序
    swapped = false; 
    // 将end设置为上一轮排序的最后一个元素的位置
    end = end - 1; 

    // 从右往左遍历需要排序的部分
    for (int i = end - 1; i >= start; i--) {
      // 对每两个相邻元素进行比较
      if (numbers[i] > numbers[i + 1]) {
        // 如果前面的元素大于后面的元素,则交换它们的位置
        int temp = numbers[i];
        numbers[i] = numbers[i + 1];
        numbers[i + 1] = temp;
        swapped = true; // 进行了交换
      }

      await Future.delayed(getDuration());
      streamController.add(numbers);
    }
    // 将start向右移一位,准备下一轮排序
    start = start + 1; 
  }
}
梳排序(一种改进的冒泡排序算法)

梳排序是一种改进的冒泡排序算法,通过逐渐减小间隔来将最大的元素逐步归位。算法中的关键部分在于确定合理的间隔值和交换操作。

///梳排序(Comb Sort)
combSort() async {
  int gap = numbers.length;

  bool swapped = true;

  // 当间隔不为1或存在交换时执行循环
  while (gap != 1 || swapped == true) {
    // 通过缩小间隔来逐步将元素归位
    gap = getNextGap(gap);
    swapped = false;
    for (int i = 0; i < numbers.length - gap; i++) {
      // 如果当前元素大于间隔位置上的元素,则交换它们的位置
      if (numbers[i] > numbers[i + gap]) {
        int temp = numbers[i];
        numbers[i] = numbers[i + gap];
        numbers[i + gap] = temp;
        swapped = true;
      }

      await Future.delayed(getDuration());
      streamController.add(numbers);
    }
  }
}

int getNextGap(int gap) {
  // 根据当前间隔值计算下一个间隔值
  gap = (gap * 10) ~/ 13;
  if (gap < 1) return 1;
  return gap;
}
鸽巢排序

鸽巢排序是一种比较简单的排序算法,它的基本思想是将待排序的数组中的数字分配到一个或多个“鸽巢”中,然后再从鸽巢中按照一定的顺序取出数字,将它们重新放回到数组中,最终得到一个有序的数组。

///鸽巢排序
pigeonHole() async {
  int min = numbers[0];
  int max = numbers[0];
  int range, i, j, index;

  // 找到数组中的最大值和最小值
  for (int a = 0; a < numbers.length; a++) {
    if (numbers[a] > max) max = numbers[a];
    if (numbers[a] < min) min = numbers[a];
  }

  // 计算鸽巢的个数
  range = max - min + 1;
  List<int> p = List.generate(range, (i) => 0);

  // 将数字分配到各个鸽巢中
  for (i = 0; i < numbers.length; i++) {
    p[numbers[i] - min]++;
  }

  index = 0;

  // 将鸽巢中的数字取出,重新放回到数组中
  for (j = 0; j < range; j++) {
    while (p[j]-- > 0) {
      numbers[index++] = j + min;
      await Future.delayed(getDuration());
      streamController.add(numbers);
    }
  }
}
希尔排序

希尔排序是一种改进的插入排序算法,它通过将待排序的数组按照一定的间隔(称为gap)拆分成若干个子序列,对每个子序列进行插入排序,然后逐渐缩小gap值,最终完成整个数组的排序。

///希尔排序
  shellSort() async {
    //定义变量 gap 并初始化为数组长度的一半。每次循环完成后将 gap 减半直到等于 0。
    for (int gap = numbers.length ~/ 2; gap > 0; gap ~/= 2) {
      //遍历每个子序列并进行插入排序。初始时从第一个子序列的第二个元素开始,即 i = gap,以 gap 为步长逐个遍历每个子序列。
      for (int i = gap; i < numbers.length; i += 1) {
        //将当前遍历到的元素赋值给它
        int temp = numbers[i];
        //内部使用一个 for 循环来实现插入排序。
        //循环开始时定义变量 j 并将其初始化为当前遍历到的元素的下标。通过不断比较前后相隔 gap 的元素大小并交换位置,将当前元素插入到正确的位置。
        int j;
        for (j = i; j >= gap && numbers[j - gap] > temp; j -= gap) {
          numbers[j] = numbers[j - gap];
        }
        numbers[j] = temp;
        await Future.delayed(getDuration());
        streamController.add(numbers);
      }
    }
  }
选择排序

选择排序是一种简单直观的排序算法,它每次在未排序部分中选择最小(或最大)的元素,然后将其与未排序部分的第一个元素进行交换,从而逐步形成有序序列。

///选择排序
selectionSort() async {
  for (int i = 0; i < numbers.length; i++) {
    for (int j = i + 1; j < numbers.length; j++) {
      // 遍历未排序部分,内层循环控制变量 j
      if (numbers[i] > numbers[j]) {
        // 判断当前元素是否比后续元素小
        int temp = numbers[j];
        // 交换当前元素和后续较小的元素
        numbers[j] = numbers[i];
        numbers[i] = temp;
      }
      await Future.delayed(getDuration(), () {});
      streamController.add(numbers);
    }
  }
}
循环排序

循环排序是一种稳定的排序算法,它通过不断将数组中的每个元素放置到其正确的位置上来完成排序。在整个排序过程中,会根据每个元素的值找到它应该位于的位置,并进行适当的交换。

///循环排序
cycleSort() async {
  int writes = 0;
  for (int cycleStart = 0; cycleStart <= numbers.length - 2; cycleStart++) {
    int item = numbers[cycleStart];
    int pos = cycleStart;

    // 在未排序部分中寻找比当前元素小的元素个数
    for (int i = cycleStart + 1; i < numbers.length; i++) {
      if (numbers[i] < item) pos++;
    }

    // 如果当前元素已经在正确位置上,则跳过此次迭代
    if (pos == cycleStart) {
      continue;
    }

    // 将当前元素放置到正确的位置上,并记录写操作次数
    while (item == numbers[pos]) {
      pos += 1;
    }
    if (pos != cycleStart) {
      int temp = item;
      item = numbers[pos];
      numbers[pos] = temp;
      writes++;
    }

    // 循环将位于当前位置的元素放置到正确的位置上
    while (pos != cycleStart) {
      pos = cycleStart;
      // 继续在未排序部分中寻找比当前元素小的元素个数
      for (int i = cycleStart + 1; i < numbers.length; i++) {
        if (numbers[i] < item) pos += 1;
      }

      // 将当前元素放置到正确的位置上,并记录写操作次数
      while (item == numbers[pos]) {
        pos += 1;
      }
      if (item != numbers[pos]) {
        int temp = item;
        item = numbers[pos];
        numbers[pos] = temp;
        writes++;
      }

      await Future.delayed(getDuration());
      streamController.add(numbers);
    }
  }
}
堆排序

堆排序是一种高效的排序算法,它利用了堆的数据结构来完成排序过程。堆是一种特殊的二叉树,其中每个父节点的值都大于或等于其子节点的值(称为最大堆)。

///堆排序
heapSort() async {
  // 从最后一个非叶子节点开始,构建最大堆
  for (int i = numbers.length ~/ 2; i >= 0; i--) {
    await heapify(numbers, numbers.length, i);
    streamController.add(numbers);
  }

  // 依次取出最大堆的根节点(最大值),并进行堆化
  for (int i = numbers.length - 1; i >= 0; i--) {
    int temp = numbers[0];
    numbers[0] = numbers[i];
    numbers[i] = temp;
    await heapify(numbers, i, 0);
    streamController.add(numbers);
  }
}


heapify(List<int> arr, int n, int i) async {
  int largest = i;
  int l = 2 * i + 1; // 左子节点索引
  int r = 2 * i + 2; // 右子节点索引

  // 如果左子节点存在并且大于父节点,则更新最大值索引
  if (l < n && arr[l] > arr[largest]) largest = l;

  // 如果右子节点存在并且大于父节点或左子节点,则更新最大值索引
  if (r < n && arr[r] > arr[largest]) largest = r;

  // 如果最大值索引不等于当前节点索引,则交换节点值,并递归进行堆化
  if (largest != i) {
    int temp = numbers[i];
    numbers[i] = numbers[largest];
    numbers[largest] = temp;
    heapify(arr, n, largest);
  }

  await Future.delayed(getDuration());
}
插入排序

插入排序是一种简单直观的排序算法,它的基本思想是将数组分为已排序和未排序两个部分。在每一轮迭代中,从未排序部分选择一个元素,并将它插入到已排序部分的合适位置,以保证已排序部分仍然有序。

///插入排序
insertionSort() async {
  for (int i = 1; i < numbers.length; i++) {
    int temp = numbers[i]; // 将当前元素存储到临时变量 temp 中
    int j = i - 1; // j 表示已排序部分的最后一个元素的索引

    // 在已排序部分从后往前查找,找到合适位置插入当前元素
    while (j >= 0 && temp < numbers[j]) {
      numbers[j + 1] = numbers[j]; // 当前元素比已排序部分的元素小,将元素后移一位
      --j; // 向前遍历
      await Future.delayed(getDuration()); 
      streamController.add(numbers); // 更新排序结果
    }

    numbers[j + 1] = temp; // 插入当前元素到已排序部分的正确位置
    await Future.delayed(getDuration(), () {});
    streamController.add(numbers); 
  }
}
地精排序 (侏儒排序)

地精排序基本思想是通过不断比较相邻元素的大小,将小的元素“拍”到正确的位置,直到所有的元素都排好序。

///地精排序 (侏儒排序)
gnomeSort() async {
  int index = 0;

  while (index < numbers.length) {
    // 当 index 小于数组长度时执行循环
    if (index == 0) index++; 
    if (numbers[index] >= numbers[index - 1]) {
      // 如果当前元素大于等于前面的元素,则将 index 加1
      index++;
    } else {
      // 否则,交换这两个元素,并将 index 减1(使得元素可以沉到正确位置)
      int temp = numbers[index];
      numbers[index] = numbers[index - 1];
      numbers[index - 1] = temp;
      index--;
    }
    await Future.delayed(getDuration()); 
    streamController.add(numbers); 
  }

  return;
}
奇偶排序(Odd-Even Sort)

奇偶排序算法(Odd-Even Sort)是一种简单的并行排序算法,它通过比较和交换数组中的相邻元素来实现排序。该算法适用于通过并行计算来加速排序过程的环境。

///奇偶排序(Odd-Even Sort)
oddEvenSort() async {
  bool isSorted = false;

  while (!isSorted) {
    // 当 isSorted 为 false 时执行循环
    isSorted = true; // 先假设数组已经排好序

    for (int i = 1; i <= numbers.length - 2; i = i + 2) {
      // 对奇数索引位置进行比较
      if (numbers[i] > numbers[i + 1]) {
        // 如果当前元素大于后面的元素,则交换它们的值
        int temp = numbers[i];
        numbers[i] = numbers[i + 1];
        numbers[i + 1] = temp;
        isSorted = false; // 若发生了交换,则说明数组仍未完全排序,将 isSorted 设为 false
        await Future.delayed(getDuration()); 
        streamController.add(numbers); 
      }
    }

    for (int i = 0; i <= numbers.length - 2; i = i + 2) {
      // 对偶数索引位置进行比较
      if (numbers[i] > numbers[i + 1]) {
        // 如果当前元素大于后面的元素,则交换它们的值
        int temp = numbers[i];
        numbers[i] = numbers[i + 1];
        numbers[i + 1] = temp;
        isSorted = false;
        await Future.delayed(getDuration());
        streamController.add(numbers);
      }
    }
  }

  return;
}
快速排序

快速排序是一种分治策略的排序算法,它通过将一个数组分成两个子数组,然后递归地对子数组进行排序,最终得到完全有序的数组。

///快速排序
quickSort(int leftIndex, int rightIndex) async {
  // 定义一个名为 _partition 的异步函数,用于划分数组,并返回划分后的基准元素的索引位置
  Future<int> _partition(int left, int right) async {
    // 选择中间位置的元素作为基准元素
    int p = (left + (right - left) / 2).toInt();

    // 交换基准元素和最右边的元素
    var temp = numbers[p];
    numbers[p] = numbers[right];
    numbers[right] = temp;
    await Future.delayed(getDuration());
    streamController.add(numbers);

    // 初始化游标 cursor
    int cursor = left;

    // 遍历数组并根据基准元素将元素交换到左侧或右侧
    for (int i = left; i < right; i++) {
      if (cf(numbers[i], numbers[right]) <= 0) {
        // 如果当前元素小于等于基准元素,则交换它和游标位置的元素
        var temp = numbers[i];
        numbers[i] = numbers[cursor];
        numbers[cursor] = temp;
        cursor++;

        await Future.delayed(getDuration());
        streamController.add(numbers);
      }
    }

    // 将基准元素放置在游标位置
    temp = numbers[right];
    numbers[right] = numbers[cursor];
    numbers[cursor] = temp;

    await Future.delayed(getDuration());
    streamController.add(numbers);

    return cursor;  // 返回基准元素的索引位置
  }

  // 如果左索引小于右索引,则递归地对数组进行快速排序
  if (leftIndex < rightIndex) {
    int p = await _partition(leftIndex, rightIndex);

    await quickSort(leftIndex, p - 1);  // 对基准元素左侧的子数组进行快速排序

    await quickSort(p + 1, rightIndex);  // 对基准元素右侧的子数组进行快速排序
  }
}

// 比较函数,用于判断两个元素的大小关系
cf(int a, int b) {
  if (a < b) {
    return -1;  // 若 a 小于 b,则返回 -1
  } else if (a > b) {
    return 1;   // 若 a 大于 b,则返回 1
  } else {
    return 0;   // 若 a 等于 b,则返回 0
  }
}
归并排序

归并排序也是一种分治策略的排序算法,它将一个数组不断分成两个子数组,直到每个子数组只包含一个元素,然后再将两个有序的子数组合并成一个有序的数组。

///归并排序
mergeSort(int leftIndex, int rightIndex) async {
  // 定义一个名为 merge 的异步函数,用于合并两个有序子数组
  Future<void> merge(int leftIndex, int middleIndex, int rightIndex) async {
    // 计算左侧子数组和右侧子数组的大小
    int leftSize = middleIndex - leftIndex + 1;
    int rightSize = rightIndex - middleIndex;

    // 创建左侧子数组和右侧子数组
    List leftList = List.generate(leftSize, (index) => 0);
    List rightList = List.generate(rightSize, (index) => 0);

    // 将原始数组中的元素分别复制到左侧子数组和右侧子数组中
    for (int i = 0; i < leftSize; i++) {
      leftList[i] = numbers[leftIndex + i];
    }
    for (int j = 0; j < rightSize; j++) {
      rightList[j] = numbers[middleIndex + j + 1];
    }

    // 初始化游标和索引
    int i = 0, j = 0;
    int k = leftIndex;

    // 比较左侧子数组和右侧子数组的元素,并按顺序将较小的元素放入原始数组中
    while (i < leftSize && j < rightSize) {
      if (leftList[i] <= rightList[j]) {
        numbers[k] = leftList[i];
        i++;
      } else {
        numbers[k] = rightList[j];
        j++;
      }

      await Future.delayed(getDuration());
      streamController.add(numbers);

      k++;
    }

    // 将左侧子数组或右侧子数组中剩余的元素放入原始数组中
    while (i < leftSize) {
      numbers[k] = leftList[i];
      i++;
      k++;

      await Future.delayed(getDuration());
      streamController.add(numbers);
    }

    while (j < rightSize) {
      numbers[k] = rightList[j];
      j++;
      k++;

      await Future.delayed(getDuration());
      streamController.add(numbers);
    }
  }

  // 如果左索引小于右索引,则递归地对数组进行归并排序
  if (leftIndex < rightIndex) {
    // 计算中间索引位置
    int middleIndex = (rightIndex + leftIndex) ~/ 2;

    // 分别对左侧子数组和右侧子数组进行归并排序
    await mergeSort(leftIndex, middleIndex);
    await mergeSort(middleIndex + 1, rightIndex);

    await Future.delayed(getDuration());
    streamController.add(numbers);

    // 合并两个有序子数组
    await merge(leftIndex, middleIndex, rightIndex);
  }
}

都看到这里啦,给个赞吧~

关于我

Hello,我是Taxze,如果您觉得文章对您有价值,希望您能给我的文章点个❤️,有问题需要联系我的话:我在这里 。如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章~万一哪天我进步了呢?😝

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

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

相关文章

【Machine Learning】03-Unsupervised learning

03-Unsupervised learning 3. Unsupervised Learning3.1 无监督学习&#xff08;Unsupervised Learning&#xff09;3.1.1 聚类&#xff08;Clustering&#xff09;3.1.2 K-均值聚类算法&#xff08;K-means Clustering&#xff09;3.1.3 高斯分布&#xff08;Gaussian distrib…

当然是做药物研发啦~~~

做药物研发 梦开始的地方 当年上本科的时候&#xff0c;我的第一台笔记本有点子差&#xff0c;根本跑不了分子动力学&#xff0c;而且还需要我这个菜鸡自己在win系统的本子上安装Linux的双系统&#xff0c;频繁的重启、安装双系统搞得我心力憔悴&#xff0c;于是我决定使用”钞…

使用navicat查看类型颜色

问题描述&#xff1a; 最近遇到一个mongodb的数据问题。 在date日期数据中&#xff0c;混入了string类型的数据&#xff0c;导致查询视图报错&#xff1a; $add only supports numeric or date types解决办法&#xff1a; 使用类型颜色工具。 找到在last_modified_date字段中…

springBoot复杂对象表示和lombok的使用

springBoot复杂对象表示 前言简单案例lombok的使用通过properties文件进行绑定在yaml文件中使用 前言 对象&#xff1a;键值对的集合&#xff0c;如&#xff1a;映射&#xff08;map)/哈希&#xff08;hash)/字典&#xff08;dictionary&#xff09; 数组&#xff1a;一组按次…

亚马逊测评关于IP和DNS的问题

最近不少人询问了关于IP和DNS的问题&#xff0c;在此进行一些科普。 当客户端试图访问一个网站时&#xff0c;首先会向其所在的ISP的DNS服务器进行查询。如果ISP的DNS服务器没有相关缓存&#xff0c;则会向上级DNS服务器进行查询。 一些诸如CDN之类的服务&#xff0c;可能会为…

【C/C++】STL——容器适配器:stack和queue的使用及模拟实现

​&#x1f47b;内容专栏&#xff1a; C/C编程 &#x1f428;本文概括&#xff1a;stack与queue的介绍与使用、模拟实现。 &#x1f43c;本文作者&#xff1a; 阿四啊 &#x1f438;发布时间&#xff1a;2023.10.17 一、stack的介绍与使用 1.1 stack的介绍 以下是stack的文档…

E044-服务漏洞利用及加固-利用redis未授权访问漏洞进行提权

任务实施: E044-服务漏洞利用及加固-利用redis未授权访问漏洞进行提权 任务环境说明&#xff1a; 服务器场景&#xff1a;p9_kali-6&#xff08;用户名&#xff1a;root&#xff1b;密码&#xff1a;toor&#xff09; 服务器场景操作系统&#xff1a;Kali Linux 192.168.3…

【Qt高阶】Linux安装了多个版本的Qt 部署Qt程序,出包【2023.10.17】

简介 linux系统下可执行程序运行时会加载一些动态库so&#xff0c;有一些是Qt的库&#xff0c;Qt的库会加载其他更基础的库。最后出包的时候需要把依赖的包整理到一个文件夹&#xff0c;来制作安装包。近期遇到已经将依赖的so文件拷贝至程序目录下&#xff0c;但还是调系统路径…

AutoCAD 2024:计算机辅助设计(CAD)软件中文版

AutoCAD是一款广受全球设计师和工程师欢迎的计算机辅助设计&#xff08;CAD&#xff09;软件。自1982年首次推出以来&#xff0c;AutoCAD已经经历了多次迭代和改进&#xff0c;不断提升用户在产品设计、建造和工程领域的工作效率。现在&#xff0c;让我们一起探索AutoCAD 2024的…

MySQL中的存储过程

MySQL中的存储过程 概述 由MySQL5.0 版本开始支持存储过程。 如果在实现用户的某些需求时&#xff0c;需要编写一组复杂的SQL语句才能实现的时候&#xff0c;那么我们就可以将这组复杂的SQL语句集提前编写在数据库中&#xff0c;由JDBC调用来执行这组SQL语句。把编写在数据库…

横向移动如何阻止以及防范?

文章目录 背景总结EDR设备监测 (这里以奇安信网神云锁为例) 背景 今天面试&#xff0c;面试官问到了这一个问题&#xff0c;云主机被getshell了&#xff0c;进行了横向移动&#xff0c;如何进行阻止以及防范&#xff1f;当时回答了两个点&#xff1a;通过防火墙出入站策略设置…

HTX 与 Zebec Protocol 展开深度合作,并将以质押者的身份参与 ZBC Staking

自2023年下半年以来&#xff0c;加密市场始终处于低迷的状态&#xff0c;在刚刚结束的9月&#xff0c;加密行业总融资额创下2021年以来的新低&#xff0c;同时在DeFi领域DEX交易额为318.9亿美元&#xff0c;同样创下2021年1月以来的新低。 对于投资者而言&#xff0c;难以从外生…

Apache DolphinScheduler 3.0.0 升级到 3.1.8 教程

安装部署可参考官网 Version 3.1.8/部署指南/伪集群部署(Pseudo-Cluster)https://dolphinscheduler.apache.org/zh-cn/docs/3.1.8/guide/installation/pseudo-cluster 也可以参考我写贴子 DolphinScheduler 3.0安装及使用-CSDN博客DolphinScheduler 3.0版本的安装教程https:…

微信查分,原来这么简单,老师必看攻略

哈喽&#xff0c;亲爱的老师们&#xff01;是不是经常为了查找学生的成绩而烦恼呢&#xff1f;别担心&#xff0c;今天我就来给大家分享一个超级实用的教程——在微信里查分&#xff01;快来一起了解一下吧&#xff01; 首先&#xff0c;我们要清楚成绩查询页面是什么。一般来说…

成都优优聚是专业美团代运营吗?

成都优优聚是一家专注于美团代运营的公司。作为全国知名的美团代运营服务商&#xff0c;成都优优聚拥有丰富的经验和优秀的团队&#xff0c;为各类商家提供全方位的美团代运营解决方案。 美团作为目前国内最大的O2O平台之一&#xff0c;拥有庞大的用户基础和强大的品牌影响力。…

激光雷达标定板精准识别前方障碍物

商用车自动驾驶率先进入商业化运营阶段&#xff0c;这主要是由于商用车对价格的敏感度更低、B端付费意愿更高&#xff0c;以及场景交通复杂程度较低和政策鼓励等因素。在矿区、港口、干线物流、机场、物流园区等细分场景&#xff0c;高级别自动驾驶正在孕育新市场。其中&#x…

【java学习—八】对象类型转换Casting(1)

文章目录 1. 数据类型转换1.1 基本数据类型的 Casting1.2. 对 Java 对象的强制类型转换(造型)2. 对象类型转换举例 1. 数据类型转换 数据类型转换分为基本数据类型转换和对象类型转换。 1.1 基本数据类型的 Casting (1) 自动类型转换&#xff1a;小的数据类型可以自动转换成…

如果你有一次自驾游的机会,你会如何准备?

常常想来一次说走就走的自驾游&#xff0c;但是光是想想就觉得麻烦的事情好多&#xff1a;漫长的公路缺少娱乐方式、偏僻拗口的景点地名难以导航、不熟悉的城市和道路容易违章…… 也因为如此&#xff0c;让我发现了HUAWEI HiCar这个驾驶人的宝藏&#xff01; 用HUAWEI HiCar…

value too long for type character varying报错处理

瀚高数据库 目录 环境 症状 问题原因 解决方案 环境 系统平台&#xff1a;N/A 版本&#xff1a;4.5 症状 使用insert into插入数据时出现报错value too long for type character varying 问题原因 458新增NLS_LENGTH_SEMANTICS参数&#xff0c;默认设置为byte。之前版本默认为…

nordic平台SDK包下载地址

nRF5 SDK downloads - nordicsemi.com