1.背包
背包是一种不支持从中删除元素的集合数据类型——它的目的就是帮助用例收集元素并迭代遍历所有收集到的元素(用例也可以检查背包是否为空或者获取背包中元素的数量)。迭代的顺序不确定且与用例无关。使用Bag的API,用例可以将元素添加进背包并根据需要随时使用foreach语句访问所有的元素。用例也可以使用栈或者队列,但是使用Bag说明元素的处理顺序不重要。【算法p76】
2.先进先出队列
先进先出队列是一种基于先进先出(FIFO)策略的集合类型。按照任务产生的书序完成它们的策略如:超时结账时的排队。任何服务型策略的基本原则都是公平。在提高公平时我们应该想到应该优先服务等待最久的人,这正是先进先出策略的准则。当使用foreach语句迭代访问队列中的元素时,元素的处理顺序就是它们被添加到队列中的顺序。在应用程序中使用队列的主要原因是在使用集合保存元素的同时保存它们的相对顺序:使它们的入列顺序和出列顺序相同。【算法p77】
3.下压栈
下压栈(或简称栈)是一种基于后进先出(LIFO)策略的集合类型。当你的邮件在桌子上放一堆时,用的就是栈。新的邮件来到时,你将它们放到最上面,当你有空时你会一封一封的从上到下阅读它们。这种策略的好处是我们能够及时的看到感兴趣的邮件,坏处是我们不把栈清空,谱写最早的邮件可能永远也不会被阅读。当用例使用foreach语句迭代遍历栈中的元素时,元素的处理顺序和它们被压入栈的顺序刚好相反。在应用程序中使用栈迭代器的一个典型原因是在用集合保存元素时颠倒它们的相对顺序。
4.算数表达式求值
(1+((2+3)*(4*5))) java内部如何计算这个表达式?
系统接受一个输入字符串(表达式)并输出表达式的值。
我们的重点在于如何解析由括号、运算符和数字组成的字符串,并按照正确的顺序完成各种初级的算数操作。
如何才能得到一个表达式的值得?
E.W.Dijkstra发明了一个简单的算法:用两个栈(一个用于保存运算符,一个用于保存操作数)完成了这个任务。
表达式由括号、运算符和操作数组成。我们个根据一下四种情况逐个将这些实体送入到栈处理:
1)将操作数压入到操作数栈
2)将运算符压入到运算符栈
3)忽略左括号
4)在遇到右括号时,弹出一个运算符,弹出所需数量的操作数,并将运算符和操作数的运算结果压入到操作数栈。
public class Evaluate{
public static void main(String[] args){
Stack<String> ops=new Stack<String>();
Stack<Double> vals=new Stack<Double>();
while(!StdIn.isEmpty()){
//先读取字符,如果是运算符,则压入到运算符栈
String s=StdIn.readString();
if(s.equals("("));//左括号不管
if(s.equals("+"))ops.push(s);
if(s.equals("-"))ops.push(s);
if(s.equals("*"))ops.push(s);
if(s.equals("/"))ops.push(s);
if(s.equals("sqrt"))ops.push(s);
if(s.equals(")")){//如果是右括号,弹出操作数和运算符进行运算,运输结果放入到操作数栈
String op=ops.pop();
Double v=vals.pop();
if(op.equals("+")) v=vals.pop()+v;
if(op.equals("-")) v=vals.pop()-v;
if(op.equals("*")) v=vals.pop()*v;
if(op.equals("/")) v=vals.pop()/v;
if(op.equals("sqrt")) v=Math.sqrt(v);
vals.push(v);
}
else vals.push(Double.paseDouble(s));
}
StdOut.println(vals.pop);
}
}
5 数组实现栈:
下压(LIFO)栈(能够动态调整数组大小),用例可以创建任意类型数据的栈并支持foreach语句按照后进先出的顺序迭代访问所有的栈元素。
public class ResizingArrayStack<Item> implements Iterable<Item> {
private int N=0;
@SuppressWarnings("unchecked")
private Item[] a=(Item[])new Object[1];
//动态调整数组大小
private void resize(int max) {
@SuppressWarnings("unchecked")
Item[]temp=(Item[])new Object[max];
for (int i = 0; i < N; i++) {
temp[i]=a[i];
}
a=temp;
}
//压栈
public void push(Item item) {
//如果栈的大小等于数组的大小,就把数组的大小增加一倍
if(N==a.length)resize(2*a.length);
a[N++]=item;//把数据压栈到栈顶
}
//出栈
public Item pop() {
Item item=a[--N];
a[N]=null;//避免对象游离(保存一个不需要对象的引用)
//如果数组太大,就将其长度减半。检测条件是栈的大小是否小于数组长度的四分之一。减半之后,它的状态是半满
if(N>0&&N==a.length/4)resize(a.length/2);
return item;
}
//返回迭代器,一个类要想迭代,就必须实现Iterable接口,然后实现iterator方法返回一个迭代器对象
@Override
public Iterator<Item> iterator() {
return new ReverseArrayIterator();
}
//定义一个内部类,迭代器对象,实现数组先进后出的迭代,逆序遍历数组,以便使用foreach来访问该栈集合
private class ReverseArrayIterator implements Iterator<Item>{
//嵌套类可以访问包含它的类的实例变量
private int i=N;
@Override
public boolean hasNext() {
return i>0;
}
@Override
public Item next() {
return a[--i];
}
}
}
6先进先出队列【数组实现】
public class ResizingArrayQueue<Item> implements Iterable<Item> {
private Item[]a;
private int head;
private int tail;
private int N;
@SuppressWarnings("unchecked")
public ResizingArrayQueue(){
a=(Item[])new Object[2];
head=0;
tail=0;
N=0;
}
public boolean isEmpty() {
return N==0;
}
public int size() {
return N;
}
//动态调整数组大小
private void resize(int max) {
Item[] temp=(Item[]) new Object[max];
for (int i = 0; i < N; i++) {
temp[i]=a[(head+i)%a.length];
}
a=temp;
head=0;
tail=N;
}
//入列
public void enqueue(Item item) {
if(N==a.length)resize(2*a.length);
a[tail++]=item;
if(tail==a.length)tail=0;
N++;
}
//出列
public Item dequeue() {
Item item=a[head];
a[head]=null;
N--;
head++;
if(head==a.length)head=0;
if(N>0&&N==a.length/4)resize(a.length/2);
return item;
}
@Override
public Iterator<Item> iterator() {
return new QueueArrayIterator();
}
//定义一个内部类,迭代器对象以便使用foreach来访问该栈集合
private class QueueArrayIterator implements Iterator<Item>{
//嵌套类可以访问包含它的类的实例变量
private int i=0;
@Override
public boolean hasNext() {
return i<N;
}
@Override
public Item next() {
Item item = a[(i + head) % a.length];
i++;
return item;
}
}
}
链表
定义:链表是一种递归的数据结构,它或者为空(null),或者是指向一个节点(node)的引用,该节点含有一个泛型的元素和一个指向另外一条链表的引用。
在链表中插入数据和删除数据都很方便。
实现任意插入和删除需要使用双向链表,其中每个节点都含有两个链接,分别指向不同的方向。
7.链表实现下压栈
链表的使用使程序达到了最优的设计目标
- 因为使用了泛型,可以处理任何类型的数据
- 所需的空间总是和集合的大小成正比
- 操作所需的时间总是和集合的大小无关
/**
* 栈的实现【链表实现】 栈的顶部为表头
*/
public class Stack<Item> implements Iterable<Item> {
private Node first;//栈顶
private int N;//栈内元素数量
//定义链表节点嵌套类
private class Node{
Item item;
Node next;
}
public boolean isEmpty() {
return first==null; // 或: N==0;
}
public int size() {
return N;
}
//向栈顶添加元素
public void push(Item item) {
Node oldfirst=first;
first=new Node();
first.item=item;
first.next=oldfirst;
N++;
}
//从栈顶删除元素
public Item pop() {
Item item=first.item;
first=first.next;
N--;
return item;
}
//栈的迭代器,后进先出,定义了迭代器之后实例可以使用foreach
private class StackIterator implements Iterator<Item>{
Node current=first;
@Override
public boolean hasNext() {
return current!=null;
}
@Override
public Item next() {
Item item=current.item;
current=current.next;
return item;
}
}
@Override
public Iterator<Item> iterator() {
return new StackIterator();
}
}
8.链表实现先进先出队列
将队列表示为一条从最早插入的元素到最近插入的元素的链表,实例变量first指向队列的开头,实例变量last指向队列的结尾
一个元素入列时,将这个元素插到表尾(在链表为空时,first和las都指向新的节点)
一个元素出列时,就是删除表头的节点(在链表为空时,需要更新last的值)
链表的使用使程序达到了最优的设计目标
- 因为使用了泛型,可以处理任何类型的数据
- 所需的空间总是和集合的大小成正比
- 操作所需的时间总是和集合的大小无关
public class Queue<Item> implements Iterable<Item> {
private Node first;//指向最早添加的节点的连接 , 表头
private Node last;//指向最近添加的节点的连接 ,表尾
private int N; //队列中元素的个数
//定义链表节点嵌套类
private class Node{
Item item;
Node next;
}
public boolean isEmpty() {
return first==null; // 或: N==0;
}
public int size() {
return N;
}
//向表尾添加元素
public void enqueue(Item item) {
Node oldlast=last;
last=new Node();
last.item=item;
last.next=null;
if(isEmpty())first=last;
else oldlast.next=last;
N++;
}
//从表头删除元素
public Item dequeue() {
Item item=first.item;
first=first.next;
if(isEmpty())last=null;
N--;
return item;
}
//对队列的迭代器
private class QueueIterator implements Iterator<Item>{
Node current=first;
@Override
public boolean hasNext() {
return first!=null;
}
@Override
public Item next() {
Item item=current.item;
current=current.next;
return item;
}
}
@Override
public Iterator<Item> iterator() {
return new QueueIterator();
}
}
9.链表实现背包
/**
* 背包【链表实现】
*/
public class Bag<Item> implements Iterable<Item> {
private Node first;//链表头节点
private int N;//背包中数据个数
//定义链表节点嵌套类
private class Node{
Item item;
Node next;
}
public boolean isEmpty() {
return first==null;
}
public int size() {
return N;
}
//往背包中添加数据
public void add(Item item) {
Node oldfirst=first;
first=new Node();
first.item=item;
first.next=oldfirst;
N++;
}
//背包的迭代器
private class BagIterator implements Iterator<Item>{
Node current=first;
@Override
public boolean hasNext() {
return current!=null;
}
@Override
public Item next() {
Item item=current.item;
current=current.next;
return item;
}
}
@Override
public Iterator<Item> iterator() {
return new BagIterator();
}
}
数组
优点:通过索引可以直接访问元素
缺点:在初始化时就需要知道元素的数量
链表
优点:使用空间的大小和元素的数量成正比
缺点:需要通过引用访问任意元素
增长数量级
1.编写一个Static用例Parentheses,读取的一个文本流使用栈来判断其中的括号是否配对完整。
例如,对于[()]{}[()()]程序应该打印true,对于[(])则打印false
public class Parentheses{
private static final char LEFT_PAREN = '(';
private static final char RIGHT_PAREN = ')';
private static final char LEFT_BRACE = '{';
private static final char RIGHT_BRACE = '}';
private static final char LEFT_BRACKET = '[';
private static final char RIGHT_BRACKET = ']';
public static boolean balance(String s){
Static<Character>static=new Static<Character>();
for(i=0;i<s.lengtn;i++){
if(s.charAt(i)==LEFT_PAREN)static.push(LEFT_PAREN);
if(s.charAt(i)==LEFT_BRACE)static.push(LEFT_BRACE);
if(s.charAt(i)==LEFT_BRACKET)static.push(LEFT_BRACKET);
if(s.charAt(i)==RIGHT_PAREN){
if(static.empty())return false;
if(static.pop()!=LEFT_PAREN)return false;
}
if(s.charAt(i)==RIGHT_BRACE){
if(static.empty())return false;
if(static.pop()!=LEFT_BRACE)return false;
}
if(s.charAt(i)==RIGHT_BRACKET){
if(static.empty())return false;
if(static.pop()!=LEFT_BRACKET)return false;
}
}
return static.isEmpty;
}
}
2.ThreeSum问题:统计一个文件中所有和为0的三整数元组的数量
//暴力算法,增长数量级为立方级别
public class ThreeSum1{
public Static int count(int[]a){
int N=a.length;
int cnt=0;
for(int i=0;i<N;i++)
for(int j=i+1;j<N;j++)
for(int k=j+1;k<N;k++){
if(a[i]+a[j]+a[k]==0)
cnt++;
}
return cnt;
}
}
3.使用了归并排序和二分法查的Twosum,增长数量级为线性对数
public class TwoSumFast{
public static int count(int[]a){
int N=a.length;
int cnt=0;
Arrays.sort(a);//先排序,为二分法做准备
for(i=0;i<N;i++){
if(BinarySerach(-a[i],a)>i)
cnt++;
}
return cnt;
}
}
4.使用了归并排序和二分法查的ThreeSum,增长数量级为平方对数
public class ThreeSumFast{
public static int count(int[]a){
int N=a.length;
int cnt=0;
Arrays.sort(a);
for(i=0;i<N;i++)
for(j=i+1;j<N;j++){
if(BinarySerach.rank(-a[i]-a[j],a)>j)
cnt++;
}
return cnt;
}
}