20210222Java数组以及数组中常见算法

Java数组以及数组中涉及到的常见算法

编辑时间:2021/02/22

读完本节:大概花费40分钟,共4421词

1.数组元素的赋值(杨辉三角、回型数等)
  1. 杨辉三角

    见“20210219Java数组”一文末尾。

  2. 回型数

    实现如下效果:

    image-20210222132603163

    方法一:逐层“口”字填充

    image-20210222131642082

    使用while(end < start)控制进新层的循环

    import  java.util.Scanner;
    public class ArrayTestExer {
        public static void main(String[] args){
    
            //回型数
            //输入一个数得到如下输出
            /*
            * 输入
            * 1
            * 输出
            * 1
            *
            * 输入
            * 4
            * 输出
            * 1     2      3       4
            * 12    13     14      5
            * 11    16     15      6
            * 10    9      8       7
            *
            * */
    
            //1. 获取输入数字
            System.out.print("输入一个整数:");
            Scanner scanner = new Scanner(System.in);
            int length = scanner.nextInt();
    
            //2. 创建相应的二维数组
            int[][] rectangle = new int[length][length];
    
            int value = 1;//存于数值当中的值
            
            //3. 给二维数组赋值
            //每一层的长度在不停减小
            int start = 0;//起点
            int end = length - 1;//终点,数组索引范围是[0~length-1]
            while(end > start){
                /*
                * 当end大于start时,
                * 循环继续,若用户给出的是偶数长度,
                * 则end = start还要在进行一次循环
                */
                //从左往右
                for(int i = start;i <= end;i++){
                    //由于位值是某一行,所以行坐标为start
                    //若length = 7,则为rectangle[0][0~6]赋值
                    rectangle[start][i] = value;
                    //每赋完一个value,value自增1,为下一次赋值做准备
                    value++;
    
                }
                //从上往下
                for(int i = start + 1;i <= end;i++){
                    //若length = 7,则为rectangle[1~6][6]赋值
                    rectangle[i][end] = value;
                    //每赋完一个value,value自增1,为下一次赋值做准备
                    value++;
                }
                //从左往右
                for(int i = end - 1;i > start - 1;i--){//往回倒
                    //若length = 7,则为rectangle[6][0~5]赋值
                    rectangle[end][i] = value;
                    //每赋完一个value,value自增1,为下一次赋值做准备
                    value++;
                }
                //从下往上
                for(int i = end - 1;i > start;i--){
                    //若length = 7,则为rectangle[1~5][0]赋值
                    rectangle[i][start] = value;
                    //每赋完一个value,value自增1,为下一次赋值做准备
                    value++;
                }
    
                //进新层
                start++;
                end--;
            }
    
    
            //5. 遍历数组
            for(int i = 0;i < rectangle.length;i++){
                for(int j = 0;j < rectangle[i].length;j++){
                    System.out.print(rectangle[i][j] + "\t");
                }
                System.out.println();
            }
    
        }
    }
    
    

    方法二:螺旋填充

    import  java.util.Scanner;
    public class ArrayTestExer {
        public static void main(String[] args){
    
            //1. 获取输入数字
            System.out.print("输入一个整数:");
            Scanner scanner = new Scanner(System.in);
            int length = scanner.nextInt();
    
            //2. 创建相应的二维数组
            int[][] rectangle = new int[length][length];
    
            int value = 1;//存于数值当中的值
       
            //3. 给数组元素赋值,方法二
            //x轴最大下标,Y轴最大下标
            int maxX = length-1, maxY = length-1;
            //X轴最小下标,Y轴最小下标
            int minX = 0, minY = 0;
            while(minX <= maxX) {
                for(int x = minX;x <= maxX;x++) {
                    rectangle[minY][x] = value++;
                }
                minY++;//最低行经过上述for已经被赋值,所以最低的行y自增1
                for(int y = minY;y <= maxY;y++) {
                    rectangle[y][maxX] = value++;
                }
                maxX--;//最高列经过上述for已经被赋值,所以最高的列x自减1
                for(int x = maxX;x>= minX;x--) {
                    rectangle[maxY][x] = value++;
                }
                maxY--;//最高行经过上述for已经被赋值,所以最高的行y自减1
                for(int y = maxY;y>=minY;y--) {
                    rectangle[y][minX] = value++;
                }
                minX++;//最低列经过上述for已经被赋值,所以最低的列x自增1
    
                //通过执行完4个for完成了最外圈的赋值,并且通过while来判断是否已经到达最内层
                //同时while中的判断语句也可以写成行的判断形式minY <= maxY
            }
    
    
            //5. 遍历数组
            for(int i = 0;i < rectangle.length;i++){
                for(int j = 0;j < rectangle[i].length;j++){
                    System.out.print(rectangle[i][j] + "\t");
                }
                System.out.println();
            }
    
        }
    }
    
    
  3. 练习:创建一个长度为6的int型数组,要求数组元素的值都在1~30之间,且都是随机值。同时要求元素的值各不相同。

    public class ArrayTestExer {
        public static void main(String[] args){
    
            //1. 创建一个长度为6int型数组
            //动态声明
            int[] arr = new int[6];//验证不重复可以改成30
            for(int i = 0;i < arr.length;i++){
                System.out.print(arr[i] + "\t");
            }
            System.out.println();
    
            //2. 要求元素的值应当是1~30之间的随机数,且元素值不重复
            //元素赋值且不重复判定
            for(int i = 0;i < arr.length;i++){
                while(true){
                    //范围[a~b]的随机数(int)(Math.random() * (b - a + 1) + a);
                    int random = (int)(Math.random() * (30 - 1 + 1) + 1);
                    boolean isSame = false;
    
                    //遍历当前已经赋过值的部分
                    for(int j = 0;j <= i;j++){
                        if(random == arr[j]){
                            //若有随机数等于其中元素,isSame标记为true
                            //同时,无需再继续后续的j遍历
                            isSame = true;
                            break;
                        }
                    }
    
                    //当且仅当isSame是false时才会将值赋给arr[i],而且跳出while循环
                    if(!isSame){
                        arr[i] = random;
                        break;
                    }
                }
            }
    
            //打印输出数组
            for(int i = 0;i < arr.length;i++){
                System.out.print(arr[i] + "\t");
            }
        }
    }
    
