一、单链表:
①用一组 地址任意的存储单元存放线性表中的数据元素。
②以元素(数据元素的值) + 指针(指示后继元素存储位置) = 结点
(表示数据元素 或 数据元素的映象),
③这种以“结点的序列”表示线性表,称作链表
1、头结点,头指针
以线性表中第一个数据元素a0的存储地址作为线性表的地址,称作线性表的头指针。
有时为了操作方便,在第一个结点(首结点)之前虚加一个“头结点”,以指向头结点的指针为链表的头指针。
2、结点类的描述:
public class Node {
private Object data; // 存放结点值
private Node next; // 后继结点的引用
public Node() { // 无参数时的构造函数
this(null, null);
}
public Node(Object data) { // 构造值为data的结点
this(data, null);
}
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
public Object getData() {
return data;
}
public Node getNext() {
return next;
}
public void setData(Object data) {
this.data = data;
}
public void setNext(Node next) {
this.next = next;
}
}
3、单链表类的描述:
import java.util.Scanner;
public class LinkList implements IList {
private Node head;// 单链表的头指针
// 单链表的构造函数
public LinkList() {
head = new Node(); // 初始化头结点
}
public LinkList(int n, boolean Order) throws Exception {
this();// 初始化头结点
if (Order) // 用尾插法顺序建立单链表
create1(n);
else
// 用头插法逆位序建立单链表
create2(n);
}
// 用尾插法顺序建立单链表。其中n为该单链表的元素个数
public void create1(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 create2(int n) throws Exception {
Scanner sc = new Scanner(System.in);// 构造用于输入的对象
for (int j = 0; j < n; j++)
// 输入n个元素的值
insert(0, sc.next());// 生成新结点,插入到表头
}
// 将一个已经存在的带头结点单链表置成空表
public void clear() {
head.setData(null);
head.setNext(null);
}
// 判断当前带头结点的单链表是否为空
public boolean isEmpty() {
return head.getNext() == null;// 判断首结点结点是否为空
}
// 求带头结点的单链表的长度
public int length() {
Node p = head.getNext();// 初始化,p指向首结点结点,length为计数器
int length = 0;
while (p != null) {// 从首结点结点向后查找,直到p为空
p = p.getNext();// 指向后继结点
++length;// 长度增1
}
return length;
}
// 读取带头结点单链表中的第i个数据元素
public Object get(int i) throws Exception {
Node p = head.getNext();// 初始化,p指向首结点,j为计数器
int j = 0;
while (p != null && j < i) {// 从首结点向后查找,直到p指向第i个元素或p为空
p = p.getNext();// 指向后继结点
++j;// 计数器的值增1
}
if (j > i || p == null) { // i小于0或者大于表长减1
throw new Exception("第" + i + "个元素不存在");// 输出异常
}
return p.getData();// 返回元素p
}
// 在带头结点单链表中第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.getNext();
++j;// 计数器的值增1
}
if (j > i - 1 || p == null) // i不合法
throw new Exception("插入位置不合理");// 输出异常
Node s = new Node(x); // 生成新结点
s.setNext(p.getNext());// 插入单链表中
p.setNext(s);
}
// 删除带头结点的第i个数据元素。其中i取值范围为:0≤i≤length()- 1,如果i值不在此范围则抛出异常
public void remove(int i) throws Exception {
Node p = head;// 初始化p为头结点,j为计数器
int j = -1;
while (p.getNext() != null && j < i - 1) {// 寻找i个结点的前驱
p = p.getNext();
++j;// 计数器的值增1
}
if (j > i - 1 || p.getNext() == null) { // i小于0或者大于表长减1
throw new Exception("删除位置不合理");// 输出异常
}
p.setNext(p.getNext().getNext());// 删除结点
}
// 在带头结点的单链表中查找值为x的元素,如果找到,则函数返回该元素在线性表中的位置,否则返回-1
public int indexOf(Object x) {
Node p = head.getNext();// 初始化,p指向首结点结点,j为计数器
int j = 0;
while (p != null && !p.getData().equals(x)) {// 从单链表中的首结点元素开始查找,直到p.getData()指向元素x或到达单链表的表尾
p = p.getNext();// 指向下一个元素
++j;// 计数器的值增1
}
if (p != null)// 如果p指向表中的某一元素
return j;// 返回x元素在顺序表中的位置
else
return -1;// x元素不在顺序表中
}
// 输出线性表中的数据元素
public void display() {
Node node = head.getNext();// 取出带头结点的单链表中的首结点元素
while (node != null) {
System.out.print(node.getData() + " ");// 输出数据元素的值
node = node.getNext();// 取下一个结点
}
System.out.println();// 换行
}
public Node getHead() {
return head;
}
public void setHead(Node head) {
this.head = head;
}
}
3、单链表的应用举例
1)、编写程序实现:建立一个单链表,然后删除单链表中“多余”的结点,即使操作之后的单链表中只留下不相同的结点,最后输出删除后的单链表中所有结点。
分析:要删除单链表中相同的结点,只要从单链表的首结点开始依次将单链表中每一个结点与它后面的所有结点进行比较,若遇到相等的,则将此结点从链表中删除。当对单链表中的每一个结点都做完处理后,算法结束。
代码:
public class Example2_4 {
public static void main(String[] args) throws Exception {
System.out.println("请输入10个单链表中的数据元素:");
LinkList L = new LinkList(10, true);// 从表头到表尾顺序建立一个表长为10的单链表
System.out.println("删除重复元素前单链表中各个数据元素:");
L.display();
removeRepeatElem(L);
System.out.println("删除重复元素后单链表中各个数据元素:");// 输出
L.display(); // 输出删除重复元素后单链表中所有的数据元素
}
// 删除单链表中"多余"的数据元素
private static void removeRepeatElem(LinkList L) throws Exception {
Node p = L.getHead().getNext(), q;// 初始化,p指向首结点
while (p != null) {// 从首结点向后查找,直到p为空
int order = L.indexOf(p.getData());// 记录p在单链表中的位置
q = p.getNext();
while (q != null) {
if (p.getData().equals(q.getData()))// 删除重复数据元素
L.remove(order + 1);
else
++order;
q = q.getNext();
}
p = p.getNext();
}
}
}
结果:
请输入10个单链表中的数据元素:
aa cd j 1 2 5 7 2 aa j
删除重复元素前单链表中各个数据元素:
aa cd j 1 2 5 7 2 aa j
删除重复元素后单链表中各个数据元素:
aa cd j 1 2 5 7
引进3个指针pa,pb和pc,其中pa,pb分别指向La和Lb表中当前待比较插入的结点,而pc指向Lc表当前最后一个结点。
若pa.getData()<=pb.getData(),则将pa所指结点链接到pc所指结点之后,否则将pb所指结点链接到pc所指结点之后。当其中一个表为空时,则只要将另一个表的剩余段链接在pc所指的结点之后即可.
代码:
import java.util.Scanner;
public class Example2_5 {
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);// 构造用于输入的对象
int m = 0, n = 0;// 初始化LA的元素个数m,LB中的元素个数n
System.out.println("请输入LA中的元素的个数:");
m = sc.nextInt();
System.out.println("请按非递减的方式输入" + m + "个数字:");
LinkList LA = new LinkList(m, true);// 用尾插法顺序建立单链表
System.out.println("请输入LB中的元素的个数:");
n = sc.nextInt();
System.out.println("请按非递减的方式输入" + n + "个数字:");
LinkList LB = new LinkList(n, true);// 用尾插法顺序建立单链表
System.out.println("将单链表LA和LB归并后,新的单链表LA中的各个数据元素:");// 输出
mergeList_L(LA, LB).display();
}
// 归并两个按值非递减排列的带头结点的单链线性表LA和LB,得到新的带头结点单链表LA,LA元素也按值非递减排列,并返回LA
public static LinkList mergeList_L(LinkList LA, LinkList LB) {
Node pa = LA.getHead().getNext();// 初始化,pa为LA的首结点
Node pb = LB.getHead().getNext();// 初始化,pb为LB的首结点
Node pc = LA.getHead();// 用LA的头结点,初始化pc
int da, db;// 元素值所对应的浮点数
while (pa != null && pb != null) {// pa和pb同时非空
da = Integer.valueOf(pa.getData().toString());// 把字符串转化成浮点数
db = Integer.valueOf(pb.getData().toString());// 把字符串转化成浮点数
if (da <= db) {
pc.setNext(pa);// 将LA中的结点加入到新的LA中
pc = pa;
pa = pa.getNext();
} else {
pc.setNext(pb);// 将LB中的结点加入到新的LA中
pc = pb;
pb = pb.getNext();
}
}
pc.setNext(pa != null ? pa : pb); // 插入剩余结点
return LA;
}
}
结果:
请输入LA中的元素的个数:
3
请按非递减的方式输入3个数字:
4 78 999
请输入LB中的元素的个数:
2
请按非递减的方式输入2个数字:
1 5
将单链表LA和LB归并后,新的单链表LA中的各个数据元素:
1 4 5 78 999
二、循环链表:
①与单链表一样,只是将尾节点指向头节点或首结点。
②和单链表的差别仅在于,判别链表中最后一个结点的条件不再是“后继是否为空”,而是“后继是否为头结点”
三、双向链表:
双向链表的操作特点:
①“查询” 和单链表相同。
②“插入” 和“删除”时需要同时修改两个方向上的指针。
双向链表的节点类:
public class DuLNode {
private Object data;// 存放结点值
private DuLNode prior; // 前驱结点的引用
private DuLNode next; // 后继结点的引用
public DuLNode() {// 无参数时的构造函数
this(null);
}
public DuLNode(Object data) {// 构造值为data的结点
this.data = data;
this.prior = null;
this.next = null;
}
public Object getData() {
return data;
}
public DuLNode getNext() {
return next;
}
public DuLNode getPrior() {
return prior;
}
public void setData(Object data) {
this.data = data;
}
public void setNext(DuLNode next) {
this.next = next;
}
public void setPrior(DuLNode prior) {
this.prior = prior;
}
}
双向循环链表类:
import java.util.Scanner;
public class DuLinkList implements IList {
private DuLNode head;// 双向链表的头结点
// 双向链表的构造函数
public DuLinkList() {
head = new DuLNode(); // 初始化头结点
head.setPrior(head);// 初始化头结点的前驱和后继
head.setNext(head);
}
// 从表尾到表头逆向建立双向链表的算法。其中n为该双向链表的元素个数
public DuLinkList(int n) throws Exception {
this();
Scanner sc = new Scanner(System.in);// 构造用于输入的对象
for (int j = 0; j < n; j++)
insert(0, sc.next());// 生成新结点,插入到表头
}
// 在带头结点双向链表的第i个数据元素之前插入一个值为x的数据元素,i等于表长时,p指向头结点;i大于表长时,p=NULL。
// 其中i取值范围为:0≤i≤length()。当i=0时表示在表头插入一个数据元素x,当i=length()时表示在表尾插入一个数据元素x
public void insert(int i, Object x) throws Exception {
DuLNode p = head.getNext();// 初始化,p指向首结点,j为计数器
int j = 0;
while (!p.equals(head) && j < i) {// 寻找插入位置i
p = p.getNext();// 指向后继结点
++j;// 计数器的值增1
}
if (j != i && !p.equals(head)) // i小于0或者大于表长
throw new Exception("插入位置不合理");// 输出异常
DuLNode s = new DuLNode(x);// 生成新结点
p.getPrior().setNext(s);
s.setPrior(p.getPrior());
s.setNext(p);
p.setPrior(s);
}
// 将带头结点的双向链表中第i个数据元素删除。其中i 取值范围为:0≤i≤ength()-1
public void remove(int i) throws Exception {
DuLNode p = head.getNext();// 初始化,p指向首节点结点,j为计数器
int j = 0;
while (!p.equals(head) && j < i) {// 寻找删除位置i
p = p.getNext();// 指向后继结点
++j;// 计数器的值增1
}
if (j != i) // i小于0或者大于表长减1
throw new Exception("删除位置不合理");// 输出异常
p.getPrior().setNext(p.getNext());
p.getNext().setPrior(p.getPrior());
}
public void display() {
DuLNode node = head.getNext();// 取出带头结点的双向链表中的首结点
while (!node.equals(head)) {
System.out.print(node.getData() + " ");// 输出数据元素的值
node = node.getNext();// 取下一个结点
}
System.out.println();// 换行
}
public DuLNode getHead() {
return head;
}
public void setHead(DuLNode head) {
this.head = head;
}
}