01JavaSE_03数组

1. 数组概述

数组是相同类型数据的有序集合;

数组描述的是相同类型的若干个数组,按照一定的先后次序排列组合而成;

其中,每一个数据称为一个数组元素,每个数组元素可以通过一个下标来访问,数组的下标从零开始。

1.1 数组定义

数组声明:首先必须声明数组变量,才能在程序中使用,声明数组变量的语法:

dataType[] arrayRefVar; //首选的方法
dataType arrayRefVar[]; //效果相同,但不推荐

数组创建:java语言使用new操作符来创建数组,语法是:

dataType[] arrayRefVar=new dataType[arraySize];

数组访问:数组的元素是通过索引(下标)来访问的,数组的索引从0开始;

获取数组长度(个数):arrays.length

dataType data=arrayRefVar[i];
//数组定义实例:D01ArrayDef.java
//声明、创建、赋值、取值
public class D01ArrayDef {
    //变量的类型 变量的名字 = 变量的值
    public static void main(String[] args) {
        int[] nums; //1.声明一个数组
        int numx[];
        nums=new int[5]; //2.创建一个数组对象
        //3.给数组元素赋值
        nums[0]=1;
        nums[1]=2;
        nums[2]=3;
        nums[3]=4; //nums[4] 为初始值/缺省值
        //4.取出数组元素的值
        for (int i = 0; i <5; i++) {
            System.out.println(nums[i]);
        }
    }
}

数组的特点

长度是确定的,数组一旦被创建,其大小就不可以改变;

元素必须是相同类型,不允许出现混合类型;

数组中的元素可以是任何类型,包括基本类型和引用类型;

数组变量属于引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量;

数组本身就是对象,java中的对象存储在堆中,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的

1.2 内存分析

java内存分析

数组内存分析

1.3 数组初始化

静态初始化

int[] arr={1,2,3};
Man[] men={new Man(1,1),new Man(2,2)};

动态初始化

int[] arr= new int[2];
arr[0]=1;
arr[1]=2;

默认初始化

数组是引用类型个,它的元素相当于类的实例变量,因此数组一经分配空间,其中每个元素也被按照实例变量同样的方式被隐式初始化。

数组下标越界

下标的合法区间:[0, length-1],如果不在此范围,就会发生下标越界异常:ArrayIndexOutOfBoundsException

//数组初始化实例:D02ArrayInit.java
//静态、动态、默认初始化,下标越界
public class D02ArrayInit {
    public static void main(String[] args) {
        //静态初始化:创建+赋值
        int[] a={1,2,3};
        System.out.println(a[0]);
        //动态初始化:先声明,再赋值,包含默认初始化
        int[] b=new int[3];
        b[0]=1;
        b[2]=3;
        System.out.println(b[0]);
        System.out.println(b[1]); //int型默认初始化值:0
        //System.out.println(b[3]); //下标越界异常:ArrayIndexOutOfBoundsException
    }
}

2 数组使用

普通for循环

//数组使用实例:D03ArrayUse.java
//打印数组元素,计算所有元素的和,计算元素的最大值
public class D03ArrayUse {
    public static void main(String[] args) {
        int[] arr={1,2,3,4,5};
        //打印全部数组元素
        for (int i=0;i<arr.length;i++){
            System.out.println(arr[i]);
        }
        //计算所有元素的和
        int sum=0;
        for (int i=0;i<arr.length;i++){
            sum=sum+arr[i];
        }
        System.out.println("sum= "+sum);
        //查找最大元素
        int max=arr[0];
        for (int i=1;i<arr.length;i++){
            if(arr[i]>max){
                max=arr[i];
            }
        }
        System.out.println("max= "+max);
    }
}

foreach循环

数组作为方法参数

数组作为返回值

//数组的增强使用实例:D04ArrayUsePro.java
//foreach,数组作为方法参数,数组作为返回值
public class D04ArrayUsePro {
    public static void main(String[] args) {
        int[] array={1,2,3,4,5};
        //jdk1.5 ,没有下标,增强for循环foreach
        for (int i:array){
            System.out.println(i);
        }
        //调用printArray,打印数组参数的元素
        printArray(array);
        //调用reversArray,打印反转后的数组元素值
        int[] revArr=reversArray(array);
        printArray(revArr); //调用打印方法打印
    }

