基础数据结构:链表
维基百科
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
在计算机科学中,链表作为一种基础的数据结构可以用来生成其它类型的数据结构。链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点的位置的链接(“links”)。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的访问往往要在不同的排列顺序中转换。而链表是一种自我指示数据类型,因为它包含指向另一个相同类型的数据的指针(链接)。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。
单向链表
链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。
一个单向链表的节点被分成两个部分。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址。单向链表只可向一个方向遍历。
链表最基本的结构是在每个节点保存数据和到下一个节点的地址,在最后一个节点保存一个特殊的结束标记,另外在一个固定的位置保存指向第一个节点的指针,有的时候也会同时储存指向最后一个节点的指针。一般查找一个节点的时候需要从第一个节点开始每次访问下一个节点,一直访问到需要的位置。但是也可以提前把一个节点的位置另外保存起来,然后直接访问。当然如果只是访问数据就没必要了,不如在链表上储存指向实际数据的指针。这样一般是为了访问链表中的下一个或者前一个(需要储存反向的指针,见下面的双向链表)节点。
相对于下面的双向链表,这种普通的,每个节点只有一个指针的链表也叫单向链表,或者单链表,通常用在每次都只会按顺序遍历这个链表的时候(例如图的邻接表,通常都是按固定顺序访问的)。
双向链表
一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个连接:一个指向前一个节点,(当此“连接”为第一个“连接”时,指向空值或者空列表);而另一个指向下一个节点,(当此“连接”为最后一个“连接”时,指向空值或者空列表)
循环链表
在一个 循环链表中, 首节点和末节点被连接在一起。这种方式在单向和双向链表中皆可实现。要转换一个循环链表,你开始于任意一个节点然后沿着列表的任一方向直到返回开始的节点。再来看另一种方法,循环链表可以被视为“无头无尾”。这种列表很利于节约数据存储缓存, 假定你在一个列表中有一个对象并且希望所有其他对象迭代在一个非特殊的排列下。指向整个列表的指针可以被称作访问指针。
单链表实现
java实现
public class LinkList {
private Node head;
public LinkList(){
this.head = new Node();
}
/* 向链表末尾中插入节点 */
public void insertNode(String data){
Node n = new Node(data);
Node tmp = getLastNode();
tmp.setNext(n);
}
/* 向节点中给定的下标位置插入节点,若给定的下标位置超出链表长度则在链表的最后面插入该节点 */
public void insertNode(String data, int index){
if(index > getLength()) {
insertNode(data);
}else {
Node n = new Node(data);
Node n1 = getNode(index-1);
n.setNext(n1.getNext());
n1.setNext(n);
}
}
/* 根据节点下标删除节点 */
public boolean delete(int index){
if (index > getLength()){
return false;
}else {
Node n = getNode(index-1);
Node n_next = n.getNext();
n.setNext(n_next.getNext());
n_next.setNext(null);
return true;
}
}
/* 删除链表中第一个元素为data的节点 */
public boolean delete(String data){
return delete(getIndex(data));
}
/* 根据节点内数据修改节点数据 */
public void alter(String d0, String d1){
getNode(getIndex(d0)).setData(d1);
}
/* 根据节点下标修改节点数据 */
public void alter(int index, String data){
getNode(index).setData(data);
}
/* 根据下标获取链表的节点 */
public Node getNode(int index){
Node tmp = this.head;
int num = 0;
while (tmp.getNext() != null && num < index){
num++;
tmp = tmp.getNext();
}
return tmp;
}
/* 根据节点元素的值获得节点下标 */
public int getIndex(String data){
Node tmp = this.head;
int index = 0;
while (tmp.getNext() != null){
if (data.equals(tmp.getData())){
return index;
}
tmp = tmp.getNext();
index++;
}
return index+1;
}
/* 获得链表的尾节点 */
private Node getLastNode(){
Node tmp = this.head;
while(tmp.getNext() != null){
tmp = tmp.getNext();
}
return tmp;
}
/* 获取链表长度 */
private int getLength(){
Node tmp = this.head;
int num = 0;
while(tmp.getNext() != null){
num++;
tmp = tmp.getNext();
}
return num;
}
/* 链表是否为空 */
private boolean isEmpty(){
return head.getNext() == null;
}
@Override
public String toString(){
if (isEmpty()){
return null;
}else {
return head.getNext().toString();
}
}
}
public class Node {
private String data;
private Node next;
public Node(){
this.data = null;
this.next = null;
}
public Node(String data){
this.data = data;
this.next = null;
}
public String getData(){
return this.data;
}
public Node getNext(){
return this.next;
}
public void setData(String data){
this.data = data;
}
public void setNext(Node next){
this.next = next;
}
@Override
public String toString() {
if(this.next == null){
return this.data;
}else {
return this.data + "\t" + this.next.toString();
}
}
}
这里只给出单向链表的实现,双向链表和循环链表与其类似。