十大排序算法

十大排序算法分别为:冒泡排序、快速排序、插入排序、希尔排序、选择排序、堆排序、归并排序、桶排序、计数排序、基数排序

他们的时间复杂度分别为:

关于时间复杂度

平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。

线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序;

O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序

线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。

关于稳定性

稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。

不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。

名词解释:

  • n:数据规模
  • k:"桶"的个数
  • In-place:占用常数内存,不占用额外内存
  • Out-place:占用额外内存
  • 稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同

冒泡排序

冒泡排序是基于交换排序的典型, 是快速排序思想的基础,是一种稳定的排序算法。它的时间复杂度为O(n2)

思路:循环遍历多次每次从前往后把大元素往后调,每次确定一个最大(最小)元素,多次后达到排序序列。(或者从后向前把小元素往前调)。

实现方式:

public static void bubble(int[] arr) {for (int i = 1; i < arr.length; i++) {// 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。boolean flag = true;for (int j = 0; j < arr.length - i; j++) {if (arr[j] > arr[j + 1]) {arr[j] = arr[j] + arr[j + 1];arr[j + 1] = arr[j] - arr[j + 1];arr[j] = arr[j] - arr[j + 1];flag = false;}}if (flag) {break;}}}

快速排序

快速排序是对冒泡排序的一种改进,采用递归分治的方法进行求解。而快排相比冒泡是一种不稳定排序,时间复杂度最坏是O(n2),平均时间复杂度为O(n log n),最好情况的时间复杂度为O(n log n)。

对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

思路:

  • 从数列中挑出一个元素,称为 “基准”(pivot);

  • 分区:将比这个数大或等于的数放在它的右边,小于它的数放在它的左边

  • 再对左右区间重复第二步,直到各区间只有一个数

具体步骤:选择一个基准数,这个基准数的位置记为第一个坑 从后往前寻找比他小的数字,找到的第一个比他小的数字则挖走填入到前一个坑中,此时挖走数据,则形成了一个新的坑,然后从前往后找比他大的数,找到的第一个也填到上一个挖走数字的坑中。以此类推,直到无法再查询。

注意:每找到一个数,就换一个方向,并且把这个数字填到上一次挖出的坑中

实现方法:

class QuickSort {public void quickSort(int[] arr, int start, int end) {// 找出分左右两区的位置索引位置,然后对左右两区进行递归调用if (start < end) {int index = getIndex(arr, start, end);// 对左区递归quickSort(arr, start, index - 1);// 对右区递归quickSort(arr, index + 1, end);}}private static int getIndex(int[] arr, int start, int end) {int i = start;int j = end;int x = arr[i];while (i < j) {// 从后往前找,如果找出来的数比基准数大,则让 j--while (i < j && arr[j] >= x) {j--;// 当 j<i 或者 arr[j]<x 则跳出循环// 跳出循环如果 i<j ,则说明找到了比基准数小的数}if (i < j) {// 将这个数字填入到上一个坑arr[i] = arr[j];// 由于上一个坑已经被填入,所以需要 i++i++;}// 从前往后找比基准数小的数字while (i < j && arr[i] < x) {i++;}if (i < j) {arr[j] = arr[i];j--;}}// 此时 i 和 j相等,arr[i]与arr[j]都是一样的// 把基准数放到最后一个坑arr[i] = x;// 返回索引位置return i;}
}

插入排序

插入排序是一种最简单的排序方法,他是将一个数据插入到一个长度为m的有序表中,并且让他保持有序性

从1索引处开始,将后面的元素插入到之前的有序列表中,并且使之保持有序。

class FastSort {// 外层循环定义轮次public void fastSort(int[] arr) {// 因为是从1索引处开始插入,所以这里 i 要从1开始for (int i = 1; i < arr.length; i++) {// 里层循环进行比较插入int j = i;while (j > 0 && arr[j] < arr[j - 1]) {arr[j] = arr[j] + arr[j - 1];arr[j - 1] = arr[j] - arr[j - 1];arr[j] = arr[j] - arr[j - 1];j--;}}}
}// 或者class FastSort {// 外层循环定义轮次public void fastSort(int[] arr) {// 因为是从1索引处开始插入,所以这里 i 要从1开始for (int i = 1; i < arr.length; i++) {// 里层循环进行比较插入for (int j = i; j > 0; j--) {if (arr[j] < arr[j - 1]) {arr[j] = arr[j] + arr[j - 1];arr[j - 1] = arr[j] - arr[j - 1];arr[j] = arr[j] - arr[j - 1];}}}}
}

希尔排序

希尔排序又称缩小增量排序,他是对插入排序的一个优化。直接插入排序实际上就是增量为1的希尔排序。

思路:先将原数组按照增量(ht)分组,每个子序列按照直接插入法排序。排好之后用下一个增量 ht/2 再将数组分为子序列,再使用直接插入法排序,直到 增量为1的时候,整个序列排序完成。经过第一轮排序,会让数组大致有序,再进行细致的排序

希尔排序的关键在于怎样选择一个合适的增量,第一次排序可以设置增量为数组长度的一半

d就是增量,每间隔为5的两个元素组成一组,每一趟排序,将每组元素中小的元素放前面,大的元素放后面(前者大则两者位置互换,前者小则不做操作)

实现方法

// 设置增量为数组一半时的方法
class ShellSort {public void shellSort(int[] arr) {// 定义ht为首次排序的增量,长度为数组的一半for (int ht = arr.length / 2; ht > 0; ht /= 2) {for (int i = ht; i < arr.length; i++) {for (int j = i; j > ht - 1; j -= ht) {if (arr[j] < arr[j - ht]) {swap(arr, j, j - ht);}}}}}public void swap(int[] arr, int i, int j) {int t = arr[i];arr[i] = arr[j];arr[j] = t;}
}

有的时候,增量设置为数组长度的一半也不是很好,这时我们就可以使用克努特序列

克努特序列( Knuth 序列 ):数组从1 开始,通过递归表达式 h = h*3 + 1 来产生。倒推公式则为h = ( h - 1 ) / 3

优化后的算法:

class ShellSort {public void shellSort(int[] arr) {// 通过克努特序列来确定增量int ht = 1;// 注意此处要让数组长度除以3while (ht <= arr.length / 3) {// 克努特序列思想ht = ht * 3 + 1;}// 缩小增量则是反向的克努特序列式子for (int h = ht; h > 0; h = (h - 1) / 3) {for (int i = h; i < arr.length; i++) {for (int j = i; j > h - 1; j -= h) {if (arr[j] < arr[j - h]) {swap(arr, j, j - h);}}}}}public void swap(int[] arr, int i, int j) {int t = arr[i];arr[i] = arr[j];arr[j] = t;}
}

选择排序

思路:从0索引处开始,依次和后面的元素进行比较 ,小的元素往前放,经过一轮比较后,最小的元素就放在了最小索引处,每经过一轮比较,索引位置 + 1。

注意:换完位置之后,比较不停止,而是使用交换了位置的元素继续比较,直到比较到最后一个

例如:13 69 80 57 24 。假设此时进行第二轮比较,即从69开始

69比57大,则互换位置,注意此时:继续用索引为1 位置的数字进行比较,即用57继续往后比较,57比24大,互换位置。

也就是说,比较只有到达数组末尾才会结束,互换位置之后继续用当前索引位置的数字进行比较。数组长度为N,则要比较 N-1 轮,

实现方法:

class ChooseSort {public void chooseSort(int[] arr) {// 第一轮比较,从0索引处开始比较,只要比较 length - 1 轮// 每比较完一轮,index++for (int index = 0; index < arr.length - 1; index++) {// 直接与索引处下一个数字进行比较,所以 i = index + 1for (int i = 1 + index; i < arr.length; i++) {if (arr[index] > arr[i]) {swap(arr, index, i);}}}}public void swap(int[] arr, int i, int j) {int t = arr[i];arr[i] = arr[j];arr[j] = t;}
}

堆排序

堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序

思路:

  • 将需要排序的序列构造成一个大顶堆(大根堆),此时,整个序列的最大值就是堆顶的根节点
  • 将其与末尾元素进行交换,此时末尾就为最大值
  • 然后将剩余 n-1 个元素重新构造成一个堆,这样会得到这 n 个元素的最小值
  • 反复执行,直到有序

建立大顶堆时,是从最后一个非子叶节点开始从下往上调整的

大顶堆结构的特点:每一个节点的值都大于或等于他左右孩子节点的值

如果数组长度为N,那么最后一个非子叶节点的位置就为 N/2 - 1。

在构建大顶堆的时候,因为最后要将最大的元素与当前轮的末尾元素进行交换,交换之后很可能剩下的元素就不满足大顶堆结构了,所以要递归调用构造大顶堆的方法,直到全部形成大顶堆结构。

每一轮构造大顶堆结构完成,最大元素放到末尾,下一轮的构造,这个元素就不参与了。所以下一轮的末尾元素位置为参与了构造大顶堆结构的数据位置。

详细理解:假设数组为 7,3,8,5,1,2,他的原始结构为

经过第一轮的构建,以及数字的交换,变成如下结构

此时,8为已经换到末尾的元素,那么下一轮他就不参与构建,所以下一轮的末尾元素就位置就变成了 1 所处的位置

关于堆排序的详细参考

数组可以看作是一个完全二叉树,数组从逻辑上来说就是一个堆结构

相让数组升序则使用大顶堆,相让数组降序则使用小顶堆。

用公式描述堆的定义

  • 大顶堆:

    arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2];
    
  • 小顶堆:

    arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2];
    

实现方法:

public class testb {public static void main(String[] args) {MaxHeapSort maxHeapSort = new MaxHeapSort();int arr[] = {1, 0, 6, 7, 2, 3, 4, 53, 76, 1, 35, 23, -6, -32, -2, 45};// 调用方法把数组调成大顶堆maxHeapSort.firstConstructHeap(arr);System.out.println(Arrays.toString(arr));// 经过以上操作,数组已经变成大顶堆,此时把根元素与最后一个元素进行交换maxHeapSort.changeValue(arr);System.out.println(Arrays.toString(arr));}
}class MaxHeapSort {// 第一次构建大顶堆的方法public void firstConstructHeap(int[] arr) {// 定义开始调整的位置(通过公式来设定),位置为最后一个非叶子节点int startIndex = arr.length / 2 - 1;// 循环开始调整位置,下一个非叶子节点就是 当前坐标 - 1for (int i = startIndex; i >= 0; i--) {maxHeapSort(arr, arr.length, i);}}public void changeValue(int[] arr) {for (int i = arr.length - 1; i > 0; i--) {// 进行调换swap(arr, 0, i);// 调换完之后,把剩余元素调换为大顶堆// 要重新获取左右节点,所以index传入0maxHeapSort(arr, i, 0);}}/*** @param arr   要进行排序的数组* @param size  需要调整的元素个数* @param index 从哪里开始调整*/// 构建大顶堆public void maxHeapSort(int[] arr, int size, int index) {// 先获取左右子节点的索引,由堆的公式定义获得int leftNodeIndex = index * 2 + 1;int rightNodeIndex = index * 2 + 2;// 查找最大节点对应的索引,先让他等于开始索引位置,再进行比较int maxNodeIndex = index;// 如果左节点的数字大于它,则互换索引// 由于有递归调用,索引为不断增加,添加条件防止下标越界if (leftNodeIndex < size && arr[leftNodeIndex] > arr[maxNodeIndex]) {maxNodeIndex = leftNodeIndex;}if (rightNodeIndex < size && arr[rightNodeIndex] > arr[maxNodeIndex]) {maxNodeIndex = rightNodeIndex;}// 如果最大索引位置不等于原来的位置,说明有比他大的数字// 此时将最大数字移动到顶端if (maxNodeIndex != index) {swap(arr, maxNodeIndex, index);// 换完位置后,可能下面的子树就不满足大顶堆结构了,所以要递归调用maxHeapSort(arr, size, maxNodeIndex);}}public void swap(int[] arr, int i, int j) {int t = arr[i];arr[i] = arr[j];arr[j] = t;}
}

归并排序

归并排序的原理:假设初始序列有N个记录,则可以看成是N个有序的子序列,每个子序列的长度为1,然后两两归并,得到 N/2 个长度为2或1的有序子序列,再两两归并,重复步骤直到得到一个长度为N的有序序列为止。这种排序方法称为二路归并排序,是归并排序中用的最多的方法 。

如果数组中只有一个元素,这个数组是有序的。

或者更直观的来看:

实现方法:

public class testb {public static void main(String[] args) {MergeSort mergeSort = new MergeSort();int arr[] = {1, 4, 6, 8, 5, 3, 9, 34, 65, 23, 13, 84, -3, -4, -7, -1};mergeSort.spilt(arr, 0, arr.length - 1);System.out.println(Arrays.toString(arr));}
}class MergeSort {// 拆分方法public void spilt(int[] arr, int startIndex, int endIndex) {// 计算中间索引int centerIndex = (startIndex + endIndex) / 2;// 如果起始索引小于结束索引,则继续拆分,直到不能再拆为止if (startIndex < endIndex) {// 对左边继续拆分,左边序列的结束索引为中间点spilt(arr, startIndex, centerIndex);// 对右边继续拆分,右边序列的起始索引为中间点 + 1spilt(arr, centerIndex + 1, endIndex);// 拆分完成,进行归并mergeSort(arr, startIndex, centerIndex, endIndex);}}/*** @param arr         原数组* @param startIndex  左边数组的起始索引* @param centerIndex 中间索引* @param endIndex    右边索引的结束索引*/// 归并方法public void mergeSort(int[] arr, int startIndex, int centerIndex, int endIndex) {// 定义一个临时数组,由于要多次归并,所以数组长度是一直在变的,所以长度要设置为endIndex-startIndex+1int[] tempArr = new int[endIndex - startIndex + 1];// 定义左边数组的起始索引int i = startIndex;// 定义右边数组的起始索引(中间索引 + 1)int j = centerIndex + 1;int index = 0;// 比较左右两个数组的元素大小,然后往临时数组中存放while (i <= centerIndex && j <= endIndex) {if (arr[i] <= arr[j]) {tempArr[index] = arr[i];i++;} else {tempArr[index] = arr[j];j++;}index++;}// 由于数组长度奇偶不定,所以可能会存在剩余元素// 处理剩余元素// i<=centerIndex,说明左边元素有剩余while (i <= centerIndex) {tempArr[index] = arr[i];i++;index++;}// j<=endIndex,说明左边元素有剩余while (j <= endIndex) {tempArr[index] = arr[j];j++;index++;}// 把临时数组赋给原数组for (int k = 0; k < tempArr.length; k++) {arr[k + startIndex] = tempArr[k];}}}

桶排序

思路:将待排序的序列分到若干个桶中,每个桶内的元素再进行个别排序

实现方法:

public class bucketSort {public static void main(String[] args) {int a[]= {1,8,7,44,42,46,38,34,33,17,15,16,27,28,24};List[] buckets=new ArrayList[5];for(int i=0;i<buckets.length;i++)//初始化{buckets[i]=new ArrayList<Integer>();}for(int i=0;i<a.length;i++)//将待排序序列放入对应桶中{int index=a[i]/10;//对应的桶号buckets[index].add(a[i]);}for(int i=0;i<buckets.length;i++)//每个桶内进行排序(使用系统自带快排){buckets[i].sort(null);for(int j=0;j<buckets[i].size();j++)//顺便打印输出{System.out.print(buckets[i].get(j)+" ");}}  }
}

计数排序

计数排序是一种特殊的桶排序

思路:

  • 找出待排序数组中的最大值和最小值
  • 统计数组中每个值为 i 的元素出现的,存入数组C的第 i 项
  • 对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加)
  • 反向填充目标数组:将每个元素 i 放在新数组的第 C( i )项,每放一个元素就将C( i )减去1。

实现方法:

class CountingSort{public int[] countingSort(int[] arr) {int maxValue = getMaxValue(arr);int bucketLen = maxValue + 1;int[] bucket = new int[bucketLen];for (int value : arr) {bucket[value]++;}int sortedIndex = 0;for (int j = 0; j < bucketLen; j++) {while (bucket[j] > 0) {arr[sortedIndex++] = j;bucket[j]--;}}return arr;}public int getMaxValue(int[] arr) {int maxValue = arr[0];for (int value : arr) {if (maxValue < value) {maxValue = value;}}return maxValue;}}

基数排序

基数排序不需要对关键字进行比较,只需要对关键字进行“分配”与“收集“两种操作即可完成

思路:

创建10个桶,分别给定编号 0 - 9。对数组进行排序,第一轮比较,从数组顺序取出数字,每取出一个,放到编号与其个位的数字相对应的桶中,直到遍历完数组。这些桶可以看成是数组。

然后依次从 0 - 9 号桶取出存入的数字,组成新数组,进行第二轮比较,从数组顺序取出数字,每取出一个,放到编号与其十位的数字相对应的桶中,直到遍历完数组。

以此类推,数组中最大的数字为几位数,那就需要进行几轮

例如:一个数组为:{ 11,23,59,1,4,9,7,123,76,98,187,132,98,78,234 }

那么第一轮比较之后再取出的数组为:{ 11,1,132,23,123,4,234,76,7,187,98,98,78,59,9 }

第二轮比较之后取出的数组为:{ 1,4,7,9,11,23,123,132,234,59,76,78,187,98,98 }

第三轮比较之后取出的数组为:{ 1,4,7,9,11,23,59,76,78,98,98,123,132,187,234 }

此时数组排序完成

实现方法:

class BaseSort {// 获取最大值的方法,用来确定需要比较的轮数public int getMax(int[] arr) {int max = arr[0];// 因为max为arr[0],所以从 i = 1 开始比较for (int i = 1; i < arr.length; i++) {if (max < arr[i]) {max = arr[i];}}return max;}public void baseSort(int[] arr) {int max = getMax(arr);// 定义二维数组,放十个桶.每个桶最大长度是 arr.length// 即:全部数字的个位都一样,此时桶达到最大长度int[][] tempArr = new int[10][arr.length];// 定义一个统计数组,用来统计每个桶中放了多少个元素// 由于有10个桶,所以统计数组长度也为10int[] count = new int[10];// 比较轮次int len = String.valueOf(max).length();// k为除以的数字,求个位要除以1,求十位要除以10,依次类推for (int i = 0, k = 1; i < len; i++, k *= 10) {// 获取每个位上的数字for (int j = 0; j < arr.length; j++) {// remainder(余数): 为每个位上数字int remainder = arr[j] / k % 10;// count[remainder]++ 表示相应的桶中存入了一个数据tempArr[remainder][count[remainder]++] = arr[j];}// 依次取出桶中的元素int index = 0;for (int m = 0; m < count.length; m++) {// 如果该位置不为0,说明这个桶中有元素if (count[m] != 0) {for (int h = 0; h < count[m]; h++) {arr[index] = tempArr[m][h];index++;}// 每取出一个桶,则清除他统计的个数,因为下一轮还要存入count[m] = 0;}}}}
}

注意:这里的算法只能排序正数,如果要排序负数,我们可以定义19个桶,十个为 0 - 9,还有九个桶为 (-1)- (-9)

本文参考:
bigsai《「图文|总结」程序员必知必会的十大排序算法》
蓝海人《堆排序》

Java实现十大排序算法相关推荐

  1. 经典十大排序算法(含升序降序,基数排序含负数排序)【Java版完整代码】【建议收藏系列】

    经典十大排序算法[Java版完整代码] 写在前面的话 十大排序算法对比 冒泡排序 快速排序 直接选择排序 堆排序 归并排序 插入排序 希尔排序 计数排序 桶排序 基数排序 完整测试类 写在前面的话   ...

  2. 十大排序算法(Java)

    文章目录 十大排序算法(Java) 一.冒泡排序(Bubble Sort) 二.选择排序(Selection Sort) 三.堆排序(Heap Sort) 四.插入排序(Insertion Sort) ...

  3. JAVA十大排序算法动画_十大排序算法(java实现)

    [前言]最近在重新研究算法,此篇博文供自己复习使用也为方便广大程序员同学!此文代码均为自己实现,通过对比经典解法校验,若有错请读者及时提出! - [对比分析图]首先,我们先来对比分析一下这十大排序算法 ...

  4. 这或许是东半球分析十大排序算法最好的一篇文章

    作者 | 不该相遇在秋天 转载自五分钟学算法(ID:CXYxiaowu) 前言 本文全长 14237 字,配有 70 张图片和动画,和你一起一步步看懂排序算法的运行过程. 预计阅读时间 47 分钟,强 ...

  5. 「干货总结」程序员必知必会的十大排序算法

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:硬刚一周,3W字总结,一年的经验告诉你如何准备校招! 个人原创100W+访问量博客:点击前往,查看更多 绪论 身 ...

  6. 「归纳|总结」程序员必知必会的十大排序算法

    微信搜一搜「bigsai」关注这个有趣的程序员 新人原创公众号,求支持一下!你的点赞三连肯定对我至关重要! 文章已收录在 我的Github bigsai-algorithm 欢迎star 本文目录 绪 ...

  7. 八十八、Python | 十大排序算法系列(下篇)

    @Author:Runsen @Date:2020/7/10 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏 ...

  8. 八十七、Python | 十大排序算法系列(上篇)

    @Author:Runsen @Date:2020/7/10 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏 ...

  9. 十大排序算法之堆排序

    十大排序算法之堆排序 本文采用Java书写选择排序,其他语言类似可以借鉴着写 思想:所谓堆排序就是通过构造最大堆(升序)或者最小堆(降序)来进行排列的方法.可能有些童鞋不知道何为最大堆,何为最小堆.这 ...

  10. 十大排序算法之快速排序(两种方法)

    十大排序算法之快速排序 本文采用Java书写选择排序,其他语言类似可以借鉴着写 思想:在待排序序列中选择一个分割元素,将待排序序列中所有比分割元素关键字小的元素移动到分割元素左侧位置:将待排序序列中所 ...

最新文章

  1. SVM算法在项目实践中的应用!
  2. struts2文件下载及 param name=inputNameinputStream/param的理解
  3. strcpy,memcpy,memset函数实现
  4. malloc和calloc的区别
  5. SWT多线程注意事项
  6. 安装与优化   98
  7. Dynamsoft WebAssembly条形码SDK预览
  8. Beta冲刺提交-星期五
  9. 钉钉日志范文100篇_工作日志应该怎么写?——以钉钉日报模板举例告诉你
  10. PostScript语言教程(六、图形变换)
  11. “海潮效应”下,聚则“生”分则“亡”360奏响“经济复苏集结号”
  12. 【我的OpenGL学习进阶之旅】EGL简介
  13. 认知层次不同的人,是很难沟通的
  14. axure8.1 授权码
  15. C#实现的系统内存清理
  16. kolla-ansible openstack登录 证书不可用
  17. clamav的病毒库文件的文件头的信息说明(clamav版本号等)
  18. linux 时间与bios,Linux操作系统时间与BIOS硬件时间
  19. iframe使用小结
  20. 关于RGBFusion无法识别和控制技嘉显卡RGB灯的特殊案例和解决办法

热门文章

  1. python 微信扫码登录_python实现微信第三方网站扫码登录(Django)
  2. 独立游戏开发日志:2021年2月12日 改进版反弹跳
  3. Android 直接生成实体类工具GsonFormat,一键生成实体类,对象;GsonFormat插件安装步骤gsonformatplus生成对象报错解决;jason,json
  4. Lingo软件的使用
  5. html无限弹窗关不掉,使用cmd命令时出现无限弹窗的故障如何终止
  6. 01Python可视化开发环境之Pycharm+Anaconda3安装配置详细步骤
  7. 学业水平测试计算机考试软件,普通高中学业水平考试系统
  8. c语言关键用法大全,c语言关键字的用法详解
  9. java dateutils工具类_DateUtils 日期工具类
  10. COSMIC的后端学习之路——1.4 + 1.5 设计模式