Java【数组】【Array、Arrays】【排序、查找算法】

数组介绍


   1、数组的定义

    -> 数组是相同类型类型数据的有序集合!
      相同类型的若干个数据,按照一定先后次序排列组合而成,而不是分大小值来排列;
      其中,每一个数据称为一个数组的元素;
      元素下标标明了元素在数组中的位置,从0开始,每个数组元素可以通过一个下标来访问调用它们;
      其长度是确定的。数组一旦被创建,它的大小就是不可以改变的。 
      其元素必须是相同类型,不允许出现混合类型。
      数组中的元素可以是任何数据类型,也包括基本数据类型和引用类型。
    -> 数组的声明
      int [ ] a ;  声明数组是为了告诉计算机数据类型是什么;声明数组时不规定数组长度;
    -> 分配空间
      a = new int [5] ;
                  分配空间是为了告诉计算机分配几个连续的空间;int类型分配完空间, 默认数组中的每个元素值为0;
    -> 声明数组并分配空间
                  数据类型[ ] 数组名 = new 数据类型[大小] ; 
    -> 赋值
      a [0]  = 8 ;  赋值是为了向分配的格子里放数据;
  

  2、数组元素默认值 

    数组中的元素在声明和分配空间的时候,系统会默认的根据数组的类型进行元素初始化
    具体根据数组的数据类型进行初始化:
      int: 0
      boolean: false
      String: null

  

  3、数组的三种创建方式

复制代码
1 // 1、先声明, 后创建
2 int[] temp;
3 temp = new int[5];  // 对应的数据均为数组类型的默认值
4 
5 // 2、声明时直接创建
6 int[] temp2 = new int[5]; // 对应的数据均为数组类型的默认值
7 
8 // 3、静态初始化, 初始化时就确定每个数据
9 int[] temp3 = {1,2,5,6,3};
复制代码

  

  4、数组的遍历 

复制代码
 1     public static void main(String[] args) {
 2 
 3         int[] temp = {1,2,5,6,3};
 4         /**
 5          * 遍历数组, 打印每一个元素
 6          */
 7         // 方式1
 8         for (int i = 0; i < temp.length; i++) {
 9             System.out.println(temp[i]);
10         }
11 
12         // 方式2
13         for (int item : temp) {
14             System.out.println(item);
15         }
16 
17         // 方式3, jdk8及以上
18         Arrays.stream(temp).iterator().forEachRemaining((IntConsumer) System.out::println);
19 
20         // 方式4, jdk8及以上
21         PrimitiveIterator.OfInt ofInt = Arrays.stream(temp).iterator();
22         while (ofInt.hasNext())
23             System.out.println(ofInt.nextInt());
24 
25     }
复制代码

  

  5、数组的拷贝方法

复制代码
 1 public static void main(String[] args) {
 2 
 3         int[] temp = {1,2,5,6,3};
 4         /**
 5          * 数组的拷贝
 6          */
 7         // 方式1
 8         int[] copy = temp; // 浅拷贝, copy和temp指向的是同一片内存空间, 修改任意一个数组中的元素, 会影响另外一个数组
 9 
10         // 方式2
11         int[] copy2 = new int[temp.length]; // 初始化一个与原数组一样长度的数组
12         for (int i = 0; i < temp.length; i++) {
13             copy2[i] = temp[i];
14         }
15 
16         // 方式3
17         int[] copy3 = Arrays.copyOf(temp, temp.length);
18 
19         // 方式4, System.arraycopy() 这个方式native修饰的, 也就是它的实现不是java, 而是其他语言实现的
20         int[] copy4 = new int[temp.length]; // 初始化一个与原数组一样长度的数组
21         System.arraycopy(temp, 0, copy4, 0, temp.length);
22         // 参数1: 被拷贝的数组, 不能为null
23         // 参数2: 从被拷贝的数组的哪个位置开始拷贝
24         // 参数3: 拷贝到哪个数组, 也就是目标数组, 不能为null
25         // 参数4: 从目标数组的哪个位置开始赋值
26         // 参数5: 拷贝的长度
27     }
复制代码

 

  6、数组的扩容

复制代码
 1     public static void main(String[] args) {
 2 
 3         int[] temp = {1,2,5,6,3};
 4         /**
 5          * 数组的扩容
 6          */
 7         // 方式1, 初始化一个长度为原数组长度2倍的数组, 然后把原数组的内容拷贝到新数组
 8         // 由于原数组的内容有限, 所以新数组超出原数组长度的值为数组类型的默认值
 9         int[] copy2 = new int[temp.length*2];
10         for (int i = 0; i < temp.length; i++) {
11             copy2[i] = temp[i];
12         }
13 
14         // 方式2, System.arraycopy() 这个方式native修饰的, 也就是它的实现不是java, 而是其他语言实现的
15         int[] copy4 = new int[temp.length*2];
16         System.arraycopy(temp, 0, copy4, 0, temp.length);
17         // 参数1: 被拷贝的数组, 不能为null
18         // 参数2: 从被拷贝的数组的哪个位置开始拷贝
19         // 参数3: 拷贝到哪个数组, 也就是目标数组, 不能为null
20         // 参数4: 从目标数组的哪个位置开始赋值
21         // 参数5: 拷贝的长度
22     }
复制代码

 

  7、数组的排序方法

    常见的内部排序算法有:冒泡排序选择排序插入排序希尔排序快速排序归并排序等。 
    百度百科:https://baike.baidu.com/item/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/5399605?fr=aladdin#3
    
    在使用当中,算法效率分为两种,一是时间效率(时间复杂度),二是空间效率(空间复杂度)。时间复杂度是指程序运行的速度。空间复杂度是指一个算法所需要的额外的空间。 

    7.1 冒泡排序  

    初版冒泡
复制代码
 1 public class test {
 2 
 3     public static void main(String[] args) {
 4         int[] arr = {5, 6, 2, 8, 10, 40, 15, 17, 14};
 5         int count = bubbleSortOpt(arr);
 6         System.out.println("比较的次数count: " + count); // 36
 7         Arrays.stream(arr).iterator().forEachRemaining((IntConsumer) System.out::println);
 8     }
 9 
10     public static int bubbleSortOpt(int[] arr) {
11         if(arr == null) {
12             throw new NullPointerException();
13         }
14         if(arr.length < 2) {
15             return 0;
16         }
17         int temp;
18         int count = 0;
19         for(int i = 0; i < arr.length - 1; i++) {
20             for(int j = 0; j < arr.length - i - 1; j++) {
21                 if(arr[j] > arr[j + 1]) {
22                     temp = arr[j];
23                     arr[j] = arr[j + 1];
24                     arr[j + 1] = temp;
25                 }
26                 count++;
27             }
28         }
29         return count;
30     }
31 }
复制代码
    冒泡排序优化
