数据结构基础
Posted Polaris
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构基础相关的知识,希望对你有一定的参考价值。
一. 概述
1. 理解
1.1 数据结构与算法的关系
数据结构是一门研究组织数据方式的学科,有了编程语言也就有了数据结构。
程序 = 数据结构 + 算法
数据结构是算法的基础
1.2 线性结构和非线性结构
线性结构
-
作为最常用的数据结构,特点是数据元素之间存在一对一的线性关系。
-
包含两种不同的存储结构:顺序存储结构(如数组) 和 链式存储结构(如链表)。
-
顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的。
-
链式存储的线性表称为链表,链表的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息。
-
线性结构常见的有:数组,链表,栈,队列,哈希表(散列表)。
非线性结构
- 树形结构:二叉树,AVL树,红黑树,B树,堆,Trie,哈夫曼树,并查集...
- 图形结构:邻接矩阵,邻接表...
2. 代码测试工具
2.1 测试某段代码的运行时间
public class TimeUtils
private static final SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss.SSS");
public interface Task
void execute();
public static void test(String title, Task task)
if (task == null) return;
title = (title == null) ? "" : ("【" + title + "】");
System.out.println(title);
System.out.println("开始:" + fmt.format(new Date()));
long begin = System.currentTimeMillis();
task.execute();
long end = System.currentTimeMillis();
System.out.println("结束:" + fmt.format(new Date()));
double delta = (end - begin) / 1000.0;
System.out.println("耗时:" + delta + "秒");
System.out.println("-------------------------------------");
2.2 断言工具
public class Asserts
public static void test(boolean value)
try
if (!value) throw new Exception("测试未通过");
catch (Exception e)
e.printStackTrace();
2.3 Integer工具
public class IntegerUtils
/** 生成随机数 */
public static Integer[] random(int count, int min, int max)
if (count <= 0 || min > max) return null;
Integer[] array = new Integer[count];
int delta = max - min + 1;
for (int i = 0; i < count; i++)
array[i] = min + (int)(Math.random() * delta);
return array;
/** 合并两个数组 */
public static Integer[] combine(Integer[] array1, Integer[] array2)
if (array1 == null || array2 == null) return null;
Integer[] array = new Integer[array1.length + array2.length];
for (int i = 0; i < array1.length; i++)
array[i] = array1[i];
for (int i = 0; i < array2.length; i++)
array[i + array1.length] = array2[i];
return array;
public static Integer[] same(int count, int unsameCount)
if (count <= 0 || unsameCount > count) return null;
Integer[] array = new Integer[count];
for (int i = 0; i < unsameCount; i++)
array[i] = unsameCount - i;
for (int i = unsameCount; i < count; i++)
array[i] = unsameCount + 1;
return array;
/**
* 生成头部和尾部是升序的数组
* disorderCount:希望多少个数据是无序的
*/
public static Integer[] headTailAscOrder(int min, int max, int disorderCount)
Integer[] array = ascOrder(min, max);
if (disorderCount > array.length) return array;
int begin = (array.length - disorderCount) >> 1;
reverse(array, begin, begin + disorderCount);
return array;
/**
* 生成中间是升序的数组
* disorderCount:希望多少个数据是无序的
*/
public static Integer[] centerAscOrder(int min, int max, int disorderCount)
Integer[] array = ascOrder(min, max);
if (disorderCount > array.length) return array;
int left = disorderCount >> 1;
reverse(array, 0, left);
int right = disorderCount - left;
reverse(array, array.length - right, array.length);
return array;
/**
* 生成头部是升序的数组
* disorderCount:希望多少个数据是无序的
*/
public static Integer[] headAscOrder(int min, int max, int disorderCount)
Integer[] array = ascOrder(min, max);
if (disorderCount > array.length) return array;
reverse(array, array.length - disorderCount, array.length);
return array;
/**
* 生成尾部是升序的数组
* disorderCount:希望多少个数据是无序的
*/
public static Integer[] tailAscOrder(int min, int max, int disorderCount)
Integer[] array = ascOrder(min, max);
if (disorderCount > array.length) return array;
reverse(array, 0, disorderCount);
return array;
/** 升序生成数组 */
public static Integer[] ascOrder(int min, int max)
if (min > max) return null;
Integer[] array = new Integer[max - min + 1];
for (int i = 0; i < array.length; i++)
array[i] = min++;
return array;
/** 降序生成数组 */
public static Integer[] descOrder(int min, int max)
if (min > max) return null;
Integer[] array = new Integer[max - min + 1];
for (int i = 0; i < array.length; i++)
array[i] = max--;
return array;
/** 反转数组 */
private static void reverse(Integer[] array, int begin, int end)
int count = (end - begin) >> 1;
int sum = begin + end - 1;
for (int i = begin; i < begin + count; i++)
int j = sum - i;
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
/** 复制数组 */
public static Integer[] copy(Integer[] array)
return Arrays.copyOf(array, array.length);
/** 判断数组是否升序 */
public static boolean isAscOrder(Integer[] array)
if (array == null || array.length == 0) return false;
for (int i = 1; i < array.length; i++)
if (array[i - 1] > array[i]) return false;
return true;
/** 打印数组 */
public static void println(Integer[] array)
if (array == null) return;
StringBuilder string = new StringBuilder();
for (int i = 0; i < array.length; i++)
if (i != 0) string.append("_");
string.append(array[i]);
System.out.println(string);
二. 复杂度
1. 算法的效率问题
使用不同算法,解决同一个问题,效率可能相差非常大
1.1 求第n个斐波拉契数
-
斐波那契数列的排列是:0,1,1,2,3,5,8,13,21,34,55,89,144...
-
它后一个数等于前面两个数的和
public class FibonacciNumber
public static void main(String[] args)
//耗时:4.674秒
TimeUtils.test("求第n个斐波那契数:fib1", new TimeUtils.Task()
@Override
public void execute()
System.out.println(fib1(45));
);
//耗时:0.0秒
TimeUtils.test("求第n个斐波那契数:fib2", new TimeUtils.Task()
@Override
public void execute()
System.out.println(fib2(45));
);
/**
* 实现一:递归
* 时间复杂度:O(2^n)
*/
public static int fib1(int n)
if (n <= 1) return n;
return fib1(n - 1) + fib1(n - 2);
/**
* 实现二:循环
* 时间复杂度:O(n)
* <p>
* 0,1,2,3,4,5,6
* 0,1,1,2,3,5,8,13
*/
public static int fib2(int n)
if (n <= 1) return n;
int first = 0, second = 1;
// int sum = first + second;
// first = second;
// second = sum;
second += first;
first = second - first;
return second;
/**
* 实现三:线性代数解法 – 特征方程
* 时间复杂度:可视为O(1)
*/
public static int fib3(int n)
double c = Math.sqrt(5);
return (int) ((Math.pow((1 + c) / 2, n) - Math.pow((1 - c) / 2, n)) / c);
1.2 度量算法优劣的方法
事后统计
这种方法可行但是有两个问题:
- 一是要想对设计的算法的运行性能进行评测,需要实际运行该程序。
- 二是所得时间的统计量依赖于计算机的硬件、软件等环境因素, 这种方式,要在同一台计算机的相同状态下运行,才能比较那个算法速度更快。
事前估计
通过分析某个算法的时间复杂度,空间复杂度来判断哪个算法更优。
2 时间复杂度
2.1 理解
一般情况下,算法中的基本操作语句的重复执行次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n) / f(n) 的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作 T(n)=O( f(n) ),称O( f(n) ) 为算法的渐进时间复杂度,简称时间复杂度
T(n) 不同,但时间复杂度可能相同。 如:T(n)=n²+7n+6 与 T(n)=3n²+2n+2 它们的T(n) 不同,但时间复杂度相同,都为O(n²)。
2.2 大O表示法
一般用大O表示法来描述复杂度,它表示的是数据规模 n 对应的复杂度。如上述O( f(n) )
忽略常数、系数、低阶
-
9 => O(1)
-
2n + 3 => O(n)
-
n^2 + 2n + 6 => O(n^2 )
-
4n^3 + 3n^2 + 22n + 100 => O(n^3 )
注意:大O表示法仅仅是一种粗略的分析模型,是一种估算,能帮助我们短时间内了解一个算法的执行效率
2.4 对数阶的细节
对数阶一般省略底数:log2(n) = log2(9) * log9(n)
所以 log2(n)、log9(n)统称为logn
2.5 计算时间复杂度的方法
-
用常数1代替运行时间中的所有加法常数 T(n)=2n²+7n+6 => T(n)=2n²+7n+1
-
修改后的运行次数函数中,只保留最高阶项 T(n)=2n²+7n+1 => T(n) = 2n²
-
去除最高阶项的系数 T(n) = 2n² => T(n) = n² => O(n²)
2.6 常见的时间复杂度
- 常数阶O(1)
- 对数阶O(logn) //注意:底数不一定是2
- 线性阶O(n)
- 线性对数阶O(nlogn)
- 平方阶O(n^2)
- 立方阶O(n^3)
- k次方阶O(n^k)
- 指数阶O(2^n)
说明:
① 常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(logn)<Ο(n)<Ο(nlogn) <Ο(n2)<Ο(n3)< Ο(n^k) <Ο(2^n) <Ο(n^n),随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低
② 从图中可见,我们应该尽可能避免使用指数阶的算法
③ 对数阶一般忽略底数,所以log2n,log9n统称logn
2.7 时间复杂度练习
public class TimeComplexityTest
public static void test1(int n)
// 1
if (n > 10)
System.out.println("n > 10");
else if (n > 5) // 2
System.out.println("n > 5");
else
System.out.println("n <= 5");
// 1 + 4 + 4 + 4
for (int i = 0; i < 4; i++)
System.out.println("test");
// 14 => O(1)
public static void test2(int n)
// 1 + 3n => O(n)
for (int i = 0; i < n; i++)
System.out.println("test");
public static void test3(int n)
// 1 + 2n + n * (1 + 45)
// => 48n + 1 => O(n)
for (int i = 0; i < n; i++)
for (int j = 0; j < 15; j++)
System.out.println("test");
public static void test4(int n)
// n = 8 = 2^3 ,可以执行3次
// n = 16 = 2^4,可以执行4次
// => n = 2^k,可以执行log2(n)次
// log2(n) => O(logn)
while ((n = n / 2) > 0)
System.out.println("test");
public static void test5(int n)
// log5(n) => O(logn)
while ((n = n / 5) > 0)
System.out.println("test");
public static void test6(int n)
// i * 2^k = n
// => k = log2(n/i) = log2(n)
// 1 + log2(n) + log2(n)
for (int i = 1; i < n; i = i * 2)
// 1 + 3n
for (int j = 0; j < n; j++)
System.out.println("test");
// 1 + 2*log2(n) + log2(n) * (1 + 3n)
// => 1 + 3*log2(n) + 2 * nlog2(n)
// => O(nlogn)
public static void test7(int n)
// 1 + 2n + n * (1 + 3n)
// => 3n^2 + 3n + 1 => O(n^2)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
System.out.println("test");
public static void test8(int n,int k)
//n
for(int i = 0;i < n;i++)
System.out.println("test");
//k
for(int i = 0;i < k;i++)
System.out.println("test");
//复杂度:O(n+k)
2.8 平均时间复杂度和最坏时间复杂度
平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。
最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。 这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长
平均时间复杂度和最坏时间复杂度是否一致,和算法本身有关
2.9 均摊复杂度
什么情况下使用均摊复杂度:经过连续的多次复杂度比较低的情况后,出现个别复杂度比较高的情况。
案例:动态数组的扩容
2.10 复杂度震荡
什么是复杂度震荡:在一些特殊的情况下,某个级别的复杂度猛地蹿到了另一个级别,并且持续这一级别不恢复,则说明产生了复杂度震荡。
案例:动态数组扩容倍数、缩容时机设计不得当(扩容倍数*缩容倍数=1),有可能会导致复杂度震荡。
3. 空间复杂度
类似于时间复杂度的讨论,一个算法的空间复杂度(Space Complexity)定义为该算法所耗费的存储间,它也是问题规模n的函数。
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。有的算法需要占用的临时工作单元数与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元,例如快速排序和归并排序算法就属于这种情况.
在做算法分析时,主要讨论的是时间复杂度。从用户使用体验上看,更看重的程序执行的速度。一些缓存产品(redis, memcache)和算法(基数排序)本质就是用空间换时间。
4. 算法的优化方向
-
用尽量少的存储空间
-
用尽量少的执行步骤(执行时间)
-
根据情况,可以选择空间换时间,也可以时间换空间
三. 线性结构
1. 动态数组ArrayList
1.1 理解
数组是一种顺序存储的线性表,所有元素的内存地址是连续的。
在很多编程语言中,数组都有个致命的缺点:无法动态修改容量。实际开发中,我们更希望数组的容量是可以动态改变的。
1.1 属性设计
1.2 接口设计
注意与ArrayList源码对比分析
public interface List<E>
/** 元素数量 */
int size();
/** 是否为空 */
boolean isEmpty();
/** 是否包含某个元素 */
boolean contains(E element);
/** 添加元素到末尾 */
void add(E element);
/** 获取index位置的元素 */
E get(int index);
/** 设置index位置的元素 */
E set(int index,E element);
/** 往index位置添加元素 */
void add(int index,E element);
/** 删除index位置对应的元素 */
E remove(int index);
/** 查看元素的位置 */
int indexOf(E element);
/** 清除所有元素 */
void clear();
1.3 图解方法
添加元素-add(E element)
添加元素-add(int index,E element)
删除元素-remove(int index)
如何扩容
1.4 实现
public class ArrayList<E> implements List<E>
/** 元素数量 */
private int size = 0;
/** 所有元素 */
private E[] elements;
/** 默认容量 */
private static final int DEFAULT_CAPACITY = 10;
/** 元素未找到返回的下标 */
private static final int ELEMENT_NOT_FOUND = -1;
public ArrayList()
this(DEFAULT_CAPACITY);
public ArrayList(int capaticy)
capaticy = capaticy < DEFAULT_CAPACITY ? DEFAULT_CAPACITY : capaticy;
elements = (E[])new Object[capaticy];
/**
* @Description 判断下标是否越界
*/
private void indexCheck(int index)
if(index < 0 || index > size)
throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);
/**
* @Description 数组容量不够则扩容
*/
private void ensureCapacity(int size)
int oldCapacity = elements.length;
if(size < oldCapacity) return;
int newCapacity = oldCapacity + (oldCapacity >> 1);//1.5倍
E[] newElements = (E[])new Object[newCapacity];
// for (int i = 0; i < size; i++)
// newElements[i] = elements[i];
//
System.arraycopy(elements,0,newElements,0,elements.length);
elements = newElements;
System.out.println("扩容:" + oldCapacity + "=>" + newCapacity);
/**
* @Description 数组容量太多则缩容
*/
private void trim()
int oldCapacity = elements.length;
if(size >= (oldCapacity >> 1)) return;
if(oldCapacity <= DEFAULT_CAPACITY) return;
//剩余空间很多,可以缩容
int newCapacity = oldCapacity >> 1;
E[] newElements = (E[])new Object[newCapacity];
// for (int i = 0; i < size; i++)
// newElements[i] = elements[i];
//
System.arraycopy(elements,0,newElements,0,elements.length);
elements = newElements;
System.out.println("缩容:" + oldCapacity + "=>" + newCapacity);
/**
* @Description 是否为空
* @return
*/
public boolean isEmpty()
return size == 0;
/**
* @Description 元素的数量
* @return size
*/
public int size()
return size;
/**
* @Description 往index位置添加元素
* @param index
* @param element
* @return
*/
public void add(int index, E element)
//最好复杂度:O(1)、最坏复杂度:O(n)、平均复杂度:O(n)
indexCheck(index);
ensureCapacity(size);
for(int i = size;i > index;i--)
elements[i] = elements[i - 1];
elements[index] = element;
size++;
/**
* @Description 添加元素到最后面
* @param element
*/
public void add(E element)
//最好:O(1)
//最坏:O(n) => 扩容的情况
//平均:O(1)
//均摊复杂度:O(1) =>把扩容情况均摊到每一种情况去
// (一般均摊等于最好)。
//什么情况下使用均摊复杂度:经过连续的多次复杂度比较低的
// 情况后,出现个别复杂度比较高的情况。
add(size, element);
/**
* @Description 删除index位置对应的元素
* @param index
* @return oldEle
*/
public E remove(int index)
//最好复杂度:O(1)、最坏复杂度:O(n)、平均复杂度:O(n)
indexCheck(index);
E oldEle = elements[index];
if(index != size - 1)
for(int i = index;i < size;i++)
elements[i] = elements[i + 1];
elements[--size] = null;//内存管理细节
trim(); //内存紧张考虑缩容
return oldEle;
/**
* @Description 删除某个元素
* @param element
*/
public void remove(E element) //O(1)
remove(indexOf(element));
/**
* @Description 设置index位置的元素
* @param index
* @param element
* @return
*/
public E set(int index, E element) //O(1)
indexCheck(index);
E old = elements[index];
elements[index] = element;
return old;
/**
* @Description 返回index位置对应的元素
* @param index
* @return
*/
public E get(int index)
indexCheck(index);
return elements[index];
/**
* @Description 查看元素的位置
* @param element
* @return
*/
public int indexOf(E element)
if(element == null)
for (int i = 0; i < size; i++)
if(elements[i] == null) return i;
else
for (int i = 0; i < size; i++)
if(element.equals(elements[i])) return i;
return ELEMENT_NOT_FOUND;
/**
* @Description 是否包含某个元素
* @param element
* @return
*/
public boolean contains(E element)
return indexOf(element) != ELEMENT_NOT_FOUND;
/**
* @Description 清除所有元素
*/
public void clear()
//方法一:只是访问不到了,数组每一个位置对应的对象还存在。
// 当对某个位置再次add操作时,此位置存储的地址值对
// 应以前的对象才会被销毁。
//size = 0;
//方法二:对每一个位置对应的对象地址值置空(内存管理细节)
for (int i = 0; i < size; i++)
elements[i] = null;
size = 0;
@Override
public String toString()
StringBuilder str = new StringBuilder();
str.append("size=").append(size).append(" : [");
for (int i = 0; i < size; i++)
if(i != 0) str.append(", ");
str.append(elements[i]);
str.append("]");
return str.toString();
2. 单向链表LinkedList
2.1 理解
动态数组有个明显的缺点:可能会造成内存空间的大量浪费。能否用到多少就申请多少内存?链表可以办到这一点。
链表存储结构的特点:
- 链表是一种链式存储的线性表,通过指针域描述数据元素之间的逻辑关系,不需要地址连续的存储空间。
- 动态存储空间分配,即时申请即时使用。
- 访问第i个元素,必须顺序依此访问前面的1 ~ i-1的数据元素,也就是说是一种顺序存取结构。
插入/删除操作不需要移动数据元素。
注意:
① Java中如何实现“指针”:Java中的对象引用变量并不是存储实际数据,而是存储该对象在内存中的存储地址。
② 链表分为带头节点的链表和没有头节点的链表,根据实际的需求来确定。
2.2 图解方法
2.3 实现
class SingleLinkedList<E> implements List<E>
/**
* 元素的数量
*/
private int size;
/**
* 指向第一个节点的指针
*/
private Node<E> first;
/**
* 元素未找到返回的下标
*/
private static final int ELEMENT_NOT_FOUND = -1;
/**
* @Description 判断下标是否越界
*/
private void indexCheck(int index)
if(index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index:"
+ index + ",Size:" + size);
/**
* @Description 获取index位置对应的节点
* @return
*/
private Node<E> getNode(int index)
indexCheck(index);
Node<E> temp = first;
for(int i = 0;i < index;i++)
temp = temp.next;
return temp;
/**
* @Description 是否为空
* @return
*/
public boolean isEmpty()
return size == 0;
/**
* @Description 元素的数量
* @return size
*/
public int size()
return size;
/**
* @Description 往index位置添加元素
* @param index
* @param element
* @return
*/
public void add(int index, E element)
if(index < 0 || index > size)
throw new IndexOutOfBoundsException("Index:"
+ index + ",Size:" + size);
if (index == 0)
first = new Node<>(element, first);
else
Node<E> prev = getNode(index - 1);
prev.next = new Node<>(element, prev.next);
size++;
/**
* @Description 添加元素到最后面
* @param element
*/
public void add(E element)
add(size, element);
/**
* @Description 删除index位置对应的元素
* @param index
* @return oldEle
*/
public E remove(int index)
indexCheck(index);
Node<E> node = first;
if (index == 0)
first = first.next;
else
Node<E> prev = getNode(index - 1);
node = prev.next;
prev.next = node.next;
size--;
return node.element;
/**
* @Description 设置index位置的元素
* @param index
* @param element
* @return
*/
public E set(int index, E element)
Node<E> node = getNode(index);
E oldElement = node.element;
node.element = element;
return oldElement;
/**
* @Description 返回index位置对应的元素
* @param index
* @return
*/
public E get(int index)
return getNode(index).element;
/**
* @Description 查看元素的位置
* @param element
* @return
*/
public int indexOf(E element)
if (element == null)
Node<E> node = first;
for (int i = 0; i < size; i++)
if (node.element == null) return i;
node = node.next;
else
Node<E> node = first;
for (int i = 0; i < size; i++)
if (element.equals(node.element)) return i;
node = node.next;
return ELEMENT_NOT_FOUND;
/**
* @Description 是否包含某个元素
* @param element
* @return
*/
public boolean contains(E element)
return indexOf(element) != ELEMENT_NOT_FOUND;
/**
* @Description 清除所有元素
*/
public void clear()
size = 0;
first = null;
@Override
public String toString()
Node<E> temp = first;
StringBuilder str = new StringBuilder();
for(int i = 0;i < size;i++)
if(i != 0)
str.append(",");
str.append(temp.element);
temp = temp.next;
return "size=" + size + ", [" + str + "]";
/**
* @Description 节点内部类
*/
private static class Node<E>
E element;
Node<E> next;
public Node(E element, Node<E> next)
this.element = element;
this.next = next;
@Override
public String toString()
return element + "";
3. 双向链表LinkedList
3.1 单向链表缺点
单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
单向链表不能自我删除,需要靠辅助节点 ,而双向链表,则可以自我删除,所以前面我们单链表删除节点,总是要先找到待删除节点的前一个节点。
3.2 实现
public class LinkedList<E> implements List<E>
/**
* 元素的数量
*/
private int size;
/**
* 指向第一个节点的指针
*/
private Node<E> first;
/**
* 指向最后一个节点的指针
*/
private Node<E> last;
/**
* 元素未找到返回的下标
*/
private static final int ELEMENT_NOT_FOUND = -1;
/**
* @Description 判断下标是否越界
*/
private void indexCheck(int index)
if(index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index:"
+ index + ",Size:" + size);
/**
* @Description 获取index位置对应的节点
* @return
*/
private Node<E> getNode(int index)
indexCheck(index);
if(index < (size << 1))
Node<E> temp = first;
for(int i = 0;i < index;i++)
temp = temp.next;
return temp;
else
Node<E> temp = last;
for(int i = size - 1;i > index;i--)
temp = temp.prev;
return temp;
/**
* @Description 是否为空
* @return
*/
public boolean isEmpty()
return size == 0;
/**
* @Description 元素的数量
* @return size
*/
public int size()
return size;
/**
* @Description 往index位置添加元素
* @param index
* @param element
* @return
*/
public void add(int index, E element)
if(index < 0 || index > size)
throw new IndexOutOfBoundsException("Index:"
+ index + ",Size:" + size);
if(index == size) //往最后面添加元素时
Node<E> oldLast = last;
last = new Node<E>(oldLast,element,null);
if(oldLast == null) //链表添加第一个元素时
first = last;
else
oldLast.next = last;
else
Node<E> next = getNode(index);
Node<E> prev = next.prev;
Node<E> node = new Node<E>(prev,element,next);
next.prev = node;
if(prev == null) //=>index == 0时
first = node;
else
prev.next = node;
size++;
/**
* @Description 添加元素到最后面
* @param element
*/
public void add(E element)
add(size, element);
/**
* @Description 删除index位置对应的元素
* @param index
* @return oldEle
*/
public E remove(int index)
indexCheck(index);
Node<E> node = getNode(index);
Node<E> prev = node.prev;
Node<E> next = node.next;
if(prev == null) //index == 0
first = next;
else
prev.next = next;
if(next == null) //index == size - 1
last = prev;
else
next.prev = prev;
size--;
return node.element;
/**
* @Description 设置index位置的元素
* @param index
* @param element
* @return
*/
public E set(int index, E element)
Node<E> node = getNode(index);
E oldElement = node.element;
node.element = element;
return oldElement;
/**
* @Description 返回index位置对应的元素
* @param index
* @return
*/
public E get(int index)
return getNode(index).element;
/**
* @Description 查看元素的位置
* @param element
* @return
*/
public int indexOf(E element)
if (element == null)
Node<E> node = first;
for (int i = 0; i < size; i++)
if (node.element == null) return i;
node = node.next;
else
Node<E> node = first;
for (int i = 0; i < size; i++)
if (element.equals(node.element)) return i;
node = node.next;
return ELEMENT_NOT_FOUND;
/**
* @Description 是否包含某个元素
* @param element
* @return
*/
public boolean contains(E element)
return indexOf(element) != ELEMENT_NOT_FOUND;
/**
* @Description 清除所有元素
*/
public void clear()
size = 0;
first = null;
last = null;
/*
* gc root对象:① 被栈指针指向的对象,如new LinkedList()
*
* => 只要断掉first和last,当前链表不被gc root对象指向就
* 会被回收。
*/
@Override
public String toString()
Node<E> temp = first;
StringBuilder str = new StringBuilder();
for(int i = 0;i < size;i++)
if(i != 0)
str.append(",");
str.append(temp);
temp = temp.next;
return "size=" + size + ", [" + str + "]";
/**
* @Description 节点内部类
*/
private static class Node<E>
Node<E> prev;
E element;
Node<E> next;
public Node(Node<E> prev,E element, Node<E> next)
this.prev = prev;
this.element = element;
this.next = next;
@Override
public String toString()
StringBuilder str = new StringBuilder();
if(prev != null)
str.append(prev.element);
str.append("_").append(element).append("_");
if(next != null)
str.append(next.element);
return str + "";
3.3 ArrayList与LinkedList对比
ArrayList开辟,销毁内存空间的次数相对较少,但可能造成内存空间浪费(缩容解决)。LinkedList开辟、销毁内存空间的次数相对较多,但不会造成内存空间的浪费。
如果频繁在尾部进行添加,删除操作,动态数组与双向链表均可选择。
如果频繁在头部进行添加,删除操作,建议选择使用双向链表。
如果有频繁的(在任意位置)添加,删除操作,建议选择双向链表。
如果有频繁的查询操作(随机访问操作),建议选择动态数组。
是否有了双向链表,单向链表就没任何用处了? => 并非如此,在哈希表的设计中就用到了单链表。
4. 循环链表LinkedList
4.1 单向循环链表
注意:单向循环链表相对于单链表(SingleLinkedList)只需修改添加和删除。
/**
* @Description 往index位置添加元素
* @param index
* @param element
* @return
*/
public void add(int index, E element)
if(index < 0 || index > size)
throw new IndexOutOfBoundsException("Index:"
+ index + ",Size:" + size);
if (index == 0)
Node<E> newFirst = new Node<>(element, first);
//拿到最后一个节点
Node<E> last = (size == 0) ? newFirst : getNode(size - 1);
last.next = newFirst;
first = newFirst;
else
Node<E> prev = getNode(index - 1);
prev.next = new Node<>(element, prev.next);
size++;
/**
* @Description 删除index位置对应的元素
* @param index
* @return oldEle
*/
public E remove(int index)
indexCheck(index);
Node<E> node = first;
if (index == 0)
if(size == 1)
first = null;
else
//拿到最后一个节点,注意一定要在改变first之前
Node<E> last = getNode(size - 1);
first = first.next;
last.next = first;
else
Node<E> prev = getNode(index - 1);
node = prev.next;
prev.next = node.next;
size--;
return node.element;
4.2 双向循环链表
注意:双向循环链表相对于双向链表(LinkedList)只用修改添加和删除。
/**
* @Description 往index位置添加元素
* @param index
* @param element
* @return
*/
public void add(int index, E element)
if(index < 0 || index > size)
throw new IndexOutOfBoundsException("Index:"
+ index + ",Size:" + size);
if(index == size) //往最后面添加元素时
Node<E> oldLast = last;
last = new Node<E>(oldLast,element,first);
if(oldLast == null) //链表添加第一个元素时
first = last;
first.next = first;
first.prev = first;
else
oldLast.next = last;
first.prev = last;
else
Node<E> next = getNode(index);
Node<E> prev = next.prev;
Node<E> node = new Node<E>(prev,element,next);
next.prev = node;
prev.next = node;
if(index == 0) //=>index == 0时
first = node;
size++;
/**
* @Description 删除index位置对应的元素
* @param index
* @return node.element
*/
public E remove(int index)
indexCheck(index);
Node<E> node = first;
if(size == 1)
first = null;
last = null;
else
node = getNode(index);
Node<E> prev = node.prev;
Node<E> next = node.next;
prev.next = next;
next.prev = prev;
if(index == 0) //index == 0
first = next;
if(index == size - 1) //index == size - 1
last = prev;
size--;
return node.element;
4.3 约瑟夫问题 (单向循环链表的应用)
约瑟夫问题:设编号为1,2,3...n的n个人围成一圈,约定编号为 k (1 <= k <= n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依此类推,直到所有人出列为止,由此产生一个出列编号的序列。
注意:约瑟夫问题也可以用其它数据结构解决,不一定要用循环链表,但是循环链表解决此问题很简单。
使用循环链表解决约瑟夫问题
为了发挥循环链表的最大威力,可对CircleLinkedList做如下改进:
public class LinkedListTest
@Test
public void test1()
CircleLinkedListForJosephus<Integer> list
= new CircleLinkedListForJosephus<Integer>();
for(int i = 1; i <= 8;i++)
list.add(i);
//current指向头节点
list.reset();
while(!list.isEmpty())
list.next();
list.next();
System.out.print(list.remove() + " ");//数了三次后删除
//3 6 1 5 2 8 4 7
class CircleLinkedListForJosephus<E> implements List<E>
/**
* 元素的数量
*/
private int size;
/**
* 指向第一个节点的指针
*/
private Node<E> first;
/**
* 指向最后一个节点的指针
*/
private Node<E> last;
/**
* 用于指向某个节点的指针
*/
private Node<E> current;
/**
* 元素未找到返回的下标
*/
private static final int ELEMENT_NOT_FOUND = -1;
/**
* @Description 判断下标是否越界
*/
private void indexCheck(int index)
if(index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index:"
+ index + ",Size:" + size);
/**
* @Description 获取index位置对应的节点
* @return
*/
private Node<E> getNode(int index)
indexCheck(index);
if(index < (size << 1))
Node<E> temp = first;
for(int i = 0;i < index;i++)
temp = temp.next;
return temp;
else
Node<E> temp = last;
for(int i = size - 1;i > index;i--)
temp = temp.prev;
return temp;
/**
* @Description 让current指向头节点
*/
public void reset()
current = first;
/**
* @Description 让current后移一步
* @return
*/
public E next()
if(current == null) return null;
current = current.next;
return current.element;
/**
* @Description 删除current所指向的节点,并将current下移
* @return
*/
public E remove()
if(current == null) return null;
Node<E> next = current.next;
int index = indexOf(current.element);
E element = remove(index);
if(size == 0)
current = null;
else
current = next;
return element;
/**
* @Description 是否为空
* @return
*/
public boolean isEmpty()
return size == 0;
/**
* @Description 元素的数量
* @return size
*/
public int size()
return size;
/**
* @Description 往index位置添加元素
* @param index
* @param element
* @return
*/
public void add(int index, E element)
if(index < 0 || index > size)
throw new IndexOutOfBoundsException("Index:"
+ index + ",Size:" + size);
if(index == size) //往最后面添加元素时
Node<E> oldLast = last;
last = new Node<E>(oldLast,element,first);
if(oldLast == null) //链表添加第一个元素时
first = last;
first.next = first;
first.prev = first;
else
oldLast.next = last;
first.prev = last;
else
Node<E> next = getNode(index);
Node<E> prev = next.prev;
Node<E> node = new Node<E>(prev,element,next);
next.prev = node;
prev.next = node;
if(index == 0) //=>index == 0时
first = node;
size++;
/**
* @Description 添加元素到最后面
* @param element
*/
public void add(E element)
add(size, element);
/**
* @Description 删除index位置对应的元素
* @param index
* @return node.element
*/
public E remove(int index)
indexCheck(index);
Node<E> node = first;
if(size == 1)
first = null;
last = null;
else
node = getNode(index);
Node<E> prev = node.prev;
Node<E> next = node.next;
prev.next = next;
next.prev = prev;
if(index == 0) //index == 0
first = next;
if(index == size - 1) //index == size - 1
last = prev;
size--;
return node.element;
/**
* @Description 设置index位置的元素
* @param index
* @param element
* @return
*/
public E set(int index, E element)
Node<E> node = getNode(index);
E oldElement = node.element;
node.element = element;
return oldElement;
/**
* @Description 返回index位置对应的元素
* @param index
* @return
*/
public E get(int index)
return getNode(index).element;
/**
* @Description 查看元素的位置
* @param element
* @return
*/
public int indexOf(E element)
if (element == null)
Node<E> node = first;
for (int i = 0; i < size; i++)
if (node.element == null) return i;
node = node.next;
else
Node<E> node = first;
for (int i = 0; i < size; i++)
if (element.equals(node.element)) return i;
node = node.next;
return ELEMENT_NOT_FOUND;
/**
* @Description 是否包含某个元素
* @param element
* @return
*/
public boolean contains(E element)
return indexOf(element) != ELEMENT_NOT_FOUND;
/**
* @Description 清除所有元素
*/
public void clear()
size = 0;
first = null;
last = null;
/*
* gc root对象:① 被栈指针指向的对象,如new LinkedList()
*
* => 只要断掉first和last,当前链表不被gc root对象指向就
* 会被回收。
*/
@Override
public String toString()
Node<E> temp = first;
StringBuilder str = new StringBuilder();
for(int i = 0;i < size;i++)
if(i != 0)
str.append(",");
str.append(temp);
temp = temp.next;
return "size=" + size + ", [" + str + "]";
/**
* @Description 节点内部类
*/
private static class Node<E>
Node<E> prev;
E element;
Node<E> next;
public Node(Node<E> prev,E element, Node<E> next)
this.prev = prev;
this.element = element;
this.next = next;
@Override
public String toString()
StringBuilder str = new StringBuilder();
if(prev != null)
str.append(prev.element);
str.append("_").append(element).append("_");
if(next != null)
str.append(next.element);
return str + "";
5. 栈(stack)
5.1 理解
栈是一个先入后出
(FILO => First In Last Out)的有序列表。往栈中添加元素的操作,一般叫做入栈(push)
。从栈中移除元素的操作,一般叫做 出栈(pop)
,注意只能移除栈顶元素,也叫做弹出栈顶元素。
栈是限制线性表中元素的插入和删除 只能在线性表的同一端
进行的一种特殊线性表。允许插入和删除的一端为变化的一端,称为 栈顶(Top)
,另一端为固定的一端,称为 栈底(Bottom)
。
出栈(pop)和入栈(push)的概念如下:
注意:这里说的“栈”与内存中的“栈空间”是两个不同的概念。
5.2 栈的应用场景
-
子程序的调用
:在跳往子程序前,会先将下一个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。 -
处理递归调用
:和子程序的调用类似,只是除了存储下一个指令的地址外,也将参数,区域变量等数据存入堆栈中。 -
表达式的转换与求值(实际解决)
:如中缀表达式转后缀表达式 -
二叉树的遍历
-
图形的深度优先(depth-first)搜索法
5.3 ArrayList模拟栈
public class StackTest
@Test
public void test1()
ArrayListStack<Integer> stack = new ArrayListStack<Integer>();
stack.push(11);
stack.push(22);
stack.push(33);
stack.push(44);
System.out.println(stack.peek());//44
stack.list();
while(!stack.isEmpty())
System.out.print(stack.pop() + " ");
//44 33 22 11
System.out.println(stack.isEmpty());
class ArrayListStack<E>
private List<E> list = new ArrayList<E>();
//栈的长度
public int size()
return list.size();
// 判断栈空
public boolean isEmpty()
return list.isEmpty();
// 入栈
public void push(E element)
list.add(element);
// 出栈
public E pop()
return list.remove(list.size() - 1);
// 获取栈顶元素
public E peek()
return list.get(list.size() - 1);
//遍历栈
public void list()
if(isEmpty())
System.out.println("栈空,无数据!");
return;
for (int i = list.size() - 1; i >= 0; i--)
System.out.println("stack[" + i + "] = " + list.get(i));
5.4 LinkedList模拟栈
//只需要将以上代码的
private List<E> list = new ArrayList<E>();
//改为
private List<E> list = new LinkedList<E>();
5.5 栈的应用-综合计算器(自定义优先级)
即使用栈计算一个中缀表达式的结果
public class Calculator
public static void main(String[] args)
String expression = "7*2*2-5+1+4/2";
calculator(expression);
//表达式 7*2*2-5+1+4/2 的结果为:26
public static void calculator(String expression)
ArrayListStack2<Integer> numStack = new ArrayListStack2<>();
ArrayListStack2<Integer> operStack = new ArrayListStack2<>();
int index = 0;//用于扫描
int num1 = 0;
int num2 = 0;
int oper = 0;
int res = 0;
char ch = \' \';//将每次扫描得到的字符保存到ch
String keepNum = "";//用于拼接多位数
//开始循环扫描expression
while(true)
ch = expression.substring(index, index + 1).charAt(0);
if(operStack.isOper(ch))
if(!operStack.isEmpty())
if(operStack.priority(ch) <=
operStack.priority(operStack.peek()))
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1, num2, oper);
//将运算结果入数栈
numStack.push(res);
//将操作符入符号栈
operStack.push(ch + 0);
else
//当前操作符的优先级大于栈中的操作符优先级,直接入符号栈
operStack.push(ch + 0);
else
//符号栈为空就直接入符号栈
operStack.push(ch + 0);
else
//如果是数就直接入数栈
//numStack.push(ch - 48);//\'1\' => 1 (只能处理一位数)
//能够处理多位数的思路:
//当处理数时,需要向expression表达式的index后再看一位,
//如果是数就拼接并继续扫描,是符号才入栈
keepNum += ch;
if(index == expression.length() - 1)
//如果ch已经是expression的最后一位,就直接入栈
numStack.push(Integer.parseInt(keepNum));
else
if(operStack.isOper(expression.substring(index + 1, index + 2)
.charAt(0)))
numStack.push(Integer.parseInt(keepNum));
//将keepNum清空
keepNum = "";
//使index + 1,并判断是否扫描到expression的最后
index++;
if(index >= expression.length())
break;
//当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号并运算
while(true)
if(operStack.isEmpty())
break;
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1, num2, oper);
numStack.push(res);
//将数栈的最后数pop出,得到结果
int res2 = numStack.pop();
System.out.println("表达式 " + expression
+ " 的结果为:" + res2);
//数组模拟栈,需要扩展一些功能
class ArrayListStack2<E>
private List<E> list = new ArrayList<E>();
//栈的长度
public int size()
return list.size();
// 判断栈空
public boolean isEmpty()
return list.isEmpty();
// 入栈
public void push(E element)
list.add(element);
// 出栈
public E pop()
return list.remove(list.size() - 1);
// 获取栈顶元素
public E peek()
return list.get(list.size() - 1);
//遍历栈
public void list()
if(isEmpty())
System.out.println("栈空,无数据!");
return;
for (int i = list.size() - 1; i >= 0; i--)
System.out.println("stack[" + i + "] = " + list.get(i));
//返回运算符的自定义优先级(假定优先级使用数字表示)
public int priority(int oper)
if(oper == \'*\' || oper == \'/\')
return 1;
else if(oper == \'+\' || oper == \'-\')
return 0;
else
return -1;//假定目前的表达式只有+-*/
//判断当前字符是否是一个运算符
public boolean isOper(char val)
return val == \'+\' || val == \'-\' || val == \'*\' || val == \'/\';
//计算两个操作数的方法
public int cal(int num1,int num2,int oper)
int res = 0;
switch(oper)
case \'+\':
res = num2 + num1;
break;
case \'-\':
res = num2 - num1;
break;
case \'*\':
res = num2 * num1;
break;
case \'/\':
res = num2 / num1;
break;
default:
break;
return res;
5.6 栈的应用-逆波兰计算器
逆波兰表达式(前缀表达式)
前缀表达式的运算符位于操作数之前。
前缀表达式的计算机求值:从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
//举例:(3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:
//① 从右至左扫描,将6、5、4、3压入堆栈
//② 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
//③ 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
//④ 最后是-运算符,计算出35-6的值,即29,由此得出最终结果
中缀表达式
中缀表达式就是常见的运算表达式,如(3+4)×5-6
中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(上述案例就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式)
后缀表达式
与前缀表达式相似,只是运算符位于操作数之后
后缀表达式的计算机求值
从左至右
扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
//例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
//① 从左至右扫描,将3和4压入堆栈;
//② 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
//③ 将5入栈;
//④ 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
//⑤ 将6入栈;
//⑥ 最后是-运算符,计算出35-6的值,即29,由此得出最终结果
中缀表达式转换为后缀表达式
后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,需要将中缀表达式转成后缀表达式。
具体步骤:
//1.初始化两个栈:运算符栈s1和储存中间结果的栈s2;
//2.从左至右扫描中缀表达式;
//3.遇到操作数时,将其压s2;
//4.遇到运算符时,比较其与s1栈顶运算符的优先级:
// ① 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
// ② 否则,若优先级比栈顶运算符的高,也将运算符压入s1;
// ③ 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶
// 运算符相比较;
//5.遇到括号时:
// ① 如果是左括号“(”,则直接压入s1
// ② 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号
// 为止,此时将这一对括号丢弃
//6.重复步骤2至5,直到表达式的最右边
//7.将s1中剩余的运算符依次弹出并压入s2
//8.依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
使用栈实现逆波兰计算器(计算整数)
public class ReversePolishCalculate
//直接输入一个后缀表达式计算结果
@Test
public void test1()
// 定义一个逆波兰表达式
// 为了方便,逆波兰表达式的数字和符号使用空格隔开
// (30+4)*5-6 => 30 4 + 5 * 6 - => 29
// 4*5-8+60+8/2 => 4 5 * 8 - 60 + 8 2 / + => 76
String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
// 思路:
// ① 先将 "3 4 + 5 * 6 -" 放入ArrayList中
// ② 将ArrayList 传递给一个方法,遍历ArrayList配合栈完成计算
List<String> list = getListString(suffixExpression);
System.out.println(list);
int res = calculate(list);
System.out.println("计算的结果是 " + res);
// 完成将一个中缀表达式转为后缀表达式的功能并计算结果
@Test
public void test2()
// 思路:
// ① 直接对str操作不方便,因此先将中缀表达式字符串转换为List
// ② 中缀表达式对应的list => 后缀表达式对应的list
String expression = "1+((2+3)*4)-5";
List<String> list = toInfixExpressionList(expression);
System.out.println(list);
// [1, +, (, (, 2, +, 3, ), *, 4, ), -, 5]
List<String> list2 = parseSuffixExpressionList(list);
System.out.println(list2);
//[1, 2, 3, +, 4, *, +, 5, -]
System.out.println("expression的计算结果为:" + calculate(list2));//16
// 将一个中缀表达式转换成对应的List
public static List<String> toInfixExpressionList(String s)
List<String> ls = new ArrayList<String>();
int i = 0;// 用于遍历中缀表达式字符串s的指针
String str;// 对多位数的拼接
char c;// 每遍历一个字符,就放入c
do
// \'0\' => [48] \'9\' => [57]
if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57)
// 如果c是一个非数字,就加入ls中
ls.add("" + c);
i++;// i需要后移
else
// 如果是一个数字,需要考虑多位数
str = "";// 先将str置空
while (i < s.length() && (c = s.charAt(i)) > 48 && (c = s.charAt(i)) <= 57)
str += c;// 拼接
i++;
ls.add(str);
while (i < s.length());
return ls;
//中缀表达式对应的list => 后缀表达式对应的list
public static List<String> parseSuffixExpressionList(List<String> ls)
Stack<String> s1 = new Stack<String>();// 符号栈
// 注意:因为s2这个栈在整个转换过程中没有pop操作且后面要逆序输出,
// 很麻烦,所以用ArrayList代替。
// Stack<String> s2 = new Stack<String>();//存储中间结果的栈
List<String> s2 = new ArrayList<String>();// 存储中间结果的List
// 遍历ls
for (String item : ls)
// 如果是一个数字,入s2
if (item.matches("\\\\d+"))
s2.add(item);
else if (item.equals("("))
s1.push(item);
else if(item.equals(")"))
while(!s1.peek().equals("("))
s2.add(s1.pop());
s1.pop();//将"("弹出s1栈
else
//思路:当item的优先级小于等于s1栈顶运算符时,将s1栈顶的运算符弹出
// 并加入s2中,再次与s1中新的栈顶运算符比较优先级。
//注意:我们需要一个比较运算符优先级高低的方法
while(s1.size() != 0 &&
Operation.getValue(s1.peek())
>= Operation.getValue(item))
s2.add(s1.pop());
//还需要将item压入栈中
s1.push(item);
//将s1中剩余的运算符依次弹出并加入s2
while(s1.size() != 0)
s2.add(s1.pop());
return s2;//注意因为是存放到List中的,所以按顺序输出即可
// 将一个逆波兰表达式的数据和运算符放入ArrayList中
public static List<String> getListString(String suffixExpression)
// 将suffixExpression分隔
String[] split = suffixExpression.split(" ");
ArrayList<String> list = new ArrayList<String>();
for (String ele : split)
list.add(ele);
return list;
// 完成对逆波兰表达式的运算
public static int calculate(List<String> ls)
Stack<String> stack = new Stack<String>();
for (String item : ls)
// 使用正则表达式取出数
if (item.matches("\\\\d+")) // 匹配的是多位数
stack.push(item);
else
// pop出两个数运算,结果入栈
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = 0;
if (item.equals("+"))
res = num1 + num2;
else if (item.equals("-"))
res = num1 - num2;
else if (item.equals("*"))
res = num1 * num2;
else if (item.equals("/"))
res = num1 / num2;
else
throw new RuntimeException("运算符有误!");
// 把res入栈
stack.push("" + res);
// 最后留在stack中数据就是运算结果
return Integer.parseInt(stack.pop());
//返回一个运算符对应的优先级的类
class Operation
private static int ADD = 1;
private static int SUB = 1;
private static int MUL = 2;
private static int DIV = 2;
public static int getValue(String operation)
int result = 0;
switch (operation)
case "+":
result = ADD;
break;
case "-":
result = SUB;
break;
case "*":
result = MUL;
break;
case "/":
result = DIV;
break;
default:
System.out.println("不存在该运算符!");
break;
return result;
6.队列(queue)
6.1 理解
-
队列是一个有序列表,可以用
数组
或链表
来实现。 -
遵循
先入先出
的原则。即:先存入队列的数据,要先取出。后存入的要后取出 -
队尾(rear)
:只能从队尾添加元素,一般以上是关于数据结构基础的主要内容,如果未能解决你的问题,请参考以下文章