我们知道数组需要一块连续的内存空间来存储数据,数组中逻辑上相邻的两个元素在物理位置上也相邻。而链表就不一样了,它不需要一块连续的存储空间,它通过指针将一组零散的内存块串起来。我们把内存块称为结点(Node)。
链表为了将所有的结点串起来,链表的每个结点除了存储数据外,还需要存储一个指向其下一个结点位置的指针。所以链表有两块存储区域,其中存储数据的域称为数据域,存储下一个结点存储位置的域称为指针域。
单链表
我们在第一个结点之前增设一个结点,叫做头结点,头结点的数据域可以不存储任何信息,也可以存储如链表的长度等附加信息。头结点用来记录链表的基地址,有了头结点我们就可以遍历得到整条链表。最后一个结点叫做尾结点,它的指针域next不是指向下一个结点,而是指向空地址NULL。
在Java中结点的表示方式
class Node {
Item item;
Node next;
}
一个Node对象含有两个实例变量,分别为数据域Item和指针域Node。
与数组一样,链表也支持查找、插入、和删除操作。
单链表的查找操作
由于链表中的数据并非连续存储的,所以我们无法像数组那样,通过首地址和下标就可以直接计算出对应的内存地址。链表只能根据头结点一个一个向后遍历找到对应结点。
比如我们要在上图的链表(a、b和c结点索引依次为0、1、2)中找到索引为1的结点,我们只需要根据头结点依次向后遍历2次即可。
Node node(int index) {
Node tmp = head;
for(int i = 0; i <= index; i++) {
tmp = tmp.next;
}
return tmp;
}
单链表的插入操作
一般情况下,我们会在链表的尾部追加元素,过程比较简单,如下:
首先根据头结点依次向后遍历找到尾结点,将尾结点的指针域指向新的结点即可。
public boolean add(Integer item) {
Node newNode = new Node(item, null);
// tmp 指向头结点
Node tmp = head;
while (tmp.next != null) {
tmp = tmp.next;
}
tmp.next = newNode;
size++;
return true;
}
如果我们需要在链表指定索引的位置插入一个结点,比如在索引为1的位置上插入一个结点,即在a结点和b结点中间插入一个结点,过程如下:
首先根据头结点依次向后遍历找到a结点,将a结点的指针域指向aa结点,再将aa结点的指针域指向b结点。
public boolean add(int index, Integer item) {
if(index < 0 || index > size) {
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
}
Node newNode = new Node(item, null);
Node tmp = head;
// 找到指定索引的前一个索引的结点
for(int i = 0; i < index; i++) {
tmp = tmp.next;
}
// 指定索引位置上原来的结点
Node node = tmp.next;
tmp.next = newNode;
newNode.next = node;
size++;
return true;
}
单链表的删除操作
删除指定索引位置上的结点,比如删除b结点,过程如下:
根据头结点依次向后遍历,找到b结点的前一个结点a,将a结点的指针域next指向b结点的后一个结点c。
public Integer remove(int index) {
this.checkIndex(index);
// 找到要删除结点的前一个结点
Node tmp = head;
for(int i = 0; i < index; i++) {
tmp = tmp.next;
}
// 待删除的结点
Node node = tmp.next;
// 前一个结点的指针域指向带删除结点的后一个结点
tmp.next = node.next;
size--;
Integer val = node.item;
node.item = null;
return val;
}
上述单链表的查找、插入和删除操作完整代码如下:
public class SingleLinkedList {
// 头结点
private Node head;
// 链表中实际元素个数
private int size;
public SingleLinkedList() {
this.head = new Node();
this.size = 0;
}
/**
* 在链表尾部插入元素
* @param item
* @return
*/
public boolean add(Integer item) {
Node newNode = new Node(item, null);
// tmp 指向头结点
Node tmp = head;
while (tmp.next != null) {
tmp = tmp.next;
}
tmp.next = newNode;
size++;
return true;
}
/**
* 在指定的索引上插入元素
* @param index 从0开始
* @param item
* @return
*/
public boolean add(int index, Integer item) {
if(index < 0 || index > size) {
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
}
Node newNode = new Node(item, null);
Node tmp = head;
// 找到指定索引的前一个索引的结点
for(int i = 0; i < index; i++) {
tmp = tmp.next;
}
// 指定索引位置上原来的结点
Node node = tmp.next;
tmp.next = newNode;
newNode.next = node;
size++;
return true;
}
/**
* 返回指定位置的元素
* @param index
* @return
*/
public Integer get(int index) {
this.checkIndex(index);
Node node = this.node(index);
return node.item;
}
/**
* 删除指定索引位置的结点
* @param index
* @return
*/
public Integer remove(int index) {
this.checkIndex(index);
// 找到要删除结点的前一个结点
Node tmp = head;
for(int i = 0; i < index; i++) {
tmp = tmp.next;
}
// 待删除的结点
Node node = tmp.next;
// 前一个结点的指针域指向带删除结点的后一个结点
tmp.next = node.next;
size--;
Integer val = node.item;
node.item = null;
return val;
}
/**
* 修改指定位置结点的值
* @param index
* @param item
* @return
*/
public Integer set(int index, Integer item) {
this.checkIndex(index);
Node node = this.node(index);
Integer oldVal = node.item;
// 替换为新值
node.item = item;
return oldVal;
}
/**
* 遍历元素
*/
public void list() {
Node tmp = head;
while(tmp.next != null) {
tmp = tmp.next;
System.out.println(tmp.item);
}
}
/**
* 返回链表元素个数
* @return
*/
public int size() {
return size;
}
Node node(int index) {
Node tmp = head;
for(int i = 0; i <= index; i++) {
tmp = tmp.next;
}
return tmp;
}
private void checkIndex(int index) {
if(index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
}
}
private static class Node {
private Integer item;
private Node next;
public Node() {
}
public Node(Integer item, Node next) {
this.item = item;
this.next = next;
}
}
}
「更多精彩内容请关注公众号geekymv,喜欢请分享给更多的朋友哦」