    //打印数组元素方法,数组作为参数
    public static void printArray(int[] arrInt){
        for (int i=0;i<arrInt.length;i++){
            System.out.print(arrInt[i]+" ");
        }
    }

    //反转数组方法,返回值为数组
    public static int[] reversArray(int[] arrInt){
        int[] result=new int[arrInt.length];
        for (int i=0,j=result.length-1;i<arrInt.length;i++,j--){
            result[j]=arrInt[i];
        }
        return result;
    }
}

4. 多维数组

所谓数组定义

多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每个元素都是一个一维数组,二维数组

int arrs[][]=new int[2][3];
int[][][] arrays = {{{1,2,3},{1,2,3}},{{3,4,1},{2,3,4}}};

以上二维数组arrs可以看成一个两行三列的数组。

多维数组长度
对于多维数组来说,也可以获得数组的长度。但是使用数组名.length获得的是数组第一维的长度。如果需要获得二维数组中总的元素个数,可以使用如下代码:

int[][] m = {{1,2,3,1},{1,3},{3,4,2}};
int sum = 0;
for(int i = 0;i < m.length;i++){     //循环第一维下标
    sum += m[i].length;         //第二维的长度相加
}

在该代码中,m.length 代表m 数组第一维的长度,内部的m[i]指每个一维数组元素,m[i].length 是m[i]数组的长度,把这些长度相加就是数组m中总的元素个数。

//多维数组实例:D05ArrayMulti.java
//打印数组元素,打印数组维度
public class D05ArrayMulti {
    public static void main(String[] args) {
        System.out.println("===1.二维数组============");
        //二维数组[3][2]
        int[][] arrs={{1,2},{2,3},{3,4}};
        for (int i=0;i<arrs.length;i++){
            for (int j=0;j<arrs[i].length;j++){
                System.out.println(arrs[i][j]);
            }
            System.out.println();
        }
        System.out.println("---调用方法按行打印--------");
        //调用方法打印
        printArray(arrs[0]);
        System.out.println();
        printArray(arrs[1]);
        System.out.println();
        printArray(arrs[2]);
        System.out.println();

        System.out.println("===2.三维数组============");
        //三维数组[?][?][?]
        int[][][] arrays = {{{1,2,3},{1,2,3}},{{3,4,1},{2,3,4}}};
        for (int i=0;i<arrays.length;i++){
            for(int j=0;j<arrays[i].length;j++){
                for (int k=0;k<arrays[i][j].length;k++){
                    System.out.print(arrays[i][j][k]+" ");
                }
                System.out.println("-------------------");
                System.out.println(i+","+j+" 维:"+arrays[i][j].length);
            }
            System.out.println("---------------------- ");
            System.out.println(i+" 维:"+arrays[i].length);
        }
        System.out.println("---------------------- ");
        System.out.println("一维: "+arrays.length);
     }

    //打印数组元素方法
    public static void printArray(int[] arr){
        for (int i=0;i<arr.length;i++){
            System.out.print(arr[i]+" ");
        }
    }
}

5. Arrays类

数组的工具类java.util.Arrays;

由于数组对象本身并没有什么方法可以供我们调用,但API中提供了一个工具类Arrays供我们使用,从而可以对数组对象进行一些基本的操作;

Arrays类中的方法都是static修饰的静态方法,使用时,可以直接使用类名进行调用,而“不用”使用对象来调用(注意:是不用,而不是“不能”);

常用功能:

​ 给数组赋值:fill()

​ 对数组排序:sort(),按升序排列

​ 比较数组:equals(),比较数组中的元素值是否相等;

​ 查找数组元素:binarySearch(),对排序好的数组进行二分查找法操作;

//Arrays类实例:D06Arrays.java
//Arrays方法:转字符串,排序,填充
import java.util.Arrays;
public class D06Arrays {
    public static void main(String[] args) {
        int[] a={1,2,3,100,10,5};
        System.out.println(a); //哈希值
        //数组转字符串 Arrays.toString
        System.out.println(Arrays.toString(a));
        //排序 Arrays.sort
        Arrays.sort(a);
        System.out.println(Arrays.toString(a));
        //填充数值 Arrays.fill
        Arrays.fill(a,0); //全填充0
        System.out.println(Arrays.toString(a));
        Arrays.fill(a,0,3,1); //索引0-3填充0
        System.out.println(Arrays.toString(a));
    }
}

