算法
是解决特定问题求解步骤的描述:自然语言描述、流程图描述、伪代码描述、程序代码描述
对于1到100的求和问题(有两种解决方案)
方案1随着N的增大,循环的次数也会增大,也就意味着执行的次数和运行的时间也会增大
方案1
int sum=0;
int N=100;
for(int i=1;i<=N;i++){
sum+=i;
}
方案2随着N的增大,其执行次数只有一次,不会随着N的增大而增大
方案2
int N=100;
int sum=(N+1)*N/2;
算法的好坏如何评判
算法的效率指的是算法的执行时间
度量一个算法的执行时间有:
1.事后统计法:
这种方法主要是通过设计好的程序和数据,利用计算机计时器对不同算法程序的运行时间进行比较,从而确定算法效率的高低。
弊端:
•必须事先编好程序,再进行运行,如果程序处理的数据量较大,则会花费大量的时间和精力
•时间的比较主要依赖于计算机硬件和软件环境
•算法的测试数据设计困难,在数据量较小的时候,不管什么算法其运行时间都是很微小的,相差几乎为零。如果数据量大了,算法的优越性就出来了,但是这样又会耗费时间。
2.事前分析法:
这种方法主要在计算机程序编制前,依据统计方法对算法进行估算。
一个高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:
1.算法采用的策略、方法
2.编译产生的代码质量
3.问题的输入规模
4.机器执行指令的速度
算法时间复杂度
算法时间复杂度主要探究的是问题输入规模N的数量级,不是算法的具体执行次数。
常数阶O(1)
就是那些无循环、无递归、与问题输入规模N无关的、逐行执行的代码。
int a = 3;
int b = 4;
int c = a + b;
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int e = 5;
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
System.out.println(e);
线性阶O(n)
与问题输入规模有关的,主要是一层循环的代码,多个一层循环可以并列但不能包含。
int N = 10;
for (int i = 1; i <= N; i++) {
System.out.println(i);
}
int N = 10;
for (int i = 1; i <= N; i++) {
System.out.println(i);
}
for (int i = 1; i <= N; i++) {
System.out.println(i);
}
线性阶O(n+m)
和线性阶O(n)一样,只不过我们有两种数据的输入规模。
int N = 10;
int M = 20;
for (int i = 1; i <= N; i++) {
System.out.println(i);
}
for (int i = 1; i <= M; i++) {
System.out.println(i);
}
平方阶O(n2)
与问题输入规模有关的,主要是二层嵌套循环的代码。
int N = 10;
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
System.out.println(i + j);
}
}
平方阶O(nm)
和平方阶O(n2)一样,只不过我们有两种数据输入规模。
int N = 10;
int M = 20;
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
System.out.println(i + j);
}
}
对数阶O(logn)
与问题输入规模有关的,主要是一层循环迭代或递归的代码。
int count = 1;
int N = 100000;
while (count < N)
count = count * 2;
int key = 8;
int min_index = 0;
int max_index = arr.length - 1;
int mid_index = (min_index + max_index) / 2;
while (arr[mid_index] != key) {
if (key < arr[mid_index])
max_index = mid_index - 1;
if (arr[mid_index] < key) {
min_index = mid_index + 1;
if (min_index > max_index) {
mid_index = -1;
break;
}
mid_index = (min_index + max_index) / 2;
}
常见阶的比较
执行次数 | 阶 | 非正式术语 |
12 | O(1) | 常数阶 |
5log2n+20 | O(logn) | 对数阶 |
2n+3 | O(n) | 线性阶 |
2n+3nlog2n+19 | O(nlogn) | nlogn阶 |
3n2+2n+1 | O(n2) | 平方阶 |
2n | O(2n) | 指数阶 |
n! | O(n!) | 阶乘阶 |
6n3+2n2+3n+4 | O(n3) | 立方阶 |
数据结构---逻辑结构
数据结构---物理结构
静态数组与动态数组
内置数组的特点和缺点
Java内置数组的特点:
•数组的长度一旦确定则不可更改
•数组只能存储同一类型的数据
•数组中每个存储空间地址是连续且相等的
•数组提供角标的方式访问元素
Java内置数组的缺点:
•长度不可变,容量不够用怎么办
•地址连续且提供角标访问很快,但增删元素怎么办
•数组只有length这个属性,没有其他的方法
线性表的定义
零个或多个元素的有限序列
List接口的定义
接下来我们用代码来实现一下线性表中的List接口
package org.oracle.jiekou;
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);
}
线性表的实现ArrayList
ArrayList就是线性结构顺序存储方式的具体实现,称为线性表
创建ArrayList类实现List接口,定义相关成员属性和构造函数
add(int index, E element) 添加
resize(int newLength) 扩容
remove(int index)移动
resize(int newLength) 缩容
indexOf(E element) 进行比较
sort(Comparator<E> c)排序(比较器)
subList(int fromIndex, int toIndex)list.subList(int fromIndex, int toIndex) 返回list中指定下标的元素组成的list集合,左闭右开(包括fromIndex元素,不包括toIndex).
Iterator<E> iterator()迭代器,实现了Iterable<E>的类型,均可以通过iterator()获得该容器上的迭代器Iterator<E>实例
package org.oracle.p2;
import org.oracle.jiekou.List;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
//自定义的线性表的顺序存储方式
public class ArrayList<E> implements List<E> {
//数组的容器 data.length 指的是当前数组的容量
private E[] data;
//元素的个数 size == 0 线性表为空 size == date.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;
}
//指定数组的构造函数:传入一个数组 将该数组封装成一个线性表
//[ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ]
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] = element;
}
//将新元素插入到指定位置
data[index] = element;
size++;
}
//扩容/缩容 操作 不应该向外界提供
private void resize(int newLen) {
E[] newDate = (E[]) new Object[newLen];
for (int i = 0; i < size; i++){
newDate[i] = data[i];
}
data = newDate;
}
@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.有效元素是容量的四分之一
//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 (this.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++];
}
}
}
ArrayList(E[] arr)的问题
data不能直接引用外部传入的数组arr,否则外部对arr的修改会引起ArrayList内部的一些问题
TestArrayList的建立
package org.oracle.Test;
import org.oracle.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);
list.add(i);
//System.out.println(list);
}
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.println(it.next() + " ");
}
}
}
测试结果
栈的定义
栈是限定仅在表尾进行插入和删除操作的线性表
栈的删除操作,叫作出栈,也称弹栈
Stack栈接口的定义
同样栈可以顺序存储实现也可以链表存储实现,所以将共性抽取定义出Stack接口
package org.oracle.jiekou;
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();
}
ArrayStack类
![](https://img-blog.csdnimg.cn/458257831a5a431b9691aef6d3dd56b0.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Yqq5Yqb5YW75bCP5pWi,size_19,color_FFFFFF,t_70,g_se,x_16)
package org.oracle.p2;
import org.oracle.jiekou.Stack;
import java.util.Iterator;
import java.util.Objects;
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;
}
@Override
public int hashCode() {
return Objects.hash(list);
}
}
中缀表达式
(例: 3 + 4 )
![](https://img-blog.csdnimg.cn/d9a31d695cb3453b9351d478d3c80cff.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Yqq5Yqb5YW75bCP5pWi,size_20,color_FFFFFF,t_70,g_se,x_16)
格式化表达式
如果原封不动的遍历表达式字符串(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
接下来我们将以(10+20/2*3)/2+8为例进行代码实现
package org.oracle.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);
}
// int result = evaluateExpression(expression);
// expression = insertBlanks(expression);
// String[] tokens = expression.split(" ");
// for (int i = 0; i < tokens.length; i++){
// System.out.println("""+ tokens[i] + """);
// }
}
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(Integer.parseInt(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 if (op == '/'){
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();
}
}