复制代码
 1 public class test {
 2 
 3     public static void main(String[] args) {
 4         int[] arr = {5, 6, 2, 8, 10, 40, 15, 17, 14};
 5         int count = bubbleSortOpt2(arr);
 6         System.out.println("比较的次数count: " + count); // 26
 7         Arrays.stream(arr).iterator().forEachRemaining((IntConsumer) System.out::println);
 8     }
 9 
10     public static int bubbleSortOpt2(int[] arr) {
11         if (arr == null) {
12             throw new NullPointerException();
13         } else if (arr.length < 2) {
14             return 0;
15         }
16         int temp;
17         int count = 0;
18         for (int i = 0; i < arr.length - 1; i++) {
19             boolean hasChange = false;
20             for (int j = 0; j < arr.length - 1 - i; j++) {
21                 if (arr[j] > arr[j + 1]) {
22                     temp = arr[j];
23                     arr[j] = arr[j + 1];
24                     arr[j + 1] = temp;
25                     hasChange = true;
26                 }
27                 count++;
28             }
29             // 没有发生交换, 说明排序已经完成
30             if (!hasChange) {
31                 return count;
32             }
33         }
34         return count;
35     }
36 }
复制代码
    冒泡排序再优化
复制代码
 1 public class test {
 2 
 3     public static void main(String[] args) {
 4         int[] arr = {5, 6, 2, 8, 10, 40, 15, 17, 14};
 5         int count = bubbleSortOpt3(arr);
 6         System.out.println("比较的次数count: " + count); // 26
 7         Arrays.stream(arr).iterator().forEachRemaining((IntConsumer) System.out::println);
 8     }
 9 
10     public static int bubbleSortOpt3(int[] arr) {
11         if (arr == null) {
12             throw new NullPointerException();
13         } else if (arr.length < 2) {
14             return 0;
15         }
16         int temp;
17         int count = 0;
18         int len = arr.length - 1;
19         for (int i = 0; i < len; i++) {
20             // 记录最后一次交换位置
21             int lastChange = 0;
22             for (int j = 0; j < len; j++) {
23                 if (arr[j] > arr[j + 1]) {
24                     temp = arr[j];
25                     arr[j] = arr[j + 1];
26                     arr[j + 1] = temp;
27                     // 每交换一次更新一次
28                     lastChange = j;
29                 }
30                 count++;
31             }
32             // 没有发生交换,排序已经完成
33             if (lastChange == 0) {
34                 return count;
35             }
36             len = lastChange;
37         }
38         return count;
39     }
40 }
复制代码
  -> 冒泡排序是原地排序算法吗?
    冒泡排序的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,因此,空间复杂度为O(1),是一个原地排序算法。
  -> 冒泡排序是稳定排序算法吗?
    在冒泡排序中,只有交换才可以改变两个元素的前后顺序。为了保证冒泡排序的稳定性,在冒泡的过程中,我们对两个大小相等的相邻的元素不做交换,这样就能保证相同大小的元素,在排序前后原有的先后顺序不会改变,因此,冒泡排序是稳定排序。
  -> 时间复杂度分析
    最好情况:就是比较一次,就是 O(1)
    最坏情况:一直排到最后,就是 O(N^2)

    在最好的情况下,待排序的序列已经有顺序的了,所以只需要比较一次就可以,这个时候的时间复杂度为O(1);
    在最坏的情况下,待排序的序列恰好是倒序的,这个时候需要进行n次冒泡,而每次冒泡需要比较n个元素(这个随者已排好序列的增加而减少,但一般都看作是n个元素),所以时间复杂度为O(n^2)。
  -> 平均时间复杂度是多少?
    这里使用一种简单,不是非常严谨的方法来计算。
    首先需要明白什么是有序度,什么是逆序度?

      有序度是指数组中具有有序关系的元素对的个数,如果用数字表达式表示出来,就是a[i]<=a[j],i<=j;如下图:
      
 
       逆序度的定义正好与有序度相反,是指数组中逆序元素对的个数,而逆序元素对的定义也与有序元素对正好相反,即:a[i]<a[j],i>j。
      对于一个倒序(假设从小到大为有序)排列的数组,如 [6,5,4,3,2,1],有序度是0,逆序度是n*(n-1)/2,也就是15;对于一个完全有序的数组,如[1,2,3,4,5,6],有序度是n*(n-1)/2,也就是15,逆序度是0。我们把完全有序的数组的有序度称为满有序度(也就是n*(n-1)/2)。满序度,逆序度以及有序度之间有一定的关系:逆序度=满序度-有序度。排序的过程就是增加有序度、减少逆序度的过程。当最终达到满序度的时候,就是有序的了。
      假设待排序的数组的初始状态是[4,5,6,3,2,1],其中,有序元素对有(4,6)、(4,5)、(5,6),因此有序度为3,而满有序度则为n*(n-1)/2=15。
      冒泡排序过程包含两个操作:比较和交换。因为冒泡排序只会交换相邻的两个元素,所以每进行一次交换操作,有序度就会增加1,因此,无论冒泡排序算法怎样改进,总交换次数是确定的,即为逆序度,也就是n*(n-1)/2减去初始有序度,在上面的序列中,要交换的次数则为15-3=12。
      那么,对于包含n个数据的数组进行冒泡排序,平均交换次数是多少呢?
      在最坏的情况下,初始状态的有序度是0,因此要进行n*(n-1)/2次交换。在最好的情况下,无须交换。那么我们就可以去中间值n*(n-1)/4,用它表示初始有序度既不是很高,也不是很低的平均情况。换句话说,在平均情况下,需要n*(n-1)/4次交换操作,也就是说,交换操作次数n^2量级的。而比较操作肯定要比交换操作多,复杂度的上限又是O(n ^ 2),因此,比较操作次数也是n ^ 2量级的。综合比较和交换两部分操作,冒泡排序平均情况下的时间复杂度为O(n^2)。
    

    7.2 选择排序

