一、数据结构与算法——动态数组
1.1 概述
程序=数据结构+算法
1.1.1 数据结构概述
逻辑结构——数据之间的关系:线性结构、树形结构、图形结构
线性结构:数据元素之间是一对一的关系
树形结构:数据元素之间存在一对多的层次关系
图形结构:数据元素是多对多的关系
物理结构——关系在计算机上的存储:顺序、链式
顺序存储结构:开辟一组连续的空间存储数据,通常用数组实现
链式存储结构:开辟一组随机的空间存储数据,通常用节点实现,节点不仅要存储数组还要存储下一个节点的位置以保证数据之间的关系。
1.1.2 算法概述
-
算法是解决特定问题求解步骤的描述
-
评价算法的好坏——执行时间
-
事后统计法:通过设计好的程序和数据,对不同算法程序的运行时间进行比较。
- 如果程序处理的数据量较大,则会花费大量的时间和精力。
- 时间的比较主要依赖于计算机硬件和软件环境
- 算法的测试数据设计困难
-
事前分析法:在计算机程序编制前,依据统计方法对算法进行估算。
- 算法采用的策略、方法——算法好坏的根本
- 编译产生的代码质量——取决于具体的编程语言
- 问题输入的规模
- 机器执行指令的速度——取决于硬件性能
一个程序的运行时间依赖于算法的好坏和问题的输入规模
-
-
算法时间复杂度
T(n)=O(f(n)) 其中T(n)-语句总的执行次数,n-问题规模,f(n)-问题规模n的某个函数
表示随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同。
- 常数阶O(1)——无循环、无递归,与问题输入规模N无关、逐行执行的代码。
- 线性阶O(n)——与问题输入规模有关,一层循环代码。
- 线性阶O(n+m)——O(n)有两种数据的输入规模。
- 平方阶O(n^2)——二层嵌套循环。
- 平方阶O(nm)——O(n^2)有两种数据的输入规模。
- 对数阶O(log^n)——一层循环迭代或递归的代码。
1.2 静态数组与动态数组
-
Java内置数组的特点
- 数组长度一旦确定则不可改变
- 数组只能存储同一类型的数据
- 数组中每个存储空间地址是连续且相等的
- 数组提供角标的方式访问元素
-
Java内置数组的缺点
- 长度不可变,容量不够——扩容
- 增删元素不方便
- 只有length属性——用面向对象思想将数组再次进行封装
1.3 顺序表的实现
1.3.1 List接口的定义
线性表可以由顺序表和链表实现,则将两者对线性结构共同的操作进行抽取,定义出线性结构的接口。
package p1.Interface;
import java.util.Comparator;
public interface List<E> extends Iterable<E> {
//默认在表尾添加一个元素
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);
}
1.3.2 ArrayList——顺序表的实现
1. ArrayList(E[] arr)的问题
传入一个数组,将该数组封装成为一个线性表
data不能直接引用外部传入的数组arr,即不能data=arr,这样E[] data和E[] arr同时指向该数组,当arr对数组进行修改后,data也会发生相应的改变,会引起ArrayList内部数据不一致的问题。
正确方法是将arr所指向的数组复制一份,让data指向这个副本。
2. "=="如何使用
== 两边是基本数据类型的话 比的是值
byte short int long
float double
char boolean
== 两边是引用数据类型的话 比的是地址
数组 字符串 其他的类对象
“==”比较的是变量中存的内容,基本数据类型存的是值,引用数据类型中存的是对象的地址。
3. 代码
package p2.LineStruct;
import p1.Interface.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++;
}
//扩容/缩容 操作 不应该向外界提供(建议表元素个数减到1/4时缩容到1/2)
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) {
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 p3.Test;
import p2.LineStruct.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));
}
}
1.4 栈
1.4.1 栈的定义
栈是限定仅在表尾进行插入和删除操作的线性表
-
把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom)
-
不含任何数据元素的栈称为空栈
-
栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构
-
栈本身是一个线性表,其数据元素具有线性关系,只不过它是一种特殊的线性表而已
-
栈的插入操作,叫作进栈,也称压栈、入栈
-
栈的删除操作,叫作出栈,也称弹栈
1.4.2 Stack栈接口的定义
package p1.Interface;
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();
}
1.4.3 ArrayStack——栈的实现
可以借用之前完成的ArrayList来实现ArrayStack
package p2.LineStruct;
import p1.Interface.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 p3.Test;
import p2.LineStruct.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.5 栈的应用——中缀计算器
1.5.1 步骤
-
格式化表达式
如果原封不动的遍历表达式字符串(10+20/2*3)/2+8
将得到如下结果:
[(, 1, 0, +, 2, 0, /, 2, *, 3, ), /, 2, +, 8]
最好的分隔结果是:
[(, 10, +, 20, /, 2, *, 3, ), /, 2, +, 8]
思路:将字符串格式化为如下情形,再进行分隔即可
#(#10#+#20#/#2#*#3#)##/#2#+#8#]#
-
出入栈
-
遇数字进数字栈
-
遇左括号进操作数栈
-
遇运算符,需判断该运算符与运算符栈顶的优先级高低
- 若栈顶>=该运算符:栈顶元素出栈,同时弹出两个数字进行运算,将运算结果进数字栈
- 若栈顶<该运算符:该运算符入栈
-
遇右括号,将运算符中的元素一一弹出进行运算,直到弹出左括号为止
package p2.LineStruct;
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();
}
}