数据结构 ArrayList与顺序表
文章目录
1. 线性表
线性表是n个具有相同特性的数据元素的有限序列。
线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表,链表,栈,队列…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储
2. 顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
在这里我们通过顺序表实现一个MyArrayList类(自定义的ArrayList
),代码如下:
-
IList接口
package mylist; public interface IList { //新增元素,默认在数组最后新增 public void add(int data); // 在 pos 位置新增元素 public void add(int pos, int data); // 判定是否包含某个元素 public boolean contains(int toFind) ; // 查找某个元素对应的位置 public int indexOf(int toFind); // 获取 pos 位置的元素 public int get(int pos); // 给 pos 位置的元素设为 value 更新 public void set(int pos, int value); //删除第一次出现的关键字key public boolean remove(int toRemove) ; // 获取顺序表长度 public int size(); // 清空顺序表 public void clear() ; // 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的 public void display(); boolean isFull(); public boolean isEmpty(); }
-
MyArrayList类实现IList接口
package mylist; import java.util.Arrays; public class MyArrayList implements IList { public int[] elem; public int usedSize; // 0 //顺序表的默认大小 public static final int DEFAULT_SIZE = 10; public MyArrayList() { this.elem = new int[DEFAULT_SIZE]; } public MyArrayList(int capacity) { this.elem = new int[capacity]; } @Override public void add(int data) { checkCapacity(); this.elem[usedSize] = data; usedSize++; } @Override public void add(int pos, int data) { try { checkPosAdd(pos); } catch (PosIllegality e) { e.printStackTrace(); return; } for (int i = usedSize - 1;i >= pos;i--) { this.elem[i+1] = elem[i]; } this.elem[pos] = data; usedSize++; } private void checkCapacity() { if (isFull()) { //扩容 elem = Arrays.copyOf(elem,elem.length * 2); } } private void checkPosAdd(int pos) throws PosIllegality { if (pos < 0 || pos > usedSize) { System.out.println("输入不合法"); throw new PosIllegality("插入下标异常:" + pos); } } @Override public boolean contains(int toFind) { if (isFull()) { return false; } for (int i = 0;i < usedSize;i++) { if (elem[i] == toFind) { System.out.println("true"); return true; } } System.out.println("false"); return false; } @Override public int indexOf(int toFind) { int pos = 0; for (int i = 0;i < usedSize;i++) { if (elem[i] == toFind) { pos = i; break; } } return pos; } @Override public int get(int pos) throws MyArrayListEmpty{ checkPosGetAndSet(pos); if (isEmpty()) { throw new MyArrayListEmpty("数组为空!"); } return elem[pos]; } public void checkPosGetAndSet(int pos) throws MyArrayListEmpty{ if (pos < 0 || pos >= usedSize) { System.out.println("输入不合法"); throw new PosIllegality("获取指定下标异常:" + pos); } } @Override public void set(int pos, int value) { checkPosGetAndSet(pos); elem[pos] = value; } @Override public boolean remove(int toRemove) { if (isEmpty()) { return false; } int pos = indexOf(toRemove); for (int j = pos; j < usedSize; j++) { elem[j] = elem[j+1]; } usedSize--; return true; } @Override public int size() { return usedSize; } @Override public void clear() { usedSize = 0; } @Override public void display() { for (int i = 0;i < usedSize;i++) { System.out.print(this.elem[i] + " "); } System.out.println(); } @Override public boolean isFull() { return usedSize == this.elem.length; } @Override public boolean isEmpty() { if (usedSize == 0) { return true; } return false; } }
-
两个自定义异常类
package mylist; public class MyArrayListEmpty extends RuntimeException{ public MyArrayListEmpty(String msg) { super(msg); } } \\\ public class PosIllegality extends RuntimeException{ public PosIllegality(String msg) { super(msg); } }
-
主程序
package mylist; public class Test { public static void main(String[] args) { MyArrayList myArrayList = new MyArrayList(); myArrayList.add(10); myArrayList.add(25); myArrayList.add(77); myArrayList.add(1,22); myArrayList.remove(770); myArrayList.contains(55); System.out.println(myArrayList.get(0)); myArrayList.display(); } } //执行结果 false 22 22 25 77
上述MyArrayList是我们自己创建的类,其中的功能也是由我们自己实现的,而在Java中也存在这这样一个类,其能实现的功能和MyArrayList类似甚至更多,它就是ArrayList
3. ArrayList
3.1 简介
在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架图如下:
注:
- ArrayList是以泛型方式实现的,使用时必须要先实例化
- ArrayLIst底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
3.2 ArrayList的构造
方法 | 解释 |
---|---|
ArrayList() | 无参构造 |
ArrayList(Collection<? extends E> c) | 利用其他 Collection 构建 ArrayList |
ArrayList(int initialCapacity) | 指定顺序表初始容量 |
代码示例:
import java.util.ArrayList;
import java.util.List;
public class TestList {
public static void main(String[] args) {
//ArrayList创建
//构造一个空的列表
List<Integer> list = new ArrayList<>(); //向上转型,推荐这样编写
//构造一个具有10个容量的列表
List<Integer> list1 = new ArrayList<>(10);
list.add(100); // 动态扩容
list.add(123);
// list.add("hello"); // List<Integer>已经限定了传参类型,此时只能输入Integer类型
System.out.println(list);
list1.add(15);
System.out.println(list1);
//此时构建的 list2 与 list1 中的元素一致
ArrayList<Integer> list2 = new ArrayList<>(list1);
System.out.println(list2);
//尽量避免这样编写,避免省略类型,否则任意类型的元素都能存放使用起来容易出问题
List list3 = new ArrayList();
list3.add(11);
list3.add("hh");
System.out.println(list3);
}
}
3.3 ArrayList常见操作
方法 | 解释 |
---|---|
int size() | 获取list中有效元素个数 |
boolean add(E e) | 尾插 e |
void add(int index, E element) | 将 e 插入到 index 位置 |
boolean addAll(Collection<? extends E> c) | 尾插 c 中的元素 |
E remove(int index) | 删除 index 位置元素 |
boolean remove(Object o) | 删除遇到的第一个 o |
E get(int index) | 获取下标 index 位置元素 |
E set(int index, E element) | 将下标 index 位置元素设置为 element |
void clear() | 清空 |
boolean contains(Object o) | 判断 o 是否在线性表中 |
int indexOf(Object o) | 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 返回最后一个 o 的下标 |
List subList(int fromIndex, int toIndex) | 截取部分 list |
代码示例:
package demo2;
import java.util.ArrayList;
import java.util.List;
public class Test1 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//在顺序表末尾添加指定元素
list.add("Java");
list.add("C++");
list.add("Python");
list.add("C");
System.out.println(list);
System.out.println("===============");
//输出list有效元素个数
System.out.println(list.size());
System.out.println("===============");
//获取index位置上的元素
System.out.println(list.get(3));
System.out.println("===============");
//设置index位置上的元素
list.set(3,"C#");
System.out.println(list.get(3));
System.out.println("===============");
//在list的index位置插入指定元素,index及后续元素统一往后搬一个位置
System.out.println(list);
list.add(1,"C");
System.out.println(list);
System.out.println("===============");
//删除指定元素,找到了就删除,该元素之后的元素统一往前搬一个位置
list.remove("C++");
System.out.println(list);
System.out.println("===============");
//删除指定位置元素,且指定位置不能超过list中有效元素的个数,否则会抛出下标越界异常
list.remove(1);
System.out.println(list);
System.out.println("===============");
//检测list中是否包含指定元素,包含返回true,否则返回false
if (!list.contains("C")) {
list.add("C");
}
System.out.println(list);
//查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前走
System.out.println(list.indexOf("C#"));
System.out.println(list.lastIndexOf("C#"));
System.out.println("===============");
//使用list中指定区间(]的元素构成一个新的SubList返回,但是和ArrayList共用一个elementData数组
List<String> list1 = list.subList(0,4);
System.out.println(list1);
System.out.println("===============");
//清空顺序表
list.clear();
System.out.println(list.size());
}
}
//执行结果
[Java, C++, Python, C]
===============
4
===============
C
===============
C#
===============
[Java, C++, Python, C#]
[Java, C, C++, Python, C#]
===============
[Java, C, Python, C#]
===============
[Java, Python, C#]
===============
[Java, Python, C#, C]
2
2
===============
[Java, Python, C#, C]
===============
0
3.4 ArrayList的遍历
ArrayList可以使用三种方式进行遍历:for循环+下标,foreach,迭代器
代码示例:
package demo3;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test1 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(12);
list.add(123);
list.add(1234);
list.add(12345);
System.out.println(list);
// 使用for循环遍历
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
// 使用foreach遍历
for (Integer i:list) {
System.out.print(i + " ");
}
System.out.println();
// 使用迭代器遍历
Iterator<Integer> it = list.listIterator();
while(it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
}
}
//执行结果
[1, 12, 123, 1234, 12345]
1 12 123 1234 12345
1 12 123 1234 12345
1 12 123 1234 12345
3.5 ArrayList的扩容机制
在上面对ArrayList常见操作的介绍中,有这样一段代码:
List<String> list = new ArrayList<>(); // 1
//在顺序表末尾添加指定元素
list.add("Java"); // 2
list.add("C++");
在代码的第一行位置,我们认为此时的list还没有被分配到内存(因为没有传入参数),那为什么它后面能进行元素分配呢?
原因就出在第二行代码:list.add("Java");
在插入元素的过程中会自动扩容:
- 当构建的List对象无参数传入时,第一次调用Add方法时会分配大小为10的内存;
- 当动态分配的10个内存满了之后,ArrayList会进行扩容,且扩容为它本身的1.5倍
- 使用
copyOf
进行扩容
以下是ArrayList源码中的扩容方式:
Object[] elementData; // 存放元素的空间
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 默认空间
private static final int DEFAULT_CAPACITY = 10; // 默认容量大小
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// 获取旧空间大小
int oldCapacity = elementData.length;
// 预计按照1.5倍方式扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果用户需要扩容大小 超过 原空间1.5倍,按照用户所需大小扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果需要扩容大小超过MAX_ARRAY_SIZE,重新计算容量大小
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用copyOf扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
// 如果minCapacity小于0,抛出OutOfMemoryError异常
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
4. ArrayList的具体使用
4.1 杨辉三角
package demo4;
import java.util.ArrayList;
import java.util.List;
class Soultion {
/*杨辉三角*/
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> ret = new ArrayList<>();
// 1.先处理第一行
List<Integer> list = new ArrayList<>();
list.add(1);
ret.add(list);
// 2.从第2行开始计算每个list当中的数据
for (int i = 1; i < numRows; i++) {
// 3.先准备当前行数据
List<Integer> curRow = new ArrayList<>();
// 4.准备当前行第一个数据
curRow.add(1);
// 5.获取上一行数据
List<Integer> prevRow = ret.get(i-1);
// 6.给当前行添加中间数据
for (int j = 1; j < i; j++) {
Integer val = prevRow.get(j-1) + prevRow.get(j);
curRow.add(val);
}
// 7.给当前行添加最后一个数据
curRow.add(1);
ret.add(curRow);
}
return ret;
}
}
public class YangHuiSanJiao {
public static void main(String[] args) {
Soultion soultion = new Soultion();
List<List<Integer>> lst = soultion.generate(10);
for (int i = 0; i < lst.size(); i++) {
for (int j = 0; j < lst.get(i).size(); j++) {
System.out.print(lst.get(i).get(j) + " ");
}
System.out.println();
}
}
}
//执行结果
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
4.2 洗牌算法
package demo5;
public class Card {
public int rank; // 牌面值
public String suit; // 花色
@Override
public String toString() {
return String.format("[%s,%d]",suit,rank);
}
}
package demo5;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class CardDemo {
public static final String[] SUITS = {"♠","♥","♦","♣"};
// 买一副牌
private static List<Card> buyDeck() {
List<Card> deck = new ArrayList<>(52);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 13; j++) {
String suit = SUITS[i];
int rank = j;
Card card = new Card();
card.rank = rank;
card.suit = suit;
deck.add(card);
}
}
return deck;
}
//交换牌面
private static void swap(List<Card> deck,int i,int j) {
Card t = deck.get(i);
deck.set(i,deck.get(j));
deck.set(j,t);
}
//洗牌
private static void shuffle(List<Card> deck) {
Random random = new Random(100000);
for (int i = deck.size() - 1;i > 0;i--) {
int r = random.nextInt(i);
swap(deck,i,r);
}
}
public static void main(String[] args) {
List<Card> deck = buyDeck();
System.out.println("刚买回来的牌:");
System.out.println(deck);
// 洗牌
shuffle(deck);
System.out.println("洗过的牌:");
System.out.println(deck);
// 三个人轮流抓五张牌
List<List<Card>> hands = new ArrayList<>();
hands.add(new ArrayList<>());
hands.add(new ArrayList<>());
hands.add(new ArrayList<>());
for (int i = 0;i < 5;i++) {
for (int j = 0;j < 3;j++) {
hands.get(j).add(deck.remove(0));
}
}
System.out.println("剩余的牌:");
System.out.println(deck);
System.out.println("A手中的牌:");
System.out.println(hands.get(0));
System.out.println("B手中的牌:");
System.out.println(hands.get(1));
System.out.println("C手中的牌:");
System.out.println(hands.get(2));
}
}
//执行结果
刚买回来的牌:
[[♠,0], [♠,1], [♠,2], [♠,3], [♠,4], [♠,5], [♠,6], [♠,7], [♠,8], [♠,9], [♠,10], [♠,11], [♠,12], [♥,0], [♥,1], [♥,2], [♥,3], [♥,4], [♥,5], [♥,6], [♥,7], [♥,8], [♥,9], [♥,10], [♥,11], [♥,12], [♦,0], [♦,1], [♦,2], [♦,3], [♦,4], [♦,5], [♦,6], [♦,7], [♦,8], [♦,9], [♦,10], [♦,11], [♦,12], [♣,0], [♣,1], [♣,2], [♣,3], [♣,4], [♣,5], [♣,6], [♣,7], [♣,8], [♣,9], [♣,10], [♣,11], [♣,12]]
洗过的牌:
[[♠,7], [♥,10], [♦,8], [♦,5], [♠,9], [♦,3], [♦,10], [♠,11], [♠,10], [♣,4], [♦,2], [♥,0], [♥,12], [♠,12], [♦,0], [♥,8], [♦,1], [♣,8], [♠,6], [♣,1], [♣,9], [♥,4], [♦,9], [♣,2], [♣,7], [♥,6], [♠,0], [♦,7], [♠,3], [♣,10], [♦,12], [♣,6], [♥,9], [♠,5], [♦,6], [♥,7], [♠,2], [♠,8], [♦,11], [♦,4], [♥,3], [♣,5], [♠,4], [♣,0], [♥,1], [♠,1], [♣,11], [♣,12], [♥,11], [♥,5], [♥,2], [♣,3]]
剩余的牌:
[[♥,8], [♦,1], [♣,8], [♠,6], [♣,1], [♣,9], [♥,4], [♦,9], [♣,2], [♣,7], [♥,6], [♠,0], [♦,7], [♠,3], [♣,10], [♦,12], [♣,6], [♥,9], [♠,5], [♦,6], [♥,7], [♠,2], [♠,8], [♦,11], [♦,4], [♥,3], [♣,5], [♠,4], [♣,0], [♥,1], [♠,1], [♣,11], [♣,12], [♥,11], [♥,5], [♥,2], [♣,3]]
A手中的牌:
[[♠,7], [♦,5], [♦,10], [♣,4], [♥,12]]
B手中的牌:
[[♥,10], [♠,9], [♠,11], [♦,2], [♠,12]]
C手中的牌:
[[♦,8], [♦,3], [♠,10], [♥,0], [♦,0]]