复制代码
 1     public static void main(String[] args) {
 2         int[] arr = {6, 5, 3, 2, 4};
 3 
 4         for (int i = 0; i < arr.length; i++) {
 5             // 默认第一个是最小的
 6             int min = arr[i];
 7             // 记录最小的下标
 8             int index = i;
 9             // 通过与后面的数据进行比较得出,最小值和下标
10             for (int j = i + 1; j < arr.length; j++) {
11                 if (min > arr[j]) {
12                     min = arr[j];
13                     index = j;
14                 }
15             }
16             // 然后将最小值与本次循环的,开始值交换
17             int temp = arr[i];
18             arr[i] = min;
19             arr[index] = temp;
20             // 说明: 将i前面的数据看成一个排好的队列,i后面的看成一个无序队列。每次只需要找无需的最小值,做替换
21         }
22         Arrays.stream(arr).iterator().forEachRemaining((IntConsumer) System.out::println);
23     }
复制代码

    7.3 插入排序

  a、默认从第二个数据开始比较。
  b、如果第二个数据比第一个小,则交换。然后在用第三个数据比较,如果比前面小,则插入(狡猾)。否则,退出循环
  c、说明:默认将第一数据看成有序列表,后面无序的列表循环每一个数据,如果比前面的数据小则插入(交换)。否则退出。
复制代码
 1     public static void main(String[] args) {
 2         int[] arr = {7, 5, 3, 2, 4};
 3 
 4         for (int i = 1; i < arr.length; i++) {
 5             // 外层循环, 从第二个开始比较
 6             for (int j = i; j > 0; j--) {
 7                 // 内存循环,与前面排好序的数据比较,如果后面的数据小于前面的则交换
 8                 if (arr[j] < arr[j - 1]) {
 9                     int temp = arr[j - 1];
10                     arr[j - 1] = arr[j];
11                     arr[j] = temp;
12                 } else {
13                     //如果不小于,说明插入完毕,退出内层循环
14                     break;
15                 }
16             }
17         }
18         Arrays.stream(arr).iterator().forEachRemaining((IntConsumer) System.out::println);
19     }
复制代码

    

    7.4 希尔排序(插入排序变种版)

  a、基本上和插入排序一样的道理
  b、不一样的地方在于,每次循环的步长,通过减半的方式来实现
  c、说明:基本原理和插入排序类似,不一样的地方在于。通过间隔多个数据来进行插入排序。
复制代码
 1     public static void main(String[] args) {
 2         int[] arr = {7, 5, 3, 2, 4};
 3 
 4         for (int i = arr.length / 2; i > 0; i /= 2) {
 5             // i层循环控制步长
 6             for (int j = i; j < arr.length; j++) {
 7                 // j控制无序端的起始位置
 8                 for (int k = j; k > 0 && k - i >= 0; k -= i) {
 9                     if (arr[k] < arr[k - i]) {
10                         int temp = arr[k - i];
11                         arr[k - i] = arr[k];
12                         arr[k] = temp;
13                     } else {
14                         break;
15                     }
16                 }
17             }
18             //j,k为插入排序, 不过步长为i
19         }
20         Arrays.stream(arr).iterator().forEachRemaining((IntConsumer) System.out::println);
21     }
复制代码

    

    7.5 快速排序

  a、确认列表第一个数据为中间值,第一个值看成空缺(低指针空缺)。
  b、然后在剩下的队列中,看成有左右两个指针(高低)。
  c、开始高指针向左移动,如果遇到小于中间值的数据,则将这个数据赋值到低指针空缺,并且将高指针的数据看成空缺值(高指针空缺)。然后先向右移动一下低指针,并且切换低指针移动。 
  d、当低指针移动到大于中间值的时候,赋值到高指针空缺的地方。然后先高指针向左移动,并且切换高指针移动。重复c、d操作。
  e、直到高指针和低指针相等时退出,并且将中间值赋值给对应指针位置。
  f、然后将中间值的左右两边看成行的列表,进行快速排序操作。 
复制代码
 1     public static void main(String[] args) {
 2         int[] arr = {7, 5, 3, 2, 4, 1, 8, 9, 6};
 3         int low = 0;
 4         int high = arr.length - 1;
 5         quickSort(arr, low, high);
 6         Arrays.stream(arr).iterator().forEachRemaining((IntConsumer) System.out::println);
 7     }
 8 
 9     public static void quickSort(int[] arr, int low, int high) {
10         // 如果指针在同一位置(只有一个数据时),退出
11         if (high - low < 1) {
12             return;
13         }
14         // 标记,从高指针开始,还是低指针(默认高指针)
15         boolean flag = true;
16         // 记录指针的起始位置
17         int start = low;
18         int end = high;
19         // 默认中间值为低指针的第一个值
20         int midValue = arr[low];
21         while (true) {
22             // 高指针移动
23             if (flag) {
24                 // 如果列表右方的数据大于中间值,则向左移动
25                 if (arr[high] > midValue) {
26                     high--;
27                 } else if (arr[high] < midValue) {
28                     // 如果小于,则覆盖最开始的低指针值,并且移动低指针,标志位改成从低指针开始移动
29                     arr[low] = arr[high];
30                     low++;
31                     flag = false;
32                 }
33             } else {
34                 // 如果低指针数据小于中间值,则低指针向右移动
35                 if (arr[low] < midValue) {
36                     low++;
37                 } else if (arr[low] > midValue) {
38                     // 如果低指针的值大于中间值,则覆盖高指针停留时的数据,并向左移动高指针。切换为高指针移动
39                     arr[high] = arr[low];
40                     high--;
41                     flag = true;
42                 }
43             }
44             // 当两个指针的位置相同时,则找到了中间值的位置,并退出循环
45             if (low == high) {
46                 arr[low] = midValue;
47                 break;
48             }
49         }
50         // 然后出现有,中间值左边的小于中间值。右边的大于中间值。
51         // 然后在对左右两边的列表在进行快速排序
52         quickSort(arr, start, low - 1);
53         quickSort(arr, low + 1, end);
54     }
复制代码

 

    7.6 归并排序

   a、将列表按照对等的方式进行拆分
   b、拆分小最小快的时候,在将最小块按照原来的拆分,进行合并
   c、合并的时候,通过左右两块的左边开始比较大小。小的数据放入新的块中
   d、说明:简单一点就是先对半拆成最小单位,然后将两半数据合并成一个有序的列表 