2.求数值型数组中元素的最大值、最小值、平均数、总和等
  1. 定义一个int的一维数组,包含10个元素,分别赋一些随机整数(所有随机数都是两位数),然后求出所有元素的最大值、最小值、和、平均值,并输出。

    public class ArrayTestExer2 {
        public static void main(String[] args){
            //1. 定义一个长度为10的int型一维数组
            //动态初始化
            int[] arr = new int[10];
    
            //2. 赋上随机值[10~99]
            for(int i = 0;i < arr.length;i++){
                int random = (int)(Math.random() * (99 - 10 + 1) + 10);
                arr[i] = random;
            }
    
            //声明变量
            int maximum = arr[0];
            int minimum = arr[0];
            int sum = 0;
            double average = 0;
    
            for(int i = 0;i < arr.length;i++){
                //maximum
                if(maximum < arr[i]){
                    maximum = arr[i];
                }
    
                //minimum
                if(minimum > arr[i]){
                    minimum = arr[i];
                }
    
                //sum
                sum += arr[i];
    
                //average
                average = sum /10.0;
            }
    
            //打印
            for(int i = 0;i < arr.length;i++){
                System.out.print(arr[i] + "\t");
            }
            System.out.println();
            System.out.println("最大值是:" + maximum);
            System.out.println("最小值是:" + minimum);
            System.out.println("和是:" + sum);
            System.out.println("均值是:" + average);
        }
    }
    
