引言
现在目前我们的一个变量,最多就保存一个数据,显然不科学。目前需要一个方案来解决这个问题,可以保存多个数据的容器。在几乎所有的编程语言中都提供了数组这么一个概念**,数组它是一种基础的数据结构**(列表数据结构)。
一. 数组概念
1.1 基本概念
数组它是存放 一组类型相同,个数有限的数据的容器。数组也是变量,由于它的数据结构的特点可以保存多个数据,数组在内存中,往往是块连续有限的空间。
1.2 图形理解
二 . 数组的声明与赋值
数组它是一种引用数据类型,它的存储需要使用到两块内存空间(堆和栈),基本数据类型只需要栈空间。
2.1 数组声明语法
// 声明一个数组(声明与初始化)
数据类型[] 数组名 = new 数据类型[ 长度 ];
int scores[] = new int[5] ; // c 系语言的风格。
int[] scores = new int[5] ; // java 的风格。
// 声明一个数组(先声明后初始化)
int[] arr ;
arr = new int[5]
// 声明一个数组(静态初始化)初始化的时候就为每个元素赋值。
int[] brr = new int[]{1,2,3,4,5}
int[] brr = { 1,2,3,4,5};
这里的数据类型 可以是全体Java数据类型(自定义类) Dog User int double 所有引用变量的默认值都是null 区别:null 和 "" 有什么区别呢
1 null 代表是我当前这个 变量是没有被初始化,就是说 在内存里面还没有分配空间
2 "" 这个是空字符串,它是分配了存储空间,只是当前这个空间的值为空字符串
如:
String str = null; _//可能随时会被 gc给回收
_String str2 = ""; //它是不会被回收;
//1 第一种定义方式
int [] arr = new int[2]; //数组 定义 数据类型 [] 变量名 = new 数据类型[长度]; 先定义 后使用
// 每个数组 都有一个下标,标识我元素的所在位置;
//我们可以通过下标 对数组进行操作
// 如:取值:变量名[index]--取值 add:变量名[index]=值
arr[0]= 10;
arr[1]= 20;
//2 第二种定义方式
int [] arr2 = new int[]{1,2,3,4,5};
//3 第三种定义方式
int [] arr3 = {1,2, 3,4};
/**
* 第一个 直接创建内存空间;第二种需要计算数据长度,在去创建空间;三种先创建对象,计算长度,创建内存空间;
*/
2.2 数组内存分配
数组初始化后,会为元素赋予初始值,比如整型数组默认为0 浮点数数组默认为0.0 引用数据类型数组为 null。
三 . 数组的组成
3.1 数组元素寻址
数组由各个元素构成,各个元素依次排列,每个元素都有一个寻址下标,选址下标自动生成,从0开始。首地址结合下标通过偏移就可以寻址到每个元素,这里有一个问题就是,如果偏移的过程中,超过了下标,会造成数组下标越界问题。最大下标为数组长度-1。
元素的访问: 对元素的赋值和取值,数组名[ 下标 ]
3.2 获得数组长度
3.3 数组下标越界
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 这个异常就是数组下标超出了范围。
public static void main(String[] args) {
int[] scores = new int[5];
//存数据:赋值
scores[0] = 1;
scores[1] = 2;
scores[2] = 3;
scores[3] = 4;
scores[4] = 5;
//取数据:
System.out.println( scores[0] );
System.out.println( scores[1] );
System.out.println( scores[2] );
System.out.println( scores[3] );
System.out.println( scores[4] );
}
四 . 数组的遍历
数组遍历,指的是将元素依次访问一遍,通常这个过程需要借助循环实现,这里介绍两种常见遍历数组的方式
4.1 通过普通for循环
借助循环的连续的循环变量作为下标。
public static void main(String[] args) {
int[] scores = new int[5];
//存数据:赋值
scores[0] = 1;
scores[1] = 2;
scores[2] = 3;
scores[3] = 4;
scores[4] = 5;
//取数据:
System.out.println( scores[0] );
System.out.println( scores[1] );
System.out.println( scores[2] );
System.out.println( scores[3] );
System.out.println( scores[4] );
//数组遍历(把每个元素都访问一遍 != 输出 )
System.out.println("---遍历方式1--"); //天意:循环计数器可以是连续的,恰好用在这里当下标
for( int i =0 ; i< scores.length ; i++ ){
System.out.println( scores[i] );
}
Arrays.sort()
Scnnar sc = new Scanner()
sc.nextInt();
}
4.2 通过增强for循环foreach
此方法是一种语法糖,也就是编写上的形式方便,底层依然采用普通for实现
public static void main(String[] args) {
int[] scores = new int[5];
//存数据:赋值
scores[0] = 1;
scores[1] = 2;
scores[2] = 3;
scores[3] = 4;
scores[4] = 5;
//取数据:
System.out.println( scores[0] );
System.out.println( scores[1] );
System.out.println( scores[2] );
System.out.println( scores[3] );
System.out.println( scores[4] );
//数组遍历 (增强for循环 语法糖= 编译的时候会过程叫 解语法糖,会采用基础原生的方式实现 )
System.out.println("---遍历方式2--");
for( int element : scores ){
System.out.println(element);
}
}
五. 数组应用
5.1 基本使用
public static void main(String[] args) {
//定义数组
String[] names = new String[5];
names[0] = "马云";
names[1] = "成龙";
names[2] = "波多野结衣";
names[3] = "王健林";
names[4] = "黄家驹";
//{ "马云","成龙","波多野结衣","王健林" };
//修改元素值
names[2] = "武老师";
//遍历
for( int i =0; i< names.length ; i++ ){
System.out.println(names[i]);
}
}
5.2 数组作为参数
/**
* 数组作为方法的参数类型
*/
public class ArrayUsedArgs {
public static void main(String[] args) {
int[] arr = {1,23,4,5,6,67,7,8,8};
System.out.println("arr的地址"+arr);
printArray( arr ); // 数组传参传的是什么? 传递的是地址
}
//定义一个函数,这个函数的作用就是来输出一个数组的全部元素
public static void printArray( int[] crr ){ // int[] crr = arr;
System.out.println("crr的地址"+crr);
for( int i=0; i< crr.length; i++ ){
System.out.print(crr[i]+"\t");
}
System.out.println();
}
}
5.3 数组作为返回值
/**
数组作为返回值类型
*/
public class ArrayUesdReturn {
//编写一个程序,生成一个数字验证码 (4位)
public static int[] createCode(){
int[] codeArray = new int[4];
Random rd = new Random();
for(int i=0;i<4;i++){
int n = rd.nextInt(10);
codeArray[i]=n;
}
System.out.println("codeArray的地址"+codeArray);
return codeArray;
}
public static void main(String[] args) {
int[] result = createCode(); // ctrl
System.out.println("result的地址"+result);
ArrayUsedArgs.printArray(result);
}
}
5.4 值传递与地址传递
- 对于基本数据类型,传参为值的拷贝,当然返回的也是值的拷贝。
- 对于引用数据类型, 传参实际是地址传递,当然返回的也是地址。
public class ValueAndRefrence {
public static void main(String[] args) {
int age = 10;
change(age);
System.out.println(age); // 10
int[] brr = {10,20};
change2(brr);
System.out.println(brr[0]+" "+brr[1]);// 100 20
}
public static void change(int num ){
num = 100;
}
public static void change2(int[] arr){
arr[0] = 100;
}
}
5.5 数组的扩容
- **什么是扩容? **
扩容指的是扩展存储空间容量,
- 为什么要扩容?
数组是的容量是声明是就确定了的(定死了)后续不可以被修改,但是我们实际情况又需要更多的容量。这里的"扩容"是一种通过其他手段实现的。因为数组本身长度声明后是不可改变的。
- 扩容的关键技术,元素拷贝
/**
* 数组的扩容问题
*/
public class ArraysExtend {
public static void main(String[] args) {
}
/**
* 扩容方式3
*/
private static void copy03() {
//原数组
int[] data = {1,2,3, 4,5};
//Arrays.copyOf(T[] ,int length )
/*
1. T[] 表示原数组,这里使用了泛型,任意类型数组
2. length 新数组的长度, 底层创建一个新数组,并且返回这个新数组的地址。
*/
data = Arrays.copyOf( data, data.length*2);// 把新数组的地址赋值给原来数组
ArrayUsedArgs.printArray(data);
}
/**
* 扩容方式2 : System.arraycopy()
*/
private static void copy02() {
//原数组
int[] data = {1,2,3,4,5};
int[] more = new int[10];
//public static native void arraycopy(Object src, int srcPos,Object dest, int destPos, int length);
//1. src 指定的原数组
//2. srcPos 指的是从原数组哪个位置开始拷贝,拷贝起始下标
//3. dest 指的是目标数组 ,新数组
//4 destPos 指的的是 从新数组的哪个位置开始存放
//5. length 指的是考被拷贝的元素个数
System.arraycopy( data, 0,more, 0, data.length );
// 把新数组的地址赋值给原来数组
data= more;
ArrayUsedArgs.printArray(data);
}
/**
* 扩容方式 1: 手写循环
*/
private static void copyOne() {
//原数组
int[] data = { 1,2,3,4,5};
//1. 创建一个更大的数组
int[] more = new int[10];
//2. 通过循环赋值,对应下标对应赋值,以原数组为基础
for(int i=0;i<data.length;i++){
//为新数组赋值
more[i] = data[i];
}
// 3. 把新数组的地址 赋值给原来的数组名
data = more;
// 4. 可以继续存放更多的数据
data[5]=6;
for (int element:data)
{
System.out.print(element+" ");
}
}
}
5.6 数组元素删除
package com.qfedu.array.use;
import java.util.Arrays;
/** 元素增减 */
public class TestArrayElementInsertDelete {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
//arr = delete(4,arr);
arr = insert(3, 100, arr);
//修改
update(arr,4,10);
System.out.println(Arrays.toString(arr));
}
public static int[] delete( int i , int[] arr ){ //2
int[] newArray = new int[ arr.length-1 ];
System.arraycopy( arr,0, newArray,0,i);//拷贝前段
System.arraycopy(arr,i+1, newArray,i, arr.length-i-1);//拷贝后段
return newArray;
}
public static int[] insert( int i, int value, int[] arr ){
int[] newArray = new int[arr.length+1]; //2
System.arraycopy( arr,0,newArray,0,i);
newArray [i] = value;
System.arraycopy( arr, i, newArray,i+1, newArray.length-i-1);
return newArray;
}
/**
* 修改的数组
* @param oldArr
* @param index
* @param data
*/
public static void update(int [] oldArr,int index,int data){
oldArr[index] = data;
}
}
5.7 可变参数
可变参数,指得是在函数/方法定义时,可以通过可变参数实现,动态参数个数,他的本质依然是数组作为参数,函数内部通过数组的方式处理参数。如果一个函数设计可变参数,那么这参数必须是最后一个参数。语法定义 (类型 ... 参数名)
//可变参数
public class ArrayVarArgsDemo {
public static void main(String[] args) {
System.out.println("-------------------单个参数传参 ---------------------------");
readBook( "三国演义" );
System.out.println("-------------------- 数组参数传参 ---------------------------");
String[] mybooks = { "小明与小红的爱情故事","小时代","大秦帝国之崛起" };
readBooks( mybooks ); //提前准备一个数组
readBooks( new String[]{"奥特曼","铁甲小宝","海尔兄弟" } ); // 现写现用一个数组
System.out.println("-------------------- 可变参数传参 ---------------------------");
readBookPlus("小明与小红的爱情故事","小时代","大秦帝国之崛起");
}
//编写一个函数,读书函数 单个参数
public static void readBook( String book ){
System.out.println("我读"+book);
}
//编写一个函数,读书函数 传数组
public static void readBooks( String[] books ){
for(String book : books){
System.out.println("我读"+book);
}
}
//编写一个函数,读书函数 传可变参数(本质也就是一个数组)
public static void readBookPlus( String ... books ){ //可变参数必须是整个函数参数的最后一个参数
for(String book : books){
System.out.println("我读"+book);
}
}
}
六 . 数组排序与查找
数组排序指的是采用某种算法来将元素按一定次序排列整齐,数组的查找,根据指的元素判断其在数组中的位置。
冒泡 选择 插入 快速 堆 希尔 归并 统计 基数 计数
排序的区别: 1. 思路上的区别 2. 时间复杂度(考虑) 3. 空间复杂度(忽略)。
常见的时间复杂度曲线:
T=f(n)=O(n)
O(1) :
O(n) :
O( n^2 ):
O( logN):
O(nlogN):
O(2^n):
6.1 冒泡排序
取相邻的两个元素比较如果前者大于后则交换,一趟的结果为找出了这趟的最大值,且交换到最最后 ,一致重复趟数直到完成。
/**
* 冒泡排序
*/
public class BubbleSort {
public static void main(String[] args) {
int[] data = {9,8,7,6,5,4,3,2,1};
/**
* 趟: 取相邻的两个元素比较如果前者大于后则交换,一趟的结果为找出了这趟的最大值,且交换到最最后 ,一致重复趟数直到完成
*/
for( int i=0;i<data.length;i++ ){ // 控制趟数
//每趟需要做的事情就是 取 相邻两个元素比较
for(int j=1; j<data.length-i;j++){
if( data[j-1] > data[j] ){
int temp = data[j-1];
data[j-1] = data[j];
data[j] = temp;
}
}
System.out.println( "第"+i+"趟"+Arrays.toString(data) );
}
// Arrays.toString(T []) 作用是接收一个数组,把数组元素通过 逗号 连接成一个字符串
System.out.println( Arrays.toString(data) );
}
}
6.2 选择排序
/**
* 选择排序
*/
public class ChooseSort {
public static void main(String[] args) {
int[] data = {12,34,22,15,1,56,33};
//依次假设出每个当前最小的数字
for( int i=0 ; i<data.length ; i++ ){
//假设当前最小值的下标为min
int min = i;
for( int j=i+1; j<data.length;j++ ){
//如果 假设最小值并非最小
if( data[min]> data[j] ){
min = j; //记录真正最小值的下标
}
}
//验证 看我们假设最小值的下标是否和最初假设一致。
if( min != i ){
int temp = data[i];
data[i] = data[min];
data[min] = temp;
}
}
System.out.println(Arrays.toString(data));
}
}
6.3 插入排序
public static void insert( int[] data){
//根据元素个数确定趟数 为什么是1 开始,假设第一个是排好
for( int i=1; i< data.length;i++ ){
//从未排序区取一个元素data[i+1] 和 已经排序区 比较寻找和位置
for( int j=i ; j>0;j-- ){
// 1 ,2 3 4 8 3|
//如果我们选择的这个数 小于当前元素则交换
if( data[j] < data[j-1] ){
int t = data[j];
data[j]= data[j-1];
data[j-1]=t;
}
}
}
}
6.4 使用JDK排序
利用JDK提供的排序方法,而这个排序方法采用的是快速排序算法, Arrays.sort(data);
public static void main(String[] args) {
int[] data = { 12,33,44,6,58,24,23,10};
Arrays.sort(data);
System.out.println(Arrays.toString(data));
}
6.5 快速排序原理
-
快速排序,特点就是块, 一般的情况 O(n*lgN ) 最坏情况是n^2
-
它是一种分治的思想。分区( 把数据 分为两个子区, 小于基准数的排列到 左边 大于基准数的排列在右边。 )
-
重复重复上述动作分区( 引入递归 )
-
确定一个基准数。(左边第一个数)
-
从右向左扫,如果大于基准数着继续前行,直到找到第一个小于基准数的值,则保存这个数到左边。
-
从左往右扫,如果小于基准数则继续前行,直到找到第一个大于基准数的值。则保存这个数到右边。
-
重复上诉过程,即可把数据分为两区。
public static void main(String[] args) {
int[] data = {38,22,44,23,14,32,28,66,25,88,77,44};
quickSort(data, 0, data.length - 1);
System.out.println(Arrays.toString(data));
}
public static void quickSort( int data[] , int left, int right ){
if( left<right ){
int index = division(data,left,right);
quickSort(data,left,index-1);
quickSort(data,index+1,right);
}
}
public static int division(int[] data , int left, int right){
int pivot = data[left];
while (left < right){
while ( left < right && data[right] >= pivot )
--right;
data[left] = data[right];
while (left<right && data[left] <= pivot )
++left;
data[right]=data[left];
}
data[left]=pivot;
return left;
}
从数组中找到目标数据,如果寻找,又涉及到相关的算法,典型查找方法,比如线性 查找,或者折半查找。
6.6 线性查找
就是从头到尾查找,如果元素非常多,效率就不高。(全文检索)
package find;
import java.util.Arrays;
//查找
public class FindDemo {
public static void main(String[] args) {
String[] team = { "诸葛亮","刘备","关羽","大乔","小乔","曹操" };
int index = lineFind(team,"吕布");
System.out.println(index);
//原数组
int[] data = {1,3,46,367,312,11,567,44,22,56};
//排序
Arrays.sort(data);
System.out.println(Arrays.toString(data));
int index2 = binarySerach(data, 56);
//int index2 = Arrays.binarySearch(data, 56);
System.out.println(index2);
}
// 线性查找:更通用,可适用于各种数据类型
public static int lineFind(String[] data, String key){
for(int i=0; i<data.length; i++){
if( data[i].equals(key) ){
return i;
}
}
return -1;
}
}
6.7 折半查找(二分法查询)[重要]
就是 通过已经排好序的数组,先判断中间值,如果相等则返回,否则不断缩小查找范围
注意:二分法查询 你是数组必须有序的
package find;
import java.util.Arrays;
//查找
public class FindDemo {
public static void main(String[] args) {
//原数组
int[] data = {1,3,46,367,312,11,567,44,22,56};
//排序
Arrays.sort(data);
System.out.println(Arrays.toString(data));
int index2 = binarySerach(data, 56);
//int index2 = Arrays.binarySearch(data, 56);
System.out.println(index2);
}
// 二分查找:针对的是数字查找,且要求数字必须先排序。
public static int binarySerach( int[] data , int key ){
int left = 0; //左起点
int right = data.length-1;//右终点
//只要 左起点下标未超过右终点 继续,否则说明 移动交叉了不存在查找的数据 停止查找。
while( left<=right ){
//计算中间值下标
int min = (left+right)/2;
// 判断与中间值的大小
if( key == data[min]){ // 查到了
return min;
}else if( key < data[min] ){ // 在左半轴查找
right = min-1;
}else{ // 在右半轴查找
left = min+1;
}
}
//没有找到
return -1;
}
}
七 . 二维数组(了解)
7.1 概念
二维数组从空间上看是有行列组成的结构,但是实际在计算机中,二维数组本质上还是一个一维数组,只是这个一维数组的每个元素,都是一个数组。
7.2 内存理解
数组的每个元素保存的右是 另一个数组的地址。
7.3 创建和声明二维数组
// 语法
数据类型[][] 数组名 = new 数据类型[ 低维度 ][ 高维度 ]
int[][] data = new int[5][3];
数据类型[][] 数组名
int[][] data ;
data = new int[5][3];
//数据类型[][] 数组名 = { {元素,...,元素 } ,{元素,...,元素 ,{元素,...,元素 } ;
int[][] data = new int[][]{ {1,2,3},{4,5,6},{7,8,9} }
int[][] data ={ {1,2,3},{4,5,6},{7,8,9} };
7.4 二维数组的元素访问
public static void main(String[] args) {
int[][] data = new int[3][3];
//元素的访问:赋值
data[0][0] = 1;
data[0][1] = 2;
data[0][2] = 3;
data[1][0] = 4;
data[1][1] = 5;
data[1][2] = 6;
data[2][0] = 7;
data[2][1] = 8;
data[2][2] = 9;
}
使用 双下标访问
7.5 二维数组遍历
public static void main(String[] args) {
int[][] data = new int[3][3];
//元素的访问:赋值
data[0][0] = 1;
data[0][1] = 2;
data[0][2] = 3;
data[1][0] = 4;
data[1][1] = 5;
data[1][2] = 6;
data[2][0] = 7;
data[2][1] = 8;
data[2][2] = 9;
// 普通for
for(int i=0;i<data.length;i++){
for(int j=0;j< data[i].length;j++ ){
System.out.print(data[i][j]+"\t");
}
System.out.println();
}
// 增强for
for( int[] arr : data ){
for( int e :arr ){
System.out.print(e+"\t");
}
System.out.println();
}
}
7.6 二维数组运用
public class Pratice {
public static void main(String[] args) {
int[][] map = new int[10][10];
Random random = new Random();
for(int i=0;i<map.length;i++){
for(int j=0;j<map[i].length;j++){
int n= 1+random.nextInt(100);
if( !contains( map, n ) ){
map[i][j]=n;
}else{
j--;
}
}
}
for(int[] arr: map ){
System.out.println(Arrays.toString(arr));
}
}
//编写一个函数 检查二维数组中是否存在该元素
public static boolean contains(int[][] map, int num ){
for(int i=0; i< map.length;i++ ){
for(int j=0;j<map[i].length;j++){
if( num == map[i][j] ){
return true;
}
}
}
return false;
}
}