《算法》第四版 1.3 练习答案

407 阅读4分钟

未完待续。。。

官网参考答案:1.3   Bags, Queues, and Stacks

1.3.1 为 FixedCapacityStackOfStrings 添加一个方法 isFull()。

答:

public boolean isFull() {
    return N == a.length;
}

1.3.2 给定以下输入,java Stack 的输出是什么?

it was - the best - of times - - - it was - the - -

说明: 这个题很简单,但是如果你没有看书的前面,你会觉得一脸懵逼,首先疑惑的是输出到底是指什么,是最后栈中的数据,还是每进行一步操作的输出。

答: was best times of the was the it

解释: 这里的输出就是每一步操作后的输出,给定的字符串中 - 代表 pop

public static void main(String[] args) {
    Stack<String> test = new Stack<>();
    test.push("it");
    test.push("was");
    System.out.println(test.pop()); // was
    test.push("the");
    test.push("best");
    System.out.println(test.pop()); // best
    test.push("of");
    test.push("times");
    System.out.println(test.pop()); // times
    System.out.println(test.pop()); // of
    System.out.println(test.pop()); // the
    test.push("it");
    test.push("was");
    System.out.println(test.pop()); // was
    test.push("the");
    System.out.println(test.pop()); // the
    System.out.println(test.pop()); // it
}

1.3.3 假设某个用例程序会进行一系列入栈和出栈的混合栈操作。入栈操作会将整数 0 到 9 按顺序压入栈;出栈操作会打印出返回值。下面哪种序列是不可能产生的?

  • a. 4 3 2 1 0 9 8 7 6 5
  • b. 4 6 8 7 5 3 2 9 0 1
  • c. 2 5 6 7 4 8 9 3 1 0
  • d. 4 3 2 1 0 5 6 7 8 9
  • e. 1 2 3 4 5 6 9 8 7 0
  • f. 0 4 6 5 3 8 1 7 2 9
  • g. 1 4 7 9 8 6 5 3 0 2
  • f. 2 1 4 3 6 5 8 7 9 0

解释: 由于栈的特点(后进先出),只要在栈中,一定是栈顶的元素先出来。我们看 b 选项,看最后的 01 ,根据前面的我们知道这两个肯定原本就已经在栈中了,那么出来的时候肯定是 1 先出来,然后才是 0 ,所以不可能产生;再看 f 选项,后面的 12 也是一样。

1.3.4 编写一个 Stack 的用例 Parentheses ,从标准输入中读取一个文本并使用栈判定其中的括号是否配对完整。例如,对于 [()]{}{[()()]()} 程序应该打印 true ,对于 [(]) 则打印 false。

答:

public boolean isBalanced(String str) {
    for (int i = 0; i < str.length(); i ++) {
        char currentCh = str.charAt(i);
        // 如果是空的话,需要先入栈
        if (characters.isEmpty()) {
            characters.push(currentCh);
            continue;
        }
        // 先出栈,用于跟当前的字符比较
        char ch = characters.pop();
        // 计算两者的差值,如果是大于 2 或者小于等于0,那么就需要接着入栈
        int sub = currentCh - ch;
        if (sub <= 0 || sub > 2) {
            // 由于之前出栈的了,所以需要将出栈的那个元素先入栈
            characters.push(ch);
            characters.push(currentCh);
        }
    }
    // 只要栈中的元素是空的,说明配对成功
    return characters.isEmpty();
}

解释: 其中 characters 是需要用到的那个栈。我这里判断的规则

1.3.5 当 N 为 50 时下面这段代码会打印什么?从较高的抽象层次描述给定正整数 N 时这段代码的行为。

Stack<Integer> s = new Stack<Integer>();
while (n > 0) {
   s.push(n % 2);
   n = n / 2;
}
while (!s.isEmpty())
    System.out.print(s.pop());
System.out.println();

答: 1)110010;2)会打印给定 N 的二进制数据。 1.3.6 下面这段代码对队列 q 进行了什么操作?

Stack<String> s = new Stack<String>();
while(!q.isEmpty())
   s.push(q.dequeue());
while(!s.isEmpty())
   q.enqueue(s.pop());

答: 对队列进行反转。

1.3.7 为 Stack 添加一个方法 peek() ,返回栈中最近添加的元素(而不弹出它)。

答:

public Item peek() {
    if (isEmpty()) throw new NoSuchElementException("Stack underflow");
    return a[N - 1];
}

1.3.8 给定以下输入,给出 DoublingStackOfStrings 的数组的内容和大小。

it was - the best - of times - - - it was - the --

答: 我没有找到 DoublingStackOfStrings 这个类的代码,不知道里面的具体细节,如果是我们之前使用的栈,那么答案就是 it ,大小为 1 。其实也就是数一下进栈和出栈的个数,相减的绝对值就是数组中的大小,至于是哪一个,就要谁没被出栈,对于这个题来说显然是 it

1.3.9 编写一段程序,从标准输入得到一个缺少左括号的表达式并打印出补全括号之后的中序表达式。

