在Java中实现通用的单链表数据结构
linked list ,用于保存基于节点的数据。链接列表中的数据分布在许多位置,而不是像数组那样存储在单一的内存位置上。每个节点都有一个引用部分,下一个节点的地址被保存在这里。此外,简短的链表有连接一些节点的链接。
阵列数据结构通过解决其局限性而得到改进,如需要连续的内存和阵列插入和删除的困难。此外,一个链接列表使得在需求发生变化时添加和删除项目变得更加简单。
最简单的链接列表类型,即单链接列表,将在本文中介绍。链接列表可以被用于广泛的应用,从简单到复杂。我们将逐一对它们进行考察。
前提条件
- 在继续学习本教程之前,你应该对Java编程语言有一定的了解。
- 对于服务器端的Java开发,要有IntelliJ IDEA作为IDE。
构建模块
一个链接列表由以下部分组成。
- 节点:列表中的元素由节点表示。面向对象的编程可以用来创建节点。必须先定义一个节点类,然后才能为你想链接的每个节点创建一个实例。
class Node(object):
def __init__(self, num):
self.num = num
self.next = None
- 节点之间通过指针进行链接:节点通过指针链接,这是关联列表的主要特征之一。为了方便和顺序,每个节点都存储一个指向其 "邻居 "的指针。
让我们看一下下面的例子。
self.next # Pointer to the right node
self.prev # Pointer to the left node
由于HEAD节点是列表中的第一个节点,它的左边没有另一个节点,所以self.prev是None。TAIL节点也没有紧邻的节点,所以它的自身将是None。接下来,使用连接节点的指针,你可以从一个节点移动到另一个节点,直到找到你要找的那个节点。
链接列表和数组的区别
链接列表和数组的不同之处如下所示。
- 一个完整的数组只有一个内存地址。而一个链接列表在不同的内存位置存储各种元素。
- 一个链接列表的数据项可以在任何时候添加或删除,而一个数组的数组大小是在声明时指定的,事后不能改变。
- 由于连续分配,我们存储数组的唯一地方是在一个大的自由空间块中,而在一个链接列表中,不同的元素被存储在不同的位置。所以,链接列表可以在小的自由空间块中创建。
- 链接列表比数组使用更少的空间,因为指针可以被存储在靠近节点的地方,而不是在列表的中间。
- 在一个链接列表中,只进行线性搜索,但在数组中则使用线性搜索和二进制搜索。
什么是泛型链表以及如何使用它
有许多方法可以创建一个通用的链表,因为它们可以携带任何形式的数据。可以存储任何数据类型的链表的实现被称为通用链表。整数被存储在一个链表中,而浮点数被存储在另一个链表中。
初级链接表成员函数
这些是可以在一个链表上执行的功能。
Sort- 对链表的节点进行排序。Search- 选择其中一个相关节点并点击它。Deletion- 摆脱已经存在的一切。Insertion- 以一个元素扩展连接列表。Traversal- 逐一访问连接列表中的元素。
单一链接列表
这是一个只能从头到尾节点单向遍历的连接列表,它被称为单链路列表(尾)。在一个链接列表中,每个元素被称为一个节点。列表结构可以通过使用包含数据和通往下一个节点的链接的节点来维护。
本节将探讨如何使用泛型来以类型安全、参数化的方式实现单链表。我们将使用下面的 Java 程序来生成我们自己的类型安全的链表。
import java.io.*;
public class Main {
public static void main(String[] args)
{
class node<T> {
T data;
node<T> next;
node(T data)
{
this.data = data;
this.next = null;
}
}
class list<T> {
// Instances of a generic node type.
node<T> head;
private int newlength = 0;
list() { this.head = null; }
void add(T data)
{
node<T> newtemp = new node<>(data);
// If the list has no items, then a new value is assigned to the head node.
if (this.head == null) {
head = newtemp;
}
else {
node<T> X = head;
while (X.next != null) {
X = X.next;
}
// Topping off the list with an extra, higher-valued node
X.next = newtemp;
}
newlength++;
}
void add(int newposition, T data)
{
if (newposition > newlength + 1) {
return;
}
if (newposition == 1) {
node<T> newtemp = head;
head = new node<T>(data);
head.next = newtemp;
return;
}
// node for traversal
node<T> newtemp = head;
// Dummy node
node<T> prev = new node<T>(null);
while (newposition - 1 > 0) {
prev = newtemp;
newtemp = newtemp.next;
newposition--;
}
prev.next = new node<T>(data);
prev.next.next = newtemp;
}
void remove(T key)
{
// Dummy node with null value
node<T> prev = new node<>(null);
prev.next = head;
node<T> next = head.next;
node<T> newtemp = head;
boolean exists = false;
if (head.data == key) {
head = head.next;
exists = true;
}
while (newtemp.next != null) {
if (String.valueOf(newtemp.data).equals(
String.valueOf(key))) {
prev.next = next;
exists = true;
break;
}
prev = newtemp;
newtemp = newtemp.next;
next = newtemp.next;
}
if (exists == false
&& String.valueOf(newtemp.data).equals(
String.valueOf(key))) {
prev.next = null;
exists = true;
}
if (exists) {
newlength--;
}
else {
System.out.println(
"The linked list does not contain the given value.");
}
}
void clear()
{
head = null;
newlength = 0;
}
// It is the value that determines if the value to be erased is already there or not.
boolean empty()
{
if (head == null) {
return true;
}
return false;
}
// Returning the length of Linked List
int newlength() { return this.newlength; }
// method
public String toString()
{
String S = "{ ";
node<T> X = head;
if (X == null)
return S + " }";
while (X.next != null) {
S += String.valueOf(X.data) + " -> ";
X = X.next;
}
S += String.valueOf(X.data);
return S + " }";
}
}
}}
输出。
javac -classpath .:/run_dir/junit-4.12.jar:target/dependency/* -d . Main.java
java -classpath .:/run_dir/junit-4.12.jar:target/dependency/* Main
>
为了简单起见,我们用一个假节点来代表单链表中的前一个节点,因为向后退是不可能的。为了表示当前节点之前的节点,我们创建一个假节点。因为头部节点没有前一个节点,所以它被分配为空值。
这里有一个例子。
// Node that has no data in it
node<T> prev = new node<>(null);
// head-pointing dummy node
prev.next = head;
// The node after the current node that points forward
node<T> next = head.next;
利用Generics的SinglyLinkedList。Node<T> 将是节点变量的类型。现在我们已经涵盖了基础知识,让我们通过实现下面的方法进入正题。
- 我们将使用add (T data)。
- 在列表的顶部添加元素。
- 将一个元素添加到一个特定的位置,使用add (T data, int index)。
测试一个 Java 单链式列表
由于我们有一个有效的链接列表实现,我们可以构建一个测试程序来查看其性能。
public class Cal {
public static void main(String[] args)
{
// Creating new empty Integer linked list
list<Integer> list1 = new list<>();
System.out.println(
"Created a linked list of integers: List1 :");
list1.add(10);
list1.add(20);
list1.add(30);
System.out.println(
"list1 after adding 10,20 and 30 :");
System.out.println(list1);
list1.remove(20);
System.out.println("list1 after removal of 20 :");
System.out.println(list1);
// An empty String-linked list is created.
list<String> list2 = new list<>();
// displaying message
System.out.println(
"\nString LinkedList created as list2");
list2.add("Hey");
list2.add("There");
// displaying message
System.out.println(
"list2 after adding hey and There :");
System.out.println(list2);
list2.add(2, "Cal");
// displaying message
System.out.println(
"list2 after adding Cal at newposition 2 :");
System.out.println(list2);
list<Float> list3 = new list<>();
System.out.println(
"\nFloat LinkedList created as list3");
// Adding elements
list3.add(10.25f);
list3.add(10.42f);
list3.add(10.99f);
System.out.println(
"list3 after adding 10.25, 10.42 and 10.99 :");
System.out.println(list3);
System.out.println("Clearing list3 :");
// clearing this list
list3.clear();
// displaying the list again
System.out.println(list3);
}
}
输出。
Created a linked list of integers: List1 :
list1 after adding 10,20 and 30 :
{ 10 -> 20 -> 30 }
list1 after removal of 20 :
{ 10 -> 30 }
String LinkedList created as list2
list2 after adding hey and There :
{ Hey -> There }
list2 after adding Cal at newposition 2 :
{ Hey -> Cal -> There }
Float LinkedList created as list3
list3 after adding 10.25, 10.42 and 10.99 :
{ 10.25 -> 10.42 -> 10.99 }
Clearing list3 :
{ }
单链表的应用
链表可以用在以下方面。
- 除了将图形表示为相邻的矩阵外,与数组相比,链接列表还可以用来以更节省空间的方式表示它们。O(N2)和O(N)分别是一个数组和一个有
N个节点的图的链接列表的内存消耗(N)。 - 在实现哈希表时,哈希表的每个桶通常可以是一个链表。
- Photoshop和Microsoft Word的撤销功能依赖于链接列表。
- 在一个链接列表中,访问下一个元素比在数组中更快。由于链接表的性质,它们与缓存配合得很好,因为它们能够利用引用的位置性。
这使我们来到了关于利用泛型来构建 Java 中的链表的文章的结尾。Java中的循环链表或双链表可以作为后续问题来实现,这取决于面试官的口味。作为一种选择,你可以把它们作为一种编码练习来提高你的技能。
实现各种链表方法,如从链表的开头、中间和结尾插入和删除节点,是面试官感兴趣的。此外,对链表数组内的元素进行排序和查找也在面试中广泛使用。
在这一部分中,有几个代码任务使用了链表,但记得在继续之前要学习如何在Java中创建一个单链表。
总结
链表是编程中使用的一种标准数据结构,许多面试题都集中在链表上。然而,你不需要创建自己的链接列表来编写生产代码。
无论如何,所有这些面试问题通常都要求你在Java中创建一个链表,以应对Java API或JDK所带来的编码挑战。除非你对自己建立链表的能力有信心,否则解决诸如反转链表或一次性找到链表中间成员的问题会很困难。