3.数组的复制、反转、查找(线性查找、二分法查找)
  1. 数组的复制

    //数组的复制
    public class ArrayCopy{
        public static void main(String[] args){
            String[] arr = new String[]{"AA","BB","CC","DD","EE"};
            
            String[] arr1 = new String[arr.length];
            for(int i = 0;i < arr1.length;i++){
                arr1[i] = arr[i];
            }
        }
    }
    

    练习:使用简单数组

    1. 创建一个名为ArrayTest的类,在main()方法中声明array1和array2两个变量,他们是int类型的数组
    2. 静态赋值array1{2,3,5,7,11,13,17,19}
    3. 打印array1
    4. 赋值array2=array1,修改array偶索引元素,使其等于索引值(如array[0] = 0; array[2] = 2;)
    5. 打印array1
    /*
    array1和array2是什么关系?
    */
    public class ArrayTest{
        public static void main(String args){
            int[] array1 = new int[]{2,3,5,7,11,13,17,19};
            int[] array2 = new int[8];
    
            //打印array1
            for(int i = 0;i < array1.length;i++){
                System.out.print(array1[i] + "\t");
            }
            System.out.println();
    
            //不能称做数组的复制
            array2 = array1;
    
            //修改array2
            for(int i = 0;i < array1.length;i++){
                if(i % 2 == 0) {
                    array2[i] = i;
                }
            }
    
            //打印array1
            for(int i = 0;i < array1.length;i++){
                System.out.print(array1[i] + "\t");
            }
        }
    }
    

    image-20210224110315422

    容易发现对array2元素的修改在array1上同样发生了,这种情况发生的原因是在堆空间中并没有对array2开辟新的空间,而是将array1在堆空间中开辟出来的首地址赋值给了array2,因此相当于array2的修改通过指向array1的元素,使array1的元素发生了改变。

    image-20210224111053483

  2. 数组的反转

    public class ArrayReverse{
        public static void main(String[] args){
                    //数组的反转
            String[] reverse = new String[]{"AA","BB","CC","DD","EE"};
    
    //        //方法一:
    //        for(int i = 0;i < reverse.length / 2;i++){
    //        //i < 长度的一半就行了,否则走到后一半又交换回来了,奇数长度除不尽中间一个元素刚好不用换
    //            String temp = reverse[i];
    //            reverse[i] = reverse[reverse.length - i - 1];
    //            reverse[reverse.length - i - 1] = temp;
    //        }
    
            //方法二:
            for(int i = 0,j = reverse.length - 1;i < j;i++,j--){
            //i从0开始,j从最高位开始,当i还在j前面时,交换。随后i自增,j自减
                String temp = reverse[i];
                reverse[i] = reverse[j];
                reverse[j] = temp;
            }
            
            //遍历
            for(int i = 0;i < reverse.length;i++){
                System.out.print(reverse[i] + "\t");
            }
        }
    }
    
  3. 数组的查找

    数组的线性查找与二分法查找(折半查找)

    public class ArraySearch {
        public static void main(String[] args){
            String[] str = new String[]{"AA","BB","CC","DD","EE"};
    
            //线性查找
            String dest = "BB";
    
            boolean isFind1 = false;
            for(int i = 0;i < str.length;i++){
                if(dest.equals(str[i])){
                    System.out.println("找到了,元素位置是:" + i);
                    isFind1 = true;
                    break;
                }
            }
            if(!isFind1){
                System.out.println("没找到");
            }
    
            //二分法查找(折半查找)
            //折半查找的前提是数组必须有序
            int[] arr = new int[]{-12,-10,-8,-2,0,5,9,10,15,20};
    
            int head = 0;//初始索引
            int end = arr.length - 1;//结束索引
            int dest1 = 10;
            boolean isFind2 = false;
            while(head <= end){
                int middle = (head + end) / 2;//中轴元素
    
                if(dest1 == arr[middle]){
                    System.out.println("找到了,元素位置是:" + middle);
                    isFind2 = true;
                    break;
                }else if(dest1 < arr[middle]){
                    //查找middle的左半部分
                    end = middle - 1;
                }else{//dest1 > arr[middle]
                    //查找middle的右半部分
                    head = middle + 1;
                }
            }
            if(!isFind2){
                System.out.println("没找到");
            }
        }
    }
    

    折半查找过程图解:

    image-20210225000129530

