数据结构-数组篇
1. 基础
‘ ‘
public class Main {
/**
* 数组基础
* 索引 索引可以有语意也可以没有语意
* @param args
*/
public static void main(String[] args) {
int[] arr = new int[10];
for(int i=0;i<arr.length;i++){
arr[i] = i;
}
int[] scores = new int[]{100, 99, 66};
for (int i=0;i<scores.length;i++){
System.out.println(scores[i]);
}
scores[0] = 98;
for (int score:scores) {
System.out.println(score);
}
}
}
数组最大的优点:快速查询
数组最好应用于‘索引有语意’的情况
但并非所有有语意的索引都适用于数组 例如:身份证号
数组也可以处理‘索引没有语意’的情况
2. 数组二次封装
基于java的数组,二次封装属于我们自己的数组类
’ ‘
public class Array {
/*
* 索引没有语意,如何表示没有元素
* 如何添加元素?如何删除元素?
* @param args
*/
private int[] data;
private int size;
/**
* 构造函数,传入数组的容量capacity构造Array
* @param capacity
*/
public Array(int capacity){
data = new int[capacity];
size = 0;
}
/**
* 无参数的构造函数,默认数组的容量capacity=10
*/
public Array(){
this(10);
}
/**
* 获取数组中的元素个数
*/
public int getSize(){
return size;
}
/**
* 获取数组的容量
* @return
*/
public int getCapacity(){
return data.length;
}
/**
* 返回数组是否为空
* @return
*/
public boolean isEmpty(){
return size==0;
}
}
3. 数组操作
- 如何在数组中添加元素
’ ‘
/**
* 向所有元素后添加一个新元素
* @param e
*/
public void addLast(int e){
/*if(size==data.length){
throw new IllegalArgumentException("AddLast failed.Array is full");
}
data[size] = e;
size++; //data[size++]=e;*/
add(size,e);
}
/**
* 在第index个位置插入一个新元素e
*/
public void add(int index,int e){
if(size==data.length){
throw new IllegalArgumentException("Add failed.Array is full");
}
if(index <0 || index >size)
throw new IllegalArgumentException("Add failed.Require index >=0 and index <= size");
for(int i=size-1;i>=index;i--){
data[i + 1] = data[i];
}
data[index] = e;
size++;
}
/**
* 在所有元素前添加一个新元素
* @param e
*/
public void addFirst(int e){
add(0,e);
}
- 查询和修改数组元素
’ ‘
/**
* 获取index索引位置的元素
*/
int get(int index){
if(index <0 || index >=size)
throw new IllegalArgumentException("Get failed. Index is illegal");
return data[index];
}
/**
* 修改index索引位置的元素e
* @param index
* @param e
*/
void set(int index,int e){
if(index <0 || index >=size)
throw new IllegalArgumentException("Set failed. Index is illegal");
data[index] = e;
}
/**
*重写toString方法
*/
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Array:size = %d,capacity = %d\n", size, data.length));
res.append("[");
for (int i = 0; i < size; i++) {
res.append(data[i]);
if(i !=size -1){
res.append(", ");
}
}
res.append("]");
return res.toString();
}
public static void main(String[] args) {
Array arr = new Array(20);
for(int i = 0;i<10;i++){
arr.addLast(i);
}
System.out.println(arr);
arr.add(1,100);
System.out.println(arr);
arr.addFirst(-1);
System.out.println(arr);
}
- 数组中的包含、搜索元素
’ ‘
/**
* 查找数组中是否有元素e
*/
public boolean contains(int e){
for(int i=0;i<size;i++){
if(data[i]==e)
return true;
}
return false;
}
/**
* 查找数组中元素e所在的索引,如果不存在元素e,则返回-1
*/
public int find(int e){
for(int i=0;i<size;i++){
if(data[i]==e)
return i;
}
return -1;
}
- 删除元素
’ ‘
/**
*从数组中删除index位置的元素,返回删除的元素
*/
public int remove(int index){
if(index<0||index>=size)
throw new IllegalArgumentException("Remove id failed.Index is Illegal");
int e=data[index];
for(int i=index+1;i<size;i++){
data[i-1]=data[i];
}
size--;
return e;
}
/**
*从数组中删除第一个元素,返回删除的元素
*/
public int removeFirst(){
return remove(0);
}
/**
* 从数组中删除最后一个元素,返回删除的元素
*/
public int removeLast(){
return remove(size-1);
}
/**
*从数组中删除元素e
*/
public void removeElement(int e){
int index = find(e);
if(index != -1){
remove(index);
}
}
使用泛型
- 让我们的数据结构可以放置“任何”数据类型
- 不可以是基本数据类型,只能是类对象 (boolean,byte,char,short,int ,long,float,double)
- 每个基本数据类型都有对应的包装类 (Boolean,Byte,Char,Short,Int,Long,Float,Double)
’ ‘
public class Array1<E> {
private E[] data;
private int size;
public Array1(int capacity) {
data = (E[])new Object[capacity];
size = 0;
}
/**
* 在第index个位置插入一个新元素e
*/
public void add(int index, E e) {
if (size == data.length)
throw new IllegalArgumentException("Add failed.Array is full");
if (index < 0 || index > size)
throw new IllegalArgumentException("Add failed.Require index >=0 and index <= size");
for (int i = size - 1; i >= index; i--) {
data[i + 1] = data[i];
}
data[index] = e;
size++;
}
- 动态数组:扩容(添加元素)
‘ ’
/**
* 动态数组:扩容
* @param args
*/
private void resize(int newCapacity){
E[] newData = (E[])new Object[newCapacity];
for(int i=0;i<size;i++){
newData[i] = data[i];
}
data = newData;
}
if (size == data.length) {
// throw new IllegalArgumentException("Add failed.Array is full");
//扩容
resize(2 * data.length);
}
- 缩小容量(删除时)
‘ ’
if(size==data.length /2){
resize(data.length /2);
}
测试类
‘ ’
public class Student {
private String name;
private int score;
public Student(String studentName,int studentScore){
name = studentName;
score = studentScore;
}
@Override
public String toString(){
return String.format("Student(name: %s,score:%d)",name,score);
}
public static void main(String[] args) {
Array1<Student> arr = new Array1<>();
arr.addList(new Student("Alice",100));
arr.addFirst(new Student("Bob",98));
arr.add(1,new Student("CharLie",99));
System.out.println(arr);
}
}
4. 时间复杂度分析
时间复杂度包括:O(1),O(n),O(lgn),O(nlogn),O(n^2)
大O描述的是算法的运行时间和输入数据之间的关系,n指元素个数
O(n)渐进时间复杂度,描述n趋近于无穷的情况(忽略低阶项和常数)
增:O(n)
删:O(n)
改:已知索引O(1);未知索引O(n)
查:已知索引O(1);未知索引O(n)
5. resize的复杂度分析
这样均摊计算,时间复杂度是O(1)的
在这个例子里,这样均摊计算,比计算最坏情况有意义,因为不是每一次都触发最坏情况
同理,removeLast操作,均摊复杂度也为O(1)
6. 复杂度震荡
同时看addLast和removeLast操作
出现问题的原因:removeLast时resize过于着急
解决方案:
当size==capacity/4时,才将capacity减半
remove时
‘’
if(size==data.length /4 && data.length/2!=0){
resize(data.length /2);
}