• [技术干货] C++轻量级代理模式
    前言    在通过QT工具C++语言开发SmartRoom过程中,发现设计代理模式时,存在代码分散的问题。    C++的代理模式有如下2种:        一、抽象类代理                代理接口(虚函数)全部放在抽象类中,后面通过派生抽象类的方式生成实现代理类,在需要代理时,将派生类对象传递给数据源,数据源后面通过抽象接口,传递数据给派生类对象,派生类对象再将数据转给使用方            二、函数代理模式函数代理,使用通过将函数地址传给数据源,需求方后面通过函数地址接着传回数据给代理函数,代理函数再将数据分发给使用方可以看出抽象类代理是函数代理的升级款,各有各的使用场景:对于只有1个场景返回时:使用函数代理对于多场景返回时:使用抽象类代理优点:1、代理方和使用方完全分离,代理方只处理数据不需要处理业务,业务在抛给对应的使用方缺点:1、使用方和代理方代码过于分散,代码分布广,查找阅读困难2、对于多使用方的场景使用成本高,不利于管理,需专门构建分发模块,通过分发模块将数据分配给对应的使用方3、而且存在野指针、空指针风险4、对于小场景使用成本高,比如:http请求,每个使用http请求的点都需要实现代理函数从下图可以看出缺陷:设计针对这些存在问题,需要一个更轻量的代码模型,根据Objective-C的block思想,串讲如下模式核心:std::function<lambda>作为代理针对常规代理模式存在的问题,这里都可以解决1、代码集中,使用方和代理方在一块,可以相关输出数据,是一对一的关系2、多使用方时,各使用方独立管理自身逻辑,代码完全分割3、代码集中,存在野指针和空指针的风险较小4、成本特别小举例说明:合入请求:cid:link_0原型:独立代理:代理嵌套实现原理lambda捕获外部变量时,会根据捕获方式生成对应的捕获类,并根据捕获类生成对应的对象比如:第一种:拷贝捕获捕获类:第二种 引用捕获        捕获类:使用场景:iOS/Mac中间层问题:iOS和mac的中间层就是通过第一种代理模式,通过数组来保存多使用方,导致请求和返回非一对一关系,经常存在错乱,使用此种模式可解决此问题
  • [生态对接] C#如何使用HttpClient请求华为FusionInsignt平台带认证的ElasticSearch?
    我下载了krb5.conf和user.keytab这两个Kerberos认证所需的文件,C#如何使用HttpClient请求华为FusionInsignt平台带认证的ElasticSearch?
  • [技术干货] 解决DEV C++源码编译后控制台输出中文乱码问题-转载
     在使用DEV C++编译源码通过win10控制台输出时发现中文显示乱码!网上查了很多资料,都不靠谱。后来结合网上的各种不同方式解决了该问题。Embarcadero DEV C++ 6.3亲测有效~  网上有文章说修改注册表HKEY_CURRENT_USER\Console\%SystemRoot%_system32_cmd.exe的CodePage值,这个方法可以解决部分人的问题。但还有一部分人的win10在该路径下是没有cmd相关信息的,只有powershell,这部分用户此方法就没有可行性。  针对上图所示情况,可按如下操作解决乱码问题  切换CMD版本(不论是从新至旧,或旧至新都行)  首先打开DEV C++编译运行源码,然后右键单击CMD顶部空白区域,选择“属性”,勾选“使用旧版控制台”  2. 按如下路径打开注册表  计算机\HKEY_CURRENT_USER\Console  刷新注册表(重要)!!!刷新前如上图,刷新后如下图 会生成DEV C++相关的注册信息  右侧修改HKEY_CURRENT_USER\Console\D:_Dev-Cpp_ConsolePauser.exe 的CodePage值为65001  修改完成后切换CMD为新版(旧版重新编译运行源码后可能仍为乱码)!!! 运行源码显示正常,搞定~~   -----------------------------E------N------D----------------------------- ———————————————— 版权声明:本文为CSDN博主「江枫听雨」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/baidu_41711780/article/details/122090345 
  • [技术干货] 【算法题】1630. 等差子数组-转载
     题目: 如果一个数列由至少两个元素组成,且每两个连续元素之间的差值都相同,那么这个序列就是 等差数列 。更正式地,数列 s 是等差数列,只需要满足:对于每个有效的 i , s[i+1] - s[i] == s[1] - s[0] 都成立。  例如,下面这些都是 等差数列 :  1, 3, 5, 7, 9 7, 7, 7, 7 3, -1, -5, -9 下面的数列 不是等差数列 :  1, 1, 2, 5, 7 给你一个由 n 个整数组成的数组 nums,和两个由 m 个整数组成的数组 l 和 r,后两个数组表示 m 组范围查询,其中第 i 个查询对应范围 [l[i], r[i]] 。所有数组的下标都是 从 0 开始 的。  返回 boolean 元素构成的答案列表 answer 。如果子数组 nums[l[i]], nums[l[i]+1], … , nums[r[i]] 可以 重新排列 形成 等差数列 ,answer[i] 的值就是 true;否则answer[i] 的值就是 false 。  示例 1:  输入:nums = [4,6,5,9,3,7], l = [0,0,2], r = [2,3,5] 输出:[true,false,true] 解释: 第 0 个查询,对应子数组 [4,6,5] 。可以重新排列为等差数列 [6,5,4] 。 第 1 个查询,对应子数组 [4,6,5,9] 。无法重新排列形成等差数列。 第 2 个查询,对应子数组 [5,9,3,7] 。可以重新排列为等差数列 [3,5,7,9] 。 示例 2:  输入:nums = [-12,-9,-3,-12,-6,15,20,-25,-20,-15,-10], l = [0,1,6,4,8,7], r = [4,4,9,7,9,10] 输出:[false,true,false,false,true,true]  提示:  n == nums.length m == l.length m == r.length 2 <= n <= 500 1 <= m <= 500 0 <= l[i] < r[i] < n -105 <= nums[i] <= 105  java代码: class Solution {      public List<Boolean> checkArithmeticSubarrays(int[] nums, int[] l, int[] r) {         List<Boolean> ans = new ArrayList<>();         for (int i = 0; i < r.length; i++) {             int[] arr = Arrays.copyOfRange(nums, l[i], r[i] + 1);             Arrays.sort(arr);             int dif = arr[1] - arr[0];             boolean cur = true;             for (int j = 2; j < arr.length; j++) {                 if (arr[j] - arr[j - 1] != dif) {                     cur = false;                     break;                 }             }             ans.add(cur);         }         return ans;     }  }  ———————————————— 版权声明:本文为CSDN博主「程序猿不脱发2」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/kangbin825/article/details/129739893 
  • 第十四届蓝桥杯三月真题刷题训练——第 20 天-转载
    第 1 题:纸张尺寸 问题描述 在 ISO 国际标准中定义了 A0 纸张的大小为 1189mm × 841mm, 将 A0 纸 沿长边对折后为 A1 纸, 大小为 841mm × 594mm, 在对折的过程中长度直接取 下整 (实际裁剪时可能有损耗)。将 A1 纸沿长边对折后为 A2 纸, 依此类推。  输入纸张的名称, 请输出纸张的大小。  输入格式 输入一行包含一个字符串表示纸张的名称, 该名称一定是 A0、A1、A2、 A3、A4、A5、A6、A7、A8、A9 之一。  输出格式 输出两行,每行包含一个整数,依次表示长边和短边的长度。  样例输入1 A0 样例输出1 1189 841 样例输入 2 A1 样例输出 2 841 594 运行限制 最大运行时间:1s 最大运行内存: 512M 代码: package 第十四届蓝桥杯三月真题刷题训练.day20;   import java.io.*;   /**  * @author yx  * @date 2023-03-23 13:16  */ public class 纸张尺寸 {     static PrintWriter out =new PrintWriter(System.out);     static BufferedReader ins=new BufferedReader(new InputStreamReader(System.in));     static StreamTokenizer in=new StreamTokenizer(ins);     /**      * 输入      * in.nextToken()      * int a= (int)in.nval;      *      * 输出      * out.print();      * out.flush();      *      * 读文件:      * BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("C:\\Users\\yx\\Desktop\\primes.txt")));      * String s = br.readLine();s读取每一行数据      * if (s == null)break;读取文件终止的语句      **/     public static void main(String[] args) throws IOException {         String[] spl=ins.readLine().split("");         int n=Integer.parseInt(spl[1]);         int length_long=1189;         int length_short=841;         int max=0;         for (int i = 0; i < n; i++) {             max=Math.max(length_long,length_short);             if(length_long==max){                 length_long/=2;             }else {                 length_short/=2;             }         }         out.println(Math.max(length_long,length_short));         out.println(Math.min(length_long,length_short));         out.flush();     } }  解析: 这题签到题,直接循环就完事儿,每次更新一下最长最小边即可,最后输出Max,Min  第 2 题:最大数字  第 3 题:全排列的价值_递推公式 问题描述 对于一个排列 A=(a1,a2,⋯ ,an)A=(a1​,a2​,⋯,an​), 定义价值 cici​ 为 a1a1​ 至 ai−1ai−1​ 中小于 aiai​ 的数 的个数, 即 ci=∣{aj∣j<i,aj<ai}∣。 ci​=∣{aj​∣j<i,aj​<ai​}∣。 ​  定义 A 的价值为 ∑i=1nci。  给定 nn, 求 1 至 n 的全排列中所有排列的价值之和。  输入格式 输入一行包含一个整数 nn 。  输出格式 输出一行包含一个整数表示答案, 由于所有排列的价值之和可能很大, 请 输出这个数除以 998244353 的余数。  样例输入 1 3 样例输出 1 9 样例输入 2 2022 样例输出 2 593300958 样例说明 1 至 3 构成的所有排列的价值如下: (1,2,3):0+1+2=3(1,3,2):0+1+1=2(2,1,3):0+0+2=2(2,3,1):0+1+0=1(3,1,2):0+0+1=1(3,2,1):0+0+0=0​故总和为 3+2+2+1+1=9 。  x测用例规模与约定 对于 40% 的评测用例, n≤20;  对于 70% 的评测用例, n≤5000;  对于所有评测用例, 2≤n≤10^6 。  运行限制 最大运行时间:1s 最大运行内存: 512M 代码: package 第十四届蓝桥杯三月真题刷题训练.day20;   import java.io.*; import java.util.Scanner;   /**  * @author yx  * @date 2023-03-23 14:02  */ public class 全排列的价值 {     static PrintWriter out =new PrintWriter(System.out);     static BufferedReader ins=new BufferedReader(new InputStreamReader(System.in));     static StreamTokenizer in=new StreamTokenizer(ins);     /**      * 输入      * in.nextToken()      * int a= (int)in.nval;      *      * 输出      * out.print();      * out.flush();      *      * 读文件:      * BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("C:\\Users\\yx\\Desktop\\primes.txt")));      * String s = br.readLine();s读取每一行数据      * if (s == null)break;读取文件终止的语句      **/     public static void main(String[] args) throws IOException {         Scanner scanner = new Scanner(System.in);         long n=scanner.nextLong();         /**          * 递推方程:          * f(x)=f(n-1)*n+(n*(n-1)/2)*(n-1)!          *          * 初始:          * f1=0;          * f2=1;          * f3=9;          */         if(n==1){             System.out.println(0);         }else if(n==2){             System.out.println(1);         }else if(n==3){             System.out.println(9);         }else {             //f从f3开始算             long f=9;             //阶乘从(3-1)!开始算             long jieCheng=2;             for (long i = 4; i <= n; i++) {                 jieCheng=jieCheng*(i-1)™8244353;                 f=(f*i™8244353+((i*(i-1))/2™8244353)*jieCheng)™8244353;             }             System.out.println(f);         }     } }  解析: 第十三届蓝桥杯省赛Java A 组 F 题、Python A 组 G 题、Python B 组 G题——全排列的价值 (AC)_蓝桥杯全排列的价值_执 梗的博客-CSDN博客 第十三届蓝桥杯省赛Java A 组 F 题、Python A 组 G 题、Python B 组 G题——全排列的价值 (AC) https://blog.csdn.net/m0_57487901/article/details/127897935?spm=1001.2014.3001.5502 借鉴我的好兄弟--“执梗”的题解,讲的太™好了,醍醐灌顶,看完了不会可以来找我!! ———————————————— 版权声明:本文为CSDN博主「小羊不会飞」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_55858611/article/details/129728734 
  • [技术干货] 经典七大比较排序算法 ·上-转载
     1 选择排序 1.1 算法思想 选择排序,每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。 大致操作步骤如下:  第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置; 然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾; 重复第二步操作,直到全部待排序的数据元素的个数为零。  选择排序视频演示  1.2 代码实现 选择排序还是很好理解的。但这里的实现会对直接选择排序做一些小的改进。 既然直接选择排序遍历一遍可以找出最小的数据,那遍历一遍同样可以找出最大的元素。这样每遍历一次,待排序数据元素的起始位置就变成最小的了,结束位置就变成最大的了。 参考代码如下:  //n - 数据个数 void SelectSort(int* a, int n) {     int begin = 0; //待排序数据起始位置下标     int end = n - 1;//待排序数据结束位置下标     int minIndex = 0;//记录最小数据的下标     int maxIndex = 0;//记录最大数据的下标          //待排序数据大于1时进入循环     while (begin < end)     {         //假设一开始最小元素和最大元素下标都是第一个待排序数据下标         minIndex = begin;         maxIndex = begin;                  //遍历一遍         for (int i = begin + 1; i <= end; ++i)         {             //选出最小元素下标             if (a[i] < a[minIndex])             {                 minIndex = i;             }                          //选出最大元素下标             if (a[i] > a[maxIndex])             {                 maxIndex = i;             }         }                  //将最小元素和待排序序列的起始位置交换         Swap(&a[begin], &a[minIndex]);                  //将最大元素和待排序序列的结束位置交换         Swap(&a[end], &a[maxIndex]);                  //缩小待排序区间,重复上述过程         ++begin;         --end;     } } 很多老铁在敲完上面代码之后,觉得已经没有问题了。  但实际上,像遇到上面情况。如果[maxIndex]指向的是[begin]中存放的内容,[minIndex]的内容先和[begin]交换之后,[maxIndex]指向的就不在是待排序序列中的最大元素了。需要做相应的修改。          Swap(&a[begin], &a[minIndex]);                  //如果begin和maxIndex重叠,那么修正maxInedx         if (begin == maxIndex)         {             maxIndex = minIndex;         }         Swap(&a[end], &a[maxIndex]); 1.3 选择排序特性 选择排序作为一种简单直观的排序算法,虽然好理解,但效率并不高。 无论是在面对什么样的排序序列,时间复杂度都是稳定的O ( n 2 ) O(n^2)O(n  2  )量级,实际中很少使用。  2 冒泡排序 2.1 算法思想 冒泡排序作为一种交换排序,它会重复地走访要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到不再需要交换,也就是说该数列已经排序完成。 大致操作步骤如下:  比较相邻的两个元素。如果第一个和第二个大元素的顺序不符合要求,就交换他们两个。 针对所有的元素重复以上的步骤,除了最后一个。 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。  冒泡排序视频演示  2.2 代码实现 //n - 数据个数 void BubbleSort(int* a, int n) {     for (int i = 0; i < n - 1; ++i)     {         for (int j = 0; j < n - 1 - i; ++j)         {             //排升序             if (a[j] > a[j + 1])             {                 Swap(&a[j], &a[j + 1]);             }         }     } } 冒泡排序还有一种优化的方法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序,不需要再继续进行冒泡排序了。  void BubbleSort(int* a, int n) {     for (int i = 0; i < n - 1; ++i)     {         int flag = 1;         for (int j = 0; j < n - 1 - i; ++j)         {             if (a[j] > a[j + 1])             {                 flag = 0;                 Swap(&a[j], &a[j + 1]);             }         }         if (1 == flag)         {             break;         }     } } 但这种改进优化在实际上对于提升性能来说并没有什么作用并不是很大。  2.3 冒泡排序特性 冒泡排序也是一种非常简单直观的排序。 在最好的情况下(序列接近有序),冒泡排序的时间复杂度是O ( n ) O(n)O(n)量级的; 在最坏的情况下(序列逆序),冒泡排序的时间复杂度是O ( n 2 ) O(n^2)O(n  2  )量级的。  3 堆排序 关于堆排序的算法思想和代码实现相关内容,可以参考阿顺的堆结构的两个应用这篇博客,里面有比较详细的介绍。这里就不在赘述了。  3.1 堆排序特性: 堆排序使用堆的结构来选数,效率就高了很多。 堆排序的平均时间复杂度是O ( n ∗ l o g 2 n ) O(n*log_2n)O(n∗log  2 ​  n)量级的。  4 快速排序 4.1 算法思想 快速排序是Hoare提出的一种二叉树结构的交换排序方法。  其基本思想为: 任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两个子序列,其中左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后对左右子序列重复该过程,直到所有元素都排列在相应位置上为止。 算法步骤:  从序列中挑出一个元素作为基准 排序数列,将所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆放在基准后面(相同的数可以在任一边)。在这个分区排完之后,该基准就处于数列的中间位置 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序  快速排序视频演示  4.2 代码实现 快速排序在发展的过程中涌现出了很多种不同的版本,但核心思想是不会变的。这里会介绍3种常见版本,以供大家参考。  //a - 待排序序列 //begin - 待排序序列起始位置 //end - 待排序序列结束位置 void QuickSort(int* a, int begin, int end) {     //序列只有一个值或没有值就不再排序     if (begin >= end)     {         return;     }          //对区间进行快速排序,并返回排序后的基准值位置     int keyIndex = QuickPartSort(a, begin, end);          //[begin, keyIndex - 1] keyIndex [keyIndex + 1, end]     //左区间排序     QuickSort(a, begin, keyIndex - 1);     //右区间排序     QuickSort(a, keyIndex + 1, end);      } 上面代码就是快速排序实现的主框架,QuickPartSort函数有不同的实现方式。 其实从主框架上可以看出快速排序的递归实现和二叉树的前序遍历还是很像的。  hoare版本 //以排升序为例 int QuickPartSort1(int* a, int begin, int end) {     int left = begin;     int right = end;          //取序列第1个数据做基准值     int keyIndex = left;     while (left < right)     {         //右边先走,找小         while (left < right && a[right] >= a[keyIndex])         {             --right;         }          //左边再走,找大         while (left < right && a[left] <= a[keyIndex])         {             ++left;         }                  //左大右小-交换         Swap(&a[left], &a[right]);     }          //left和right相遇,此时将基准值和相遇值交换     Swap(&a[keyIndex], &a[left]);     //基准值下标跟着改变     keyIndex = left;      return keyIndex; } 观察hoare版本的代码发现,选择的是最左边的值做基准值。此时,却是让右边的索引先走,那能不能让左边的值先走呢?又为什么选择右边的值先走呢? 答案是左边的值先走也可以,但是处理起来细节上更复杂一些。 选择右边先走的原因是为了保证相遇位置的值,比基准值小(或者就是基准位置的值)。 右边先走的情况无非就是:  right先走,right遇到比基准值小的,right停下来,让left走,left去遇到了right。 这种情况相遇位置就是right停下来的位置,right停的位置也就是比基准值要小的位置。 right先走,right没有遇到比基准值要小的值,right去遇到了left。 这种情况下,相遇位置是left上一轮停下来的位置,该位置要么是基准值的位置,要么就是比基准值小的位置。 这时,反过来想一下,如果选最左边为基准值,left先走的话,相遇位置的值就是大于基准值的,这样相遇位置的值就不能和基准值交换,这里需要做一些处理,来让基准值交换到合适的位置去,最终徒劳增加了麻烦。 所以,一般建议是: 左边做基准值,让右边先走; 右边做基准值,让左边先走。 挖坑版本 在面对上面hoare版本比较不好理解的左边做基准值,右边先走的问题时,挖坑法的版本能让快速排序更好理解一点点。 int QuickPartSort2(int* a, int begin, int end) {     //最左边做基准值     int key = a[begin];     //坑位给到基准值位置     int pitIndex = begin;      int left = begin;     int right = end;          while (left < right)     {         //右边找小,填到左边的坑里面去。这个位置形成新的坑。         while (left < right && a[right] >= key)         {             --right;         }         a[pitIndex] = a[right];         pitIndex = right;          //左边找大,填到右边的坑里面去。这个位置形成新的坑。         while (left < right && a[right] <= key)         {             ++left;         }         a[pitIndex] = a[left];         pitIndex = left;     }     //坑位再填上基准值     a[pitIndex] = key;      return pitIndex; } 因为坑位的存在,左边有坑,右边就走,然后填坑;右边有坑,左边就走,然后填坑。这个过程就更好理解了。但是挖坑法在算法性能上相比hoare版本并没有什么大的改进。 3. 前后指针版本 前后指针的使用和前两个版本的排序方式就有较大的区别了。  int QuickPartSort3(int* a, int begin, int end) {     //基准值选择     int keyIndex = begin;     int prev = begin;//前指针     int cur = begin + 1;//后指针      while (cur <= end)     {         //cur位置的值小于keyIndex位置的值         if (a[cur] < a[keyIndex])         {             ++prev;             Swap(&a[prev], &a[cur]);         }          ++cur;     }          Swap(&a[prev], &a[keyIndex]);     keyIndex = prev;     return keyIndex; } 前后指针方法中两个指针都是从左向右的方向在走,直到cur走完整个序列。 但是在走的过程中,prev的位置+1后,如果和cur位置相等,因为指向同一个位置,就不需要交换了,代码进一步优化如下:  if (a[cur] < a[keyIndex] && ++prev != cur) {     Swap(&a[prev], &a[cur]); } 前后指针版本不仅好理解,而且代码也很简洁,是几个版本中最推荐的了。 但是,以上方法选基准值还是存在一定问题的。 鉴于以上几个版本都选择的是最左边作为基准值,如果是排已经有序的序列的话,如下图所示:  序列有多少的数据就递归多少次,很容易栈溢出。 所以,为了让每次选择基准值避免选择到序列中的最值,有的地方给出用随机数的方法选择基准值,但这里主要介绍三数取中选择基准值的方法。 所谓三数取中,不过是在序列的起止位置,中间位置,末位位置选择这三个数出来,通过比较,找到不是最大也不是最小的一个数,让这个数做基准值。  int GetMidIndex(int* a, int begin, int end) {     int mid = begin + (end - begin) / 2;      if (a[begin] < a[mid])     {         if (a[mid] < a[end])         {             return mid;         }         else if (a[begin] < a[end])         {             return end;         }         else         {             return begin;         }     }     else//begin>=mid     {         if (a[mid] > a[end])         {             return mid;         }         else if (a[begin] < a[end])         {             return begin;         }         else         {             return end;         }     } } 找到基准值之后,可以将基准值和序列的起始位置进行一个交换,这样就和之前排序的代码一样仍是选择序列的起始位置做基准值了。代码也不需要更多的修改了。  //三数取中优化 int mid = GetMidIndex(a, begin, end); Swap(&a[keyIndex], &a[mid]); 1 2 3 因为三数取中的优化,使的每次递归左右子序列更加的二分,代码效率自然就得到了提高。 当然,我们知道递归需要是创建销毁和销毁栈帧的(具体参考阿顺的这篇博文你也能看懂的的函数栈帧创建与销毁) 因为快速排序的递归和二叉树前序递归很像,如果快速递归的划分接近二分的话,就会像满二叉树的遍历一样,最后一层的数量(2 h − 1 2^{h-1}2  h−1  )会是整棵树数量(2 h − 1 2^h-12  h  −1)的一半,也就是快速排序最后一层的递归次数是整个过程递归次数的一半。在面对小数据量时,还要递归这么多次,进行栈帧的开销,确实会影响到程序的效率。 所以,可以对小区间进行优化:当递归划分小区间,区间比较小的时候,就不再递归划分去排序这个小区间。而是可以考虑直接用其它排序对小区间进行处理。 这里就给出对小区间元素小于10时的插入排序处理。  if (end - begin > 10) {     //int keyIndex = PartSort1(a, begin, end);     //int keyIndex = PartSort2(a, begin, end);     int keyIndex = QuickPartSort3(a, begin, end);      QuickSort(a, begin, keyIndex - 1);     QuickSort(a, keyIndex + 1, end); } else {     InsertSort(a + begin, end - begin + 1); } 通过计算可以得出,以上代码在采用小区间优化后,函数调用次数可以减少80 % 80\€%左右,这种提升还是很明显的。 以上都是对快速排序的递归版本的介绍。但是递归有一个死穴就是递归太深,栈溢出,程序会崩溃。所以,也要能够对快速排序进行非递归的实现。 可以借助栈数据结构或者队列数据结构进行非递归实现。这里就选择栈数据结构来进行实现。借助栈来实现的运行逻辑是可以和递归实现的递归逻辑达成一致的。 因为是C语言实现,所以对于栈数据结构的需要可以参考阿顺的这篇博文顺序表实现栈(C语言)  void QuickSortNonR(int* a, int begin, int end) {     Sk stack;     StackInit(&stack);          StackPush(&stack, end);     StackPush(&stack, begin);      while (!StackEmpty(&stack))     {         int left = StackTop(&stack);         StackPop(&stack);         int right = StackTop(&stack);         StackPop(&stack);          int keyIndex = QuickPartSort3(a, left, right);         // [left, keyIndex-1] keyIndex [keyIndex+1, right]          if (keyIndex + 1 < right)         {             StackPush(&stack, right);             StackPush(&stack, keyIndex + 1);         }          if (left < keyIndex - 1)         {             StackPush(&stack, keyIndex - 1);             StackPush(&stack, left);         }     }      StackDestroy(&stack); } 代码结合画图就很容易理解快速排序的非递归了。  4.3 快速排序特性 快速排序,一听到这个名字你就知道它存在的意义l。而且快速排序整体的综合性能和使用场景都是比较好的,所以也才敢叫快速排序。  快速排序的一次划分算法从两头交替搜索,直到left和right重合,因此其时间复杂度是O ( n ) O(n)O(n);而整个快速排序算法的时间复杂度与划分的趟数有关。 理想的情况是,每次划分所选择的中间数恰好将当前序列几乎等分,经过l o g 2 n log_2nlog  2 ​  n趟划分,便可得到长度为1的子表。这样,整个算法的时间复杂度为O ( n ∗ l o g 2 n ) O(n*log_2n)O(n∗log  2 ​  n)。 最坏的情况是,每次所选的中间数是当前序列中的最大或最小元素,这使得每次划分所得的子表中一个为空表,另一子表的长度为原表的长度-1。这样,长度为n的数据表的快速排序需要经过n趟划分,使得整个排序算法的时间复杂度为O ( n 2 ) O(n2)O(n2)。 从空间性能上看,尽管快速排序只需要一个元素的辅助空间,但快速排序需要一个栈空间来实现递归。最好的情况下,即快速排序的每一趟排序都将元素序列均匀地分割成长度相近的两个子表,所需栈的最大深度为l o g 2 ( n + 1 ) log_2{(n+1)}log  2 ​  (n+1);但最坏的情况下,栈的最大深度为n。  5 归并排序 5.1 算法思想 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序。若将两个有序表合并成一个有序表,称为二路归并。 算法步骤:  申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列; 设定两个指针,最初位置分别指向两个已经排序序列的起始位置; 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置; 重复步骤 3 直到某一指针到达序列尾; 将另一序列剩下的所有元素直接复制到合并序列尾。  归并排序视频演示  5.2 代码实现 作为一种典型的分而治之思想的算法应用,归并排序的实现有两种方法:  自上而下的递归; //n - 数据个数 void MergeSort(int* a, int n) {     //开辟归并空间     int* tmp = (int*)malloc(n * sizeof(int));     assert(tmp != NULL);          //归并排序     _MergeSort(a, 0, n - 1, tmp);      free(tmp); } void _MergeSort(int* a, int begin, int end, int* tmp) {     //递归区间不足1个数据就不再递归     //因为1个数据即有序     if (begin >= end)     {         return;     }          int mid = begin + (end - begin) / 2;     // [begin, mid][mid+1, end] 分治递归,让子区间有序      //左区间递归     _MergeSort(a, begin, mid, tmp);     //又去见递归     _MergeSort(a, mid+1, end, tmp);      //归并过程     int begin1 = begin;     int end1 = mid;     int begin2 = mid + 1;     int end2 = end;     int i = begin1;     while (begin1 <= end1 && begin2 <= end2)     {         if (a[begin1] < a[begin2])         {             tmp[i++] = a[begin1++];         }         else         {             tmp[i++] = a[begin2++];         }     }      while (begin1 <= end1)     {         tmp[i++] = a[begin1++];     }      while (begin2 <= end2)     {         tmp[i++] = a[begin2++];     }      //把归并数据拷贝回原数组     memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int)); } 自下而上的迭代。  所谓自下而上的迭代,不过是最开始从1个和1个开始归并(因为1个数据即有序)。 之后每次归并数据都扩2倍,直到最后一组的数据已经大于等于整个序列的数据为止就完成归并了。可以用循环来解决。 void MergeSortNonR(int* a, int n) {     int* tmp = (int*)malloc(n * sizeof(int));     assert(tmp != NULL);          //最开始,一组只有一个数据     int gap = 1;     //一组的数据要小于序列长度     while (gap < n)     {         for (int i = 0; i < n; i += 2 * gap)         {             //[i,i+gap-1]和[i+gap, i+2*gap-1]区间的数据进行归并             int begin1 = i;             int end1 = i + gap - 1;             int begin2 = i + gap;             int end2 = i + 2 * gap - 1;              int j = begin1;             while (begin1 <= end1 && begin2 <= end2)             {                 if (a[begin1] < a[begin2])                 {                     tmp[j++] = a[begin1++];                 }                 else                 {                     tmp[j++] = a[begin2++];                 }             }              while (begin1 <= end1)             {                 tmp[j++] = a[begin1++];             }              while (begin2 <= end2)             {                 tmp[j++] = a[begin2++];             }         }         //一趟归并结束进行拷贝         memcpy(a, tmp, n * sizeof(int));                  //一组数据扩2倍         gap *= 2;     }          free(tmp); } 但是上面代码有一个致命的bug就是,只能适用于2 n 2^n2  n  个数据的归并。否则就存在越界的错误。 所以需要对归并区间进行界线的判断和修正。 因为begin1=i不会越界,而end1 begin2 end2都可能越界。 所以存在三种情况下的修正:  //修正边界 if (end1 >= n) {     end1 = n - 1;     begin2 = n;     end2 = n - 1; } else if (begin2 >= n) {     begin2 = n;     end2 = n - 1; } else if (end2 >= n) {     end2 = n - 1; } 上述是修正的一种方法。还可以采用更省事的策略:在遇到越界的区间时,就不再归并了。  if (end1 >= n || begin1 >= n) {     break; } else if (end2 >= n) {     end2 = n - 1; } 但这种方法,需要每归并一次,就拷贝一次,而不是等到一趟归并完在拷贝。memcpy函数应该放在for循环之内。  5.3 归并排序特性 归并排序的时间复杂度是稳定的O ( n ∗ l o g 2 n ) O(n*log_2n)O(n∗log  2 ​  n),而因为要开辟额外的归并数组,所以空间复杂度是O ( n ) O(n)O(n)。 归并排序速度仅次于快速排序,一般用于对总体无序,但是各子项相对有序的数列排序。 归并排序的比较次数小于快速排序的比较次数,移动次数一般多于快速排序的移动次数。 虽然归并排序比较占用内存,但却是一种效率高且稳定的算法。 ———————————————— 版权声明:本文为CSDN博主「阿阿阿顺Yaya」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_62172209/article/details/129620963 
  • [技术干货] 算法leetcode|30. 串联所有单词的子串(rust重拳出击)-转载
     30. 串联所有单词的子串: 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。  s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。  例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", "abefcd","cdabef", "cdefab","efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。 返回所有串联字串在 s 中的开始索引。你可以以 任意顺序 返回答案。  样例 1: 输入:     s = "barfoothefoobarman", words = ["foo","bar"]      输出:     [0,9]      解释:     因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。     子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。     子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。     输出顺序无关紧要。返回 [9,0] 也是可以的。  样例 2: 输入:     s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]      输出:     []      解释:     因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。     s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。     所以我们返回一个空数组。 样例 3: 输入:     s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]  输出:     [6,9,12]      解释:     因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。     子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。     子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。     子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。 提示: 1 <= s.length <= 104 1 <= words.length <= 5000 1 <= words[i].length <= 30 words[i] 和 s 由小写英文字母组成 分析: 面对这道算法题目,二当家的陷入了沉思。 由于题干里罗列了所有单词可能的串联出的子串,所以可能理所当然的考虑罗列所有子串,然后再去字符串中查看是否存在,但是这样太慢了,肯定是不对的。 由于题干中说所有字符串 长度相同,考虑用单词计数,滑动窗口解决。 需要注意的一些点是,首先窗口滑动的大小应该是单词的长度才对,另外,由于窗口的大小是单词的长度,那么起始下标就需要考虑偏移。 题解: rust impl Solution {     pub fn find_substring(s: String, words: Vec<String>) -> Vec<i32> {         let mut ans = Vec::new();         let (m, n, ls) = (words.len(), words[0].len(), s.len());         for i in 0..n {             if i + m * n > ls {                 break;             }             let mut differ = std::collections::HashMap::new();             for j in 0..m {                 let word = &s[i + j * n..i + (j + 1) * n];                 *differ.entry(word).or_insert(0) += 1;             }             for word in words.iter() {                 let count = differ.entry(word).or_insert(0);                 if *count == 1 {                     differ.remove(word as &str);                 } else {                     *count -= 1;                 }             }             for start in (i..ls - m * n + 1).step_by(n) {                 if start != i {                     let mut word = &s[start + (m - 1) * n..start + m * n];                     let mut count = differ.entry(word).or_insert(0);                     if *count == -1 {                         differ.remove(word);                     } else {                         *count += 1;                     }                     word = &s[start - n..start];                     count = differ.entry(word).or_insert(0);                     if *count == 1 {                         differ.remove(word);                     } else {                         *count -= 1;                     }                 }                 if differ.is_empty() {                     ans.push(start as i32);                 }             }         }         return ans;     } } go func findSubstring(s string, words []string) (ans []int) {     m, n, ls := len(words), len(words[0]), len(s)     for i := 0; i < n && i+m*n <= ls; i++ {         differ := map[string]int{}         for j := 0; j < m; j++ {             differ[s[i+j*n:i+(j+1)*n]]++         }         for _, word := range words {             if differ[word] == 1 {                 delete(differ, word)             } else {                 differ[word]--             }         }         for start := i; start < ls-m*n+1; start += n {             if start != i {                 word := s[start+(m-1)*n : start+m*n]                 if differ[word] == -1 {                     delete(differ, word)                 } else {                     differ[word]++                 }                 word = s[start-n : start]                 if differ[word] == 1 {                     delete(differ, word)                 } else {                     differ[word]--                 }             }             if len(differ) == 0 {                 ans = append(ans, start)             }         }     }     return } c++ class Solution { public:     vector<int> findSubstring(string s, vector<string>& words) {         vector<int> ans;         int m = words.size(), n = words[0].size(), ls = s.size();         for (int i = 0; i < n && i + m * n <= ls; ++i) {             unordered_map<string, int> differ;             for (int j = 0; j < m; ++j) {                 ++differ[s.substr(i + j * n, n)];             }             for (string &word: words) {                 if (--differ[word] == 0) {                     differ.erase(word);                 }             }             for (int start = i; start < ls - m * n + 1; start += n) {                 if (start != i) {                     string word = s.substr(start + (m - 1) * n, n);                     if (++differ[word] == 0) {                         differ.erase(word);                     }                     word = s.substr(start - n, n);                     if (--differ[word] == 0) {                         differ.erase(word);                     }                 }                 if (differ.empty()) {                     ans.emplace_back(start);                 }             }         }         return ans;     } }; python class Solution:     def findSubstring(self, s: str, words: List[str]) -> List[int]:         ans = []         m, n, ls = len(words), len(words[0]), len(s)         for i in range(n):             if i + m * n > ls:                 break             differ = Counter()             for j in range(m):                 word = s[i + j * n: i + (j + 1) * n]                 differ[word] += 1             for word in words:                 if differ[word] == 1:                     del differ[word]                 else:                     differ[word] -= 1             for start in range(i, ls - m * n + 1, n):                 if start != i:                     word = s[start + (m - 1) * n: start + m * n]                     if differ[word] == -1:                         del differ[word]                     else:                         differ[word] += 1                     word = s[start - n: start]                     if differ[word] == 1:                         del differ[word]                     else:                         differ[word] -= 1                 if len(differ) == 0:                     ans.append(start)         return ans java class Solution {     public List<Integer> findSubstring(String s, String[] words) {         List<Integer> ans = new ArrayList<Integer>();         int           m   = words.length, n = words[0].length(), ls = s.length();         for (int i = 0; i < n && i + m * n <= ls; ++i) {             Map<String, Integer> differ = new HashMap<String, Integer>();             for (int j = 0; j < m; ++j) {                 String word = s.substring(i + j * n, i + (j + 1) * n);                 differ.put(word, differ.getOrDefault(word, 0) + 1);             }             for (String word : words) {                 differ.put(word, differ.getOrDefault(word, 0) - 1);                 if (differ.get(word) == 0) {                     differ.remove(word);                 }             }             for (int start = i; start < ls - m * n + 1; start += n) {                 if (start != i) {                     String word = s.substring(start + (m - 1) * n, start + m * n);                     differ.put(word, differ.getOrDefault(word, 0) + 1);                     if (differ.get(word) == 0) {                         differ.remove(word);                     }                     word = s.substring(start - n, start);                     differ.put(word, differ.getOrDefault(word, 0) - 1);                     if (differ.get(word) == 0) {                         differ.remove(word);                     }                 }                 if (differ.isEmpty()) {                     ans.add(start);                 }             }         }         return ans;     } }  ———————————————— 版权声明:本文为CSDN博主「二当家的白帽子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/leyi520/article/details/128422146 
  • [技术干货] 菜鸟刷题Day5-转载
     一.一维数组的动态和:1480. 一维数组的动态和 - 力扣(LeetCode) 描述  给你一个数组 nums 。数组「动态和」的计算公式为:runningSum[i] = sum(nums[0]…nums[i]) 。  请返回 nums 的动态和。  示例:  输入:nums = [1,2,3,4] 输出:[1,3,6,10] 解释:动态和计算过程为 [1, 1+2, 1+2+3, 1+2+3+4] 。  解题思路  1.通过观察示例可以发现,其实runningSum[0]和nums[0]相等,runningSum[1]=runningSum[0]+nums[1];所以我们可以得到这样一个结论:当 i > 0时:runningSum[i]=runningSum[i-1]+nums[i];  我可以新开辟一个数组(大小和nums一样大),将累加的结果存放到这个新开的数组中,再返回这个新开辟的数组。  int* runningSum(int* nums, int numsSize, int* returnSize) {         int*tmp=(int*)malloc(sizeof(int)*numsSize);     tmp[0]=nums[0];          for(int i=1;i<numsSize;i++)     {         tmp[i]=tmp[i-1]+nums[i];     }     *returnSize=numsSize;          return tmp; } 2.直接在原地改变,除了第一项不用改变以外,后面的每一项元素都是前面元素累加的结果。  int* runningSum(int* nums, int numsSize, int* returnSize) {     for(int i=1;i<numSize;i++)     {          nums[i]+=nums[i-1];     }          *returnSize=numsSize;          return nums; } 二.搜索插入位置:35. 搜索插入位置 - 力扣(LeetCode) 描述  给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。  请必须使用时间复杂度为 O(log n) 的算法。  解题思路  看到说时间复杂度位O(log n)就让人想起二分查找算法,二分查找虽然强大,但是有一个很大的缺陷就是要求被查找的数据必须要有序。正好题目说了是一个排序数组,那还不二分走起  int searchInsert(int* nums, int numsSize, int target) {     int begin=0;     int end=numsSize-1;     int mid=0;          while(begin<=end)     {         mid=(begin+end)/2;                  if(nums[mid]<target)         {             begin=mid+1;         }         else if(nums[mid]>target)         {             end=mid-1;         }         else//相等         {             return mid;         }               }    return begin; 关于最后的返回值:位于begin左边的数一定小于目标值,而在end右边的数一定是大于end的,也就是说目标值要么在begin和end的中间,要么就在begin和end之间的某个位置插入,而最后的结束条件是begin>end,也就是说这中间只有begin是一直在改变的,因此如果最后在数组中找不到这个元素,那么它的插位置一定是begin位置  三.搜索旋转排序数组:33. 搜索旋转排序数组 - 力扣(LeetCode) 描述  整数数组 nums 按升序排列,数组中的值 互不相同 。  在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。  给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。  你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。  解题思路  首先映入眼帘的还是O(log n),那么用二分查找吗?一看是升序排序数组,但是这里又说了,会在某个位置旋转一下。好像不能用二分  但是(我知道你在等但是),在k位置旋转以后,仍然是局部有序的。如果中间的数小于最右边的数,则右半段是有序的,若中间数大于最右边数,则左半段是有序的,我们只要在有序的半段里用首尾两个数组来判断目标值是否在这一区域内,这样就可以确定保留哪一半了  这里只要通过控制下标就可以实现在一个数组内分割出两个数组的目的  int search(int* nums, int numsSize, int target) {     //核心在于只是局部有序      int begin=0,end=numsSize-1;     while(begin<=end)     {         int mid=(begin+end)/2;          if(nums[mid]==target)             return mid;          //二分查找只能在有序数列中进行,所以要判断有序范围,一定是局部有序         if(nums[mid]<nums[end])//说明右边数组有序         {             //判断一下目标值是否在右边数组中,既然在右边数组,那么就要重新调整一下mid的值             if(nums[mid]<target&&nums[end]>=target)                  begin=mid+1;             else                 end=mid-1;               }         else         {             //左边数组有序,还要判断一下是否在左边数组中             if(nums[mid]>target&&nums[begin]<=target)                 end=mid-1;             else                 begin=mid+1;         }      }     //到这里就是找不到     return -1; } 四.二进制链表转整数:1290. 二进制链表转整数 - 力扣(LeetCode) 描述  给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。请你返回该链表所表示数字的 十进制值 链表不为空; 链表的结点总数不超过 30; 每个结点的值不是0就是 1  示例:  输入:head = [1,0,1] 输出:5 解释:二进制数 (101) 转化为十进制数 (5) 解题思路  1.建立一个数组,然后遍历链表,将链表中的值存取到数组中,将数组的内容转换成十进制,在size-1位置的反而是于二的零次方相乘,所以可以得到如下代码      int getDecimalValue(struct ListNode* head)  {      int arr[31];     int i=0;     int flag=0;//做标记,如果数组中没有1,无论链表多长结果都是0     while(head!=NULL)     {         arr[i]=head->val;         i++;         if(head->val==1)         {             flag=1;         }         head=head->next;     }     //将链表中的数值全部提取到数组中,必须要拿到数组的有效长度     int sum=0;     int sz=i;      if(flag==0)         return 0;     else     {         for(i=0;i<sz;i++)         {            if(arr[i]!=0)            {                 sum+=pow(2,sz-1-i);            }         }     }      return sum; } 2.此外还可以利用位运算,第一次就从链表中拿到的那个数其实是二的size-1次方,而左移一位就有乘二的效果  int getDecimalValue(struct ListNode* head) {     int sum=0;          while(head!=NULL)     {         sum=(sum<<1)+head->val;                  head=head->next;     }          return sum; } ———————————————— 版权声明:本文为CSDN博主「别动我的饭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_62633482/article/details/129737637 
  • [技术干货] 米哈游春招算法岗-2023.03.19-第一题-交换字符-简单题-转载
     交换字符 Problem Description 米小游拿到了一个仅由小写字母组成的字符串,她准备进行恰好一次操作:交换两个相邻字母,在操作结束后使得字符串的字典序尽可能大。 请你输出最终生成的字符串。  input 一个仅由小写字母组成的字符串,长度不小于 2 ,不超过200000 。  ouput 操作后的字符串。  Sample Input ba  Sample Output ab  题目类型、难度、来源 类型:暴力 难度:简单 来源:米哈游春招算法岗-2023.03.19-第一题-交换字符 总体思路: 此题很简单,输入一个字符串,必须要交换一次,使得字符串的字典序尽可能大。 ①首先从左到右遍历,如果遇到右边的字符比左边的字符大,马上交换,并break。 ②如果第一种情况不存在,说明不能通过交换使字符串字典序变大,此时就要注意避免交换导致字典序减小。就要看字符串中是否有相邻的两个字符相同大小。如果存在,那么可以使用一次交换使字典序不变。 ③如果上面两种情况都不存在,就要尽量让字典序减少得尽可能少。此时应该交换字符串最后的两个元素。 AC代码 #include <iostream> #include <string> using namespace std; void swap(char &a, char &b){     char t = a;     a = b;     b = t; } int main(){     string s;     cin >> s;     int i, flag = 0;     for (i = 0; i < s.size()-1; i++){         if (s[i+1] > s[i]){             swap(s[i], s[i+1]);             break;         }else if (s[i+1] == s[i]){             flag = 1;         }     }     if (i == s.size()-1){         if (flag == 0){             swap(s[s.size()-1], s[s.size()-2]);         }     }     cout << s;     return 0; } ———————————————— 版权声明:本文为CSDN博主「新时代原始人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_40735291/article/details/129719584 
  • [技术干货] 【ChatGPT 中文版插件】无需注册体验 ChatGPT 的攻略-转载
     🍓前言 面对一夜爆火的 ChatGPT ,我最终也没抵得住诱惑,决定体验一下,不过这玩意要注册需要外国手机号以及科学上网,将许多人拦在门外,本篇博客将体验当下爆火的 ChatGPT 以及无需注册和科学上网,拿来即用的 ChatGPT 使用攻略,快来试试吧!  🍒ChatGPT 是什么? 被炒得火爆的ChatGPT到底是什么?  美国人工智能公司OpenAI发布免费机器人对话模型ChatGPT(GPT-3.5系列),模型中首次采用RLHF(从人类反馈中强化学习)方式。模型目前处于测试阶段,用户与ChatGPT之间的对话互动包括普通聊天、信息咨询、撰写诗词作文、修改代码等。功能如此“全面”的ChatGPT被称作“最强AI(人工智能)”,面世5天便已有超过100万用户使用。  就是一个功能强大的 “AI机器人” ,到底有多强大,怎么体验,一起来看看吧!  🍒 vscode ChatGPT 中文版插件安装 没错,这个号称最强AI的机器人在vscode中已经有中文版插件了,赶紧装一个体验一下吧!  打开你的vscode,搜索ChatGPT安装这个插件:   可以看到他的使用方法在这个插件中写的十分详细,甚至还有附有小破站的视频教程,实在不会的小白可以去看看!  🍒 ChatGPT 初体验  可以看到,这个功能还是很多的,最常用的就是提问,我们来体验一下!  快捷键ctrl+shift+p 或者 command+shift+p打开命令面板,输入"ChatGPT"。  回车后在这里输入你的问题,当然也可以直接点击左侧小方块,在下面的输入框直接输入问题!  🍅测试一:聊天  嘿嘿嘿!强!  🍅测试二:用c语言求1-100之间素数的代码 我们来体验一下问答写代码! 先来一个简单的!  我直呼好家伙,还真写出来了,而且注释很详细!这代码都不用验证,完全没有问题哇!  我们把代码提取出来,跑一下吧!  #include <stdio.h>  int main() {   // 定义一个变量用来保存当前数字   int num;    // 使用一个循环来遍历1-100之间的所有数字   for (num = 1; num <= 100; num++) {     // 定义一个变量用来保存当前数字是否为素数,默认为true     int is_prime = 1;      // 使用另一个循环来遍历2-num-1之间的所有数字     for (int i = 2; i < num; i++) {       // 如果当前数字能够被i整除,那么它不是素数       if (num % i == 0) {         is_prime = 0;         break;       }     }      // 如果is_prime仍然为true,那么当前数字是素数,输出它     if (is_prime) {       printf("%d is a prime number\n", num);     }   }    return 0; } 我倒要看看这个AI有多强,给他上难度!  🍅测试三:写一个C语言管理系统 用c 语言写一个会员信息管理系统。录入会员信息并可选择功能查询。会员信息包括会员号,会员姓和名,什么时间加入的,从2017年至2021年缴纳的会员费(注:maloc , calloc , realloc , free 等函数希望可以用上)!  具体包含功能如下: 1.增加新会员 2.移除会员 3.更新某一会员信息 4.输出所有会员信息 5.查询某一会员信息 6.退出会员系统   只能说,蒸的c,只是这个插件代码格式化功能不太好,代码块显示不太好,但也很明显的看到用结构体存储数据,实现了基础的增删改查! 需要注意的是,当本次输出达到上限,你需要输入“继续”,他会继续接着输出! 本题代码庞大,我就不测试了,感兴趣的可以去尝试!  🍅测试四:询问代码功能 total = 0 num_list = [1,2,3,4] for i in num_list:     for j in num_list:         for k in num_list:             if (i!=j) and (i!=k) and (j!=k):                 print(i,j,k)                 total += 1 print(total) 确实没啥问题!  🍓 结语 ChatGPT 的强大毫无疑问,确实可以帮助我们写代码,前提是你的需求要足够清晰,简直就是开发的一大利器,可以借助它实现基础功能,然后拿来修改,关键是它啥语言都能写…,并且注释很详细,利用得当也可以帮助小白学习!那么你认为程序员最终会被人工智能替代吗?  赶紧去体验体验体验吧!只有体验了才能感触到它的强大!当然也有现成的网站可以直接体验,链接我放评论区了哦! ———————————————— 版权声明:本文为CSDN博主「馆主阿牛」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_57421630/article/details/128272956 
  • [技术干货] list的简单介绍和模拟实现-转载
     1.list的介绍 list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 list和forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。 与其他序列式容器相比(array,vector,deque),list通常在任意位置插入,移除元素的执行效率更好。 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问 2.list的使用 2.1.list的构造 构造函数    接口说明 list(size_type n,const value_type& val = value_type())    构造的list中包含n个值为val的元素 list    构造空的list list(const list& x)    拷贝构造函数 list(InputIterator first,InputIterator last)    用[first,last)区间中的元素构造list 2.2.list iterator的使用 函数声明    接口说明 begin + end    返回第一个元素的迭代器+返回最后一个元素的下一个位置的迭代器 rbegin + rend    返回最后一个数据的位置迭代器+返回第一个数据的前一个位置的迭代器 2.3.list capacity 函数声明    接口说明 empty    检测list是否为空,是返回true,不是返回false size    返回list节点中有效节点的个数 2.4.list element access 函数声明    接口说明 front    返回list的第一个节点中值的引用 back    返回list的最后一个节点中值的引用 2.5.list modifiers 函数声明    接口说明 push_front    在list首元素前插入值为list的元素 pop_front    删除list中的第一个元素 push_back    在list尾部插入值为val的元素 pop_back    删除list的最后一个元素 insert    在list position位置插入值为val的元素 erase    删除list position位置的元素 swap    交换两个list中的元素 clear    清空list中的有效元素 3.list的迭代器失效 我们可以类似先把迭代器暂时理解成指针。则迭代器失效即迭代器所指向的节点无效。即该节点被删除了。因为list的底层结构为带头节点的双向循环链表,因此在list中进行插入不会导致list迭代器失效的,只有在删除时才会失效,并且失效的只是指向删除节点的迭代器,其他迭代器不会受到影响。  4.list和vector的对比 list和vector都是STL中非常重要的序列式容器,由于两个容器的底层结构不一样,导致其特性以及应用场景不同  vector    list 底层结构    动态顺序表,一段连续空间    带头节点的双向循环链表 随机访问    支持随机访问,访问某个元素的效率O(1)    不支持随机访问,访问某个元素的效率O(N) 插入和删除    任意位置插入和删除效率低,需要移动元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低    任意位置插入和删除效率高,不需要移动元素,时间复杂度为O(1) 空间利用率    底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高    底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 迭代器    原生态指针    对原生态指针进行封装 迭代器失效    在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能导致重新扩容,导致原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效    插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 使用场景    需要高效存储,支持随机访问,不关心插入和删除    大量的插入和删除,不关心随机访问 5.list的模拟实现 #define  _CRT_SECURE_NO_WARNINGS  #include <iostream> #include <algorithm> #include <assert.h> using namespace std;  namespace smk {     //一个一个的节点     template <class T>     struct ListNode     {         ListNode* next;         ListNode* prev;         T data;          ListNode(const T& x)             :next(nullptr)             ,prev(nullptr)             ,data(x)         {}     };      //封装 + 运算符重载     template <class T,class Ref,class Ptr>     struct list_iterator     {         typedef ListNode<T> Node;         typedef list_iterator<T ,Ref,Ptr> Self;          list_iterator(Node* p)             :_pNode(p)         {}         Ref operator*()         {             return _pNode->data;         }          //找到节点的指针         Self& operator++()         {             _pNode = _pNode->next;             return *this;         }          Self& operator--()         {             _pNode = _pNode->prev;             return *this;         }         bool operator!=(const Self& it)         {             return _pNode != it._pNode;         }          bool operator==(const Self& it)         {             return _pNode == it._pNode;         }          Ptr operator->()         {             return &_pNode->data;         }         //节点的指针         Node* _pNode;     };          //带头双向循环链表     template <class T>     class list     {         typedef ListNode<T> Node;     public:         typedef list_iterator<T,T&,T*> iterator;         typedef list_iterator<T, const T&,const T*> const_iterator;          iterator begin()         {             return iterator(_head->next);         }         const_iterator begin() const         {             return const_iterator(_head->next);         }         iterator end()         {             return iterator(_head);         }         const_iterator end() const         {             return const_iterator(_head);         }          void Initialize()         {             _head = new Node(T());             _head->next = _head;             _head->prev = _head;              _size = 0;         }          list()         {             Initialize();         }          template <class Interiterator>         list(Interiterator first, Interiterator last)         {             Initialize();             while (first != last)             {                 push_back(*first);                 ++first;             }         }          void swap(list<T>& st)         {             std::swap(_head, st._head);             std::swap(_size, st._size);         }          //现代写法的拷贝构造         list(const list<T>& st)         {             Initialize();             list<T> tmp(st.begin(), st.end());             swap(tmp);         }          //拷贝构造         /*list(const list<T>& st)         {             Initialize();              for (auto& e : st)             {                 push_back(e);             }         }*/          list<T>& operator=(list<T> st)         {             swap(st);             return *this;         }          //传统写法的赋值重载         /*list<int>& operator=(const list<T>& st)         {             if (this != &st)             {                 clear();                 for (auto& e : st)                 {                     push_back(e);                 }             }              return *this;         }*/          void clear()         {             iterator it = begin();             while (it != end())             {                 it = erase(it);             }         }          ~list()         {             clear();              delete _head;             _head = nullptr;         }          size_t size() const         {             return _size;         }          bool empty() const         {             return _size == 0;         }          void push_back(const T& val)         {             //Node* newNode = new Node(val);             //Node* tail = _head->prev;             _head  tail  newNode             //tail->next = newNode;             //newNode->prev = tail;             //newNode->next = _head;             //_head->prev = newNode;              insert(end(),val);         }          void push_front(const T& val)         {             insert(begin(), val);         }          void pop_front()         {             erase(begin());         }          void pop_back()         {             erase(--end());         }          iterator insert(iterator pos, const T& val)         {             Node* newnode = new Node(val);             Node* cur = pos._pNode;             Node* prev = cur->prev;             //链接 prev newnode cur             prev->next = newnode;             newnode->prev = prev;             newnode->next = cur;             cur->prev = newnode;              ++_size;              return iterator(newnode);         }          iterator erase(iterator pos)         {             assert(pos._pNode != _head);             Node* prev = pos._pNode->prev;             Node* next = pos._pNode->next;              prev->next = next;             next->prev = prev;              --_size;              return iterator(next);         }     private:         //链表的指针         Node* _head;          size_t _size;     };  ———————————————— 版权声明:本文为CSDN博主「孙梦康」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/SASCII/article/details/128315305 
  • [问题求助] c#在win7调用ccgateway的https任意接口报错
    【问题来源】【必填】浙公院【问题简要】【必填】c#在win7调用ccgateway的https任意接口报错,SSL通道无法创建。调用AgentClient-20210419005736.jar包8443端口的https接口是正常的。排查发现请求端必须是tls1.2版本,但现在win7系统的Schannel套件默认不开启该版本,希望ccgateway的tls版本能支持,非互联网应用。【问题类别】【必填】CTI平台提供的cc-gateway接口【AICC解决方案版本】【必填】AICC 8.15.0 CC-Gateway开发指南 (RESTful)【问题现象描述】【必填】调用jar包和ccgateway不同结果参考如下图,调用ccgateway的8043端口出错,调用agentclient.jar包的返回数据正常​​
  • [C/C++] 一起来看看auto推导规则
    🎄auto推导规则1️⃣如果auto声明的变量是按值初始化,则推导出的类型会忽略const、volatile限定符。进一步解释为,在使用auto声明变量时,既没有使用引用,也没有使用指针,那么编译器在推导的时候会忽略const和volatile限定符。当然auto本身也支持添加const、volatile限定const int i = 10; auto j = i; // auto推导类型为int auto &m = i; // auto推导类型为const int& auto *k = i; // auto推导类型为const int const auto n = j; // auto推导类型为int✨✨解释:因为在进行类型推导时忽略了const、volatile,所以i虽然我const int类型结果却是int类型 而m和k分别为引用和指针初始化,所以原本的const和volatile属性得以保留 最后,const auto结合让n的类型推导为const int2️⃣在使用auto声明变量初始化时,目标对象如果是引用&,则引用属性会被忽略:int i = 10; int &j = i; auto m = j; // auto推导类型为int✨✨解释:虽然j是i的引用,j的类型为int&但是在推导m的时候auto会直接忽略掉其中的引用推导为int类型,也就是说明对谁的引用就会直接推导成谁本身的类型3️⃣使用auto和万能引用声明变量时,对于左值会将auto推导为引用类型:int i = 10; auto&& k = i; // auto推导类型为int& auto&& j = 10; // auto推导类型为int解释: 因为i是一个左值,所以k的类型会被推导为int&,auto被推导为int&,这其中用到了引用折叠的规则。而5是一个右值,因此j的类型被推导为int&&,auto被推导为int4️⃣使用auto声明变量,如果目标对象是一个数组或者函数,则auto会被推导为对应的指针类型int i[10]; auto m = i; // auto推导类型为int* int add(int A, int A){ return A+A; } auto j = add // auto推导类型为int (__cdecl *)(int,int)解释: i[10]是数组类型,但是在推导时变为int*,m化为指针类型指向i的首地址,同样,j也退为函数指针类型5️⃣当auto关键字与列表初始化组合时直接使用列表初始化,列表中必须为单元素,否则无法编译,auto类型被推导为单元素的类型。用等号加列表初始化,列表中可以包含单个或者多个元素,auto类型被推导为**std::initializer_list**,其中T是元素类型。请注意,在列表中包含多个元素的时候,元素的类型必须相同,否则编译器会报错。auto A1 = { 10, 12 }; // x1类型为 std::initializer_list auto A2 = { 10, 12.0 }; // 编译失败,花括号中元素类型不同 auto A3{ 10, 12 }; // 编译失败,不是单个元素 auto A4 = { 13 }; // x4类型为std::initializer_list auto A5{ 13 }; // x5类型为int
  • [C/C++] 一起来看看重新定义的auto关键字
    ✨一起来看看重新定义的auto关键字🎁auto作为一个在c++98标准就已经开始存在的关键字,因为现在我们已经几乎不再去使用去声明自动变量,于是乎在c++11中我们重新对其进行定义赋予了它新的含义:🎄声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。🎈例如:auto A = 1;这里没有对A去声明类型,但是通过auto进行推导时已经将A推导为int类型的变量🎈再以字符为例:auto str="hello world";在这里同样是auto自动进行推导,推导类型为 const char*🎈再以函数返回值为例:auto add(int A,int B)->int{return A+B;}再这里add函数声明采用了函数返回类型后置的方法,在这里同样可以看出返回值A+B为int型推导出的函数类型为int型✨但是我们在使用auto时一定要注意,在进行类型推导时一定要对变量进行初始化,比如下面没有初始化就不能编译通过auto A;A=1;✨这里很明显没有进行初始化,也就导致了编译器无法推导具体类型而报错除此之外auto在进行编译推导时还有以下4点规则1️⃣当我们使用auto声明多个变量时,编译器遵循从左往右的推导规则,以最左边的表达式推断auto的具体类型:auto n=12,m=10;在上面的代码因为是以左边的表达式类型推导auto的具体类型,n=12可以推导出为int型,于是m这个时候同样也被声明成int类型,在这种情况下,要注意声明多个变量时要保证声明的变量都是相同的,比如下面的变量类型不同就编译不通过;auto n=12,m=10.0;在这种情况下由于n为int型,m为浮点型,前后不同所以报错2️⃣当使用条件表达式初始化auto声明的变量时要注意,编译器往往会初始化为表达能力更强的类型而并不是是我们想的条件表达式的返回类型,例如:auto A=true?10:12.0;在上面的代码种,虽然条件表达式的返回类型为int但是A的类型依然会被推导为double3️⃣在C++11中静态成员变量是可以用auto声明并且初始化的,不过前提是auto必须使用const限定符: 例如:struct nametype { static const auto A = 12; };一旦使用const就将导致变量的常量化,为了解决这一问题,在c++17中就已经提出了响应的解决方法,代码如下:struct nametype{ static inline auto A=12; }4️⃣无法在形参列表中使用auto声明形参void sum(auto A,auto B){ ...... }
  • [C/C++] 内联命名空间的定义和使用
    内联命名空间的定义和使用✨✨在进行大型开发过程中,我们偶尔发现会遇到函数重名或者类型同名造成的编译冲突问题。为了解决类似的问题,我们需要合理使用命名空间,于是我们可以将函数和类型纳入命名空间中,这样,因为不在同一命名空间下就不会产生函数和类型的冲突比如:namespace name1 { void foo() {} } namespace name2 { void foo() {} } using namespace name1; int main(){ stu(); name2::stu(); }✨✨在上面的例子中我们用有俩个命名空间name1、name2,在两块空间下都具有相同的函数stu()于是我们可以使用命名空间的名称去调用空间内的函数,也可以去通过using关键字去打开命名空间,但是在每次使用空间都需要打开空间就显得有些复杂了,于是在c++11中就增强了命名空间的特性;✨✨在c++11中提出了内联空间的概念,我们可以使用inline关键字将空间内的函数和类型导出在父空间中,这样内联空间内的函数就可以直接通过父空间直接调用,则样就是即使不指定子命名空间也可以使用其子命名空间内的函数和类型了例如:#include #include namespace Parent { namespace name1 { void stu() { std::cout << "命名空间1.0" << std::endl; } } inline namespace name2 { void stu() { std::cout << "命名空间2.0" << std::endl; } } } int main(){ Parent::name1::stu(); Parent::stu(); }✨✨在上面的代码中在对name2使用了inline关键字标记为内联命名空间,所以调用name1的stu函数需要明确指定所属的命名空间。而在调用name2当中的stu函数就显得十分方便了,可以直接通过父空间进行调用但是这一点好像目前看来并没有太大的作用,那么内联命名空间的作用到底是上面呢?其实内联函数命名空间的这一特性在版本的迭代方面就可以看出作用,给大家举个例子看看当我们在进行版本迭代的时候,就比如下面的这段代码#include #inculde namespace Parent { void stu() { std::cout << "版本1.0" << std::endl; } } int main(){ Parent::stu(); }✨✨在对这段代码进行的时候我们就需要对这代代码进行大量的删改操作,但是问题来了,当我们想要再次体验老版本时候就很难再找回原来的版本,那么我们就可以通过内联命名命名空间这样一个特性进行新老版本的更迭比如下面这段代码:namespace Parent { namespace name1 { void stu() { std::cout << "版本1.0" << std::endl; } } inline namespace name2 { void stu() { std::cout << "版本2.0" << std::endl; } } } int main(){ Parent::stu(); }✨✨从上面的代码中可以看出,当我们需要将版本升级到2.0时只需要在将Parent::stu()改为Parent::name2::stu()。我们就可以使用新的版本,不光如此我们也可以在同一个父空间下创建版本3.0、版本4.0、版本5.0,我们只需要创建对应的内联命名空间就好;🎃🎃值得注意的是在同一个父命名空间下只允许一个内联命名空间,不然编译时就会因为不知道使用那个内联命名空间而报错🎃🎃
总条数:42 到第
上滑加载中