4.数组元素的排序算法
  1. 排序:假设有n个记录的序列{R1,R2,…,Rn},其相应的关键字序列为{K1,K2,…,Kn}。将这些记录重新排序为{Ri1,Ri2,…Rin},使其相应的关键字序列值满足条件Ki1 <= Ki2 <= … <=Kin,这样的一种操作称为排序。通常来说排序的目的是快速查找。

  2. 衡量排序算法的优劣:

    1. 时间复杂度:分析关键字的比较次数和记录的移动次数
    2. 空间复杂度:分析排序算法中需要多少辅助内存
    3. 稳定性:若两个记录A和B的关键字值相等,但排序后的A、B的先后次序保持不变,则称这种排序算法是稳定的。
  3. 排序算法的分类:内部排序和外部排序

    1. 内部排序:整个排序过程不需要借助于外部存储器,所有排序操作都在内存中完成。

    2. 外部排序:参与排序的数据非常多,数据量非常大,计算机无法把整个排序过程放在内存中完成,必须借助外部存储器。外部排序最常见的是多路并归排序。可以认位外部排序是由多次内部排序组成的。

    3. 常见的内部排序算法:

      选择排序:直接选择排序、堆排序

      交换排序:冒泡排序快速排序

      插入排序:直接插入排序、折半插入排序、希尔排序

      归并排序

      桶式排序

      基数排序

  4. 算法的5大特征:输入(input)、输出(output)、有穷性(finiteness)、确定性(definiteness)、可行性(effectiveness)

    1. 输入:由0个或多个输入数据,这些输入必须有清楚的描述和定义
    2. 输出:至少有1个或多个输出结果,不可以没有输出结果
    3. 有穷性:算法在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤可以在可接受的时间内完成
    4. 确定性:算法中的每一步都有明确的含义,不会出现二义性。满足确定性的算法也称为确定性算法,相对的不确定算法有并行算法、概率算法等。
    5. 可行性:算法的每一步都是清楚且可行的,能让用户用纸笔计算而求出答案
  5. 冒泡排序算法:

    1. 冒泡算法的原理:重复的走访要排序的数列,一次比较两个元素,如果他们的顺序错误就交换。

    2. 冒泡排序的思想:

      比较相邻的元素。如果第一个元素比第二个大(升序),就交换这两个元素。

      对每一对相邻元素做同样的操作,从开始第一对到结尾的最后一对。这步完成之后,最后的元素会是最大的数。

      除最后一个元素外,针对所有的元素重复上述操作。

      持续每次对越来越少的元素重复上面的操作,之到没有任何一对元素需要比较为止。

    3. 冒泡排序优化:在冒泡排序过程中,各元素不断接近自己的位置,如果,一趟比较下来没有进行过交换,就说明序列有序,因此在排序过程中设置一个标志swap用以判断元素是否进行过交换。从而减少不必要的比较。

    4. //冒泡排序
      public class BubbleSortTest{
          public static void main(String[] args){
              int[] arr = new int[]{43,32,76,-98,0,64,33,-21,32,99};
              
              //bubblesort
              for(int i = 0;i < arr.length - 1;i++){
                  for(int j = 0;j < arr.length - 1 - i;j++){
                      if(arr[j] > arr[j + 1]){
                          int temp = arr[j];
                          arr[j] = arr[j + 1];
                          arr[j + 1] = temp;
                      }
                  }
              }
          }
      }
      
  6. 快速排序算法:快速排序通常明显比同为O(nlogn)的其他算法更快,因此常被采用,而且快速排序采用了分治法的思想。

  7. 排序算法性能对比

    排序方法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性
    插入排序O(n²)O(n²)O(n)O(1)稳定
    希尔排序O(n^1.3)O(n²)O(n)O(1)不稳定
    选择排序O(n²)O(n²)O(n²)O(1)不稳定
    堆排序O(nlog₂n)O(nlog₂n)O(nlog₂n)O(1)不稳定
    冒泡排序O(n²)O(n²)O(n)O(1)稳定
    快速排序O(nlog₂n)O(n²)O(nlog₂n)O(nlog₂n)不稳定
    归并排序O(nlog₂n)O(nlog₂n)O(nlog₂n)O(n)稳定
    计数排序O(n + k)O(n + k)O(n + k)O(n + k)稳定
    桶排序O(n + k)O(n²)O(n)O(n + k)稳定
    基数排序O(n * k)O(n * k)O(n * k)O(n + k)稳定

    堆排序、快速排序、归并排序虽然平均时间复杂度相同,但是nlog₂n的系数不同

  8. 各种内部排序方法的性能比较

    1. 从平均时间而言:快速排序最佳。但在最坏情况下时间性能不如堆排序和归并排序
    2. 从算法简单性看:由于直接选择排序、直接插入排序和冒泡排序的算法比较简单,将其 认位是简单算法。对于希尔排序、堆排序、快速排序和归并排序算法,其算法较为复杂,认为是复杂排序
    3. 从稳定性看:直接插入排序、冒泡排序、和归并排序是稳定的;而直接选择排序、快速排序、希尔排序和堆排序都是不稳定的排序
    4. 从待排序的记录n的大小来看,n比较小时(n <= 50),宜采用简单排序。当记录规模较小时,直接插入排序较好。否则应选择直接选择排序,因为直接选择移动的记录数少于直接插入。
    5. 从待排序的记录n的大小来看,n较大时宜采用改进排序。快速排序、堆排序、归并排序