6. 排序

常见的排序算法:插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序、基数排序;

所需辅助空间最多:归并排序

所需辅助空间最少:堆排序

平均速度最快:快速排序

不稳定:快速排序,希尔排序,堆排序。

稳定与非稳定:如果一个排序算法能够保留数组中重复元素的相对位置则可以被称为是“稳定”的,反之,则是“非稳定”的;

6.1 插入排序

基本思想:通常人们整理桥牌的方法是一张一张的来,将每一张牌插入到其他已经有序的牌中的适当位置。在计算机的实现中,为了要给插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位;

在要排序的一组数中,假设前面(n-1)[n>=2] 个数已经是排好顺序的,现在要把第n 个数插到前面的有序数中,使得这 n个数也是排好顺序的。如此反复循环,直到全部排好顺序。

算法描述:一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤2~5。
//插入排序实例:D07SortInsert.java
//交换位置和空位右移
public class D07SortInsert {
    public static void main(String[] args) {
        System.out.println("===交换排序==============");
        //通过交换进行插入排序,借鉴冒泡排序
        int[] a={1,2,3,9,7,6,5,0,4,8};
        for (int i=0;i<a.length-1;i++){
            for (int j=i+1;j>0;j--){
                if (a[j]<a[j-1]){
                    int temp=a[j];
                    a[j]=a[j-1];
                    a[j-1]=temp;
                }
            }
        }
        for (int x:a) {
            System.out.print(x+" ");
        }
        System.out.print("\n");

        System.out.println("===右移排序==============");
        //通过将较大元素都向右移动
        int[] b={1,2,3,9,7,6,5,0,4,8};
        for (int i=1;i<b.length;i++){
            int num=b[i];
            int j;
            for (j=i;j>0&&num<b[j-1];j--){
                b[j]=b[j-1];
            }
            b[j]=num;
        }
        for (int x:b) {
            System.out.print(x+" ");
        }
    }
}

6.2 希尔排序(最小增量排序)

希尔排序(Shell Sort):是 DL.Shell在1959年提出的,是插入排序的一种,它是对插入排序算法的改进。其实质是一种分组排序。把数据分成几组,然后再进行组内插入排序,不断重复这样的分组过程,直到只比较相邻元素的最后一趟排序为止。

基本思想:将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次再将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。

可以看到步长的选择是希尔排序的重要部分。只要最终步长为1任何步长序列都可以工作。一般来说最简单的步长取值是初次取数组长度的一半为增量,之后每次再减半,直到增量为1。

算法先将要排序的一组数按某个增量 d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差 d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到 1 时,进行直接插入排序后,排序完成。

算法描述

  1. 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
  2. 按增量序列个数 k,对序列进行 k 趟排序;
  3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
//希尔排序:D08SortShell.java
//按gap分组分别执行插入排序
import java.util.Arrays;
public class D08SortShell {
    public static void main(String[] args) {
        int[] num={1,3,5,0,2,4,9,7,8,6};
        System.out.println("排序前的数组: "+ Arrays.toString(num));
        //增量序列的选择没有具体公式
        for (int inc=num.length/5;inc>0;inc=inc/3){
            for (int i=inc;i<num.length;i++){
                int index=i;
                //进行插入排序
                while((index-inc)>=0&&num[index]<num[index-inc]){
                    int temp=num[index];
                    num[index]=num[index-inc];
                    num[index-inc]=temp;
                    index=index-inc;
                }
            }
        }
        System.out.println("排序后的数组: "+Arrays.toString(num));
    }
}

6.3 选择排序

基本思想:选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

在要排序的一组数中,选出最小的一个数与第一个位置的数交换; 然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。

算法描述

  1. 从未排序序列中,找到关键字最小的元素;
  2. 如果最小元素不是未排序序列的第一个元素,将其和未排序序列第一个元素互换;
  3. 重复1、2步,直到排序结束。
//选择排序:D09SortSelect.java
//从第2个元素开始,每轮从没排序的元素中,找最小的往前排
import java.util.Arrays;
public class D09SortSelect {
    public static void main(String[] args) {
        int[] arr={3,1,5,9,7};
        System.out.println("排序前的数组: "+ Arrays.toString(arr
        ));
        for (int i=0;i<arr.length-1;i++){
            int min=i;
            //遍历区待排序数组,选出待排序中值最小的位置
            for (int j=i+1;j<arr.length;j++) {
                if (arr[j] < arr[min]) {
                    min = j;
                }
            }
            //最小值不等于当前值时进行交换
            if (min!=i){
                int temp=arr[min];
                arr[min]=arr[i];
                arr[i]=temp;
            }
        }
        System.out.println("排序后的数组: "+ Arrays.toString(arr
        ));
    }
}