例如,给定输入:

1 + 2 ) * 3 - 4 ) * 5 - 6 ) ) )

你的程序应该输出:

( ( 1 + 2 ) * ( ( 3 - 4 ) * ( 5 - 6 ) ) )

答:

public String perfectMiddleOrderExpression(String expression) {
    int k = 0; // 记录 ) 的个数
    Stack<Character> stack = new Stack<>();
    for (int i = 0; i < expression.length(); i ++) {
        char ch = expression.charAt(i);
        if (ch != ' ' && ch != ')') {
            stack.push(ch);
        } else if (ch == ')') {
            // 使用队列,这样出队的时候顺序才是正确的
            Queue<Character> chs = new Queue<>();
            // 将当前的 ) 入队
            chs.enqueue(ch);
            // 看看接挨着的 ) 有多少个
            while (!stack.isEmpty()) {
                char pCh = stack.pop();
                chs.enqueue(pCh);
                if (pCh == ')') {
                    k++;
                    continue;
                }
                break;
            }
            // 用于记录是不是最后一个遇到运算符
            boolean isOpe = false;
            while (!stack.isEmpty()) {
                char pCh = stack.pop();
                // 如果已经是第一个了,那么直接添加 ( 入栈
                if (stack.isEmpty()) {
                    stack.push('(');
                    chs.enqueue(pCh);
                    break;
                } else if (isOpe && (pCh < '0' || pCh > '9')) {
                    // 当遇到了操作符,再遇到非数字的时候说明已经到达正确位置
                    // 先将刚才出栈的入栈
                    stack.push(pCh);
                    // 再让 ( 入栈
                    stack.push('(');
                    break;
                }
                // 只有当没有 ) 括号的情况下看看是不是遇到了操作符
                if (k == 0 && (pCh == '-' || pCh == '+' || pCh == '/' || pCh == '*')) {
                    isOpe = true;
                }
                // 每遇到一个 ) 就对 k - 1
                if (pCh == ')') {
                    k --;
                }
                // 每一次出栈记得入队
                chs.enqueue(pCh);
            }
            // 再把出来的数据装回去
            while (!chs.isEmpty()) {
                stack.push(chs.dequeue());
            }
        }
    }
    StringBuilder temp = new StringBuilder();
    for (int i = stack.getSize() - 1; i >= 0; i --) {
        temp.insert(0, stack.pop());
    }
    return temp.toString();
}

1.3.10 编写一个过滤器 InfixToPostfix ,将算术表达式由中序表达式转为后序表达式。

说明: 这里的中序表达式是通过上一个题转换以后的结果,也就是都包含括号,所以这道题很简单。

答:

void InfixToPostfix(String infix) {
    Stack<String> stack = new Stack<String>();
    while (!StdIn.isEmpty()) {
        String s = StdIn.readString();
        switch (s) {
            case "+":
            case "*":
                stack.push(s);
                break;
            case ")":
                StdOut.print(stack.pop() + " ");
                break;
            case "(":
                StdOut.print("");
                break;
            default:
                StdOut.print(s + " ");
                break;
        }
    }
    StdOut.println();
}

解释: 其实也就是遇到运算符就进栈,遇到反括号就出栈,其余的直接输出即可,因为传入的表达式已经是按优先级括起来的。

1.3.11 编写一段程序 EvaluatePostfix ,从标准输入中得到一个后序表达式,求值并打印结果(将上一题的程序中的得到的输出用管道传递给这一段程序可以得到和 Evaluate 相同的行为)。

答:

double evaluatePostfix(String infix) {
    Stack<Double> stack = new Stack<>();
    for (int i = 0; i < infix.length(); i ++) {
        char ch = infix.charAt(i);
        if (ch >= '0' && ch <= '9') {
            stack.push((double)(ch - '0'));
        } else {
            double b = stack.pop();
            double a = stack.pop();
            switch (ch) {
                case '*':
                    stack.push(a * b);
                    break;
                case '/':
                    stack.push(a / b);
                    break;
                case '+':
                    stack.push(a + b);
                    break;
                case '-':
                    stack.push(a - b);
                    break;
            }
        }
    }
    return stack.pop();
}

根据后序表达式求值很简单。

1.3.12 编写一个可迭代的 Stack 用例,它含有一个静态的 copy() 方法,接受一个字符串的栈作为参数并返回该栈的一个副本。注意:这种能力是迭代器价值的一个重要体现,因为有了它我们无需改变基本 API 就能实现这种功能。

答:

public static Stack<String> copy(Stack<String> stack) {
    Stack<String> temp = new Stack<>();
    stack.forEach(temp::push);
    return temp;
}

1.3.13 假设某个用例程序会进行一系列入列和出列的混合队列操作。入列操作会将整数 0 到 9 按顺序插入队列;出列操作会打印出返回值。下面哪种序列是不可能产生的?

(a)  0 1 2 3 4 5 6 7 8 9
(b)  4 6 8 7 5 3 2 9 0 1 
(c)  2 5 6 7 4 8 9 3 1 0
(d)  4 3 2 1 0 5 6 7 8 9

