线性表的顺序存储结构的特点是逻辑关系上相邻的两个元素在物理位置上也相邻。因此可以随机存储表中任一元素,它的存储位置可以用一个简单的,直观的公式来表示。然而,从另一方面来看,这个特点也铸成了这种存储结构的弱点:在作插入或者删除操作时,需要移动大量元素。
正相反,线性表的另一种存储结构—链式存储结构不要求逻辑上相邻的元素在物理位置上也相邻,因此,它没有顺序存储结构所具有的弱点,但它同时也失去了顺序表可随机存取的优点。
链表是一种链式存储结构的线性表,其特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素 ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的位置)。这两部分信息组成了数据元素ai的存储映像,称为结点(node)。它包括两个域:存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域(Java没有指针,故直接用一个结点对象代替指针指示其直接后继结点)。n个结点链结成一个链表。
单链表
单链表的每一个结点中只包含一个指针域,故又称为线性链表。单链表的存取必须从头结点开始进行,头结点指示链表中的第一个结点。同时,由于最后一个数据元素没有直接后继,则单链表中最后一个结点的指针域为“空”(NULL)。
单链表的基本操作有:添加结点,删除结点,在指定结点后插入结点等等操作
单链表的底层实现
MyNode 是我定义的一个结点类
- MyNode append(MyNode node) 添加结点(在队尾处添加结点)
- void removenext() 删除当前结点的下一个结点
- void after(MyNode node) 在当前结点后插入结点
- MyNode next() 获取当前结点的下一个结点
- int getData() 获取当前结点的信息
- boolean isLast() 判断当前结点是否为最后一个结点
- void show() 显示所有结点信息
package util;
public class MyNode {
//单链表
//节点内容
int data;
//下一个节点
MyNode next;
public MyNode(int data) {
this.data = data;
}
//追加节点
public MyNode append(MyNode node) {
//当前节点
MyNode currentnode = this;
//循环向后找末尾
while(true) {
//取出下一个节点
MyNode nextnode = currentnode.next;
//如果已经到最后一个节点
if(nextnode==null) {
break;
}
currentnode=nextnode;
}
//将需要追加的节点追加到找到的当前节点的下一个节点
currentnode.next=node;
return this;
}
//删除下一个节点
public void removenext() {
//取出当前节点的下下个节点
MyNode newNext = this.next.next;
//把下下个节点设置为当前节点的下一个节点
this.next = newNext;
}
//插入一个节点作为当前节点的下一个节点
public void after(MyNode node) {
//取出当前节点的下一个节点
MyNode nextNext = this.next;
//把新节点作为当前节点的下一个节点
this.next = node;
//把此时当前节点的下下个节点设置为新节点的下一个节点
node.next = nextNext;
}
//获取下一个节点
public MyNode next() {
return this.next;
}
//获取当前节点的信息
public int getData() {
return this.data;
}
//查看当前节点是否为最后一个节点
public boolean isLast() {
return this.next==null;
}
//显示所有节点信息
public void show() {
MyNode currentnode = this;
while(true) {
System.out.print(currentnode.data+" ");
//取到下一个节点
currentnode=currentnode.next;
//如果是最后一个节点
if(currentnode==null) {
break;
}
}
}
}
简单Test:
package classify;
import util.MyNode;
public class SinglyListTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建节点(每一个节点都是单一的单链表)
MyNode n1 = new MyNode(1);
MyNode n2 = new MyNode(2);
MyNode n3 = new MyNode(3);
//追加节点
n1.append(n2).append(n3).append(new MyNode(8));
//取出下一个节点的数据
System.out.println(n1.next().next().next().getData()); //8
//判断是否为最后一个节点
System.out.println(n1.next().isLast()); //false
System.out.println(n1.next().next().next().isLast()); //true
//显示所有节点信息
n1.show(); // 1 2 3 8
//删除第2个节点(自0开始)
n1.next().next().removenext();
//显示所有节点信息
System.out.println();
n1.show(); // 1 2 3
//在第一个结点后插入一个节点
n1.after(new MyNode(6));
//显示所有节点信息
System.out.println(); // 1 6 2 3
n1.show();
}
}
单链表反转
所谓的单链表反转就是将链表的指针方向改变。
这里介绍两种方法:递归法,遍历法。
递归法
总体来说,递归法从最后一个Node开始,在弹栈的过程中将指针顺序置换的。
实现代码:
//递归法实现单链表的反转
public static MyNode reverse(MyNode head) {
if(head == null || head.next == null) {
return head;
}
MyNode temp = head.next;
//递归
MyNode newhead = reverse(head.next);
temp.next = head;
head.next = null;
return newhead;
}
测试(测试代码接上面的Test):
MyNode t = reverse(n1);
t.show(); // 3 2 6 1
遍历法
遍历法就是在链表遍历的过程中将指针顺序置换。
实现代码:
//遍历法实现单链表的反转
public static MyNode reverseList(MyNode node) {
//记录反转后的链表
MyNode pre = null;
MyNode next = null;
//遍历
while(node != null) {
//记录当前结点的下一个结点
next = node.next;
//使当前结点链接至当前已反转的结点链表的头
node.next = pre;
//使pre指向已反转链表
pre = node;
//继续访问未反专转的结点链表
node = next;
}
return pre;
}
测试(继上文的测试):
MyNode f = reverseList(t);
f.show(); // 1 6 2 3
相对来说,使用递归法实现单链表的反转比较优雅。有不好理解的地方多画图看看!!
后续链表内容请转至 数据结构之链表2