线性表的链式存储结构的特点是用一组任意的存储单位存储线性表的数据元素,这组存储单位可以是连续的,也可以使不连续的。
种类 | 单链表 | |
【e.g.】Message | ||
单循环链表 | ||
【e.g.】丢手绢 | ||
双链表 | ||
【e.g.】LinkedList | ||
双向循环链表 | ||
【e.g.】 |
节点 | |
数据域:可以有很多内容组成 【e.g.】Message的数据域有:what,arg1,arg2,obj等等 指针域:可以看做是内存地址的引用 【e.g.】Message next; // 下一个节点的引用 |
|
数据链 | |
删除一个节点 | |
增加一个节点 |
public final class MessageQueue {
// 添加message, 到消息队列中
boolean enqueueMessage(Message msg, long when) {
...
Message p = mMessages; // mMessages头指针
if (p == null || when == 0 || when < p.when) { // 链表为空的时候
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
Message prev;
for (; ; ) {
prev = p;
p = p.next;
if (p == null || when < p.when) { // message 是按照时间when排序的
break;
}
...
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
}
// 从消息队列中删除一个节点
Message next() {
for (;;) {
Message prevMsg = null;
Message msg = mMessages; // mMessages头指针
...
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null; // 一但GC有空,就会把msg回收
}
}
}
for (; ; ) {
prev = p;
p = p.next;
if (p == null || when < p.when)
break;
...
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
if (prevMsg != null) {
prevMsg.next = msg.next;
}
msg.next = null;
if (prevMsg == null) {
mMessages = msg.next;
}
msg.next = null;
实操:将一副麻将排序。如果用冒泡排序,最坏的情况下时间复杂度:15*15,在服务端进行排序,如果用户量大,那就会特别恶心。可以采用空间换时间的方式解决。
实现逻辑:1、一副牌的点数有9种,9组数据存放这9种点数,第一次循环,将牌的点数归位。
2、一副牌的花色有3种,3组数据存放着3种花色,第二次循环,将牌的花色归位。
采用LinkedList,或者使用自定义的单链表,不能使用ArrayList(内部会产生大量的数组拷贝等操作)。运行空间的大小取决于你定义的麻将的大小,和链表没有关系。这个排序算法是最优的。
适用于麻将,斗地主等排序。
/**
* 功能:麻将类
*
* Created by danke on 2018/11/28.
*/
public class Mahjong {
/**
* 花色:万,条,筒
*/
public int suit;
/**
* 点数:一,二,三
*/
public int rank;
public static int SUIT_WANG = 1; // 花色:万
public static int SUIT_TIAO = 2; // 花色:条
public static int SUIT_TONG = 3; // 花色:筒
public Mahjong(int suit, int rank) {
this.rank = rank;
this.suit = suit;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
if (suit == SUIT_WANG) {
sb.append("万");
} else if (suit == SUIT_TIAO) {
sb.append("条");
} else if (suit == SUIT_TONG) {
sb.append("筒");
}
sb.append(rank);
return sb.toString();
}
}
private int ORDER_BY_SUIT = 0; // 按照花色排序
private int ORDER_BY_RANK = 1; // 按照点数排序
public void radixSort(LinkedList list) {
// 1、按照点数分组
sortGroup(list, ORDER_BY_RANK, 9);
// 2、按照花色分组
sortGroup(list, ORDER_BY_SUIT, 3);
}
/**
* 根据对应的orderBy进行分类合并
*
* @param list
* @param orderBy
* @param groupNumber
*/
private void sortGroup(LinkedList list, int orderBy, int groupNumber) {
LinkedList[] numList = new LinkedList[groupNumber];
for (int i = 0; i < numList.length; i++) {
numList[i] = new LinkedList<>();
}
while (!list.isEmpty()) {
Mahjong remove = list.remove(); // 将麻将取出
int index = remove.rank - 1; // 下标为点数-1
if (orderBy == ORDER_BY_SUIT) { // 下标为花色-1
index = remove.suit - 1;
}
numList[index].add(remove); // 放到对应链表中
}
// 将链表组拼在一起
for (int i = 0; i < numList.length; i++) {
list.addAll(numList[i]);
}
}
自己写一个单链表代替JDK提供的LinkedList,性能会最优。
(具体代码见GitHub)
对比单向链表和双向链表:
- 单向链表查询只能从前往后
- 双向链表查询可以从前往后,也可以从后往前,增加了查询的效率
针对专项问题处理,用自己定义的链式结构要比JDK的性能更好,内存开销小10倍
/**
* 功能:自己写的LinkedList
*
* Created by danke on 2018/12/1.
*/
public class LinkedList {
transient int size = 0; // 大小
/**
* 头结点
*/
transient Node first; // 前驱
/**
* 尾结点
*/
transient Node last; // 后继
/**
* Constructs an empty list.
*/
public LinkedList() {
}
/**
* 添加数据
* @param e
* @return
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 添加到index的位置
* @param index
* @param element
* @return
*/
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size) {
// 加到最后一位
linkLast(element);
} else {
// 加到指定位置
linkBefore(element, node(index));
}
}
/**
* 加到指定位置
* @param element 需要添加的元素
* @param succ 指定的节点
*/
private void linkBefore(E element, Node succ) {
Node prev = succ.prev;
Node newNode = new Node<>(prev, element, succ);
succ.prev = newNode;
if (prev == null) {
first = newNode;
} else {
prev.next = newNode;
}
size++;
}
/**
* 从尾部添加数据
* @param e
*/
private void linkLast(E e) {
Node l = last;
Node newNode = new Node<>(l, e, null);
last = newNode;
if (l == null) {
first = newNode;
} else {
l.next = newNode;
}
size++;
}
public boolean isEmpty() {
return size == 0;
}
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node x = first; x != null ; x = x.next) {
result[i++] = x.item;
}
return result;
}
public E remove() {
return removeFirst();
}
private E removeFirst() {
final Node f = first;
if (f == null) {
throw new NoSuchElementException();
}
return unlinkFirst(f);
}
private E unlinkFirst(Node f) {
E element = f.item;
Node next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null) {
last = null;
} else {
next.prev = null;
}
size--;
return element;
}
public boolean addAll(LinkedList extends E> c) {
return addAll(size, c);
}
private boolean addAll(int index, LinkedList extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0) {
return false;
}
Node pred, succ;
if (index == size) {
pred = last;
succ = null;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node newNode = new Node(pred, e, null);
if (pred == null) {
first = newNode;
} else {
pred.next = newNode;
}
pred = newNode; // 更新最后的节点
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
return false;
}
/**
* 获取结点:链式结构查找数据比较困难,需要循环
* 单向链表和双向链表的区别就在此处:双向链表的查找会比单向链表快
* @param index
* @return
*/
private Node node(int index) {
if (index < (size >> 1)) { // 如果index在整个链表的前半部分,从前往后找 (size >> 1 = size / 2)
Node x = first;
for (int i = 0; i < index; i++) {
x = x.next;
}
return x;
} else { // 如果index在整个链表的后半部分,从后往前找
Node x = last;
for (int i = size - 1; i > index; i--) {
x = x.prev;
}
return x;
}
}
private void checkElementIndex(int index) {
if (!(isElementIndex(index))) {
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
}
/**
* 判断是否index是否越界
* @param index
*/
private void checkPositionIndex(int index) {
if (!(isPositionIndex(index))) {
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
private String outOfBoundsMsg(int index) {
return "Index: " + index + ", Size: " + size;
}
public int size() {
return size;
}
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
private E unlink(Node x) {
E item = x.item;
Node prev = x.prev;
Node next = x.next;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
return item;
}
/**
* 结点
* @param
*/
protected static class Node {
E item; // 数据域
Node next; // 后继
Node prev; // 前驱
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
}
/**
* 从尾部添加数据
* @param e
*/
private void linkLast(E e) {
Node l = last;
Node newNode = new Node<>(l, e, null);
last = newNode;
if (l == null) {
first = newNode;
} else {
l.next = newNode;
}
size++;
}
/**
* 加到指定位置
* @param element 需要添加的元素
* @param succ 指定的节点
*/
private void linkBefore(E element, Node succ) {
Node prev = succ.prev;
Node newNode = new Node<>(prev, element, succ);
succ.prev = newNode;
if (prev == null) {
first = newNode;
} else {
prev.next = newNode;
}
size++;
}
/**
* 删除指定位置的元素
* @param x
* @return
*/
private E unlink(Node x) {
E item = x.item;
Node prev = x.prev;
Node next = x.next;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
return item;
}