《数据结构与算法之美》专栏阅读笔记2——线性表

换个方式来写笔记,最近啃完了《Thinking in Java》,想要在看专栏的时候多做点扩展性的东西,比如把难撩的泛型加进来做实现,代码还是要写起来才晓得怎么写更酷。总之最近看书的过程中、搜索答案的过程中发出了很多“哇~超厉害!超酷!我也要这样棒棒哒!”的叹声。新的开始,继续加油

文章目录

        • 数组和链表
          • 扩展:泛型与数组
        • 队列
          • 阻塞队列和并发队列
        • 递归
          • 递归需要满足的三个条件
          • 写递归代码的关键
          • 递归时如何避免堆栈溢出
          • 如何避免递归中的重复计算
          • 脏数据产生的无限递归问题

数组和链表

《数据结构与算法之美》专栏阅读笔记2——线性表_第1张图片

简单说明下:
1、单链表反转:两种方式,新建链表,逆序遍历转储;递归改变next的指向。此处用的是递归。
2、环的检测:快慢指针法。对于环的入口节点的证明还是每太吃透(数学不好?
3、有序链表合并:直接是写了个有序链表,insert。在插入位置的查找上应该是可以根据有序这个特点来优化的,偷懒了,没写……
4、删除倒数第N个节点:用双指针来确定长度。
5、链表的中间节点:还是双指针,写法和环的检测很像,木有证明哦,只是画了简单的图来推一下逻辑。(我好像直接删掉了中间节点,嘎~

代码如下
【单链表】

public class SingleLinkedList {
    private int size;
    private Node head = new Node();

    public Node addFirst(T data) {
        Node node = new Node(data);
        node.next = head.next;
        head.next = node;
        size++;
        return node;
    }

    public Node addLast(T data) {
        Node tail = getTail();
        Node node = new Node(data);
        node.next = tail.next;
        tail.next = node;
        return node;
    }

    public Node getTail() {
        Node tail = head;
        Node entry = getEntryNodeOfLoop();
        if (entry != null) {
            return entry;
        }

        while (tail != null && tail.next != null) {
            tail = tail.next;
        }
        return tail;
    }

    public Node find(T data) {
        Node ptr = head.next;
        while (ptr != null) {
            if (ptr.item.equals(data)) {
                return ptr;
            }
            ptr = ptr.next;
        }
        return null;
    }

    // 递归反转
    public void reversal() {
        head.next = reversal(head.next);
    }

    // 链表反转
    public Node reversal(Node nextNode) {
        if (nextNode == null || nextNode.next == null) {
            return nextNode;
        }
        Node newHead = reversal(nextNode.next);
        nextNode.next.next = nextNode;
        nextNode.next = null;
        return newHead;
    }

    // 环的检测,入口节点非空表示有环
    public Node getEntryNodeOfLoop() {
        if (head == null || head.next == null || head.next.next == null) {
            return null;
        }
        Node fastNode = head.next;
        Node slowNode = head.next;
        while (fastNode != null && fastNode.next != null) {
            slowNode = slowNode.next;
            fastNode = fastNode.next.next;
            if (fastNode == slowNode) {
                slowNode = head.next;
                while (slowNode != fastNode) {
                    slowNode = slowNode.next;
                    fastNode = fastNode.next;
                }
                return slowNode;
            }
        }
        return null;
    }

    // 删除链表的倒数第N个节点
    public Node removeFromEnd(int n) {
        Node pHead = head.next;
        Node pTail = pHead;
        for (int i = 0; i < n; i++) {
            pTail = pTail.next;   // 给定的N可以保证该操作有效
        }
        while (pTail.next != null) {
            pHead = pHead.next;
            pTail = pTail.next;
        }

        Node target = pHead.next;
        pHead.next = pHead.next.next;

        return target;
    }
    public Node removeMidNode() {
        Node firstNode = head.next;
        if (firstNode == null || firstNode.next == null) {
            head.next = null;
            return head;
        }
        Node slow = firstNode;
        Node fast = slow.next.next;
        while(fast.next.next != null && slow.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        Node target = slow.next;
        slow.next = slow.next.next;
        return target;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        Node node = head;
        while(node != null) {
            sb.append(node.item);
            sb.append(" ");
            node = node.next;
        }
        sb.append("]");
        return sb.toString();
    }

    // 链表节点
    private class Node {
        private T item;
        private Node next;

        Node() {
            item = null;
            next = null;
        }
        Node(T data) {
            item = data;
            next = null;
        }
        Node(T data, Node next) {
            this.item = data;
            this.next = next;
        }

        @Override
        public String toString() {
            return "Node: " + item;
        }
    }

    public static void main(String[] args){
        System.out.println(" ---- Test reversal of linkedlist ----");
        SingleLinkedList ssl = new SingleLinkedList();
        for (String s : "A B C D E F G".split(" ")) {
            ssl.addLast(s);
        }

        System.out.println("source: " + ssl);
        ssl.reversal();
        System.out.println("reversal: " + ssl);

        System.out.println(" ---- remove node from end ----");
        ssl.removeFromEnd(3);
        System.out.println("remove the 3rd node from the end: " + ssl);

        System.out.println(" --- remove mid node -----");
        ssl.removeMidNode();
        System.out.println("remove mid node: " + ssl);

        System.out.println(" ---- Check loop in linkedlist ----");
        SingleLinkedList loopList = new SingleLinkedList();
        for (String s : "A B C D E F G H I J K".split(" ")) {
            loopList.addLast(s);
        }

        System.out.println("Set Loop in D");
        loopList.getTail().next = loopList.find("D");

        System.out.println("entry node of loop: " + loopList.getEntryNodeOfLoop());

    }
}

【有序链表】

import java.util.Iterator;

/**
 * @Description : TODO
 * @Author : Ellie
 * @Date : 2018/11/7
 */
public class SortedLinkedList> {
    Node head = new Node();
    Node curNode = head;


    // 由大到小
    public Node insert(T data) {
        Node cur = head.next;
        Node pre = head;

        while (cur != null && cur.item.compareTo(data) > 0) {
            pre = cur;
            cur = cur.next;
        }
        Node node = new Node(data);
        node.next = cur;
        pre.next = node;

        return node;
    }
    public void insert(Node o) {
        Node cur = head.next;
        Node pre = head;

        while (cur != null && cur.item.compareTo(o.item) > 0) {
            pre = cur;
            cur = cur.next;
        }
        o.next = cur;
        pre.next = o;
    }
    public void insert(SortedLinkedList sortedLinkedList) {
        Node ptr = sortedLinkedList.head.next;
        while (ptr != null) {
            insert(ptr.item);
            ptr = ptr.next;
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        Node node = head.next;
        while (node != null) {
            sb.append(node.item);
            sb.append(" ");
            node = node.next;
        }
        return sb.toString();
    }

    class Node implements Comparable {
        T item;
        Node next;

        Node() {
            item = null;
            next = null;
        }
        Node(T data) {
            item = data;
            next = null;
        }
        Node(T data, Node next) {
            this.item = data;
            this.next = next;
        }

        public int compareTo(Node o) {
            return item.compareTo(o.item);
        }

        @Override
        public String toString() {
            return "Node: " + item;
        }
    }

    public static void main(String[] args){
        SortedLinkedList sll1 = new SortedLinkedList();
        for (String s : "A B C X Y Z D E F G".split(" ")) {
            sll1.insert(s);
        }
        System.out.println("s1: " + sll1);

        SortedLinkedList sll2 = new SortedLinkedList();
        for (String s : "Q X P J K M D A".split(" ")) {
            sll2.insert(s);
        }
        System.out.println("s2: " + sll2);

        sll1.insert(sll2);
        System.out.println("s1+s2: " + sll1);
    }
}

多写多练部分完整工程:https://github.com/EllieYY/linkedlistDemo.git

特定的数据结构是对特定场景的抽象。栈这种结构属于接口比较简单的,后进先出。

《数据结构与算法之美》专栏阅读笔记2——线性表_第2张图片

栈的两种实现:数组实现的顺序栈和链表实现的链式栈。最近看泛型比较多,实现的时候也用上了。代码如下:

// 基于linkedList实现
public class LinkedListStack {
    private LinkedList storage = new LinkedList();
    public boolean push(T item) {
        storage.addFirst(item);
        return true;
    }
    public T pop() {
        return storage.removeFirst();
    }
    public T peek() {
        return storage.getFirst();
    }
}

// 基于数组实现
public class ObjectArrayStack {
    private Object[] storage = null;
    private int size;
    private int count;

    public ObjectArrayStack(int size) {
        if (size >= 0) {
            this.size = size;
            this.count = 0;
            storage = new Object[size];
        } else {
            throw  new RuntimeException("Illegal stack size: " + size);
        }
    }

    public boolean push(T item) {
        if (count == size) {
            return false;
        }
        storage[count++] = item;
        return true;
    }

    public T pop() {
        if (count == 0) {
            return null;
        }
        --count;
        T item = (T)storage[count];
        storage[count] = null;
        return item;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(getClass().getSimpleName());
        sb.append(":");
        for (int i = 0; i < count; i++) {
            sb.append(storage[i]).append(" ");
        }
        return sb.toString();
    }

    public static void main(String[] args){
        ObjectArrayStack stack = new ObjectArrayStack(15);
        for (String s : "A B C D E F G H I".split(" ")) {
            stack.push(s);
        }
        System.out.println(stack);
    }
}
扩展:泛型与数组

上面基于数组的实现是看过其他技术博客之后这样写的。很迷书里面讲的Generator,之前写的版本是强行把Generator用上了的。
一开始没有使用Object数组是因为,看过书中的例子,Object数组是可以同时放多种类型的数据,最大的弊端就是无法进行编译器检查。但是此处因为是泛型类,反而没有这样的困扰呢~
强用Generator的代码如下:

public class ArrayStack {
    private T[] storage;
    private int size;       // 栈的大小
    private int count;      // 元素的个数

    public ArrayStack(Class type, Generator gen, int n) {
        if (size >= 0) {
            this.size = n;
            this.count = 0;
            storage = Generated.array(type, gen, n);
        } else {
            throw  new RuntimeException("Illegal stack size: " + size);
        }
    }

    public boolean push(T item) {
        if (count == size) {
            return false;
        }
        storage[count++] = item;
        return true;
    }
    public boolean push(Generator gen) {
        if (count == size) {
            return false;
        }
        storage[count++] = gen.next();
        return true;
    }

    public T pop() {
        if (count == 0) {
            return null;
        }
        --count;
        T item = storage[count];
        storage[count] = null;
        return item;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(getClass().getSimpleName());
        sb.append(":");
        for (int i = 0; i < count; i++) {
            sb.append(storage[i]).append(" ");
        }
        return sb.toString();
    }

    public static void main(String[] args){
        ArrayStack stack = new ArrayStack(String.class,
                new PrimitiveGenerator.String(), 15);
        for (String s : "A B C D E F G H I".split(" ")) {
            stack.push(s);
        }
        System.out.println(stack);
    }
}

关于生成器的示例代码可以参考:生成器
在用生成器的过程中也搞明白了一个事情。利用反射机制写出来的BasicGenerator的使用条件:

  • 具有默认构造器(无参构造器)
  • public

无法用于基本类型和包装器类型的原因也在于此,所以另外单独写了CountingGenerator(书中代码的名称),我改成了PrimitiveGenerator啦。


队列

队列和栈非常相似,只是取数据的方式不同。就是名字看到的那种,排队用的。

《数据结构与算法之美》专栏阅读笔记2——线性表_第3张图片

关于队列的实现,无论是构造还是优化,两点判断十分重要:队满条件和队空条件。循环队列就是构造利用率最大的两个条件的做法。
基于数组的实现如下:

public class ArrayQueue {
    private Object[] storage = null;
    private int size;
    private int head;
    private int tail;

    public ArrayQueue(int size) {
        if (size >= 0) {
            this.size = size;
            head = tail = 0;
            storage = new Object[size];
        } else {
            throw  new RuntimeException("Illegal queue size: " + size);
        }
    }

    public boolean enqueue(T item) {
        if ((tail + 1) % size == head) {
            return false;
        }
        storage[tail] = item;
        tail = (tail + 1) % size;

        return true;
    }

    public T dequeue() {
        if (tail == head) {
            return null;
        }
        T item = (T)storage[head];
        storage[head] = null;
        head = (head + 1) % size;
        return item;
    }
}

(直接丢代码好像很没意思耶,那下次丢bug吧~)

阻塞队列和并发队列

四月份接触到的一个项目:客户端需要调用服务端的接口(Thrift……如果我表示出了鄙视,可能是因为我对Thrift用法有什么误解)进行计算,一般情况下单个计算的时间在1~15分钟不等,客户端需要处理多个并发计算请求。当时使用了线程池,面临的问题是,服务端计算失败或者由于计算方法(计算发起人编辑)导致计算时间超长的情况时,服务端没有任何反馈(团队一致认为这个很正常……),此时客户端就会面临线程池被占满需要进行排队的情况。
排队队列要怎么处理可能很快就被塞满的情况,对于生产者和消费者效率协调问题,想起来昨天看的一个段子:

《数据结构与算法之美》专栏阅读笔记2——线性表_第4张图片

艺术是对特定现实的抽象?

专栏作者给出的一个建议是:多配置几个消费者

《数据结构与算法之美》专栏阅读笔记2——线性表_第5张图片

那在服务端已经是分布式计算(用的Akka)的情况下,是否算多个消费者呢?


递归

大概是第一次用递归的时候就用对了,所以常常自信心满满地讲:这都不是事~(不要lian
专栏作者对递归的几个总结的超好呢~

《数据结构与算法之美》专栏阅读笔记2——线性表_第6张图片
递归需要满足的三个条件
  • 一个问题可以分解成几个子问题的解
  • 这个问题和分解之后的子问题,除了数据规模不同,求解思路完全一样
  • 存在递归终止条件
写递归代码的关键
  • 找到大问题化小问题的规律
  • 基于规律写递推公式
  • 推敲终止条件
  • 翻译以上步骤
递归时如何避免堆栈溢出
  • 限制递归深度
int depth = 0;
int f() {
	++depth;
	if (depth > N) throw new RuntimeException();
}

适用场景:最大深度比较小。

如何避免递归中的重复计算

使用数据结构来保存已经计算过的结果。

脏数据产生的无限递归问题

环的检测。链表中也有环的检测?

你可能感兴趣的:(05_极客时间阅读笔记)