1.单链表
在 Java 中没有显式的指针类型,然而实际上对象的访问就是使用指针来实现的,即在Java 中是使用对象的引用来替代指针的。因此在使用 Java 实现该结点结构时,一个结点本身就是一个对象。结点的数据域 data 可以使用一个 Object 类型的对象来实现,用于存储任何类型的数据元素,并通过对象的引用指向该元素;而指针域 next 可以通过节点对象的引用来实现。
结点接口
public interface Node {
//获取结点数据域
public Object gerData();
//设置结点数据域
public void setData(Object obj);
}
单链表结点定义
public class SLNode implements Node {
public Object element;
public SLNode next;
public SLNode() {
this(null, null);
}
public SLNode(Object ele, SLNode next) {
this.element = ele;
this.next = next;
}
public SLNode getNext() {
return next;
}
public void setNext(SLNode next) {
this.next = next;
}
/** ************** Methods of Node Interface ************* */
public Object gerData() {
return element;
}
public void setData(Object obj) {
element = obj;
}
}
单链表结构
需要注意的是:
在单链表结构中还需要注意的一点是,由于每个结点的数据域都是一个 Object 类的对象。
与数组类似,单链表中的结点也具有一个线性次序,即如果结点 P 的 next 引用指向结点 S,则 P 就是 S 的直接前驱, S 是 P 的直接后续。单链表的一个重要特性就是只能通过前驱结点找到后续结点,而无法从后续结点找到前驱结点。在单链表中通常需要完成数据元素的查找、插入、删除等操作。
(1)查找:在单链表中进行查找操作,只能从链表的首结点开始,通过每个结点的 next 引用来一次访问链表中的每个结点以完成相应的查找操作。
在单链表中进行查找操作,只能从链表的首结点开始,通过每个结点的 next 引用来一次访问链表中的每个结点以完成相应的查找操作。
除了单链表的首结点由于没有直接前驱结点,所以可以直接在首结点之前插入一个新的结点之外,在单链表中的其他任何位置插入一个新结点时,都只能是在已知某个特定结点引用的基础上在其后面插入一个新结点。并且在已知单链表中某个结点
引用的基础上,完成结点的插入操作需要的时间是Θ(1)。由于在单链表中数据元素的插入是通过节点的插入来完成的,因此在单链表中完成数据元素的插入操作要比在数组中完成数据元素的插入操作所需Ο(n)的时间要快得多。
引用的基础上,完成结点的插入操作需要的时间是Θ(1)。由于在单链表中数据元素的插入是通过节点的插入来完成的,因此在单链表中完成数据元素的插入操作要比在数组中完成数据元素的插入操作所需Ο(n)的时间要快得多。
(3)删除
链表的删除和插入相似,数据元素的删除也是通过对结点的操作完成的。在不同的位置,操作会略有不同。
在单链表中删除一个结点时,除首结点外都必须知道该结点的直接前驱结点的引用。并且在已知单链表中某个结点引用的基础上,完成其后续结点的删除操作需要的时间是Θ (1)。由于在单链表中数据元素的删除是通过节点的删除来完成的,因此
在单链表中完成数据元素的删除操作要比在数组中完成数据元素的删除操作所需Ο (n)的时间要快得多。
(4)结论:在单链表中进行顺序查找与在数组中完成相同操作具有相同的时间复杂度,而在单链表中在已知特定结点引用的前提下完成数据元素的插入与删除操作要比在数组中完成相同操作快得多。
(4)结论:在单链表中进行顺序查找与在数组中完成相同操作具有相同的时间复杂度,而在单链表中在已知特定结点引用的前提下完成数据元素的插入与删除操作要比在数组中完成相同操作快得多。
<strong>2.单链表的实现
class LinkList {
private Node head = null;
private Node tail = null;
int Length = 0;
public Node getHead() {
return head;
}
public void setHead(Node head) {
this.head = head;
}
public Node getTail() {
return tail;
}
public void setTail(Node tail) {
this.tail = tail;
}
// 判断链表是否为空
public boolean isEmpty() {
return (Length == 0);
}
// 查找指定位置的结点
public Node FindKth(int k) {
Node temp = head;
int i = 1;
while (temp != null && i < k) { // 移动元素,直到找到相应位置
temp = temp.next;
i++;
}
if (i == k)
return temp;
else
return null;
}
// 查找指定元素所在的位置
public int Find(String str) {
Node temp = head;
int current = 1;
while (temp != null && temp.key.equals(str)) {
temp = temp.next;
current++;
}
if (temp.key.equals(str))
return current;
else
return -1;
}
// 头插法
public void insertHead(String str) {
Node newLink = new Node(str);
newLink.next = head;
head = newLink;
Length++;
}
// 将结点插入指定位置
public void insert(int index, String str) {
Node newLink = new Node(str);
if (index > 1 && index < Length) {
newLink.next = FindKth(index - 1).next;
FindKth(index - 1).next = newLink;
// System.out.println(head.key); 此时head的值为4
Length++;
} else if (index == 1) {
newLink.next = head;
head = newLink;
Length++;
} else {
System.out.println("超出链表长度,插入无效");
}
}
// 删除指定位置的结点
public void delete(int index) {
if (index == 1) { // 删除位置为头结点
head = head.next;
Length--;
} else if (index > 1 && index < Length) { // 删除位置不为头结点或尾结点
Node temp = FindKth(index).next;
FindKth(index - 1).next = temp;
Length--;
} else {
System.out.println("超出链表长度,查找无效");
}
}
// 删除指定元素
public void deleteNode(String str) {
int current = Find(str);
if (current > 1 && current < Length) { // 删除的位置为头结点
Node temp = FindKth(current).next;
FindKth(current - 1).next = temp;
Length--;
} else if (current == 1) {
head = head.next;
Length--;
} else if (current == -1) {
System.out.println("要删除的元素不存在,请重新选择!");
}
}
// 初始化链表
public void initList(Node node) {
head = node;
head.next = tail;
}
// 打印单链表
public void display() {
Node current = head;
while (current != null) {
current.displayNode();
current = current.next;
}
}
}
public class NodeTest {
public static void main(String args[]) {
LinkList list = new LinkList();
list.insertHead("1");
list.insertHead("2");
list.insertHead("3");
list.insertHead("4");
System.out.println("头插法操作后的单链表:");
list.display();
System.out.println();
// 把元素插入指定位置
int index = 1;
System.out.println("插入元素后的单链表:");
list.insert(index, "a");
list.display();
// 删除元素
System.out.println();
System.out.println("删除元素后的单链表(删除指定元素):");
list.deleteNode("7");
list.display();
// 删除元素
System.out.println();
System.out.println("删除元素后的单链表(删除结点位置):");
list.delete(4);
list.display();
}
}
class Node {
public String key;
public Node next;// 指向下一个元素的指针
// 初始化头结点
public Node(String str) {
this.key = str;
this.next = null;
}
public void displayNode() {
System.out.print(key + " -> ");
}
}</strong>
3.基于时间的比较
线性表有查找,删除,插入三类操作。
对于查找操作有基于序号的查找,即存取线性表中 i 号数据元素。由于数组有随机存取的特性,在线性表的顺序存储实现中可以在Θ(1)的时间内完成;而在链式存储中由于需要从头结点开始顺着链表才能取得,无法在常数时间内完成,因此顺序存储优于链式存储。查找操作还有基于元素的查找,即线性表是否包含某个元素、元素的序号是多少,这类操作线性表的顺序存储与链式存储都需要从线性表中序号为 0 的元素开始依次查找,因此两种实现的性能相同。综上所述,如果在线性表的使用中主要操作是查找,那么应当选用顺序存储实现的线性表。对于基于数据元素的插入、删除操作而言,当使用数组实现相应操作时,首先需要采用顺序查找定位相应数据元素,然后才能插入、删除,并且在插入、删除过程又要移动大量元素;相对而言链表的实现只需要在定位数据元素的基础上,简单的修改几个指针即可完成,因此链式存储优于顺序存储。对于基于序号的插入、删除操作,因为在顺序存储中平均需要移动一半元素;而在链式存储中不能直接定位,平均需要比较一半元素才能定位。因此顺序存储与链式存储性能相当。综上所述,如果在线性表的使用中主要操作是插入、删除操作,那么选用链式存储的线性表为佳。
4.基于空间的比较