答: b , c , d 。

解释: 队列的特点是先进先出,所以 0 一旦进入了一定要先出来,故 b , c , d 都是不行的。

1.3.14 编写一个类 ResizingArrayQueueOfStrings ,使用定长数组实现队列的抽象,然后扩展实现,使用调整数组的方法突破大小的限制。

答:

public class ResizingArrayQueueOfStrings implements Iterable<String> {
    private static final int INIT_CAPACITY = 8;
    String[] queue;
    int rear = 0;
    int front = 0;
    int n = 0;
    ResizingArrayQueueOfStrings() {
        queue = new String[INIT_CAPACITY];
    }


    boolean isEmpty() {
        return n == 0;
    }
    int size() {
        return n;
    }
    void resize(int newSize) {
        String[] temp = new String[newSize];
        int k = 0;
        for (int i = front; k < n; i ++, k ++) {
            if (i == queue.length) {
                i = 0;
            }
            temp[k] = queue[i];
        }
        queue = temp;
        rear = k;
        n = k;
        front = 0;
    }

    void enqueue(String e) {
        if (n >= queue.length) {
            resize(2 * n);
        }
        queue[rear] = e;
        rear++;
        n ++;
        if (rear == queue.length) {
            rear = 0;
        }
    }

    String dequeue() {
        if (isEmpty()) throw new NoSuchElementException("Queue underflow");
        if (n > 0 && n == queue.length/4) {
            resize(queue.length / 2);
        }
        String temp = queue[front];
        queue[front] = null;
        front ++;
        n --;
        if (front == queue.length) {
            front = 0;
        }
        return temp;
    }

    String peek() {
        if (isEmpty()) throw new NoSuchElementException("Queue underflow");
        return queue[front];
    }

    @NotNull
    @Override
    public Iterator<String> iterator() {
        return new Iterator<String>() {
            int N = front;
            int k = 0;
            @Override
            public boolean hasNext() {
                return k < n;
            }

            @Override
            public String next() {
                if (!hasNext()) throw new NoSuchElementException();
                String temp = queue[N ++];
                k ++;
                if (N == queue.length) {
                    N = 0;
                }
                return temp;
            }
        };
    }

    @Override
    public void forEach(Consumer<? super String> action) {
        Iterable.super.forEach(action);
    }

    @Override
    public Spliterator<String> spliterator() {
        return Iterable.super.spliterator();
    }
}

1.3.15 编写一个 Queue 的用例,接受一个命令行参数 k 并打印出标准输入中的倒数第 k 个字符串(假设标准输入中至少有 k 个字符串)。

答:

public static void main(String[] args) {
    int k = Integer.parseInt(args[1]);
    Queue<String> queue = new Queue<>();
    for (int i = 2; i < args.length; i++) {
        queue.enqueue(args[i]);
    }
    int needK = queue.size() - k;
    while (needK-- != 0) {
        queue.dequeue();
    }
    System.out.println(queue.dequeue());
}

1.3.16 使用 1.3.1.5 节中的 readInts() 作为模板为 Date 编写一个静态方法 readDates() ,从标准输入中读取由练习 1.2.19 的表格所指定的格式的多个日期并返回一个它们的数组。

1.3.1.5 readInts 模板:

public static int[] readInts(String name) {
    In in = new In((name));
    Queue<Integer> q = new Queue<>();
    while (!in.isEmpty()) {
        q.enqueue(in.readInt());
    }
    int N = q.size();
    int[] a = new int[N];
    for (int i = 0; i < N; i ++) {
        a[i] = q.dequeue();
    }
    return a;
}

练习 1.2.19 表格:

parsing.png

答:

public static Date[] readDates(String date) {
    In in = new In(date);
    Queue<Date> q = new Queue<>();
    while (!in.isEmpty()) {
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
        try {
            q.enqueue(sdf.parse(in.readString()));
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
    int N = q.size();
    Date[] a = new Date[N];
    for (int i = 0; i < N; i ++) {
        a[i] = q.dequeue();
    }
    return a;
}

1.3.17 为 Transaction 类完成练习 1.3.16 。

答:

static class Transaction {
    String name;
    Date date;
    Double price;

    @Override
    public String toString() {
        return "Transaction{" +
                "name='" + name + ''' +
                ", date=" + date +
                ", price=" + price +
                '}';
    }
}

public static Transaction[] readTransactions(String transaction) {
    In in = new In(transaction);
    Queue<Transaction> q = new Queue<>();
    while (!in.isEmpty()) {
        Transaction t = new Transaction();
        t.name = in.readString();
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
        try {
            t.date = sdf.parse(in.readString());
        } catch (ParseException e) {
            e.printStackTrace();
        }
        t.price = Double.parseDouble(in.readString());
        q.enqueue(t);
    }
    int N = q.size();
    Transaction[] a = new Transaction[N];
    for (int i = 0; i < N; i ++) {
        a[i] = q.dequeue();
    }
    return a;
}