复制代码
 1     public static void main(String[] args) {
 2         int[] arr = {7, 5, 3, 2, 4, 1, 6};
 3         int start = 0;
 4         int end = arr.length - 1;
 5         mergeSort(arr, start, end);
 6         Arrays.stream(arr).iterator().forEachRemaining((IntConsumer) System.out::println);
 7     }
 8 
 9     public static void mergeSort(int[] arr, int start, int end) {
10         //判断拆分的不为最小单位
11         if (end - start > 0) {
12             // 再一次拆分,知道拆成一个一个的数据
13             mergeSort(arr, start, (start + end) / 2);
14             mergeSort(arr, (start + end) / 2 + 1, end);
15             // 记录开始/结束位置
16             int left = start;
17             int right = (start + end) / 2 + 1;
18             // 记录每个小单位的排序结果
19             int index = 0;
20             int[] result = new int[end - start + 1];
21             // 如果查分后的两块数据,都还存在
22             while (left <= (start + end) / 2 && right <= end) {
23                 // 比较两块数据的大小,然后赋值,并且移动下标
24                 if (arr[left] <= arr[right]) {
25                     result[index] = arr[left];
26                     left++;
27                 } else {
28                     result[index] = arr[right];
29                     right++;
30                 }
31                 // 移动单位记录的下标
32                 index++;
33             }
34             // 当某一块数据不存在了时
35             while (left <= (start + end) / 2 || right <= end) {
36                 // 直接赋值到记录下标
37                 if (left <= (start + end) / 2) {
38                     result[index] = arr[left];
39                     left++;
40                 } else {
41                     result[index] = arr[right];
42                     right++;
43                 }
44                 index++;
45             }
46             // 最后将新的数据赋值给原来的列表,并且是对应分块后的下标。
47             for (int i = start; i <= end; i++) {
48                 arr[i] = result[i - start];
49             }
50         }
51     }
复制代码

 

    7.7 基数排序

    基数排序的原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。为此需要将所有待比较的数值统一为同样的数位长度,数位不足的数在高位补零
复制代码
 1 public class test {
 2 
 3     public static void main(String[] args) {
 4         int[] arr = {7, 5, 3, 2, 4, 1, 8, 9, 6};
 5         radixSort(arr);
 6         Arrays.stream(arr).iterator().forEachRemaining((IntConsumer) System.out::println);
 7     }
 8 
 9     public static void radixSort(int[] arr) {
10         // 存放数组中的最大数字
11         int max = Integer.MIN_VALUE;
12         for (int value : arr) {
13             if (value > max) {
14                 max = value;
15             }
16         }
17         // 计算最大数字是几位数
18         int maxLength = (max + "").length();
19         // 用于临时存储数据
20         int[][] temp = new int[10][arr.length];
21         // 用于记录在 temp 中相应的下标存放数字的数量
22         int[] counts = new int[10];
23         // 根据最大长度的数决定比较次数
24         for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
25             // 每一个数字分别计算余数
26             for (int j = 0; j < arr.length; j++) {
27                 // 计算余数
28                 int remainder = arr[j] / n % 10;
29                 // 把当前遍历的数据放到指定的数组中
30                 temp[remainder][counts[remainder]] = arr[j];
31                 // 记录数量
32                 counts[remainder]++;
33             }
34             // 记录取的元素需要放的位置
35             int index = 0;
36             // 把数字取出来
37             for (int k = 0; k < counts.length; k++) {
38                 // 记录数量的数组中当前余数记录的数量不为 0
39                 if (counts[k] != 0) {
40                     // 循环取出元素
41                     for (int l = 0; l < counts[k]; l++) {
42                         arr[index] = temp[k][l];
43                         // 记录下一个位置
44                         index++;
45                     }
46                     // 把数量置空
47                     counts[k] = 0;
48                 }
49             }
50         }
51     }
52 }
复制代码

 

    7.8 堆排序

    对于任何一个数组都可以看成一颗完全二叉树,不了解这一方面的同学可以去看这篇博客。堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。

    

 

 

    像上图的大顶堆,映射为数组,就是 [50, 45, 40, 20, 25, 35, 30, 10, 15]。可以发现第一个下标的元素就是最大值,将其与末尾元素交换,则末尾元素就是最大值。所以堆排序的思想可以归纳为以下两步:
      1、根据初始数组构造堆
      2、每次交换第一个和最后一个元素,然后将除最后一个元素以外的其他元素重新调整为大顶堆
    重复以上两个步骤,没有元素可操作,就完成排序了。
    我们需要把一个普通数组转换为大顶堆,调整的起始点是最后一个非叶子结点,然后从左至右,从下至上,继续调整其他非叶子结点,直到根结点为止。
复制代码
 1 public class test {
 2 
 3     public static void main(String[] args) {
 4         int[] arr = {7, 5, 3, 2, 4, 1, 8, 9, 6};
 5         heapSort(arr);
 6         Arrays.stream(arr).iterator().forEachRemaining((IntConsumer) System.out::println);
 7     }
 8 
 9     /**
10      * 转化为大顶堆
11      * @param arr 待转化的数组
12      * @param size 待调整的区间长度
13      * @param index 结点下标
14      */
15     public static void maxHeap(int[] arr, int size, int index) {
16         // 左子结点
17         int leftNode = 2 * index + 1;
18         // 右子结点
19         int rightNode = 2 * index + 2;
20         int max = index;
21         // 和两个子结点分别对比,找出最大的结点
22         if (leftNode < size && arr[leftNode] > arr[max]) {
23             max = leftNode;
24         }
25         if (rightNode < size && arr[rightNode] > arr[max]) {
26             max = rightNode;
27         }
28         // 交换位置
29         if (max != index) {
30             int temp = arr[index];
31             arr[index] = arr[max];
32             arr[max] = temp;
33             // 因为交换位置后有可能使子树不满足大顶堆条件,所以要对子树进行调整
34             maxHeap(arr, size, max);
35         }
36     }
37 
38     /**
39      * 堆排序
40      * @param arr 待排序的整型数组
41      */
42     public static void heapSort(int[] arr) {
43         // 开始位置是最后一个非叶子结点,即最后一个结点的父结点
44         int start = (arr.length - 1) / 2;
45         // 调整为大顶堆
46         for (int i = start; i >= 0; i--) {
47             maxHeap(arr, arr.length, i);
48         }
49         // 先把数组中第 0 个位置的数和堆中最后一个数交换位置,再把前面的处理为大顶堆
50         for (int i = arr.length - 1; i > 0; i--) {
51             int temp = arr[0];
52             arr[0] = arr[i];
53             arr[i] = temp;
54             maxHeap(arr, i, 0);
55         }
56     }
57 }
复制代码

 

  8、数组的查找方法

    顺序(线性)查找、二分查找/折半查找、插值查找、斐波那契查找
    8.1 顺序(线性)查找(Sequential Search)