6.4 堆排序

1991年的计算机先驱奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德(Robert W.Floyd) 和威廉姆斯(J.Williams) 在1964年共同发明了著名的堆排序算法(Heap Sort);

基本思想:此处以大顶堆为例,堆排序的过程就是将待排序的序列构造成一个堆,选出堆中最大的移走,再把剩余的元素调整成堆,找出最大的再移走,重复直至有序。

堆排序是一种树形选择排序,是对直接选择排序的有效改进。 堆的定义如下:具有n个元素的序列(h1,h2,...,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有 n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。

实例

初始序列:46,79,56,38,40,84

建堆

交换,从堆中踢出最大数

剩余结点再建堆,再交换踢出最大数

依次类推:最后堆中剩余的最后两个结点交换,踢出一个,排序完成。

算法描述

  1. 初始化一个堆型数据结构:调整堆结构,调整为大顶堆(从最后一个非叶子节点进行调整,子节点比父节点大,则交换,直到根节点(顶节点)最大);
  2. 将首末进行交换(顶节点与最后一个节点);
  3. 将交换完的堆,继续调整为大顶堆。并将首与倒数第二个进行交换,然后依次类推知道根节点(建堆、交换、建堆…知道要与根节点进行交换。
//大顶堆排序:D10SortHeap.java
import java.util.Arrays;
public class D10SortHeap {
    public static void main(String[] args) {
        int[] a={1,3,9,5,7,2,8,6,4};
        System.out.println("排序前的数组: "+ Arrays.toString(a));
        heapSort(a);
        System.out.println("排序后的数组: "+ Arrays.toString(a));
    }

    /**
     * 构建大顶堆
     * @param arr 待调整数组
     * @param size 调整多少
     * @param index 调整哪一个,最后一个叶子节点的父节点开始
     */
    public static void maxHeapify(int[] arr,int size,int index){
        //左子节点
        int leftNode=2*index+1;
        //右子节点
        int rightNode=2*index+2;
        int max=index; //假设自己最大
        //分别比较左右两个节点找出最大
        if(leftNode<size&&arr[leftNode]>arr[max]){ //若左子节点大于父节点
            max=leftNode; //将最大值索引改为左子节点的索引
        }
        if(rightNode<size&&arr[rightNode]>arr[max]){ //若左子节点大于父节点
            max=rightNode; //将最大值索引改为左子节点的索引
        }
        //如果索引不相等,就需要交换
        if(max!=index){
            int temp=arr[index];
            arr[index]=arr[max];
            arr[max]=temp;
            //若下边还有叶子节点并且破坏了原有的堆,需要重新调整
            maxHeapify(arr,size,max); //位置为刚才改动的位置;
        }
    }

    /**
     * 需要将最大的顶部与最后一个交换
     * @param arr 待调整数组
     */
    public static void heapSort(int[] arr){
        int start=(arr.length-1)/2; //最后一个子节点的父节点
        for(int i=start;i>=0;i--){
            maxHeapify(arr,arr.length,i);
        }
        //最后一个跟第一个进行调整
        for(int i=arr.length-1;i>0;i--){ //最后一个是数组长度减1
            int temp=arr[0]; //最前面一个
            arr[0]=arr[i]; //最后一个
            arr[i]=temp;
            //调整后再进行大顶堆调整
            maxHeapify(arr,i,0);
        }
        
    }
}

6.5 冒泡排序

冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

算法描述

  1. 比较相邻的元素,如果第一个比第二个大,就交换它们;
  2. 对每一对相邻元素作同样的操作,从开始第一对到结尾的最后一对;这步结束后,最后的元素会是最大的数;
  3. 针对所有元素重复以上步骤,除了最后一个,即需要进行length-1次;
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何数字要比较。
//冒泡排序:D11SortBubble.java
import java.util.Arrays;
public class D11SortBubble {
    public static void main(String[] args) {
        int[] a={5,3,1,8,9,6,4,2,7};
        System.out.println("排序前的数组 "+ Arrays.toString(a));
        bubbleSort(a);
        System.out.println("排序后的数组 "+ Arrays.toString(a));
    }

    public static void bubbleSort(int[] arr){
        //外层循环,判断要循环多少次
        for(int i=0;i<arr.length-1;i++){
            boolean flag=false; //通过flag标识位减少没有意义的比较
            //内层循环,比较并交换位置
            for(int j=0;j<arr.length-1-i;j++){
                if (arr[j+1]>arr[j]){
                    int temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                    flag=true;
                }
            }
            if (flag==false){
                break;
            }
        }
    }
}

6.6 快速排序

基本思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。

算法描述

  1. 从数列中挑出一个元素,称为"基准"(pivot)。

  2. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。

  3. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。

    递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

//快速排序实例:D12SortQuick.java
public class D12SortQuick {
    public static void main(String[] args) {
        int[] arr={6,1,5,2,4,8,9,7,3,0};
        System.out.print("排序前:");
        for (int i=0;i<arr.length;i++){
            System.out.print(arr[i]+",");
        }
        System.out.println();
        quickSort(arr,0,arr.length-1);
        System.out.print("排序后:");
        for (int i=0;i<arr.length;i++) {
            System.out.print(arr[i] + ",");
        }
    }
    //排序算法
    public static void quickSort(int[] a, int low, int high) {
        //已经排完
        if (low >= high) {
            return;
        }
        int left = low;
        int right = high;

        //保存基准值
        int pivot = a[left];
        while (left < right) {
            //从后向前找到比基准小的元素
            while (left < right && a[right] >= pivot)
                right--;
            a[left] = a[right];
            //从前往后找到比基准大的元素
            while (left < right && a[left] <= pivot)
                left++;
            a[right] = a[left];
        }
        // 放置基准值,准备分治递归快排
        a[left] = pivot;
        quickSort(a, low, left - 1);
        quickSort(a, left + 1, high);
    }
}

6.7 归并排序

基本思想

归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

//合并排序实例:D13SortMerge.java
//归并排序实际上就是两个操作,拆分+合并
import java.util.Arrays;
public class D13SortMerge {
    public static void main(String[] args) {
        int[] arr={5,1,3,2,8,9,7,6,0,4};
        System.out.println("排序前:"+ Arrays.toString(arr));
        mergeSort(arr,0,arr.length-1);
        System.out.println("排序后:"+ Arrays.toString(arr));
    }

    public static void mergeSort(int[] arr,int left,int right){
        if (left<right){
            int center=(left+right)/2; //找出中间索引
            mergeSort(arr,left,center); //对左边数组进行递归
            mergeSort(arr,center+1,right); //对右边数组进行递归
            merge(arr,left,center,right);
        }
    }

    public static void merge(int[] arr,int left,int center,int right){
        int[] tempArr=new int[arr.length];
        int mid=center+1;
        int third=left; //third 记录中间数组的索引
        int temp=left;
        while (left<=center&&mid<=right){
            //从两个数组中取出最小的放入中间数组
            if(arr[left]<=arr[mid]){
                tempArr[third++]=arr[left++];
            }else{
                tempArr[third++]=arr[mid++];
            }
        }
        //剩余部分依次放入中间数组
        while(mid<=right){
            tempArr[third++]=arr[mid++];
        }
        while (left<=center){
            tempArr[third++]=arr[left++];
        }
        //将中间数组的内容复制回原数组
        while (temp<=right){
            arr[temp]=tempArr[temp++];
        }
        System.out.println(Arrays.toString(arr));
    }
}

6.8 基数排序

基本思想:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

算法描述

我们以LSD(低位开始)为例,从最低位开始,具体算法描述如下:

  1. 取得数组中的最大数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点);
//基数排序实例:D14SortRadix.java
//通过序列中各个元素的值,对排序的N个元素进行若干趟的“分配”与“收集”来实现排序。
import java.util.Arrays;
public class D14SortRadix {
    public static void main(String[] args) {
        int[] arr={5,1,31,2,81,9,7,65,0,4};
        System.out.println("排序前:"+ Arrays.toString(arr));
        radixSort(arr);
        System.out.println("排序后:"+ Arrays.toString(arr));
    }
    public static void radixSort(int[] arr) {
        if (arr.length <= 1) return;

        //取得数组中的最大数,并取得位数
        int max = 0;
        for (int i = 0; i < arr.length; i++) {
            if (max < arr[i]) {
                max = arr[i];
            }
        }
        int maxDigit = 1;
        while (max / 10 > 0) {
            maxDigit++;
            max = max / 10;
        }
        //申请一个桶空间
        int[][] buckets = new int[10][arr.length];
        int base = 10;

        //从低位到高位,对每一位遍历,将所有元素分配到桶中
        for (int i = 0; i < maxDigit; i++) {
            int[] bktLen = new int[10];  //存储各个桶中存储元素的数量

            //分配:将所有元素分配到桶中
            for (int j = 0; j < arr.length; j++) {
                int whichBucket = (arr[j] % base) / (base / 10);
                buckets[whichBucket][bktLen[whichBucket]] = arr[j];
                bktLen[whichBucket]++;
            }

            //收集:将不同桶里数据挨个捞出来,为下一轮高位排序做准备,由于靠近桶底的元素排名靠前,因此从桶底先捞
            int k = 0;
            for (int b = 0; b < buckets.length; b++) {
                for (int p = 0; p < bktLen[b]; p++) {
                    arr[k++] = buckets[b][p];
                }
            }
            System.out.println("Sorting: " + Arrays.toString(arr));
            base *= 10;
        }
    }
}

6. 稀疏数组

【案例】编写五子棋游戏中,有存盘退出和续上盘的功能;因为该二维数组的很多值是默认值0,因此记录了很多没有意义的数据。可采用“稀疏数组”来解决

当一个数组中的大部分元素都为0,或者为同一值的数组时,可以使用稀疏数组来保存该数组;

稀疏数组的处理方式是:

​ 记录数组一共有几行几列,有多少个不同的值

​ 把具有不同值得元素和行列及值记录在一个小规模的数组中,从而缩小程序的规模

//稀疏数组实例:D15SparseArray.java
//五子棋开始下了二手:在(2,3)和(3,4)位置分别为白棋、黑棋
public class D15SparseArray {
    public static void main(String[] args) {
        //创建一个二维数组11*11,0:没有棋子,1:黑棋,2:白棋
        int[][] array=new int[11][11];
        array[1][2]=1;
        array[2][3]=2;
        //输出原始的数据
        System.out.println("输出原始数组");
        for (int[] ints:array){
            for(int anInt:ints){
                System.out.print(anInt+"\t");
            }
            System.out.println();
        }
        //转换为稀疏数组保存
        //获取有效值的个数
        int sum=0;
        for(int i=0;i<array.length;i++){
            for(int j=0;j<array[i].length;j++){
                if (array[i][j]!=0){
                    sum++;
                }
            }
        }
        System.out.println("有效值的个数:"+sum);
        //创建一个稀疏数组
        int[][] arrSparse=new int[sum+1][3];
        arrSparse[0][0]=11;
        arrSparse[0][1]=11;
        arrSparse[0][2]=sum;
        //遍历二维数组,将非零的值,存放在稀疏数组中
        int count=0;
        for(int i=0;i<array.length;i++){
            for(int j=0;j<array[i].length;j++){
                if(array[i][j]!=0){
                    count++;
                    arrSparse[count][0]=i;
                    arrSparse[count][1]=j;
                    arrSparse[count][2]=array[i][j];
                }
            }
        }
        //输出稀疏数组
        System.out.println("稀疏数组");
        for (int i=0;i<arrSparse.length;i++){
            System.out.println(arrSparse[i][0]+"\t"
                    +arrSparse[i][1]+"\t"
                    +arrSparse[i][2]+"\t");
        }
        System.out.println("===还原====================");
        //1.读取稀疏数组
        int[][] arr=new int[arrSparse[0][0]][arrSparse[0][1]];
        //2.给其中的元素还原它的值
        for (int i=1;i<arrSparse.length;i++){
            arr[arrSparse[i][0]][arrSparse[i][1]]=arrSparse[i][2];
        }
        //3.打印
        System.out.println("输出还原的数组");
        for (int[] ints:arr){
            for(int anInt:ints){
                System.out.print(anInt+"\t");
            }
            System.out.println();
        }
    }
}
posted @ 2022-01-23 23:20  老李学Java  阅读(40)  评论(0)    收藏  举报