一、线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列,线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列...
线性表在逻辑上是线性结构,也就是连续的一条直线,但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储
二、顺序表
顺序表时用一段物理地址连续的存储单元依次存放数据元素的线性结构,一般情况下采用数组存储,在数组上完成增删查改
自己实现顺序表,帮助理解ArrayList中方法的底层原理
框架代码:
public interface IList {
// 新增元素,默认在数组最后新增
void add(int data);
// 在 pos 位置新增元素
void add(int pos, int data);
// 判定是否包含某个元素
boolean contains(int toFind);
// 查找某个元素对应的位置
int indexOf(int toFind);
// 获取 pos 位置的元素
int get(int pos);
// 给 pos 位置的元素设为 value
void set(int pos, int value);
//删除第一次出现的关键字key
void remove(int toRemove);
// 获取顺序表长度
int size();
// 清空顺序表
void clear();
// 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
void display();
//判满
boolean isFull();
}
public class MyArrayList implements IList{
public int[] elem;
public int usedSize;
public MyArrayList() {
this.elem = new int[10];
}
@Override
public void add(int data) {
}
@Override
public boolean isFull() {
}
@Override
public void add(int pos, int data) {
}
@Override
public boolean contains(int toFind) {
return false;
}
@Override
public int indexOf(int toFind) {
return 0;
}
@Override
public int get(int pos) {
return 0;
}
@Override
public void set(int pos, int value) {
}
@Override
public void remove(int toRemove) {
}
@Override
public int size() {
return 0;
}
@Override
public void clear() {
}
@Override
public void display() {
}
}
public class Test {
public static void main(String[] args) {
//两种创建对象的方式
IList iList = new MyArrayList();
MyArrayList myArrayList = new MyArrayList();
}
}
add(int data)方法:
add(int data,int pos)方法:
定义一个异常类,用来判断pos位置是否合法
public class PosNotLegalException extends RuntimeException {
public PosNotLegalException() {
}
public PosNotLegalException(String msg) {
super(msg);
}
}
private 修饰的 checkPosOfAdd() 函数,检测pos,若不合法则抛出异常
private void checkPosOfAdd(int pos) throws PosNotLegalException{
if(pos < 0 || pos > usedSize) {
throw new PosNotLegalException("pos位置不合法!");
}
}
从最后一个元素位置开始,将pos之后的元素往后移一位,再将data插入到pos位置,最后usedSize++
@Override
public void add(int pos, int data) {
//判断位置是否合法
try {
checkPosOfAdd(pos);
}catch(PosNotLegalException e) {
e.printStackTrace();
}
//判满
if(isFull()) {
this.elem = Arrays.copyOf(elem,2*elem.length);
}
for (int i = this.usedSize-1; i >= pos; i--) {
this.elem[i+1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
判断是否包含某个元素contains方法
@Override
public boolean contains(int toFind) {
for (int i = 0; i < usedSize; i++) {
if(this.elem[i] == toFind) {
return true;
}
}
return false;
}
返回查找元素下标indexOf方法
@Override
public int indexOf(int toFind) {
for (int i = 0; i < usedSize; i++) {
if(this.elem[i] == toFind) {
return i;
}
}
return -1;
}
获取pos下标位置元素get方法
@Override
public int get(int pos) {
try {
checkPosOfAddGetAndSet(pos);//判断pos位置是否合法,此处pos不能等于usedSize,和判满异常区分
}catch(PosNotLegalException e) {
e.printStackTrace();
}
return elem[pos];
}
private void checkPosOfAddGetAndSet(int pos) throws PosNotLegalException{
if(pos < 0 || pos >= usedSize) {
throw new PosNotLegalException("get or set 中pos位置不合法!");
}
}
设置pos下标位置的值为value的set方法(相当于更新元素)
@Override
public void set(int pos, int value) {
try {
checkPosOfAddGetAndSet(pos);
}catch(PosNotLegalException e) {
e.printStackTrace();
}
elem[pos] = value;
}
删除第一次出现的元素,remove方法
@Override
public void remove(int toRemove) {
int pos = indexOf(toRemove);//判断该元素是否存在
if(pos == -1) {
System.out.println("没有要删除的数字!");
return;
}
for (int i = pos; i < usedSize-1; i++) {//将后续元素前移覆盖即可
elem[i] = elem[i+1];
}
this.usedSize--;
}
删除所有出现的toRemove元素,removeAll方法
public void removeAll(int toRemove) {
int pos = indexOf(toRemove);
if(pos == -1) {
System.out.println("没有要删除的数字!");
return;
}
int i = 0;
while(i < usedSize) {
if(elem[i] == toRemove) {
for(int j = i; j < usedSize-1; j++) {
elem[j] = elem[j+1];
}
this.usedSize--;
}
if(elem[i] != toRemove) {
i++;
}
}
}
获取顺序表长度,清空顺序表,打印顺序表
@Override
public int size() {
return usedSize;
}
@Override
public void clear() {
/*若顺序表元素为引用类型,需回收内存
for (int i = 0; i < usedSize; i++) {
elem[i] = null;
}
*/
usedSize = 0;
}
@Override
public void display() {
for (int i = 0; i < usedSize; i++) {
System.out.print(elem[i] + " ");
}
System.out.println();
}
三、ArrayList的使用
我们查看ArratList的源码
发现其是一个泛型类,创建其对象的两种方法如下:
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
List<Integer> list = new ArrayList<>();//推荐这种
}
区别:
3.1 ArrayList的构造
我们查看ArrayList的源码,发现其有三个构造方法:
第一个无参构造方法:
查看源码:
虽然没有给数组分配大小,但是当我们add元素时,却依然能成功:
这是因为:当调用不带参数的构造方法进行add时,会分配大小为10的内存,当内存占满了,会进行1.5倍扩容
第二种,有一个整型参数的构造方法
第三种:
3.2 ArrayList的常见方法
1. boolean addAll(Collection c) 尾插 c 中的元素
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(12);//自动装箱,int类型变为Integer类型
System.out.println(list);
List<Integer> list1 = new ArrayList<>(list);
System.out.println(list1);
}
运行结果:
2. E remove(int index) 删除 index 位置元素
报错为数组越界异常,remove不会进行装箱操作
若想直接输入删除的元素:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(12);
System.out.println(list);
//list.remove(1);//删除1下标,没问题
//System.out.println(list);
//list.remove(12);//想删除12这个元素,会报错
list.remove(Integer.valueOf(12));//将int类型的12,转为Integer类型对象,remove删除值为12的对象
}
3. List subList(int fromIndex, int toIndex) 截取部分 list
3.3 Arrays的遍历
ArrayList 可以使用三方方式遍历:for循环+下标、foreach、使用迭代器
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(11);
list.add(12);
list.add(13);
//第一种打印方式:
System.out.println(list);//该方法或其父类中肯定重写了toString方法
System.out.println("===fori===");
//第二种for循环
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
System.out.println("===foreach===");
//第三种foreach循环
for(Integer x : list) {
System.out.print(x + " ");
}
System.out.println();
System.out.println("===iterator打印===");
//第四种 iterator打印
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {//当it后面还有元素就进入循环
System.out.print(it.next() + " ");
}
System.out.println();
System.out.println("===ListIterator,专门打印List===");
//第五种 ListIterator,专门打印List
ListIterator<Integer> it2 = list.listIterator();
while(it2.hasNext()) {
System.out.print(it2.next() + " ");
}
System.out.println();
System.out.println("===ListIterator 倒着打印===");
//第六种 ListIterator 倒着打印
ListIterator<Integer> it3 = list.listIterator(list.size());
while(it3.hasPrevious()) {
System.out.print(it3.previous() + " ");
}
}
运行结果:
四、ArrayList具体使用
4.1 简单的洗牌算法
需求:生成52张牌,没有大小王,四种花色("♠", "♥", "♣", "♦"),每种13张,乱序洗牌,3个人轮流抓牌,每人抓5张,最后打印每个人手中牌及剩下的牌
框架:
Card类,声明牌的属性,牌的构造方法,重写toString
public class Card {
public int rank;//数字
public String suit;//花色
public Card(int rank,String suit) {
this.rank = rank;
this.suit = suit;
}
@Override
public String toString() {
return "{" + suit + " " + rank + "}";
}
}
Cards类,生成52张牌的方法buyCard
import java.util.ArrayList;
import java.util.List;
public class Cards {
public static final String[] suits = {"♠", "♥", "♣", "♦"};
//4个花色 每种13张
public List<Card> buyCard() {
List<Card> cardList = new ArrayList<>();//创建泛型类型为Card的顺序表
for (int i = 0; i < 4; i++) {//四种花色循环
for (int j = 1; j <= 13; j++) {//生成每种花色的1~13数字牌
int rank = j;
String suit = suits[i];
Card card = new Card(rank,suit);//构造牌对象
cardList.add(card);//将牌对象添加到顺序表中
}
}
return cardList;
}
}
Test类
import java.util.List;
public class Test {
public static void main(String[] args) {
Cards cards = new Cards();//创建牌对象
List<Card> cardList = cards.buyCard();//buyCard()返回值类型为List<Card>
System.out.println(cardList);
}
}
洗牌操作
//Cards类
public void shuffle(List<Card> cardList) {
Random random = new Random();
for (int i = cardList.size()-1; i > 0; i--) {//从后向前循环
int randIndex = random.nextInt(i);//生成随机整数,该整数范围:0~i之间
swap(cardList,i,randIndex);//交换i下标与生成随机整数下标处的值
}
}
//根据生成随机整数来打乱牌序
private void swap(List<Card> cardList,int i,int j) {
Card tmp = cardList.get(i);
cardList.set(i,cardList.get(j));
cardList.set(j,tmp);
}
3个人轮流抓牌,每人抓5张
public void drawCard(List<Card> cardList) {
List<Card> hand1 = new ArrayList<>();//三个手对象,用来存放每个人每次摸的牌
List<Card> hand2 = new ArrayList<>();
List<Card> hand3 = new ArrayList<>();
List<List<Card>> hands = new ArrayList<>();//创建一个泛型类为List<Card>的对象,用来存放三个人
hands.add(hand1);//将三个人放到hans的顺序表中
hands.add(hand2);
hands.add(hand3);
for (int i = 0; i < 5; i++) {//外循环5次,模拟摸5次牌,
for (int j = 0; j < 3; j++) {//内循环3次,模拟三个人
Card card = cardList.remove(0);//创建Card类型对象,暂存摸出的牌
hands.get(j).add(card);//将牌放到相应人的相应手里面
}
}
System.out.println("第一个人的牌" + hand1);
System.out.println("第二个人的牌" + hand2);
System.out.println("第三个人的牌" + hand3);
}
扩展:给摸完牌后的牌排序
4.2 杨辉三角
//杨辉三角
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> ret = new ArrayList<>();
List<Integer> list = new ArrayList<>();
list.add(1);
ret.add(list);
for (int i = 1; i < numRows; i++) {
List<Integer> curRow = new ArrayList<>();
curRow.add(1);
for(int j = 1; j < i; j++) {
List<Integer> preRow = ret.get(i-1);
int x = preRow.get(j);
int y = preRow.get(j-1);
curRow.add(x+y);
}
curRow.add(1);
ret.add(curRow);
}
return ret;
}
4.3 面试题:将str1中出现的str2中的相同元素删除
例:
str1:welcome to china
str2:come
要求输出结果:wl t hina
两种解决方式:
//ArrayList实现
public static void func2(String str1,String str2) {
List<Character> list = new ArrayList<>();//用ArrayList实现该方法,最好使用接口进行对象创建
for (int i = 0; i < str1.length(); i++) {
char ch = str1.charAt(i);
//str2是字符串,ch是字符,为了解决他们用contains方法会报错的问题
//if(!str2.contains(ch)) {//将该行代码改为以下,在ch后面+""即可变为String类型
if(!str2.contains(ch+"")) {
list.add(ch);
}
}
for(char ch : list) {
System.out.print(ch);
}
System.out.println();
}
//StringBuilder实现
public static void func(String str1,String str2) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < str1.length(); i++) {
char ch = str1.charAt(i);
if(!str2.contains(ch+"")) {
stringBuilder.append(ch);
}
}
System.out.println(stringBuilder);
}