换个方式来写笔记,最近啃完了《Thinking in Java》,想要在看专栏的时候多做点扩展性的东西,比如把难撩的泛型加进来做实现,代码还是要写起来才晓得怎么写更酷。总之最近看书的过程中、搜索答案的过程中发出了很多“哇~超厉害!超酷!我也要这样棒棒哒!”的叹声。新的开始,继续加油
简单说明下:
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
特定的数据结构是对特定场景的抽象。栈这种结构属于接口比较简单的,后进先出。
栈的两种实现:数组实现的顺序栈和链表实现的链式栈。最近看泛型比较多,实现的时候也用上了。代码如下:
// 基于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的使用条件:
无法用于基本类型和包装器类型的原因也在于此,所以另外单独写了CountingGenerator(书中代码的名称),我改成了PrimitiveGenerator啦。
队列和栈非常相似,只是取数据的方式不同。就是名字看到的那种,排队用的。
关于队列的实现,无论是构造还是优化,两点判断十分重要:队满条件和队空条件。循环队列就是构造利用率最大的两个条件的做法。
基于数组的实现如下:
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分钟不等,客户端需要处理多个并发计算请求。当时使用了线程池,面临的问题是,服务端计算失败或者由于计算方法(计算发起人编辑)导致计算时间超长的情况时,服务端没有任何反馈(团队一致认为这个很正常……),此时客户端就会面临线程池被占满需要进行排队的情况。
排队队列要怎么处理可能很快就被塞满的情况,对于生产者和消费者效率协调问题,想起来昨天看的一个段子:
艺术是对特定现实的抽象?
专栏作者给出的一个建议是:多配置几个消费者
那在服务端已经是分布式计算(用的Akka)的情况下,是否算多个消费者呢?
大概是第一次用递归的时候就用对了,所以常常自信心满满地讲:这都不是事~(不要lian
专栏作者对递归的几个总结的超好呢~
int depth = 0;
int f() {
++depth;
if (depth > N) throw new RuntimeException();
}
适用场景:最大深度比较小。
使用数据结构来保存已经计算过的结果。
环的检测。链表中也有环的检测?