线性表、栈、队列和优先队列
为一个特定的任务选择最好的数据结构和算法是开发高性能软件的一个关键。
数据结构(data structure)是以某种形式将数据组织在一起的集合(collection)。数据结构不仅存储数据,还支持访问和处理数据的操作。
在面向对象思想里,一种数据结构也被认为是一个容器(container)或者容器对象(container object),它是一个能存储其他对象(常称为数据或元素)的对象。定义一种数据结构从本质上讲就是定义一个类。数据结构应该使用数据域存储数据,并提供方法支持查找,插入和删除等操作。因此,创建一个数据结构就是创建这个类的一个实例。然后可以使用这个实例上的方法来操作这个数据结构,例如,向该数据结构中插入一个元素,或者从这个数据结构中删除一个元素。
java提供了很多能有效组织和操作数据的数据结构。这些数据结构通常称为java集合框架(Java Conllections Framework)。
collection接口为线性表、向量、栈、队列,优先队列以及集合定义了共同的操作。
Java集合框架支持以下两种类型的容器:
- 一种是为了存储一个元素集合,简称为集合(collection)
- 另一种是为了存储键/值对,称为映射表(map)
现在我们把注意力集中在以下集合上,键值对后续再做介绍
- List用于存储一个有序元素集合。
- Set用于存储一组不重复的元素。
- Stack用于存储采用后进先出方式处理的对象。
- Queue用于存储采用先进先出方式处理的对象。
- Priority Queue用于存储按照优先级顺序处理的对象。
这些集合的通用特性在接口中定义,而实现在具体类中提供。
Collection接口是处理对象集合的根接口,公共方法如下
注:方法addAll、removeAll、retainAll类似于集合上的并、差、交运算。
注:Collection接口中的有些方法是不能在具体子类中实现的。在这种情况下,这些方法会抛出异常java.lang.UnsupportedOperationException,它是RuntimeException异常类的一个子类。这样设计很好,可以在自己的项目中使用。如果一个方法在子类中没有意义,可以按如下方式实现它:
pub1ic void someMethod() { throw new UnsupportedOperationException("Method not supported"); }
迭代器
每种集合都是可迭代的(Iterable)。可以获得集合的Iterator对象来遍历集合中的所有元素。
Iterator是一种经典的设计模式,用于在不需要暴露数据是如何保存在数据结构的细节的情况下,来遍历一种数据结构。
public class TestCollection { public static void main(String[] args) { /* * ArrayList:顺序表,底层采用数组存储元素 * 地址连续,元素有序(插入顺序),可重复 */ ArrayList<String> names = new ArrayList<>(); names.add("PEPPA"); names.add("EMILY"); names.add("PEDRO"); names.add("SUZY"); System.out.println("小猪佩奇:"); System.out.println(names); names.remove("PEDRO"); System.out.println(names.size()); Collection<String> collection = new ArrayList<>(); collection.add("danny"); collection.add("candy"); collection.add("rebbca"); System.out.println(collection); //将names集合中的所有元素添加进collection集合中 collection.addAll(names); //采用迭代器迭代集合中的元素 Iterator<String> iterator = collection.iterator(); while(iterator.hasNext()) { System.out.println(iterator.next()); } } }
线性表(List)
List接口继承自Collection,定义了一个用于顺序存储元素的集合。可以使用它的两个具体类ArrayList和LinkedList来创建一个线性表
数组线性表类ArrayList和链表类LinkedList是实现List接口的两个具体类。ArrayList用数组存储元素,这个数组是动态创建的。如果元素个数超过了数组的容量,就创建一个更大的新数组,并将当前数组中的所有元素都复制到新数组中。LinkedList在一个链表中存储元素。要选用这两类中的哪一个依赖于特定需求。如果需要通过索引随机访问元素,而不会在线性表起始位置插入和删除元素,那么ArrayList提供了最高效率的集合。但是,如果应用需要在线性表的起始位置插入或者删除元素,就应该选择LinkedList类。线性表的大小是可以动态增大或减小的。然而数组一旦被创建,它的大小就是固定的。如果应用不需要在线性表中插入或删除元素,那么数组是效率最高的数据结构。
线性表可以存储相同的元素。ArrayList和LinkedList的操作类似,它们最主要的不同体现在内部实现上,内部实现会影响到它们的性能。ArrayList获取元素的效率比较高;若在线性表的起始位置插入和删除元素,那么LinkedList效率会高一些。两种线性表在中间位置或者末尾位置插入和删除元素方面具有相同的性能。
链表可以使用get(i)方法,但这是一个耗时的操作。不要使用它来遍历线性表中的所有元素,应该使用迭代器。foreach迭代隐式地使用了迭代器。
Comparator接口
前面我们已经学习如何使用Comparable接口比较元素。Comparable接口定义了compareTo方法,用于比较实现了Comparable接口的同一个类的两个元素。如果元素的类没有实现Comparable接口又将如何,这些元素还可以比较么?定义一个比较器(Comparator)来比较不同类的元素。要做到这一点,需要创建一个实现java.util.Comparator<T>接口的类并重写它的compare方法。
int compare(T o1, T o2); //如果o1小于o2,就返回一个负值,o1大于o2就返回一个正值,若两者相等,就返回0
import java . util. Comparator ; pub1ic class GeometricComparator implements Comparator <Geometric0bject>,Serializable { public int compare (Geometric0bject o1, GeometricObject o2) { return o1.getArea() < o2.getArea() ? -1 :
o1.getArea() > o2.getArea() ? 1 : 0;
}
}
线性表和集合的静态方法
Collections类包含了执行集合和线性表中通用操作的静态方法
线性表的操作:
sort(list:List):void 对指定的线性表进行排序===>使用Comparable接口中的CompareTo方法,对线性表中的可比较的元素以自然顺序排序(自然排序)
sort(list:List,c:Comparator):void 使用比较器对指定的线性表进行排序。(比较排序)
binarySearch(list:List,key:Object):int 采用二分查找到排好序的线性表中的值key
binarySearch(list:List,key:Object,c:Comparator):int 使用比较器,采用二分查找到排好序的线性表中的值key
reverse(list:List):void 对指定线性表进行逆序排序
reverseOrder():Comparator 返回一个逆序排序的比较器
shuffle(list:List):void 随机打乱指定的线性表
copy(des:List,src:List):void 复制源线性表到目标线性表中
nCopies(n:int,o:Object):List 返回一个由n个对象副本组成的线性表
fill(list:List,o:Object):void 使用对象填充线性表
集合的操作
max(c:Collection):Object 返回集合中的max对象
max(c:Collection,c:Comparator):Object 使用比较器返回集合中max对象
min(c:Collection):Object 返回集合中的min对象
min(c:Collection,c:Comparator):Object 使用比较器返回集合中min对象
disjoint(c1:Collection,c2:Collection):boolean 如果c1和c2没有共同的元素,则返回真
frequency(c:Collection,o:Object):int 返回集合中指定元素o出现的次数
向量类和栈类
java集合框架是在java 2中引入的。Vector和Stack是JDK1.0版本添加的类。继承于AbstractList。除了包含用于访问和修改向量的同步方法之外,Vector和ArrayList是一样的。同步方法用于防止两个或多个线程同时访问和修改某个向量引起的数据损坏。对于许多不需要同步的应用程序来说,使用ArrayList比使用Vector效率更高。如果不需要同步,最好使用ArrayList类,因为它比Vector快的多
Stack类是作为Vector类的扩展来实现的
方法empty()与isEmpty()的功能是一样的。
peek()可以返回栈顶元素而不移除它
pop()返回栈顶元素并移除它。
push()将指定元素添加进栈中;
search()检查指定的元素是否在栈中
队列和优先队列
队列是一种先进先出的数据结构。元素被追加到队列末尾,然后从队列头删除。在优先队列(priority queue)中,元素被赋予优先级。当访问元素时,拥有最高优先级的元素首先被删除。
Queue接口
Queue接口继承自Collection,加入了插入,提取和检验等操作
offer(element:E):boolean 插入一个元素到队列中
poll():E 获取并且移除队列的头元素,如果队列为空则返回null
remove():E 获取并且移除队列的头元素,如果队列为空则抛出异常
peek():E 获取但不移除队列的头元素,如果队列为空则返回null
element():E 获取但不移除队列的头元素,如果队列为空则抛出异常
双端队列Deque和链表LinkedList
LinkedList类实现了Deque接口,Deque接口又继承自Queue接口。可以使用LinkedList创建一个队列。LinkedList很适合用于队列操作,因为它可以很高效地在线性表的两端插入和移除元素。
Deque支持在两端插入和删除元素,增加了从队列两端插入和删除元素的方法(addFisrt(),removeFirst(),addLast(),removeLast())
package edu.uestc.avatar; import java.util.LinkedList; import java.util.Queue; public class QueueDemo { public static void main(String[] args) { Queue<String> queue = new LinkedList<>(); queue.offer("peppa"); queue.offer("eimly"); queue.offer("suzy"); queue.offer("pedro"); while(queue.size() > 0) { System.out.println(queue.remove()); } } }
PriorityQueue类实现了一个优先队列,默认情况下,优先队列使用Comparable以元素的自然顺序进行排序,拥有最小值的元素被赋予最高优先级,因此最先从队列中删除。如果几个元素拥有相同的最高优先级,则任意选择一个。也可以使用构造方法PriorityQueue(initialCapacity,comparator)中的comparator来指定一个顺序。
package edu.uestc.avatar; import java.util.Collections; import java.util.PriorityQueue; public class PriorityQueueDemo { public static void main(String[] args) { //默认情况下,优先队列使用Comparable以元素的自然顺序进行排序 PriorityQueue<Integer> queue = new PriorityQueue<Integer>(); queue.add(10); queue.add(20); queue.add(20); queue.add(30); queue.add(10); queue.add(40); while(queue.size() > 0) System.out.print(queue.remove() + " ");//最高优先级最先被删除 //也可以使用构造方法PriorityQueue(initialCapacity,comparator)中的comparator来指定一个顺序 //Collections.reverseOrder():逆序比较器 System.out.println(); PriorityQueue<Integer> queue2 = new PriorityQueue<Integer>(Collections.reverseOrder()); queue2.add(10); queue2.add(20); queue2.add(20); queue2.add(30); queue2.add(10); queue2.add(40); while(queue2.size() > 0) System.out.print(queue2.remove() + " ");//最高优先级最先被删除 } }
示例:表达式求值
/** * 使用两个栈(operandStack,operatorStack)分别存放操作数和操作符 * 阶段1:扫描表达式 ,(15+2)*5-23,从左到右扫描表达式,提取出操作数和操作符以及括号 * 1.如果提取的是操作数,则将其压入operandStack。 * 2.如果提取的是+或者-号运算符,处理operatorStack栈顶所有的运算符,并将提取到的运算符压入到operatorStack * 3.如果提取的是*或者/号运算符,处理operatorStack栈顶所有的*和/运算符,并将提取到的运算符压入到operatorStack * 4.如果提取的是(,将提取到的运算符压入到operatorStack。 * 5.如果提取的是),重复处理来自operatorStack栈顶的运算符,直到遇到(为止。 * 阶段2:清除栈 * 重复处理来自operatorStack栈顶的运算符,直到operatorStack栈为空 * * */ public class EvaluateExpression { public static void main(String[] args) { System.out.println("请输入一个整数的简单运算表达式,比如:( 15 + 2 ) * 5 - 23"); Scanner input = new Scanner(System.in); String expression = input.nextLine(); int ret = evaluateExpression(expression); System.out.println(expression + " = " + ret); input.close(); } /** * 计算表达式 * @param expression 表达式 * @return 结果 */ public static int evaluateExpression(String expression) { //创建operandStack存储操作数 Stack<Integer> operandStack = new Stack<>(); //创建operatorStack存储操作符 Stack<Character> operatorStack = new Stack<>(); //在表达式的操作符前后 添加空格,以便于提取操作数和操作符 expression = insertBlanks(expression); //提取操作数及操作符 String[] tokens = expression.split(" "); //阶段1:扫描表达式 for(String token : tokens) { if(token.length() == 0) continue;//如果为 else if(token.charAt(0) == '+' || (token.length == 1 && token.charAt(0) == '-') ) {//考虑负数的情况 //处理运算符栈顶的所有+,-,*,/ while(!operatorStack.isEmpty() && ( operatorStack.peek() == '+' || operatorStack.peek() == '-' || operatorStack.peek() == '*' || operatorStack.peek() == '/')) { processAnOperator(operandStack,operatorStack); } //将+或者-压入operatorStack operatorStack.push(token.charAt(0)); }else if (token.charAt(0) == '*' || token.charAt(0) == '/' ) { while(!operatorStack.isEmpty() && ( operatorStack.peek() == '*' || operatorStack.peek() == '/')) { processAnOperator(operandStack,operatorStack); } operatorStack.push(token.charAt(0)); }else if (token.trim().charAt(0) == '(') operatorStack.push('('); else if(token.trim().charAt(0) == ')') { while(!operatorStack.isEmpty() && ( operatorStack.peek() != '(')) { processAnOperator(operandStack,operatorStack); } operatorStack.pop();//弹出( }else operandStack.push(Integer.valueOf(token)); } //阶段2:清空栈 while(!operatorStack.isEmpty()) { processAnOperator(operandStack,operatorStack); } return operandStack.pop(); } /** * 在表达式的操作符前后 添加空格,以便于提取操作数和操作符 * @param expression 表达式 * @return 添加空格后的表达式 */ private static String insertBlanks(String expression) { //考虑到用户输入的随意性,先去掉所有的空格,然后在表达式的操作符前后 添加空格 String str = ""; for(int i = 0; i < expression.length(); i++) { if(expression.charAt(i) != ' ') str += expression.charAt(i); } expression = str; String result = ""; for(int i = 0; i < expression.length(); i++) { if(expression.charAt(i) == '(' || expression.charAt(i) == ')' || expression.charAt(i) == '+' ||expression.charAt(i) == '*' || expression.charAt(i) == '/') result += " " + expression.charAt(i) + " "; else if(expression.charAt(i) == '-') {//处理负数 if(i == 0) result += expression.charAt(0); else { char c = expression.charAt(i - 1); if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(') result += expression.charAt(i); else result += " " + expression.charAt(i) + " "; } }else result += expression.charAt(i) ; } return result; } private static void processAnOperator(Stack<Integer> operandStack,Stack<Character> operatorStack) { char op = operatorStack.pop(); int num1 = operandStack.pop(); int num2 = operandStack.pop(); if(op == '+') operandStack.push(num2 + num1); else if(op == '-') operandStack.push(num2 - num1); else if(op == '*') operandStack.push(num2 * num1); else if(op == '/') operandStack.push(num2 / num1); } }
编程练习题:
1.(按字母升序显示单词)编写一个程序,从文本文件读取单词,并按字母的升序显示所有的单词(可重复)。单词必须以字母开始。
2.编写一个测试程序,在一个链表上存储500万个整数,分别使用iterator和get(index)的遍历时间。
3.编程实现编组符号对
圆括号:(和)
花括号:{和}
方括号:[和]
注意:符号不能交错,编写程序检测编组符号是否正确匹配。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南