复制代码
 1     public static void main(String[] args) {
 2         int[] arr = {1,9,11,-1,34,89};//没有顺序的数组
 3         int index = seqSearch(arr,-1);
 4         if(index == -1){
 5             System.out.println("没有找到需要查找的值");
 6         }else{
 7             System.out.printf("找到了,下标为%d\n",index);
 8         }
 9     }
10 
11     //顺序查找
12     public static int seqSearch(int[] arr, int val){
13         //线性查找 逐一比对
14         for(int i = 0; i < arr.length; ++i){
15             if(arr[i] == val){
16                 return i;
17             }
18         }
19         return -1;
20     }
复制代码

     

    8.2 二分查找(Binary Search)
    注意点:二分查找仅适用于有序数组
    思路分析
      1、首先确定该数组的中间的下标mid = (left + right) / 2
      2、然后让需要查找的数 findVal 与 arr[mid] 比较
        2.1 findVal > arr[mid] 说明查找的值在 mid 右面,因此需要递归向右查找
        2.2 findVal < arr[mid] 说明查找的值在 mid 左面,因此需要递归向左查找
        2.3 findVal = arr[mid] 说明找到了
      3、什么时候结束递归
        3.1 找到就结束递归
        3.2 递归完整个数组,仍然没有找到 findVal ,也需要结束递归,当left > right,就需要退出
复制代码
 1 public class test {
 2 
 3     public static void main(String[] args) {
 4         int[] arr = {-1, 3, 5, 66, 66, 66, 90, 101};
 5         ArrayList<Integer> index = binarySearch2(arr, 0, arr.length - 1, 66);
 6         System.out.println(index);
 7     }
 8 
 9     // 方式1: 二分查找
10     public static int binarySearch(int[] arr, int left, int right, int findVal) {
11         if (left > right) {
12             return -1;
13         }
14         int mid = (left + right) / 2;
15         if (findVal > arr[mid]) {
16             //在 mid 右面
17             return binarySearch(arr, mid + 1, right, findVal);
18         } else if (findVal < arr[mid]) {
19             //在 mid 左边
20             return binarySearch(arr, left, mid - 1, findVal);
21         } else {
22             return mid;
23         }
24     }
25 
26     // 方式2: 改进二分查找:若数组中存在多个相同的数 将它们的下标全部返回
27     // 思路分析:
28     // 1、找到 mid 时不要马上返回
29     // 2、向 mid 的左边和右边扫描,将所有满足条件的下标都加入集合中
30     public static ArrayList binarySearch2(int[] arr, int left, int right, int findVal) {
31         if (left > right) {
32             return new ArrayList<Integer>();
33         }
34         int mid = (left + right) / 2;
35         if (findVal > arr[mid]) {
36             //在 mid 右面
37             return binarySearch2(arr, mid + 1, right, findVal);
38         } else if (findVal < arr[mid]) {
39             //在 mid 左边
40             return binarySearch2(arr, left, mid - 1, findVal);
41         } else {
42             ArrayList<Integer> resIndexlist = new ArrayList<Integer>();
43             //向左边扫描
44             int temp = mid - 1;
45             while (true) {
46                 if (temp < 0 || arr[temp] != findVal) {
47                     break;
48                 }
49                 resIndexlist.add(temp);
50                 temp--;
51             }
52             resIndexlist.add(mid);
53 
54             //向右边扫描
55             temp = mid + 1;
56             while (true) {
57                 if (temp > arr.length - 1 || arr[temp] != findVal) {
58                     break;
59                 }
60                 resIndexlist.add(temp);
61                 temp++;
62             }
63             return resIndexlist;
64         }
65     }
66 }
复制代码

    

    8.3 插值查找(Interpolation Search)
    插值查找原理介绍:
      1、插值查找算法类似于二分查找,不同的是插值查找每次从自适应 mid 处开始查找
      2、int midIndex = low + (high - low)*(key - arr[low])/(arr[high] - arr[low])
    插值查找注意事项:
      1、对于数据量较大,关键字分布较均匀的查找表来说,采用插值查找,速度较快
      2、关键字分布不均匀的情况下,该方法不一定比折半查找要好
复制代码
 1 public class test {
 2 
 3     public static void main(String[] args) {
 4         int[] arr = new int[100];
 5         for (int i = 0; i < arr.length; ++i) {
 6             arr[i] = i + 1;
 7         }
 8         // Arrays.stream(arr).iterator().forEachRemaining((IntConsumer) System.out::print);
 9         int index = insertValueSearch(arr, 0, arr.length - 1, 100);
10         System.out.println(index);
11     }
12 
13 
14     //插值查找
15     public static int insertValueSearch(int[] arr, int left, int right, int findVal) {
16         // 注意:为了防止数组越界,必须加入此判断
17         if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
18             return -1;
19         }
20         int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);
21         int midVal = arr[mid];
22         if (findVal > midVal) {
23             // 向右递归
24             return insertValueSearch(arr, mid + 1, right, findVal);
25         } else if (findVal < midVal) {
26             return insertValueSearch(arr, left, mid - 1, findVal);
27         } else {
28             return mid;
29         }
30     }
31 }
复制代码

    

    8.4 斐波那契查找(Fibonacci Search)
    斐波那契法(黄金分割法)基本介绍:
      黄金分割是指把一条线段分割为两部分,使其一部分与全长之比等于两一部分与这部分之比,取其前三位数字的近似值是0.618。由于按此比例设计的造型十分美丽,因此称之为黄金分割,也称中外比,这是一个神奇的数字,会带来意想不到的结果
      斐波那契数列{1,1,2,3,5,8,13,21,34,55,···},发现斐波那契数列的两个相邻数的比例,无限接近 黄金分割0.618
    斐波那契原理:
​      斐波那契查找原理与前两种相似,仅仅改变了中间节点(mid)的位置,mid不再是中间或者插值得到,而是位于黄金分割点附近,即 mid=low+F(k-1)-1
    对 F(k-1)-1的理解:
      1、由斐波那契数列 F[k] = F[k-1] + F[k-2] 的性质,可以得到 (F[k]-1) = (F[k-1]-1) + (F[k-2]-1) + 1 。该式说明:只要顺序表的长度为 F[k] - 1 ,则可以将该表分成长度为 F[k-1]-1 和 F[k-2]-1 的两段,从而中间位置为 mid = low + F[k-1] -1
      2、类似的,每一子段也可以用相同的方式分割
      3、但顺序表的长度 n 不一定刚好等于 F[k]-1,所以需要将原来的顺序表长度 n 增加到 F[k]-1,这里的 k 值只要能使得 F[k]-1 恰好大于或等于 n 即可,由以下代码得到,顺序表长度增加后,新增的位置(从 n+1 到 F[k]-1 位置),都赋为 n 位置的值即可
      while(n > fib(k) - 1)
        k++;
复制代码
 1 public class test {
 2 
 3     public static int maxsize = 20;// 定义斐波那契数列长度
 4 
 5     public static void main(String[] args) {
 6         int[] arr = {1, 8, 10, 89, 1000, 1234};
 7         System.out.println(fibSearch(arr, 1234));
 8     }
 9 
10     // 由于需要用到斐波那契数列,先创建一个
11     public static int[] fib() {
12         int[] f = new int[maxsize];
13         f[0] = 1;
14         f[1] = 1;
15         for (int i = 2; i < maxsize; ++i) {
16             f[i] = f[i - 1] + f[i - 2];
17         }
18         return f;
19     }
20 
21     // 斐波那契查找算法
22     public static int fibSearch(int[] arr, int key) {
23         int low = 0;
24         int high = arr.length - 1;
25         int k = 0; // 表示斐波那契分割数值的下标
26         int mid = 0;
27         int[] f = fib(); // 获取斐波那契数列
28 
29         // 获取斐波那契数列的下标
30         while (high > f[k] - 1) {
31             k++;
32         }
33 
34         // f[k] 可能大于 arr 的长度,因此需要使用 Arrays 类,构造一个新的数组,指向 a[]
35         // 不足的部分使用 0 填充
36         int[] tmp = Arrays.copyOf(arr, f[k]);
37         // 实际上需要使用 arr数组最后的数填充 tmp,因为要保证是一个有序数组
38         for (int i = high + 1; i < tmp.length; ++i) {
39             tmp[i] = arr[high];
40         }
41 
42         // 使用 while 找到key
43         while (low <= high) {
44             mid = low + f[k - 1] - 1;
45             if (key < tmp[mid]) {
46                 // 向左查找
47                 high = mid - 1;
48                 // k--说明
49                 // 1、全部元素 = 前面的元素 + 后面元素
50                 // 2、f[k] = f[k-1] + f[k-2]
51                 // 3、因为前面有 f[k-1] 个元素,所以可以继续拆分 f[k-1] = f[k-2] + f[k-3]
52                 // 4、即在 f[k-1] 的前面继续查找 k--
53                 k--;
54             } else if (key > tmp[mid]) {
55                 // 向右查找
56                 low = mid + 1;
57                 // k -= 2说明
58                 // 1、全部元素 = 前面的元素 + 后面元素
59                 // 2、f[k] = f[k-1] + f[k-2]
60                 // 3、因为后面有 f[k-2] 个元素,所以可以继续拆分 f[k-1] = f[k-3] + f[k-4]
61                 // 4、即在 f[k-2] 的前面继续查找 k -= 2
62                 k -= 2;
63             } else {
64                 //需要确定返回的是哪个下标
65                 if (mid <= high) {
66                     return mid;
67                 } else {
68                     return high;
69                 }
70             }
71         }
72         return -1;
73     }
74 }
复制代码

 

Arrays源码及方法介绍

