第一章 概述
第一节 数据结构概述
数据结构 是一门研究非数值计算的程序设计学科,曾获图灵奖的 Pascal
之父Nicklaus Wirth
提出过一个有名的公式:
算法 + 数据结构 = 程序
数据结构 是计算机存储、组织数据的方式。数据结构 是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。
1.基本概念
(1)数据(Data)
数据是计算机程序处理对象的总称。例如在计算微分方程的时候处理对象就是一些数据,在图像处理中对象就是可编码的图像信息等。
(2)数据元素(Data Element)
数据的基本单位,可分为若干数据项,数据项为数据的最小单位。例如在一个学生成绩管理系统中,每个学生的成绩可以看成一个数据元素,而每个科目的成绩就可以看作不同的数据项。
(3)数据对象(Data Object)
是性质相同的数据元素的集合,是数据的一个子集。如实数的数据对象是实数集R。
(4)数据结构(Data Structure)
是相互之间存在一种或多种特定关系的数据元素的集合。
2.逻辑结构与物理结构
(1)逻辑结构
逻辑结构 是指数据对象中数据元素之间的逻辑关系,分为以下四种结构:
- 集合结构 :集合结构中的数据元素除了同属于一个集合外,它们之间没有任何关系
- 线性结构: 线性结构中的数据元素之间的关系是一对一的线性关系
- 树形结构: 树形结构中的元素存在一对多的层次关系
- 图形结构: 图形结构中的元素存在多对多的任意关系
(2)物理结构
物理结构 是指数据逻辑结构在计算机中的存储形式,一般有以下两种形式:
-
顺序存储结构: 顺序存储结构是把数据元素存放在地址连续的存储单元里,其数据建的逻辑关系和物理关系是一致的。如下图:
-
链式存储结构: 链式存储结构是把数据元素存放在任意的数据单元里,这组存储单元可以是连续的,也可以是不连续的。如下图:
第二节 算法概述
算法 是解决特定问题求解步骤的描述,在计算集中表现为指令的有限序列,并且每条指令表示一个或多个操作。
1. 算法的特性
算法具有五个基本特性:输入、输出、有穷性、确定性、可行性
- 输入: 算法应该具有零个或多个输入
- 输出: 算法至少具有一个或多个输出
- 有穷性: 有穷性是指算法在执行有限的的步骤之后自动结束,而不会出现无限循环,并且每一步都能在可以接受的时间内完成。
- 确定性: 算法的每一步骤都具有确定的意义
- 可行性: 算法的每一步都必须是可行的,每一步都能通过执行有限的次数完成
2. 算法设计的要求
算法设计有以下几种要求:
- 正确性: 算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反应问题的需求、能够得到问题的正确答案
- 可读性: 算法设计的另一个目的是为了便于阅读、理解和交流
- 健壮性: 当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名奇妙的结果
- 时间效率高和存储量低: 设计算法应该尽量满足时间效率高和存储量低的需求
3.算法效率的度量方法
(1)事后统计法
这种方法主要时通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序运行时间进行比较,从而确定算法效率的高低
缺陷:
- 必须实现编制好程序
- 时间的比较依赖计算机硬件和软件等环境因素
- 算法的测试数据设计困难,并且呈的运行时间往往还与测试数据的规模有很大关系
(2)事前分析估计方法
事前分析估算方法在计算机程序编制前,一句统计方法对算法进行估算
取决于以下因素:
- 算法采用策略、方法
- 编译产生的代码质量
- 问题输入规模
- 机器执行指令的速度
4. 算法时间复杂度
在进行算法分析时,语句总的执行次数 T(n)
是关于问题规模 n 的函数,进而分析T(n)
随 n 的变化情况确定 T(n)
的数量级。算法的时间复杂度也就是算法的时间度量,记作: T(n) = O(f(n))
常见的时间复杂度:
O ( 1 ) < O ( l o g n ) < O ( n l o g n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) {O(1) < O(log n) < O(nlog n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)} O(1)<O(logn)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
第二章 线性表(List)
线性表(List): 线性表表示零个或多个数据元素的有限序列
第一节 线性表的顺序存储(ArrayList)
1.概述
顺序存储的定义: 线性表的顺序存储结构指的是用一段地址连续的存储单元一次存储线性表的数据元素,相当于一维数组
通过顺序存储方式存储的线性表又称为 顺序表 (ArrayList)
顺序表的属性:
- data[]:实现顺序表的数组
- size:顺序表存储数据的数量
顺序表的方法:
·
2. 通过Java代码实现List和ArrayList
(1)创建List接口
package p1.interfaces;
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);//获取指定下标处元素
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);//线性表按照比较器内容排序
public List<E> subList(int fromIndex, int toIndex);//在原线性表中按[fromIndex,toIndex)区间截取子列表
}
(2)创建ArrayList类实现List接口
package p2.array.lists;
import p1.interfaces.List;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Objects;
public class ArrayList<E> implements List<E> {
private E[] data;
private int size = 0;
//默认容量
private static int DEFAULT_CAPACITY = 10;
//默认构造函数,创建一个默认容量的线性表
public ArrayList(){
data = (E[]) new Object[DEFAULT_CAPACITY];
size = 0;
}
//创建一个指定大小的线性表
public ArrayList(int capacity) {
if (capacity < 0){
throw new IllegalArgumentException("capacity are not be null");
}
DEFAULT_CAPACITY = capacity;
data = (E[]) new Object[DEFAULT_CAPACITY];
size = 0;
}
//将一个数组转换为线性表
public ArrayList(E[] array) {
if (array == null || array.length == 0){
throw new IllegalArgumentException("capacity are not be null");
}
data = (E[]) new Object[DEFAULT_CAPACITY];
for (int i = 0; i < array.length ; i++) {
add(array[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) {
throw new IllegalArgumentException("The element to be deleted does not exist");
}
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];
}
data[size-1] = null;
size --;
//有效元素是容量的四分之一及容量小于等于默认容量时,进行缩容
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 = element;
//data[index] = element;
return ret;
}
@Override
public int size() {
return size;
}
private int capacity(){
return data.length;
}
@Override
public int indexOf(E element) {
/*
* == 引用数据类型比较地址
* == 基本数据类型比较值
* */
for (int i = 0; i < size; i++) {
if (data[i] == element) {
return i;
}
}
return -1;
}
@Override
public boolean contains(E element) {
int count = indexOf(element);
boolean flag = false;
if (count != -1) {
flag = true;
}
return flag;
}
@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("index out of range");
}
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;
}
if (this == null) {
return false;
}
if (o instanceof ArrayList) {
ArrayList<E> other = (ArrayList<E>) o;
if (this.size != other.size) {
return false;
}
for (int i = 0; i < size; i++) {
if (!(data[i].equals(other.data[i]))) {
return false;
}
}
return true;
}
return false;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append('[');
if(isEmpty()){
stringBuilder.append(']');
}else{
for (int i = 0; i < size; i++) {
stringBuilder.append(data[i]);
if (i == size-1){
stringBuilder.append(']');
}else{
stringBuilder.append(',');
stringBuilder.append(' ');
}
}
}
return stringBuilder.toString();
}
@Override
public Iterator<E> iterator() {
return new ArrayListIterator();
}
class ArrayListIterator implements Iterator<E>{
private int cur = 0;
@Override
public boolean hasNext() {
return cur < size;
}
@Override
public E next() {
return data[cur++];
}
}
}
第二节 栈(Stack)
1.栈的定义
栈 是一种特殊的线性表,它限定仅在线性表的一段进行插入和删除操作,允许插入和删除的一端称为栈顶,另一端称为栈底。由于只在一端插入或删除,所以后插入的元素会比先插入的元素先删除,故栈又称为“后进先出”线性表
入栈(进栈、压栈):向栈内添加元素的过程
出栈 (弹栈):删除栈内元素的操作
2. 通过代码实现栈(Stack)和顺序栈(ArrayStack)
(1)定义Stack接口
package p1.interfaces;
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();//清空栈
}
(2)通过ArrayStack类实现Stack接口
package p2.array.lists;
import p1.interfaces.Stack;
import java.util.Iterator;
public class ArrayStack<E> implements Stack<E> {
ArrayList<E> list ;
public ArrayStack(){
list = new ArrayList<>();
}
public ArrayStack(int capacity){
list = new ArrayList<>(capacity);
}
public ArrayStack(E[] arr){
list = new ArrayList<>(arr);
}
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@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 void push(E element) {
list.add(list.size(), element);
}
@Override
public String toString() {
return list.toString();
}
@Override
public Iterator iterator() {
return list.iterator();
}
}
3. 栈的应用:中缀表达式的运算
(1)中缀表达式的定义
- 是一个通用的算术或逻辑公式表示方法, 操作符是以中缀形式处于操作数的中间(例:3 + 4)
- 与前缀表达式(例:+ 3 4)或后缀表达式(例:3 4 +)相比,中缀表达式不容易被计算机解析,但仍被许多程序语言使用,因为它符合人们的普遍用法。
- 与前缀或后缀记法不同的是,中缀记法中括号是必需的。
(2)中缀表达式运算实现过程概述
以 ( 10 + 20 / 2 ∗ 3 ) / 2 + 8 {(10+20/2*3)/2+8} (10+20/2∗3)/2+8 为例
首先,可以在中缀表达式中添加适当的分隔符,以便对这个表达式进行分割,初始化这个字符串
public static String initString(String expression){
//给所有非数字字符两端添加空格
//从左到右遍历字符串,使用stringbuilder来拼接
StringBuilder stringBuilder = new StringBuilder();
char c;
for (int i = 0; i < expression.length(); i++) {
c = expression.charAt(i);
if (c=='('||c==')'||c=='+'||c=='-'||c=='*'||c=='/'){
stringBuilder.append(',');
stringBuilder.append(c);
stringBuilder.append(',');
}else{
stringBuilder.append(c);
}
}
return stringBuilder.toString();
}
其次对这个字符串进行分割,定义两个栈,一个用来存放运算符,另一个用来存放数字,然后对这些栈进行操作。
private static int eValuete(String expression) {
ArrayStack<Character> operator = new ArrayStack<>();
ArrayStack<Integer> integers = new ArrayStack<>();
//1.初始化表达式,给每个符号两边加空格
expression = initString(expression);
//2.对字符串进行切割xz
String[] tokens = expression.split(",");
for (String token:tokens) {
//过滤空字符串
if (token.length() == 0){
continue;
}else if (token.equals("+") || token.equals("-")){//遍历到加减号时,看栈顶,栈顶无元素直接进
while(!operator.isEmpty() && (operator.peek() == '+' || operator.peek() == '-' || operator.peek() == '*' || operator.peek() == '/')){
//如果之前是+-*/或其他运算符,弹栈计算
processAnOperator(integers,operator);
}
operator.push(token.charAt(0));
}else if (token.equals("*") || token.equals("/")){
while(!operator.isEmpty() && (operator.peek() == '*' || operator.peek() == '/')){
//如果之前是+-*/或其他运算符,弹栈计算
processAnOperator(integers,operator);
}
operator.push(token.charAt(0));
}else if(token.equals("(")){
operator.push(token.charAt(0));
}else if(token.equals(")")){
while(operator.peek() != '('){
//如果之前是+-*/或其他运算符,弹栈计算
processAnOperator(integers,operator);
}
operator.pop();
}else{
integers.push(Integer.parseInt(token));
}
}
//处理最后一个操作符
while(!operator.isEmpty()){
processAnOperator(integers,operator);
}
return integers.pop();
}
//操作符栈弹栈一个元素,数字栈弹栈两个元素并运算,然后结果再进栈
private static void processAnOperator(ArrayStack<Integer> integers, ArrayStack<Character> operator) {
char op = operator.pop();
int num1 = integers.pop();
int num2 = integers.pop();
if (op == '+') {
integers.push(num2 + num1);
} else if (op == '-') {
integers.push(num2 - num1);
} else if (op == '*') {
integers.push(num2 * num1);
} else {
integers.push(num2 / num1);
}
}
最后,在用一个main()
测试这些函数
public static void main(String[] args) {
String expression = "(10+20/2*3)/2+8";
try {
int result = eValuete(expression);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Wrong expression :" + expression);
}
}