双端队列(deque,double-ended queue)是一种具有队列和栈特性的数据结构,允许在其两端进行插入和删除操作。在Java中,java.util.Deque
接口就是双端队列的实现,而ArrayDeque
和LinkedList
是其中的具体实现类。
下面是一个使用ArrayDeque
实现的双端队列的例子,我们将使用它来管理一个在线购物车系统中的商品列表。用户可以从任何一端(前面或后面)添加或移除商品。
java
import java.util.ArrayDeque;
import java.util.Deque;
// 商品类
class Product {
String name;
double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
public class ShoppingCart {
private Deque<Product> cart;
public ShoppingCart() {
cart = new ArrayDeque<>();
}
// 向购物车尾部添加商品
public void addProduct(Product product) {
cart.addLast(product);
}
// 向购物车头部添加商品
public void addFirstProduct(Product product) {
cart.addFirst(product);
}
// 从购物车尾部移除商品
public Product removeProduct() {
return cart.pollLast();
}
// 从购物车头部移除商品
public Product removeFirstProduct() {
return cart.pollFirst();
}
// 显示购物车中的所有商品
public void displayCart() {
while (!cart.isEmpty()) {
System.out.println(cart.pollFirst());
}
}
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
// 添加商品
cart.addProduct(new Product("Apple iPhone 13", 999.99));
cart.addFirstProduct(new Product("Google Pixel 6", 599.99));
cart.addProduct(new Product("Samsung Galaxy S21", 799.99));
// 显示购物车内容
System.out.println("Shopping Cart Contents:");
cart.displayCart();
// 移除商品
System.out.println("\nAfter removing the last item:");
cart.removeProduct();
cart.displayCart();
// 再次移除商品
System.out.println("\nAfter removing the first item:");
cart.removeFirstProduct();
cart.displayCart();
}
}
在这个例子中:
Product
类表示商品,包含名称和价格属性。ShoppingCart
类使用ArrayDeque
作为内部数据结构来存储商品。它提供了添加商品到队列尾部或头部的方法,以及从队列尾部或头部移除商品的方法。main
方法创建了一个ShoppingCart
对象,添加了一些商品,显示了购物车的内容,然后移除了商品并再次显示购物车的内容。
这个例子展示了如何使用Java中的双端队列来管理一个购物车系统中的商品列表,允许用户从任意一端添加或移除商品,这在许多实际应用场景中都非常有用。
让我们通过一个详细的案例和表格来进一步说明如何使用双端队列(Deque)在Java中管理一个购物车系统。我们将使用ArrayDeque
作为我们的双端队列实现,并跟踪购物车中的商品添加和移除操作。
示例代码
java
import java.util.ArrayDeque;
import java.util.Deque;
// 商品类
class Product {
String name;
double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
public class ShoppingCart {
private Deque<Product> cart;
public ShoppingCart() {
cart = new ArrayDeque<>();
}
// 向购物车尾部添加商品
public void addProduct(Product product) {
cart.addLast(product);
}
// 向购物车头部添加商品
public void addFirstProduct(Product product) {
cart.addFirst(product);
}
// 从购物车尾部移除商品
public Product removeProduct() {
return cart.pollLast();
}
// 从购物车头部移除商品
public Product removeFirstProduct() {
return cart.pollFirst();
}
// 显示购物车中的所有商品
public void displayCart() {
System.out.println("Current Cart Contents:");
for (Product product : cart) {
System.out.println(product);
}
}
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
// 添加商品到购物车
cart.addProduct(new Product("Apple iPhone 13", 999.99));
cart.addFirstProduct(new Product("Google Pixel 6", 599.99));
cart.addProduct(new Product("Samsung Galaxy S21", 799.99));
// 显示购物车内容
cart.displayCart();
// 移除商品
System.out.println("\nAfter removing the last item:");
cart.removeProduct();
cart.displayCart();
// 再次移除商品
System.out.println("\nAfter removing the first item:");
cart.removeFirstProduct();
cart.displayCart();
}
}
表格说明
购物车操作记录
操作 | 描述 | 购物车状态(从左至右为队首至队尾) |
---|---|---|
初始化 | 创建一个空的购物车 | [] |
添加到队尾(addLast) | 添加"Apple iPhone 13" | ["Apple iPhone 13"] |
添加到队首(addFirst) | 添加"Google Pixel 6" | ["Google Pixel 6", "Apple iPhone 13"] |
添加到队尾(addLast) | 添加"Samsung Galaxy S21" | ["Google Pixel 6", "Apple iPhone 13", "Samsung Galaxy S21"] |
移除队尾(removeLast) | 移除"Samsung Galaxy S21" | ["Google Pixel 6", "Apple iPhone 13"] |
移除队首(removeFirst) | 移除"Google Pixel 6" | ["Apple iPhone 13"] |
通过这个案例和表格,我们可以清楚地看到双端队列在购物车管理中的应用:商品可以被添加到队列的任一端,也可以从任一端移除。这种灵活性使得双端队列成为处理双向数据流或需要快速访问两端数据的场景的理想选择。
让我们继续深入探讨双端队列(Deque)的应用,这一次我们将使用它来解决一个常见的编程问题:回文检测。回文是指正读反读都一样的字符串,例如"racecar"。我们将使用双端队列的特性来高效地检查一个给定的字符串是否是回文。
示例代码
java
import java.util.ArrayDeque;
import java.util.Deque;
public class PalindromeChecker {
public static boolean isPalindrome(String str) {
Deque<Character> deque = new ArrayDeque<>();
// 将字符串中的字符添加到双端队列中
for (char c : str.toLowerCase().toCharArray()) {
if (Character.isLetter(c)) {
deque.addLast(c);
}
}
char first, last;
while (deque.size() > 1) {
first = deque.removeFirst();
last = deque.removeLast();
if (first != last) {
return false;
}
}
return true;
}
public static void main(String[] args) {
String testString = "A man a plan a canal Panama";
if (isPalindrome(testString)) {
System.out.println(testString + " is a palindrome.");
} else {
System.out.println(testString + " is not a palindrome.");
}
}
}
代码解释
在这段代码中,我们定义了一个名为PalindromeChecker
的类,其中包含一个静态方法isPalindrome
,该方法接受一个字符串参数并返回一个布尔值,指示该字符串是否为回文。
- 初始化双端队列 :我们使用
ArrayDeque
作为双端队列的实现,并将其实例化。 - 添加字符:遍历输入字符串的每个字符,只将字母字符转换为小写并添加到队列的尾部。
- 比较字符 :从双端队列的头部和尾部同时移除字符,并比较它们是否相同。如果任何时候两个字符不匹配,那么字符串就不是回文,方法立即返回
false
。 - 结束条件 :如果队列中只剩下一个字符或没有字符,则说明字符串是回文,方法返回
true
。
运行结果
当你运行main
方法时,它会检查字符串"A man a plan a canal Panama"是否为回文。由于忽略空格和大小写,这个字符串实际上是一个回文,因此控制台将输出:"A man a plan a canal Panama is a palindrome."
这个例子展示了如何利用双端队列的特性来简化和优化一些算法,如回文检测,这在实际编程中是非常有用的技巧。
接下来,我们将使用双端队列(Deque)来解决一个有趣的问题:浏览器的历史记录管理。当我们浏览网页时,通常会使用浏览器的前进和后退按钮来在历史记录之间导航。双端队列非常适合模拟这一行为,因为它允许我们在队列的两端高效地添加和移除元素。
示例代码
java
import java.util.ArrayDeque;
import java.util.Deque;
public class BrowserHistory {
private Deque<String> history;
private Deque<String> forwardHistory;
public BrowserHistory() {
history = new ArrayDeque<>();
forwardHistory = new ArrayDeque<>();
}
// 访问新的网址
public void visit(String url) {
history.addLast(url);
forwardHistory.clear(); // 清空前进历史
}
// 返回上一个页面
public String back() {
if (history.size() > 1) {
String currentUrl = history.removeLast();
forwardHistory.addFirst(currentUrl);
return history.getLast();
} else {
return "No more pages to go back.";
}
}
// 前往下一个页面
public String forward() {
if (!forwardHistory.isEmpty()) {
String nextUrl = forwardHistory.removeFirst();
history.addLast(nextUrl);
return nextUrl;
} else {
return "No more pages to go forward.";
}
}
public static void main(String[] args) {
BrowserHistory browser = new BrowserHistory();
// 访问一些网址
browser.visit("google.com");
browser.visit("youtube.com");
browser.visit("facebook.com");
// 后退到上一个页面
System.out.println("Back: " + browser.back());
// 再次后退
System.out.println("Back again: " + browser.back());
// 前进到下一个页面
System.out.println("Forward: " + browser.forward());
}
}
表格说明
浏览器历史记录操作记录
操作 | 描述 | 当前页面 | 历史记录(从左至右为最新至最旧) | 前进历史(从左至右为最近至最远) |
---|---|---|---|---|
初始化 | 创建一个新的浏览器历史管理器 | N/A | [] | [] |
访问(visit) | 访问"google.com" | google.com | ["google.com"] | [] |
访问(visit) | 访问"youtube.com" | youtube.com | ["google.com", "youtube.com"] | [] |
访问(visit) | 访问"facebook.com" | facebook.com | ["google.com", "youtube.com", "facebook.com"] | [] |
后退(back) | 后退到上一个页面 | youtube.com | ["google.com", "youtube.com"] | ["facebook.com"] |
后退(back) | 再次后退 | google.com | ["google.com"] | ["youtube.com", "facebook.com"] |
前进(forward) | 前进到下一个页面 | youtube.com | ["google.com", "youtube.com"] | ["facebook.com"] |
通过这个案例和表格,我们可以看到双端队列如何有效地模拟浏览器的历史记录管理。每当访问一个新的网址时,它会被添加到历史记录的尾部,并清空前进历史。后退操作会从历史记录的尾部移除当前页面并将其添加到前进历史的头部,而前进操作则相反。这种方法保证了高效且直观的网页导航体验。