复制代码
  1 package offer.com.leetcode;
  2 
  3 public class Arrays {
  4     // 私有构造, 不能实例化, 所有方法都是静态方法
  5     private Arrays() {}
  6 
  7     // 根据传入的多个元素, 返回一个ArrayList
  8     public static <T> List<T> asList(T... a)
  9 
 10     // 1、针对不同基本数据类型的数组进行排序, 单线程排序
 11     // 2、针对不同基本数据类型的数组进行排序, 指定排序的范围, 从fromIndex到toIndex
 12     public static void sort(int[] a)
 13     public static void sort(int[] a, int fromIndex, int toIndex)
 14     public static void sort(long[] a)
 15     public static void sort(long[] a, int fromIndex, int toIndex)
 16     public static void sort(short[] a)
 17     public static void sort(short[] a, int fromIndex, int toIndex)
 18     public static void sort(char[] a)
 19     public static void sort(char[] a, int fromIndex, int toIndex)
 20     public static void sort(byte[] a)
 21     public static void sort(byte[] a, int fromIndex, int toIndex)
 22     public static void sort(float[] a)
 23     public static void sort(float[] a, int fromIndex, int toIndex)
 24     public static void sort(double[] a)
 25     public static void sort(double[] a, int fromIndex, int toIndex)
 26     public static void sort(Object[] a)
 27     public static <T> void sort(T[] a, Comparator<? super T> c)
 28     public static <T> void sort(T[] a, int fromIndex, int toIndex,Comparator<? super T> c)
 29 
 30     // 1、针对不同 基本数据类型 或 对象类型 的数组进行排序, 并行排序, 有前提条件, 数组的长度必须大于(1 << 13 = 8192)并且cpu核数大于1
 31     // 2、针对不同 基本数据类型 或 对象类型 的数组进行排序, 指定排序的范围, 从fromIndex到toIndex
 32     public static void parallelSort(byte[] a)
 33     public static void parallelSort(byte[] a, int fromIndex, int toIndex)
 34     public static void parallelSort(char[] a)
 35     public static void parallelSort(char[] a, int fromIndex, int toIndex)
 36     public static void parallelSort(short[] a)
 37     public static void parallelSort(short[] a, int fromIndex, int toIndex)
 38     public static void parallelSort(int[] a)
 39     public static void parallelSort(int[] a, int fromIndex, int toIndex)
 40     public static void parallelSort(long[] a)
 41     public static void parallelSort(long[] a, int fromIndex, int toIndex)
 42     public static void parallelSort(float[] a)
 43     public static void parallelSort(float[] a, int fromIndex, int toIndex)
 44     public static void parallelSort(double[] a)
 45     public static void parallelSort(double[] a, int fromIndex, int toIndex)
 46     // 针对对象类型数组进行排序, 对象需要是实现Comparable接口的
 47     public static <T extends Comparable<? super T>> void parallelSort(T[] a)
 48     public static <T extends Comparable<? super T>>void parallelSort(T[] a, int fromIndex, int toIndex)
 49     // 针对对象类型数组进行排序, 并且指定一个比较器
 50     public static <T> void parallelSort(T[] a, Comparator<? super T> cmp)
 51     public static <T> void parallelSort(T[] a, int fromIndex, int toIndex,Comparator<? super T> cmp)
 52 
 53 
 54     public static <T> void parallelPrefix(T[] array, BinaryOperator<T> op)
 55     public static <T> void parallelPrefix(T[] array, int fromIndex,int toIndex, BinaryOperator<T> op)
 56     public static void parallelPrefix(long[] array, LongBinaryOperator op)
 57     public static void parallelPrefix(long[] array, int fromIndex,int toIndex, LongBinaryOperator op)
 58     public static void parallelPrefix(double[] array, DoubleBinaryOperator op)
 59     public static void parallelPrefix(double[] array, int fromIndex,int toIndex, DoubleBinaryOperator op)
 60     public static void parallelPrefix(int[] array, IntBinaryOperator op)
 61     public static void parallelPrefix(int[] array, int fromIndex,int toIndex, IntBinaryOperator op)
 62 
 63     // 二分查找
 64     public static int binarySearch(long[] a, long key)
 65     public static int binarySearch(long[] a, int fromIndex, int toIndex,long key)
 66     public static int binarySearch(int[] a, int key)
 67     public static int binarySearch(int[] a, int fromIndex, int toIndex,int key)
 68     public static int binarySearch(short[] a, short key)
 69     public static int binarySearch(short[] a, int fromIndex, int toIndex,short key)
 70     public static int binarySearch(char[] a, char key)
 71     public static int binarySearch(char[] a, int fromIndex, int toIndex,char key)
 72     public static int binarySearch(byte[] a, byte key)
 73     public static int binarySearch(byte[] a, int fromIndex, int toIndex, byte key)
 74     public static int binarySearch(double[] a, double key)
 75     public static int binarySearch(double[] a, int fromIndex, int toIndex, double key)
 76     public static int binarySearch(float[] a, float key)
 77     public static int binarySearch(float[] a, int fromIndex, int toIndex,float key)
 78     public static int binarySearch(Object[] a, Object key)
 79     public static int binarySearch(Object[] a, int fromIndex, int toIndex,Object key)
 80     public static <T> int binarySearch(T[] a, T key, Comparator<? super T> c)
 81     public static <T> int binarySearch(T[] a, int fromIndex, int toIndex,T key, Comparator<? super T> c)
 82 
 83     // 两个数组比较, 比较地址, 比较长度, 比较每个元素
 84     public static boolean equals(long[] a, long[] a2)
 85     public static boolean equals(int[] a, int[] a2)
 86     public static boolean equals(short[] a, short a2[])
 87     public static boolean equals(char[] a, char[] a2)
 88     public static boolean equals(byte[] a, byte[] a2)
 89     public static boolean equals(boolean[] a, boolean[] a2)
 90     public static boolean equals(double[] a, double[] a2)
 91     public static boolean equals(float[] a, float[] a2)
 92     public static boolean equals(Object[] a, Object[] a2)
 93 
 94     // 数组的填充值, 将val值赋值给数组的每一个元素
 95     public static void fill(long[] a, long val)
 96     // 数组的填充值, 将val值赋值给数组的每一个元素, 在指定的下标范围内, [fromIndex, toIndex)
 97     public static void fill(long[] a, int fromIndex, int toIndex, long val)
 98     public static void fill(int[] a, int val)
 99     public static void fill(int[] a, int fromIndex, int toIndex, int val)
100     public static void fill(short[] a, short val)
101     public static void fill(short[] a, int fromIndex, int toIndex, short val)
102     public static void fill(char[] a, char val)
103     public static void fill(char[] a, int fromIndex, int toIndex, char val)
104     public static void fill(byte[] a, byte val)
105     public static void fill(byte[] a, int fromIndex, int toIndex, byte val)
106     public static void fill(boolean[] a, boolean val)
107     public static void fill(boolean[] a, int fromIndex, int toIndex,boolean val)
108     public static void fill(double[] a, double val)
109     public static void fill(double[] a, int fromIndex, int toIndex,double val)
110     public static void fill(float[] a, float val)
111     public static void fill(float[] a, int fromIndex, int toIndex, float val)
112     public static void fill(Object[] a, Object val)
113     public static void fill(Object[] a, int fromIndex, int toIndex, Object val)
114 
115     // 数组拷贝, 指定新数组的长度, 如果长度大于原数组长度, 填充0, 否则截断
116     public static byte[] copyOf(byte[] original, int newLength)
117     public static short[] copyOf(short[] original, int newLength)
118     public static int[] copyOf(int[] original, int newLength)
119     public static long[] copyOf(long[] original, int newLength)
120     public static char[] copyOf(char[] original, int newLength)
121     public static float[] copyOf(float[] original, int newLength)
122     public static double[] copyOf(double[] original, int newLength)
123     public static boolean[] copyOf(boolean[] original, int newLength)
124     public static <T> T[] copyOf(T[] original, int newLength)
125     public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType)
126 
127     // 范围拷贝, 从指定下标, 拷贝到指定下标结束
128     public static byte[] copyOfRange(byte[] original, int from, int to)
129     public static short[] copyOfRange(short[] original, int from, int to)
130     public static int[] copyOfRange(int[] original, int from, int to)
131     public static long[] copyOfRange(long[] original, int from, int to)
132     public static char[] copyOfRange(char[] original, int from, int to)
133     public static float[] copyOfRange(float[] original, int from, int to)
134     public static double[] copyOfRange(double[] original, int from, int to)
135     public static boolean[] copyOfRange(boolean[] original, int from, int to)
136     public static <T> T[] copyOfRange(T[] original, int from, int to)
137     public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType)
138 
139 
140     public static int hashCode(long a[])
141     public static int hashCode(int a[])
142     public static int hashCode(short a[])
143     public static int hashCode(char a[])
144     public static int hashCode(byte a[])
145     public static int hashCode(boolean a[])
146     public static int hashCode(float a[])
147     public static int hashCode(double a[])
148     public static int hashCode(Object a[])
149 
150     public static int deepHashCode(Object a[])
151     public static boolean deepEquals(Object[] a1, Object[] a2)
152     public static String toString(long[] a)
153     public static String toString(int[] a)
154     public static String toString(short[] a)
155     public static String toString(char[] a)
156     public static String toString(byte[] a)
157     public static String toString(boolean[] a)
158     public static String toString(float[] a)
159     public static String toString(double[] a)
160     public static String toString(Object[] a)
161     public static String deepToString(Object[] a)
162 
163     // 根据指定的生成器函数设置数组中的每一个元素
164     public static <T> void setAll(T[] array, IntFunction<? extends T> generator)
165     public static <T> void parallelSetAll(T[] array, IntFunction<? extends T> generator)
166     public static void setAll(int[] array, IntUnaryOperator generator)
167     public static void parallelSetAll(int[] array, IntUnaryOperator generator)
168     public static void setAll(long[] array, IntToLongFunction generator)
169     public static void parallelSetAll(long[] array, IntToLongFunction generator)
170     public static void setAll(double[] array, IntToDoubleFunction generator)
171     public static void parallelSetAll(double[] array, IntToDoubleFunction generator)
172 
173     // 获取指定类型数组的分离器, 分离器支持数据的顺序和并行处理
174     // 分离器的方法:
175     // 1、tryAdvance()方法在多个线程中分别迭代元素以支持并行处理
176     // 2、forEachRemaining()在单个线程中顺序迭代元素的方法
177     // 3、trySplit()将自身分为Sub-Spliterators的方法以支持并行处理
178     public static Spliterator.OfInt spliterator(int[] array)
179     public static Spliterator.OfInt spliterator(int[] array, int startInclusive, int endExclusive)
180     public static Spliterator.OfLong spliterator(long[] array)
181     public static Spliterator.OfLong spliterator(long[] array, int startInclusive, int endExclusive)
182     public static Spliterator.OfDouble spliterator(double[] array)
183     public static Spliterator.OfDouble spliterator(double[] array, int startInclusive, int endExclusive)
184     public static <T> Spliterator<T> spliterator(T[] array)
185     public static <T> Spliterator<T> spliterator(T[] array, int startInclusive, int endExclusive)
186 
187 
188     // 根据指定数组获取一个流
189     public static IntStream stream(int[] array)
190     public static IntStream stream(int[] array, int startInclusive, int endExclusive)
191     public static LongStream stream(long[] array)
192     public static LongStream stream(long[] array, int startInclusive, int endExclusive)
193     public static DoubleStream stream(double[] array)
194     public static DoubleStream stream(double[] array, int startInclusive, int endExclusive)
195     public static <T> Stream<T> stream(T[] array)
196     public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive)
197 }
复制代码

 

