链表是一种线性的数据结构类型,由一系列的节点组成。每个节点由存储数据的数据域跟指向下一个节点的指针域构成。链表又有分单向链表,双向链表,循环链表等。
单向链表
单向链表的结构示意图就跟上图结构一样,只能单向遍历,最开始的称之为头节点,最后一个称之为尾节点。遍历或者查找顺序只能是从头节点向尾节点方向依次访问每一个节点,直到找到需要的那个节点或者位置。这也是单向链表的一点不足。下面用代码简单实现。
开始的是链表的节点类。每个节点包含数据域跟指针域。
通常下一个链表只要包含了节点对象跟此链表的容量大小这两个成员变量就可以了。实现了几个链表的基本操作:头插法,尾插法,随机插值法,删除节点,输出节点内容。
public class MyLinkedList {
private Node headNode; //链表节点对象
private int size = 0; //链表容量大小
public MyLinkedList() {
headNode = null;
}
public int getSize() {
return size;
}
/**
* 在链表尾部添加节点
*
* @param data
*/
public void insertAtTail(String data) {
Node newNode = new Node(data);
if (size == 0) {
headNode = newNode;
} else {
Node node = headNode;
while (node.next != null) {
node = node.next;
}
node.next = newNode;
}
size++;
}
/**
* 在链表头部添加节点
*
* @param data
*/
public void insertAtHead(String data) {
Node newNode = new Node(data);
newNode.next = headNode;
headNode = newNode;
size++;
}
/**
* 指定位置增加节点
*
* @param data
* @param index 要大于0
*/
public boolean insertAtPosition(String data, int index) {
if (index < 0) {
return false;
}
Node newNode = new Node(data);
Node preNode = headNode;
Node curNode = headNode;
int curIndex = 1;
while (curNode != null) {
if (index == curIndex) {
preNode.next = newNode;
newNode.next = curNode;
size++;
return true;
}
preNode = curNode;
curNode = curNode.next;
curIndex++;
}
return false;
}
/**
* 删除头节点
*/
public boolean deleteHead() {
if (size <= 0) {
return true;
}
Node node = headNode;
if (node.next != null) {
headNode = node.next;
size--;
return true;
} else {
headNode = null;
size--;
return true;
}
}
/**
* 删除指定位置的节点
*
* @param index
*/
public boolean delete(int index) {
if (index <= 0 || index > size) {
return false;
}
Node preNode = headNode;
Node curNode = headNode;
int curIndex = 1;
while (curNode != null) {
if (index == curIndex) {
preNode.next = curNode.next;
size--;
return true;
}
preNode = curNode;
curNode = curNode.next;
curIndex++;
}
return false;
}
/**
* 输出节点内容。
*/
public void out() {
Node node = headNode;
int index = 1;
while (node != null || index <= size) {
Log.d("out", "outPutLinkedList: " + node.data);
node = node.next;
index++;
}
Log.d("xx", "outPutLinkedList: " + "----------这是一条严谨的分割线--------");
}
双向链表
双向链表的每个节点都有,数据域,前驱指针域,后继指针域,指针分别指向前一个节点跟后一个节点,看示意图:
除去头节点的前驱指针指向null跟尾节点的后继指针指向null外,所有的节点的前驱指针跟后继指针都指向前一个节点或后一个节点。即可以从头节点开始操作链表,也可以从尾节点开始操作链表。跟单向列表对比,如果是操作尾节点或者更加靠近尾节点时操作链表,不需要又从头节点开始查找直到尾节点,而是倒序从尾节点开始操作链表了。下面简单实现一下双向链表。
public class DuplexingLinkedList {
public static class Node {
Node next; //指针域,指向下一个节点
Node pre; //指针域,指向上一个节点
String data; //数据域,这里统一数据类型为string
Node(String data) {
this.data = data;
}
}
private Node headNode; //头节点
private Node tailNode; //双向链表需要比单向链表多出一个尾节点来
private int size; // 链表容量大小
public DuplexingLinkedList() {
headNode = null;
tailNode = null;
}
/**
* 头插法
*
* @param data
*/
public void insertAtHead(String data) {
Node newNode = new Node(data);
if (size == 0) {
headNode = newNode;
tailNode = newNode;
} else {
newNode.next = headNode;
headNode.pre = newNode;
headNode = newNode;
}
size++;
}
/**
* 尾插法
*
* @param data
*/
public void insertAtTail(String data) {
Node newNode = new Node(data);
if (size == 0) {
headNode = newNode;
tailNode = newNode;
} else {
tailNode.next = newNode;
newNode.pre = tailNode;
tailNode = newNode;
}
size++;
}
/**
* @param data
* @param index 这里定义以1开始,要大于0。
* @return
*/
public boolean insertAtPosition(String data, int index) {
if (index <= 0) {
return false;
}
if (index == 1) {
insertAtHead(data);
return true;
} else if (index == size) {
insertAtTail(data);
return true;
} else {
//双向链表,可以选择从头节点开始遍历,也可以选择尾节点开始遍历。这里使用尾节点吧,头节点就跟单向链表一致的。
Node newNode = new Node(data);
Node c = tailNode; //当前节点。
int curIndex = size;
while (c != null) {
if (index == curIndex) {
newNode.pre = c.pre;
newNode.next = c;
c.pre.next = newNode;
c.pre = newNode;
size++;
return true;
}
c = c.pre;
curIndex--;
}
}
return false;
}
/**
* 删除头节点
*
* @return
*/
public boolean deleteHead() {
if (size == 0) {
return false;
}
if (headNode.next != null) {
headNode = headNode.next;
headNode.pre = null;
size--;
} else {
headNode = null;
tailNode = null;
size--;
}
return true;
}
/**
* 删除尾节点
*
* @return
*/
public boolean deleteTail() {
if (size == 0) {
return false;
}
if (tailNode.pre != null) {
tailNode = tailNode.pre;
tailNode.next = null;
size--;
} else {
headNode = null;
tailNode = null;
size--;
}
return true;
}
/**
* 删除指定位置节点
*
* @param index
* @return
*/
public boolean delete(int index) {
if (index <= 0) {
return false;
}
if (index == 1) {
return deleteHead();
} else if (index == size) {
return deleteTail();
} else {
//倒序遍历删除。
Node c = tailNode; //当前节点。
int curIndex = size;
while (c != null) {
if (index == curIndex) {
c.next.pre = c.pre;
c.pre.next = c.next;
size--;
return true;
}
c = c.pre;
curIndex--;
}
}
return false;
}
/**
* 顺序遍历
*/
public void out() {
Node node = headNode;
int index = 1;
while (node != null || index <= size) {
Log.d("xx", "outPutLinkedList: " + node.data);
node = node.next;
index++;
}
Log.d("xx", "outPutLinkedList: " + "---------这是一条严谨的分割线---------");
}
/**
* 倒序遍历
*/
public void outForAfter() {
Node node = tailNode;
int index = size;
while (node != null || index > 0) {
Log.d("xx", "outPutLinkedList: " + node.data);
node = node.pre;
index--;
}
Log.d("xx", "outPutLinkedList: " + "----------这是一条严谨的分割线--------");
}
}
总得来说,链表有特殊的指针域指向前一个节点或者后一个节点,会使得增删操作效率更加高效。可以对比数组,不需要重复地移动其余的元素,只需要改变将指向节点的指针就可以了。同样的,链表没有索引使得查询效率低些。无论如何查找,都要从头节点或者尾节点依次查找。