目录
●线性表
●栈
●双端栈
●队列
●循环队列
●双端队列
静态数组与动态数组
Java内置数组的特点:
数组的长度一旦确定则不可更改
数组只能存储同一类型的数据
数组中每个存储空间地址是连续且相等的
数组提供角标的方式访问元素
Java内置数组的缺点:
长度不可变,容量不够用怎么办
地址连续且提供角标访问很快,但增删元素怎么办
数组只有length这个属性,没有其他的方法
解决问题:
1、容量不够,扩容
重新申请一块大容量地址,把原来数组中的元素复制进来,让原数组名字新开辟的地址
2、增删元素:
需要移动其他元素,时间复杂度为O(n)
3、查改元素:
时间复杂度为O(1)
动态数组:
把数组和size(数组中已有元素的个数)(成员变量),和增删改查等操作(成员函数)封装成一个类,这个类,就是动态数组的概念,再将这些信息进行二次封装,便可以实现线性表,栈,队列,等相关内容
动态数组就是顺序存储结构具体实现的核心思想
线性表的定义(重点)
零个或多个元素的有限序列
●ai-1是a;的直接前驱
●ai+1是a;的直接后继
●除了第1个元素a1之外,其他元素都有唯一的直接前驱
●除了第n个元素an之外,其他元素都有唯一的直接后继
●n表示线性表的长度, 当n=0时, 称为空表
接下来开始封装,考虑到顺序存储和链式存储都需要增删改查,但增删改查操作不同,所以,我们先定义一个含有增删改查的接口,再分别进行对象的封装
上代码:
首先进行接口的定义
package p1.接口;
import java.util.Comparator;
//线性表接口定义
public interface List<E> extends Iterable<E> {
//因为此处是封装一个数组,而数组的类型是由用户决定,所以此处使用E(泛型),表示类型由使用时确定
//此处继承迭代接口,作用:可以使用for等循环语句遍历里面内容,因为此处封装的是数组,需要用到遍历
//默认在表尾添加一个元素
public void add(E element);
//在指定角标处添加元素
public void add(int index, E element);
//删除指定元素
public void remove(E element);
//删除指定角标处的元素 并返回原先的值
public E remove(int index);
//获取指定角标处的元素
public E get(int index);
//修改指定角标index处的值为element 并返回原先的值
public E set(int index, E element);
//获取线性表中的元素个数
public int size();
//查看元素第一次出现的角标位置(从左到右)
public int indexOf(E element);
//判断是否包含元素
public boolean contains(E element);
//判断线性表是否为空
public boolean isEmpty();
//清空线性表
public void clear();
//按照比较器的内容进行排序
//用户在使用此函数是,需要传进来一个比较器,按什么比较需要用户定义
//因为封装的是数组,而类型可能是类、字符串等类型,并不是==就能进行判断的
public void sort(Comparator<E> c);
//获取子线性表 原线性表中[fromIndex, toIndex]这个部分
public List<E> subList(int fromIndex, int toIndex);
}
接下来开始定义线性表
package p2.线性结构;
import p1.接口.List;
import java.util.Comparator;
import java.util.Iterator;
//自定义的线性表的顺序存储方式
public class ArrayList<E> implements List<E> {
//数组的容器 data.length 指的就是当前数组的容量
private E[] data;
//元素的个数 size == 0 线性表为空 size == data.length 线性表满了
//size 新元素默认尾部添加时要去的角标
private int size;
//默认容量
private static int DEFAULT_CAPACITY = 10;
//创建一个默认容量为10的线性表
public ArrayList() {
data = (E[]) new Object[DEFAULT_CAPACITY];
size = 0;
}
//创建一个指定容量的线性表
public ArrayList(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("capacity must > 0");
}
DEFAULT_CAPACITY = capacity;
data = (E[]) new Object[DEFAULT_CAPACITY];
size = 0;
}
//传入一个数组 将该数组封装成为一个线性表
public ArrayList(E[] arr) {
if (arr == null || arr.length == 0) {
throw new IllegalArgumentException("arr can not be null");
}
data = (E[]) new Object[DEFAULT_CAPACITY];
for (int i = 0; i < arr.length; i++) {
add(arr[i]);
}
}
@Override
public void add(E element) {
add(size, element);
}
@Override
public void add(int index, E element) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("add index out of range");
}
//判断线性表是否是满状态
if (size == data.length) {
resize(2 * data.length);
}
//向后移动元素
for (int i = size - 1; i >= index; i--) {
data[i + 1] = data[i];
}
//将新元素插入到指定位置
data[index] = element;
size++;
}
//扩容/缩容 操作 不应该向外界提供
private void resize(int newLen) {
E[] newData = (E[]) new Object[newLen];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
@Override
public void remove(E element) { //删除指定元素 只删除一次 && 删除所有的指定元素
int index = indexOf(element);
if (index != -1) {
remove(index);
}
}
@Override
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("remove index out of range");
}
//先保存要删除的值
E ret = data[index];
//移动元素
for (int i = index + 1; i < size; i++) {
data[i - 1] = data[i];
}
size--;
//什么时候缩容
//1.有效元素是容量的1/4
//2.当前容量不得小于等于默认容量
if (size == data.length / 4 && data.length > DEFAULT_CAPACITY) {
resize(data.length / 2);
}
return ret;
}
@Override
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get index out of range");
}
return data[index];
}
@Override
public E set(int index, E element) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("set index out of range");
}
E ret = data[index];
data[index] = element;
return ret;
}
@Override
public int size() {
return size;
}
//额外添加一个函数 获取线性表中那个数组的容量
private int getCapacity() {
return data.length;
}
@Override
public int indexOf(E element) {
/*
== 比的是啥?主要看等号的两边是啥
== 两边是基本数据类型的话 比的是值
byte short int long
float double
char boolean
== 两边是引用数据类型的话 比的是地址
数组 字符串 其他的类对象
*/
for (int i = 0; i < size; i++) {
if (data[i].equals(element)) {
return i;
}
}
return -1;
}
@Override
public boolean contains(E element) {
return indexOf(element) != -1;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void clear() {
data = (E[]) new Object[DEFAULT_CAPACITY];
size = 0;
}
@Override
public void sort(Comparator<E> c) {
if (c == null) {
throw new IllegalArgumentException("comparator can not be null");
}
for (int i = 1; i < size; i++) {
E e = data[i];
int j = 0;
for (j = i; j > 0 && c.compare(data[j - 1], e) > 0; j--) {
data[j] = data[j - 1];
}
data[j] = e;
}
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
if (fromIndex < 0 || toIndex >= size || fromIndex > toIndex) {
throw new IllegalArgumentException("must 0 <= fromIndex <= toIndex <= size - 1");
}
ArrayList<E> list = new ArrayList<>();
for (int i = fromIndex; i <= toIndex; i++) {
list.add(data[i]);
}
return list;
}
@Override
public boolean equals(Object o) {
//1.判空
if (o == null) {
return false;
}
//2.判自己
if (this == o) {
return true;
}
//3.判类型
if (o instanceof ArrayList) {
//4.按照自己的逻辑进行比较
ArrayList<E> other = (ArrayList<E>) o;
//5.先比有效元素的个数
if (size != other.size) {
return false;
}
//6.有效元素个数相等的情况下 逐个比较元素
for (int i = 0; i < size; i++) {
if (!data[i].equals(other.data[i])) {
return false;
}
}
return true;
}
return false;
}
/*
[1, 2, 3, 4, 5, 6]
[]
Arrays.toString(arr);
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('[');
if (isEmpty()) {
sb.append(']');
} else {
for (int i = 0; i < size; i++) {
sb.append(data[i]);
if (i == size - 1) {
sb.append(']');
} else {
sb.append(',');
sb.append(' ');
}
}
}
return sb.toString();
}
//获取当前这个数据结构/容器 的 迭代器
//通过迭代器对象 更方便挨个取出每一个元素
//同时 实现了Iterable 可以让当前的数据结构/容器 被foreach循环遍历
@Override
public Iterator<E> iterator() {
return new ArrayListIterator();
}
//创建一个属于ArrayList的迭代器
class ArrayListIterator implements Iterator<E> {
private int cur = 0;
@Override
public boolean hasNext() {//判断是否有下一个元素
return cur < size;
}
@Override
public E next() {//如果有下一个元素 则把当前元素返回 并移至到下一个元素
return data[cur++];
}
}
}
代码测试:
package p0.测试;
import p2.线性结构.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Random;
public class TestArrayList {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
System.out.println(list);
Random random = new Random();
for (int i = 0; i < 10; i++) {
list.add(random.nextInt(100));
}
System.out.println(list);
for (int i = 0; i < 10; i++) {
list.add(0, i);
}
System.out.println(list);
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
System.out.println(list);
for (Integer num : list) {
System.out.println(num);
}
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
ArrayList<Integer> l1 = new ArrayList<>();
ArrayList<Integer> l2 = new ArrayList<>(8);
for (int i = 0; i < 5; i++) {
l1.add(i);
l2.add(i);
}
System.out.println(l1);
System.out.println(l2);
System.out.println(l1.equals(l2));
}
}
案例输出:
[]
[71, 7, 58, 44, 60, 77, 5, 78, 8, 41]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 71, 7, 58, 44, 60, 77, 5, 78, 8, 41]
[78, 77, 71, 60, 58, 44, 41, 9, 8, 8, 7, 7, 6, 5, 5, 4, 3, 2, 1, 0]
78
77
71
60
58
44
41
9
8
8
7
7
6
5
5
4
3
2
1
0
78 77 71 60 58 44 41 9 8 8 7 7 6 5 5 4 3 2 1 0
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]
true
栈的定义(重点)
●栈是限定仅在表尾进行插入和删除操作的线性表
●我们把允许插入和删除的一端称为栈顶(top) ,另-端称为栈底(bottom)
●不含任何数据元素的栈称为空栈
●栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构
●栈本身是一个线性表,其数据元素具有线性关系,只不过它是一种特殊的线性表而已
●栈的插入操作,叫作进栈,也称压栈、入栈
●栈的删除操作,叫作出栈,也称弹栈
Stack栈接口的定义
因为是特殊的线性表,所以此处(原理和线性表一样),只需要对上述的线性表再次进行封装即可
上代码:
接口定义:
package p1.接口;
public interface Stack<E> extends Iterable<E> {
public int size();
public boolean isEmpty();
//入栈 进栈一个元素 在线性表的表尾添加一个元素
public void push(E element);
//出栈 弹出一个元素 在线性表的表尾删除一个元素
public E pop();
//查看当前栈顶元素 并不是移除 查看线性表中最后一个元素
public E peek();
public void clear();
}
栈的定义:
package p2.线性结构;
import p1.接口.Stack;
import java.util.Iterator;
public class ArrayStack<E> implements Stack<E> {
private ArrayList<E> list;
public ArrayStack() {
list = new ArrayList<>();
}
public ArrayStack(int capacity) {
list = new ArrayList<>(capacity);
}
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public void push(E element) {
list.add(element);
}
@Override
public E pop() {
return list.remove(list.size() - 1);
}
@Override
public E peek() {
return list.get(list.size() - 1);
}
@Override
public void clear() {
list.clear();
}
@Override
public Iterator<E> iterator() {
return list.iterator();
}
@Override
public String toString() {
return list.toString();
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (o instanceof ArrayStack) {
ArrayStack other = (ArrayStack) o;
return this.list.equals(other.list);
}
return false;
}
}
代码测试:
package p0.测试;
import p2.线性结构.ArrayStack;
public class TestArrayStack {
public static void main(String[] args) {
ArrayStack<Integer> stack01 = new ArrayStack<>();
ArrayStack<Integer> stack02 = new ArrayStack<>(15);
for (int i = 1; i <= 12; i++) {
stack01.push(i);
stack02.push(i);
}
System.out.println(stack01);
System.out.println(stack02);
System.out.println(stack01.equals(stack02));
System.out.println(stack01.pop());
System.out.println(stack01);
System.out.println(stack01.peek());
}
}
案例输出:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
true
12
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
11
栈解决了之后,就可以实现小型计算器了
计算器(重点)
中缀表达式
●是一个通用的算术或逻辑公式表示方法,操作符是以中缀形式处于操作数的中间(例: 3 + 4)
●与前缀表达式(例: + 34)或后缀表达式(例: 34+)相比,中缀表达式不容易被计算机解
析,但仍被许多程序语言使用,因为它符合人们的普遍用法。
●与前缀或后缀记法不同的是,中缀记法中括号是必需的。
利用中缀表达式计算四则运算
(可含括号,不含小数点,除数直接使用去尾法;可以自己实现这些功能,原理都是一样)
思路
以 (10+20/2*3)/2+8 为例
1、将字符串格式化为如下情形,再进行分隔即可
#(#10#+#20#/#2#*#3#)#/#2#+#8(#代表空格,利用StringBuilder遇到数字不加,只有遇到符号两边加上空格)
2、利用split进行分割:[(,10,+,20,/, 2,*3,),/,2,+,8]
3、运算符入运算符栈,数字直接入数字栈
运算符入栈规则:
扫描到’+‘或者’-’:需要判断运算符栈是否为空、栈顶元素是否为’+’、’-’、’*’、’/’,是的话,直接弹出一个运算符和两个数字进行运算压入数字栈,其他直接压栈
扫描到’×’或者’-’:需要判断运算符栈是否为空、栈顶元素是否为’+’、’-’,是的话,直接弹出一个运算符和两个数字进行运算压入数字栈,其他直接压栈
扫描到’(’:直接入栈
扫描到’)’:直接弹出一个运算符和两个数字进行运算压入数字栈,直到’(’
扫描到数字:直接入数字栈
最后运算符栈为空,数字栈还剩一个也就是最终结果
上代码
package p2.线性结构;
//中缀表达式计算器
public class InfixCalculator {
public static void main(String[] args) {
String expression = "(10+20/2*3)/2+8";//此处可以改成键盘输入一个字符串
try {//如果输入异常,进行异常捕捉
int result = evaluateExpression(expression);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Wrong expression :" + expression);
}
}
private static int evaluateExpression(String expression) {
//需要两个辅助栈
ArrayStack<Character> operatorStack = new ArrayStack<>();
ArrayStack<Integer> numberStack = new ArrayStack<>();
//格式化表达式
expression = insertBlanks(expression);
String[] tokens = expression.split(" ");
for (String token : tokens) { //token == tokens[i]
//过滤空串
if (token.length() == 0) {
continue;
//遍历到 + - 号
} else if (token.equals("+") || token.equals("-")) {
while (!operatorStack.isEmpty() && (operatorStack.peek() == '+' || operatorStack.peek() == '-' || operatorStack.peek() == '*' || operatorStack.peek() == '/')) {
//如果之前是别的+ - * / 则需要弹栈 并计算
processAnOperator(numberStack, operatorStack);
}
//如果操作符栈为空 或者 不为空但栈顶为(
operatorStack.push(token.charAt(0));
//遍历到 * / 号
} else if (token.equals("*") || token.equals("/")) {
while (!operatorStack.isEmpty() && (operatorStack.peek() == '*' || operatorStack.peek() == '/')) {
//如果之前是别的* / 则需要弹栈 并计算
processAnOperator(numberStack, operatorStack);
}
//如果操作符栈为空 或者 不为空但栈顶为(
operatorStack.push(token.charAt(0));
//遍历到 (
} else if (token.equals("(")) {
operatorStack.push(token.charAt(0));
//遍历到 )
} else if (token.equals(")")) {
//只要操作符栈的栈顶不是左括号( 就挨个弹栈计算即可
while (operatorStack.peek() != '(') {
processAnOperator(numberStack, operatorStack);
}
//最后 清掉左括号
operatorStack.pop();
//遍历到数字
} else {
numberStack.push(new Integer(token));
}
}
//处理最后面的操作符
while (!operatorStack.isEmpty()) {
processAnOperator(numberStack, operatorStack);
}
return numberStack.pop();
}
//操作符栈弹栈一个元素 数字栈弹栈两个数字 进行计算 并将新的结果进栈到数字栈
private static void processAnOperator(ArrayStack<Integer> numberStack, ArrayStack<Character> operatorStack) {
char op = operatorStack.pop();
int num1 = numberStack.pop();
int num2 = numberStack.pop();
//num2 op num1
if (op == '+') {
numberStack.push(num2 + num1);
} else if (op == '-') {
numberStack.push(num2 - num1);
} else if (op == '*') {
numberStack.push(num2 * num1);
} else {
numberStack.push(num2 / num1);
}
}
//对原表达式进行格式化处理 给所有的非数字字符两边添加空格
private static String insertBlanks(String expression) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/') {
sb.append(' ');
sb.append(c);
sb.append(' ');
} else {
sb.append(c);
}
}
return sb.toString();
}
}