Array源码及介绍

  所在包:java.lang.reflect     利用反射创建数组

  示例:

复制代码
 1 import java.lang.reflect.Array;
 2 import java.util.Arrays;
 3 
 4 public class test  {
 5 
 6     public static void main(String[] args) {
 7         // 创建一个一维数组, 数组长度为10
 8         int[] a  = (int[])Array.newInstance(int.class, 10);
 9         System.out.println(Arrays.toString(a));
10         
11         // 创建一个二维数组, 每层的元素个数为2
12         int[][] o = (int[][])Array.newInstance(int.class, 2, 2);
13         System.out.println(Arrays.toString(o[0]));
14         System.out.println(o[0][0]);
15         System.out.println(o[0][1]);
16         System.out.println(Arrays.toString(o[1]));
17         System.out.println(o[1][0]);
18         System.out.println(o[1][1]);
19     }
20 }
复制代码

  源码:

复制代码
 1 package java.lang.reflect;
 2 
 3 public final class Array {
 4 
 5     private Array() {}
 6 
 7     // 根据指定class和长度, 创建一个数组
 8     public static Object newInstance(Class<?> componentType, int length)
 9     // 根据指定class和多个int值, 创建多维数组
10     public static Object newInstance(Class<?> componentType, int... dimensions)
11 
12     // 获取指定数组的长度
13     public static native int getLength(Object array)
14     // 根据下表获取数组中的元素
15     public static native Object get(Object array, int index)
16     public static native boolean getBoolean(Object array, int index)
17     public static native byte getByte(Object array, int index)
18     public static native char getChar(Object array, int index)
19     public static native short getShort(Object array, int index)
20     public static native int getInt(Object array, int index)
21     public static native long getLong(Object array, int index)
22     public static native float getFloat(Object array, int index)
23     public static native double getDouble(Object array, int index)
24 
25     // 根据给定的数组, 设置下表对应元素的值
26     public static native void set(Object array, int index, Object value)
27     public static native void setBoolean(Object array, int index, boolean z)
28     public static native void setByte(Object array, int index, byte b)
29     public static native void setChar(Object array, int index, char c)
30     public static native void setShort(Object array, int index, short s)
31     public static native void setInt(Object array, int index, int i)
32     public static native void setLong(Object array, int index, long l)
33     public static native void setFloat(Object array, int index, float f)
34     public static native void setDouble(Object array, int index, double d)
35 }
复制代码

 

posted @   为你编程  阅读(336)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
历史上的今天:
2018-09-07 Java【获取客户端真实IP地址】
2018-09-07 并发工具
2018-09-07 并发执行任务
点击右上角即可分享
微信分享提示