一.链表
当需要动态的减少或增加数据项时,可以使用链表这种数据结构
链表是由若干个被称为结点的对象组成的一种数据结构,每个结点含有一个数据和下一个结点的引用(单链表),或含有一个数据并含有上一个结点的引用和下一个结点的引用(双链表)
单链表示意图
双链表示意图
LinkedList<E>泛型类
java.util包中的LinkedList<E>泛型类创建的对象以链表结构存储数据,习惯上称LinkedList类创建的对象为链表对象。
LinkedList<String>mylist = new LinkedList<String>();
创建一个空双链表。
使用LinkedList<E>泛型类声明和创建链表时,必须指定E的具体类型,然后链表就可以使用add(E obj)方法向链表依次增加结点。
上述链表mylist使用add方法添加结点,结点中的数据必须是String对象,
mylist.add("How");
mylist.add("Are");
mylist.add("You");
mylist.add("Java");
链表mylist就有了4个结点,结点是自动链接在一起的,不需要做链接。也就是说,不需要操作安排结点中所存放的下一个或上一个结点的引用。
ListedList<E>泛型类的常用方法
LinkedList<E>是实现了泛型接口List<E>的泛型类,而泛型接口List<E>又是Collection<E>泛型接口的子接口。LinkedList<E>泛型类中的绝大部分方法都是泛型接口方法的实现。
在编程时,可以使用接口回调技术,即把LinkedList<E>对象的引用赋给Collection<E>接口变量或List<E>接口变量,那么接口就可以调用类实现的接口方法。
下面是LinkedList<E>泛型类实现List<E>泛型接口中的一些常用方法。
public boolean add(E element): 向链表末尾添加一个新的结点,该结点中的数据是参数element指定的数据。
public void add(int index, E element): 向链表的指定位置添加一个新的结点,该结点 中的数据是参数element指定的数据。
public void clear(): 删除链表的所有结点,使当前链表成为空链表。
public E remove(int index) :删除指定位置上的结点。
public boolean remove(E element):删除首次出现含有数据element的结点。
public E get(int index):得到链表中指定位置处结点中的数据。
public int indexOf(E element):返回含有数据 element的结点在链表中首次出现的位置,如果链表中无此结点则返回-1。
public int lastIndexOf(E element):返回含有数据 element的结点在链表中最后出现的位置,如果链表中无此结点则返回-1。
public E set(int index,E element); 将当前链表 index位置结点中的数据替换为参数 element指定的数据, 并返回被替换的数据。
public int size(): 返回链表的长度,即结点的个数。
public boolean contains (Object element): 判断链表结点中是否有结点含有数据
element。
下面是LinkedList<E>泛型类本身新增加的常用方法
public void addFirst(E element): 向链表的头添加新结点,该结点中的数据是参数
element指定的数据。
public void addLast(E element): 向链表的末尾添加新结点,该结点中的数据是参数 elememt指定的数据。
public E getFirst(): 得到链表中第一个结点中的数据。
public E getLast(): 得到链表中最后一个结点中的数据。
public E removeFirst(): 删除第一个结点, 并返回这个结点中的数据。
public E removeLast(): 删除最后一个结点, 并返回这个结点中的数据。
public Object clone(): 得到当前链表的一个克隆链表, 该克隆链表中结点数据的改变不会影响当前链表中结点的数据。
遍历链表
无论何种集合,应当允许客户以某种方法遍历集合中的对象,而不需要知道这些对象在集合中是如何表示及存储的,Java集合框架为各种数据结构的集合,例如链表、散列表等不同存储结构的集合都提供了选代器。
某些集合根据其数据存储结构和所具有的操作也会提供返回数据的方法,例如 LinkedList类中的 get(int index)方法将返回当前链表中第index结点中的对象。LinkedList 的存储结构不是顺序结构,因此,链表调用get(int index)方法的速度比顺序存储结构的集合调用 get(int index)方法的速度慢。因此,当用户需要遍历集合中的对象时,应当使用该集合提供的选代器,而不是让集合本身来遍历其中的对象。由于选代器遍历集合的方法在找到集合中的一个对象的同时, 也得到了待遍历的后继对象的引用,因此,选代器可以快速地遍历集合。
链表对象可以使用iterator()方法获取一个Iterator对象,该对象就是针对当前链表的迭代器。
下列比较使用了迭代起遍历链表和使用get(int index)方法遍历链表所用时间:
import java.util.Iterator;
import java.util.LinkedList;
public class Ex13_3 {
public static void main(String[] args) {
// TODO Auto-generated method stub
LinkedList<String> list = new LinkedList<String>();
for(int i=0;i<=60096;i++) {
list.add("speed"+i);
}
Iterator<String> iter = list.iterator();
long starttime = System.currentTimeMillis();
while(iter.hasNext()) {
String te = iter.next();
}
long endTime = System.currentTimeMillis();
long result = endTime-starttime;
System.out.println("使用迭代器遍历集合所用时间:"+result+"毫秒");
starttime=System.currentTimeMillis();
for(int i=0;i<list.size();i++) {
String te = list.get(i);
}
endTime = System.currentTimeMillis();
result = endTime - starttime;
System.out.println("使用get方法遍历集合所用时间:"+result+"毫秒");
}
}
运算结果:
使用迭代器遍历集合所用时间:6毫秒
使用get方法遍历集合所用时间:2509毫秒
注:Java也提供了顺序结构的动态数组表类ArrayList,数组表采用顺序结构来存储数据。数组表不适合动态地改变它存储的数据,如增加、删除单元等(比链表慢)。但是,由于数组表采用顺序结构存储数据,数组表获得第n个单元中数据的速度要比链表获得第n个单元中数据的速度快。ArrayList类的很多方法与LinkedList类似,二者的本质区别是一个使用顺序结构,一个使用链式结构。
可以用普通的LinkedList创建一个链表对象
LinkedList mylist = new LinkedList();
然后,mylist链表可以使用add(Object obj)方法向这个链表依次添加结点。由于任何类都是Object类的子类,因此可以把任何一个对象作为链表结点中的对象。需要注意的是,使用get() 获取一个结点中的对象时,要用类型转换运算符转换回原来的类型。Java泛型的主要目的是建立具有类型安全的集合框架,优点是在使用这些泛型类建立的数据结构时,不必进行强制类型转换,即不要求进行运行时类型检查。
import java.util.Iterator;
import java.util.LinkedList;
public class Example13_4 {
public static void main(String[] args) {
// TODO Auto-generated method stub
LinkedList mylist = new LinkedList();
mylist.add("你"); //链表中的第一个结点
mylist.add("好"); //链表中的第二个结点
int number = mylist.size(); //获取链表的长度
for(int i=0;i<number;i++) {
String temp = (String)mylist.get(i); //必须强制转换取出的数据
System.out.println("第"+i+"结点中的数据:"+temp);
}
Iterator iter = mylist.iterator();
while(iter.hasNext()) {
String te = (String)iter.next();
System.out.println(te);
}
}
}
运行结果:
第0结点中的数据:你
第1结点中的数据:好
你
好
使用对象流实现商品库存的录入与显示系统。有一个实现接口Serializable的”商品“类,程序将该类的对象作为链接的结点,然后把链表写入文件。
public class Example13_5 {
public static void main(String[] args) {
// TODO Auto-generated method stub
WindowGoods win = new WindowGoods();
win.setTitle("商品的录入与显示");
}
}
Goods.java
public class Goods implements java.io.Serializable {
String name, mount, price;
public void setName(String name) {
this.name = name;
}
public void setMount(String mount) {
this.mount = mount;
}
public void setPrice(String price) {
this.price = price;
}
public String getName() {
return name;
}
public String getMount() {
return mount;
}
public String getPrice() {
return price;
}
}
InputArea.java
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.LinkedList;
import javax.swing.*;
public class InputArea extends JPanel implements ActionListener {
File f = null; // 存放链表的文件
Box baseBox, boxV1, boxV2;
JTextField name, mount, price; // 为Goods对象提供的视图
JButton button; // 控制器
LinkedList<Goods> goodsList; // 存放Goods对象的链表
InputArea(File f) {
this.f = f;
goodsList = new LinkedList<Goods>();
name = new JTextField(12);
mount = new JTextField(12);
price = new JTextField(12);
button = new JButton("录入");
button.addActionListener(this);
boxV1 = Box.createVerticalBox();
boxV1.add(new JLabel("输入名称"));
boxV1.add(Box.createVerticalStrut(8));
boxV1.add(new JLabel("输入库存"));
boxV1.add(Box.createVerticalStrut(8));
boxV1.add(new JLabel("输入单价"));
boxV1.add(Box.createVerticalStrut(8));
boxV1.add(new JLabel("单击录入"));
boxV2 = Box.createVerticalBox();
boxV2.add(name);
boxV2.add(Box.createVerticalStrut(8));
boxV2.add(mount);
boxV2.add(Box.createVerticalStrut(8));
boxV2.add(price);
boxV2.add(Box.createVerticalStrut(8));
boxV2.add(button);
baseBox = Box.createHorizontalBox();
baseBox.add(boxV1);
baseBox.add(Box.createHorizontalStrut(10));
baseBox.add(boxV2);
add(baseBox);
}
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if (f.exists()) {
try {
FileInputStream fi = new FileInputStream(f);
ObjectInputStream oi = new ObjectInputStream(fi);
goodsList = (LinkedList<Goods>) oi.readObject();
fi.close();
oi.close();
Goods goods = new Goods();
goods.setName(name.getText());
goods.setMount(mount.getText());
goods.setPrice(price.getText());
goodsList.add(goods);
FileOutputStream fo = new FileOutputStream(f);
ObjectOutputStream out = new ObjectOutputStream(fo);
out.writeObject(goodsList);
out.close();
} catch (Exception ee) {
}
} else {
try {
f.createNewFile();
Goods goods = new Goods();
goods.setName(name.getText());
goods.setMount(mount.getText());
goods.setPrice(price.getText());
goodsList.add(goods);
FileOutputStream fo = new FileOutputStream(f);
ObjectOutputStream out = new ObjectOutputStream(fo);
out.writeObject(goodsList);
out.close();
} catch (Exception ee) {
}
}
}
}
ShowArea.java
import java.awt.BorderLayout;
import java.awt.Event;
import java.util.Iterator;
import java.util.LinkedList;
import javax.swing.JPanel;
import javax.swing.JTable;
public class ShowArea extends JPanel {
JTable table;
Object tableElement[][], name[] = { "名称", "库存", "单价" };
public ShowArea() {
setLayout(new BorderLayout());
table = new JTable();
add(table);
}
public void show(LinkedList<Goods> goodsList) {
remove(table);
int lenght = goodsList.size();
tableElement = new Object[lenght][3];
table = new JTable(tableElement, name);
add(table);
Iterator<Goods> iter = goodsList.iterator();
int i = 0;
while (iter.hasNext()) {
Goods goods = iter.next();
tableElement[i][0] = goods.getName();
tableElement[i][1] = goods.getMount();
tableElement[i][2] = goods.getPrice();
i++;
}
table.repaint();
}
}
WindowGoods.java
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.LinkedList;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class WindowGoods extends JFrame implements ActionListener {
File file = null;
JMenuBar bar;
JMenu fileMenu;
JMenuItem login, show;
InputArea inputMessage; // 录入界面
ShowArea showMessage; // 显示界面
JPanel pCenter;
CardLayout card;
WindowGoods() {
file = new File("库存.txt"); // 存放链表的文件
login = new JMenuItem("录入");
show = new JMenuItem("显示");
bar = new JMenuBar();
fileMenu = new JMenu("菜单选项");
fileMenu.add(login);
fileMenu.add(show);
bar.add(fileMenu);
setJMenuBar(bar);
login.addActionListener(this);
show.addActionListener(this);
inputMessage = new InputArea(file); // 创建录入界面
showMessage = new ShowArea(); // 创建显示界面
card = new CardLayout();
pCenter = new JPanel();
pCenter.setLayout(card);
pCenter.add("录入", inputMessage);
pCenter.add("显示", showMessage);
add(pCenter, BorderLayout.CENTER);
card.show(pCenter, "录入");
setVisible(true);
setBounds(100, 50, 420, 380);
validate();
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if (e.getSource() == login) {
card.show(pCenter, "录入");
} else if (e.getSource() == show) {
try {
FileInputStream fi = new FileInputStream(file);
ObjectInputStream oi = new ObjectInputStream(fi);
LinkedList<Goods> goodsList = (LinkedList<Goods>) oi.readObject();
fi.close();
oi.close();
card.show(pCenter, "显示");
showMessage.show(goodsList);
} catch (Exception ee) {
System.out.println(ee);
JOptionPane.showMessageDialog(this, "没有信息", "提示对话框", JOptionPane.WARNING_MESSAGE);
}
}
}
}
二.堆栈
堆是一种“后进先出”的数据结构,只能在一端进行输入或输出数据的操作。堆栈把第一个放人该堆栈的数据放在最底下, 而把后续放入的数据放在已有数据的上面。向堆栈中输入数据的操作称为“压栈”,从堆栈中输出数据的操作称为“弹栈”。由于堆栈总是在顶端进行数据的输入/输出操作,所以,弹栈总是输出(删除)最后压入堆栈中的数据,这就是“后进先出”的原因。
使用java.util包中的Stack<E>泛型类创建一个堆栈对象,堆栈对象可以使用"public E push(E item);"实现压栈操作; 使用"public E pop();"实现弹栈操作;使用"public boolean empty();"判断堆找是否还有数据,有数据返回false,否则返回true;使用"public E peek();"获取堆栈顶端的数据,但不删除该数据;使用"public int search(Object data);"获取数据在堆栈中的位置,最顶端的位置是1,向下依次增加,如果堆栈不含此数据,则返回-1。
堆栈是很灵活的数据结构,使用堆栈可以节省内存的开销。
例如,递归是一种很消耗内存的算法,可以借助堆栈消除大部分递归,达到和递归算法同样的目的。Fibonacci整数序列是我们熟悉的一个递归序列,它的第n项是前两项的和,第1项和第2项是1。下面的例13.6用堆栈输出该递归序列的若干项。
import java.util.Stack;
public class Example13_6 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Stack<Integer>stack = new Stack<Integer>();
stack.push(1);
stack.push(1);
int k = 1;
while(k<=10) {
for(int i=1;i<=2;i++) {
int f1 = stack.pop();
int f2 = stack.pop();
int next = f1+f2;
System.out.println(""+next);
stack.push(next);
stack.push(f2);
k++;
}
}
}
}
运行结果:
2
3
5
8
13
21
34
55
89
144