*文中内容来源于《数据结构 --Java语言描述》(第二版) 刘小晶 杜选 主编
*此系列文章作为学习记录,若文中内容有误,请大家指出,谢谢
链表
顺序存储的局限性:
- 若要为线性表扩充存储空间,则需重新创建一个地址连续的更大的存储空间,并把原有的数据元素都复制到新的存储空间中;
- 顺序存储要求逻辑上相邻的数据元素,在物理存储位置上也相邻,所以增删数据元素则会引起平均约一半的数据元素的移动。
对于需要频繁执行插入和删除操作的“动态”线性表,通常用链式存储结构。
链式存储结构不要求逻辑是上相邻的数据元素在物理上也相邻,它是用一组地址任意的存储单元来存放数据元素的值。因此,链式存储结构失去了可随机存储的特点,在链式存储结构上只能进行顺序存储。
采用链式存储方式存储的线性表称为链表,链表中每一个节点包含存放数据元素值的数据域和存放指向逻辑上相邻结点的指针域。若一个结点中只包含一个指针域,则称此链表为单链表(Single Linked List)。
单链表是通过指向后继结点的指针把它的一串结点链接成一个链。
以线性表中第一个数据元素的存储地址作为线性表的起始地址,称作线性表的头指针。一个单链表就是由它的头指针head来唯一标识它。单链表中的最后一个结点(也称为尾结点)没有后继,所以它的指针域的值为空指针null。
结点类的描述
/**
* data是数据域,用来存放数据元素的值;next是指针域,用来存放后继结点的地址
*/
public class Node {
public Object data; //存放结点值
public Node next; //后继结点的引用
//无参数时的构造函数
public Node(){
this(null,null);
}
//带一个参数时的构造函数
public Node(Object data){
this(data,null);
}
//带两个参数时的构造函数
public Node(Object data,Node next){
this.data = data;
this.next = next;
}
}
顺序表的描述
public interface IList {
public void clear();
public boolean isEmpty();
public int length();
public Object get(int i) throws Exception;
public void insert(int i, Object x) throws Exception;
public void remove(int i) throws Exception;
public int indexOf(Object x);
public void display();
}
链表的实现
import java.util.Scanner;
public class LinkList implements IList{
public Node head; //单链表的头指针
public LinkList(){ //单链表的构造函数
head = new Node(); //初始化头结点
}
public LinkList(int n, boolean Order) throws Exception{ //创建一个长度为n的单链表
this(); //初始化头系欸带你
if (Order)
create_end(n); //用尾插法顺序建立单链表
else
create_start(n); //用头插法逆位序建立单链表
}
//用尾插法顺序建立单链表,其中n为单链表的结点个数
public void create_end(int n) throws Exception{
Scanner sc = new Scanner(System.in); //构造用于输入的对象
for (int j = 0; j < n; j++){ //输入n个结点的数据域值
insert(length(), sc.next()); //生成新结点,插入到表尾
}
}
//用头插法逆位序建立单链表,其中n为单链表的结点个数
public void create_start(int n) throws Exception{
Scanner sc = new Scanner(System.in);
for (int j = 0; j < n; j++){
insert(0,sc.next()); //生成新结点,插入到表头
}
}
//将一个已存在的带头结点单链表置成空表
public void clear(){
head.data = null;
head.next = null;
}
//判断带头结点的单链表是否为空
public boolean isEmpty(){
return head.next == null;
}
//求带头结点的单链表的长度
public int length(){
Node p = head.next; //初始化,p指向首结点,length为计数器
int length = 0;
while (p != null){ //从首结点开始向后查找,直到p为空
p = p.next; //指向后继结点
++length; //长度加1
}
return length;
}
/**
* 单链表的存储空间不连续,单链表是一种顺序存储的结构,要去第i个结点的值,只能从头指针
* 所指的结点开始沿着后继指针一次进行查找。
*/
//读取带头结点的单链表中的第i个结点
public Object get(int i) throws Exception{
Node p = head.next;
int j = 0;
while (p != null && j < i){ //从首结点开始向后查找,直到p指向第i个结点或p为空
p = p.next; //指向后继结点
++j; //计数器的值增加1
}
if (j > i || p == null) { //i小于0或者大于表长减1时,即i不合法
throw new Exception("第" + i + "个元素不存在"); //抛出异常
}
return p.data; //返回结点p的数据域值
}
//在带头结点的单链表中查找值为x的结点,遍历
public int indexOf(Object x){
Node p = head.next; //初始化,p指向首结点,j为计数器
int j = 0;
//下面从单链表中的首结点开始查找,直到p.data为x或者到达单链表的表尾
while (p != null && p.data.equals(x)){
p = p.next; //指向下一个结点
++j; //计数器的值增加1
}
if (p != null)
return j; //返回值为x结点在单链表中的位置
else
return -1;
}
/**
* 插入操作的主要步骤:
* (1)查找到待插入位置的前驱结点(或待插入的结点)
* (2)创建数据域值为x的新结点
* (3)修改相关结点的指针域值从而使新结点插入到单链表中给定的位置
*
* 新结点要插入到不带头结点的单链表的表头,需要将新结点s的后继指针指向原来单链表的
* 第一个结点,并将头指针指向新结点,使新结点成为插入后的单链表中的第一个结点,
* 对应语句为:
* s.next = head;
* head = s;
*
* 若是在不带头结点的单链表的中间位置或表尾插入一个新结点,其修改链的语句与在
* 带头结点的单链表上的插入操作相同:
* s.next = p.next;
* p.next = s;
*/
/*
//在不带头结点的单链表上的插入操作
public void insert(int i, Object x) throws Exception{
Node p = head; //请用i = -1\0\1去测试
int j = 0;
while(p != null && j < i - 1){
p = p.next;
++j;
}
if(j > i || p == null){
throw new Exception("插入位置不合理");
}
Node s = new Node(x);
if(i == 0){ //插入位置为表头
s.next = head;
head = s;
}
else{ //插入位置为表的中间或表尾时
s.next = p.next;
p.next = s;
}
*/
//在带头结点的单链表中的第i个结点之前插入一个值为x的新结点
public void insert(int i, Object x) throws Exception{
Node p = head; //初始化p为头结点,j为计数器
int j = -1;
while (p != null && j < i - 1){ //寻找第i个结点的前驱
p = p.next;
++j; //计数器的值增加1
}
if (j > i - 1 || p == null) //i不合法
throw new Exception("插入位置不合法"); //抛出异常
Node s = new Node(x); //生成新结点
s.next = p.next; //修改链,使新结点插入单链表中
p.next = s;
}
/**
* 删除操作的主要步骤:
* (1)判断单链表是否为空,若为空则结束操作,否则转(2)
* (2)查找到待删除结点的前驱结点(或确定待删除结点的位置)
* (3)修改链指针,使待删除结点从单链表中脱离出来
*
* 在不带头结点的单链表上删除第i个结点,则也要像插入操作一样分成两种情况分别处理
* 一种是删除第一个结点,另一种是删除其他位置结点
*/
//删除带头结点的单链表中的第i个结点
public void remove(int i) throws Exception{
Node p = head; //初始化p指向头结点,j为计数器
int j = -1;
while(p.next != null && j < i - 1){ //寻找第i个结点的前驱
p = p.next;
++j;
}
if (j > i - 1 || p.next == null){
throw new Exception("删除位置不合法");
}
p.next = p.next.next; //修改链指针,使待删除结点从单链表中脱离出来
}
//输出单链表中的所有结点
public void display(){
Node node = head.next; //读取带头结点的单链表中的首结点
while (node != null){
System.out.print(node.data + ""); //输出结点的值
node = node.next; //输出下一个结点
}
System.out.println(); //换行
}
}