5.Arrays工具类的使用
  1. java.util.Arrays类即为操作数组的工具类,包含了用来操作数组的的各种方法(排序、搜索等)

    方法操作
    boolean equals(int[] a,int[] b)判断两个数组是否相等
    String toString(int[] a)格式输出数组信息
    void fill(int[] a,int val)将指定值填充到数组中
    void sort(int[] a)对数组进行排序
    int binarySearch(int[] a,int key)对排序后的数组进行二分法检索指定的值
  2. 练习

    import java.util.Arrays;
    
    public class ArraysTest {
        public static void main(String[] args){
            //1. boolean equals(int[] a,int[] b)
            int[] arr1 = new int[]{1,2,3,4};
            int[] arr2 = new int[]{1,3,2,4};
            boolean isEquals = Arrays.equals(arr1,arr2);
            System.out.println(isEquals);
    
            //2. String toString(int[] a)
            System.out.println(Arrays.toString(arr1));
    
            //3. void fill(int[] a,int val)
            Arrays.fill(arr1,10);
            System.out.println(Arrays.toString(arr1));
    
            //4. void sort(int[] a)
            Arrays.sort(arr2);
            System.out.println(Arrays.toString(arr2));
    
            //5.int binarySearch(int[] a,int key)
            int[] arr3 = new int[]{-98,-34,2,34,54,66,79,105,210,333};
            int index = Arrays.binarySearch(arr3,210);
            System.out.println(index);
            if(index >= 0){
                System.out.println("找到了");
            }else{
                System.out.println("没找到");
            }
        }
    }
    
6.数组中常见的异常
  1. 数组角标越界异常:ArrayIndexOutOfBoundsException

    int[] arr = new int[]{1,2,3,4,5};
    //for(int i = 0;i <= arr.length;i++){
    //    System.out.print(arr[i] + "\t");
    //}
    
    //System.out.print(arr[-2]);
    
    //一旦程序抛出异常之后,后续的语句不会被执行,但是位于try{}catch{}语句块的结尾的finally{}一定会执行
    
  2. 空指针异常:NullPointerException

    //情况一:
    int[] arr1 = new int[]{1,2,3};
    arr1 = null;
    System.out.println(arr1[0]);
    //相当于在内存的栈区手动将arr1指向堆区的指针设置为null
    //于是想要调动没有被任何指针指向的arr1[0]会报空指针异常
    
    //情况二:
    int[][] arr2 = new int[4][];
    System.out.println(arr2[0][0]);
    //arr2[0]存在,但是数组arr2使用的时动态初始化方式二,因此内层元素并没有被赋值
    //想要程序打印内层元素时,即会出现空指针异常
    
    //情况三:
    String[] arr3 = new String[]{"AA","BB","CC"};
    arr3[0] = null;
    System.out.println(arr3[0].toString());
    //arr3[0]被重新赋值为null,导致arr3在栈区中找不到位于堆空间的起始元素的位置
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值