作者:
逍遥Sean
简介:一个主修Java的Web网站\游戏服务器后端开发者
主页:https://blog.csdn.net/Ureliable
觉得博主文章不错的话,可以三连支持一下~ 如有需要我的支持,请私信或评论留言!
前言
算法非常重要,它是计算机科学的核心之一。算法是一组解决问题的步骤和规则,可以帮助我们在计算机程序中完成各种任务。好的算法可以优化程序的性能,提高程序的效率,并使程序更易于理解和维护。算法也是计算机科学中一种非常基础的概念,对于计算机科学专业的学生来说,学好算法将为他们日后的学习和工作奠定非常重要的基础。
上面的话是别人说的
有用没用不知道(可能潜移默化在开发中会受到这些思想影响),没事搞搞还是很有趣的~
本文写线性表的链式存储结构及其操作方法。
本文为Java代码实现,默认单链表结构为:
class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
}
使用c++的玩家请移步:
数据结构与算法-线性表(下)链式存储结构(c++实例)
线性表是一种数据结构,对数据元素进行逻辑上的顺序排列。线性表链式存储结构是指用链表来存储线性表的数据元素。链表由节点组成,每个节点包含两部分:数据域和指针域。数据域存储节点的数据元素,指针域存储指向下一个节点的指针。
链式存储结构相对于顺序存储结构的优点是可以更方便地进行插入和删除操作,通过修改节点的指针即可完成操作,而不需要移动大量数据。但是链式存储结构的缺点是访问节点需要通过指针进行跳转,相对于顺序存储结构,访问速度会慢一些。此外,链式存储结构需要额外的空间存储指针信息,会占用更多的内存空间。
链式存储结构广泛应用于各种算法和数据结构中,如链表、队列、栈等。
单链表是一种经常使用的数据结构,它是由一系列“节点”组成的。每个节点包含两个属性:数据(或值)和指向下个节点的指针。单链表中只能从头到尾依次访问,不能从任意位置访问。
读取单链表中的节点值:
要读取单链表中的节点值,需要从头节点开始,依次访问每个节点。具体代码如下:
ListNode head = ...; // 单链表头节点
while (head != null) {
int val = head.val;
System.out.println(val);
head = head.next;
}
插入节点:
在单链表中插入一个节点,需要先找到要插入位置的前一个节点,然后把新节点插入到这个节点的后面。具体代码如下:
public class LinkedList {
Node head;
static class Node {
int data;
Node next;
Node(int d) {
data = d;
next = null;
}
}
public void insertAtEnd(int data) {
Node newNode = new Node(data);
if (head == null) {
head = newNode;
return;
}
Node lastNode = head;
while (lastNode.next != null) {
lastNode = lastNode.next;
}
lastNode.next = newNode;
}
public void insertAfter(Node prevNode, int data) {
if (prevNode == null) {
System.out.println("Previous node cannot be null");
return;
}
Node newNode = new Node(data);
newNode.next = prevNode.next;
prevNode.next = newNode;
}
public void printList() {
Node currentNode = head;
while (currentNode != null) {
System.out.print(currentNode.data + " ");
currentNode = currentNode.next;
}
}
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.insertAtEnd(1);
linkedList.insertAtEnd(2);
linkedList.insertAtEnd(4);
linkedList.insertAfter(linkedList.head.next, 3);
linkedList.printList();
}
}
删除节点:
在单链表中删除一个节点,需要先找到要删除的节点,然后把其前一个节点指向它后一个节点。具体代码如下:
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
public class Solution {
public ListNode deleteNode(ListNode head, int val) {
if (head == null) return null;
if (head.val == val) return head.next;
ListNode prev = head;
ListNode curr = head.next;
while (curr != null && curr.val != val) {
prev = curr;
curr = curr.next;
}
if (curr != null) {
prev.next = curr.next;
}
return head;
}
}
单链表是一种链式存储结构,每个节点包含一个数据元素和一个指向下一个节点的指针。单链表的整表创建方法如下:
1.声明一个头节点,不包含任何数据元素。
2.声明一个指向头节点的指针,即头指针。
3.逐个插入节点,每个节点的数据元素和指针由用户输入。
4.最后一个节点的指针指向 NULL,表示链表结束。
以下是单链表的整表创建与删除的代码示例:
class Node {
public int data;
public Node next;
public Node(int data) {
this.data = data;
this.next = null;
}
}
class LinkedList {
public Node head;
public LinkedList() {
this.head = null;
}
// 创建链表
public void createLinkedList(int[] arr) {
if (arr == null || arr.length == 0) {
return;
}
this.head = new Node(arr[0]);
Node curNode = this.head;
for (int i = 1; i < arr.length; i++) {
Node newNode = new Node(arr[i]);
curNode.next = newNode;
curNode = curNode.next;
}
}
// 删除链表
public void deleteLinkedList() {
this.head = null;
}
// 打印链表
public void printLinkedList() {
Node curNode = this.head;
while (curNode != null) {
System.out.print(curNode.data + " ");
curNode = curNode.next;
}
System.out.println();
}
}
public static void main(String[] args) {
// 创建链表
LinkedList linkedList = new LinkedList();
int[] arr = new int[]{1, 2, 3, 4, 5};
linkedList.createLinkedList(arr);
linkedList.printLinkedList();
// 删除链表
linkedList.deleteLinkedList();
linkedList.printLinkedList();
}
输出结果为:
1 2 3 4 5
以上是单链表的整表创建和删除操作的代码示例,可以根据实际需要进行修改。
单链表和顺序存储结构都是线性结构,但它们在实现方式和性能方面有很大的不同。
实现方式:
单链表使用指针来连接每个节点,每个节点只有一个指针域,它指向下一个节点或者为空。而顺序存储结构使用连续的存储空间来存储数据元素,可以通过数组下标来访问每个元素。
插入和删除操作:
单链表在插入和删除节点时,只需要修改指针域即可,不需要移动其他节点。这使得单链表的插入和删除操作非常高效。而顺序存储结构在插入和删除元素时需要移动其他元素,因此在大量插入和删除操作时性能较差。
随机访问性能:
顺序存储结构支持随机访问,可以通过数组下标来访问任何一个元素。而单链表不支持随机访问,只能从头开始遍历整个链表来访问某个节点。
存储空间利用率:
单链表每个节点只需要存储一个指针域和一个数据域,不需要预留连续空间,因此存储空间利用率较高。而顺序存储结构需要预留连续空间,因此存储空间利用率较低。
综上所述,单链表适合频繁插入和删除操作、不需要随机访问、对存储空间利用率要求较高的场合。而顺序存储结构适合需要随机访问、对存储空间利用率要求不高的场合。
除了单链表,还有以下其他常见的链表:
这些链表的使用场景各不相同,需要根据具体问题场景选择合适的链表。
静态链表是一种使用数组模拟链表的数据结构,它不同于普通链表,它使用数组来存储结点,每个结点都包含两个信息:数据域和指针域。指针域实际上存储的是下一个结点在数组中的索引,这样可以避免指针操作的开销和内存分配的问题。静态链表在插入和删除时,需要对数组进行修改和移动,因此可能导致效率低下。但是,它的一个优点是可以随意访问链表中的任何元素,而不需要从头开始遍历链表。它还可以在一些存储空间受限的场合下使用。
静态链表是一种使用数组来实现链表的数据结构,其特点是空间固定,元素大小不变。静态链表的操作包括:
以下是一个静态链表的代码实例。静态链表是使用数组实现的链表。
public class StaticLinkedList {
private Node[] data; // 存储节点的数组
private int size; // 链表大小
private int free; // 空闲节点索引
private static class Node {
int data;
int next;
public Node(int data, int next) {
this.data = data;
this.next = next;
}
}
public StaticLinkedList(int maxSize) {
data = new Node[maxSize];
for (int i = 0; i < maxSize - 1; i++) {
data[i] = new Node(0, i + 1); // 初始化每个节点的下一个索引
}
data[maxSize - 1] = new Node(0, -1); // 最后一个节点的下一个索引为-1
size = 0;
free = 0;
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return free == -1;
}
public int size() {
return size;
}
public boolean add(int value) {
if (isFull()) {
return false;
}
Node node = data[free];
free = node.next; // 找到新的空闲节点
node.data = value;
node.next = -1;
if (isEmpty()) {
data[0] = node; // 链表为空时插入第一个节点
} else {
Node tail = data[size - 1];
tail.next = free; // 将新节点插入链表尾部
data[size] = node;
}
size++;
return true;
}
public boolean remove(int value) {
int index = 0;
for (int i = 0; i < size; i++) {
Node node = data[i];
if (node.data == value) {
if (i == 0) {
data[0] = data[1]; // 删除头节点
} else {
Node prev = data[index - 1];
prev.next = node.next; // 删除中间节点
}
node.next = free;
free = i;
size--;
return true;
}
index = i;
}
return false;
}
public void print() {
if (isEmpty()) {
System.out.println("[]");
} else {
StringBuilder sb = new StringBuilder("[");
Node node = data[0];
while (node != null) {
sb.append(node.data);
node = node.next != -1 ? data[node.next] : null;
if (node != null) {
sb.append(", ");
}
}
sb.append("]");
System.out.println(sb.toString());
}
}
}
StaticLinkedList list = new StaticLinkedList(5);
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.print(); // [1, 2, 3, 4, 5]
list.add(6);
list.print(); // [1, 2, 3, 4, 5]
list.remove(3);
list.print(); // [1, 2, 4, 5]
循环链表与普通链表最大的区别在于其最后一个节点指向链表的第一个节点,形成一个循环。
这种数据结构可以更容易地处理需要访问链表中所有元素的任务,因为访问最后一个元素后可以直接访问第一个元素,而不需要从头开始访问。在实现循环队列和循环缓冲区时,循环链表也经常被使用。使用循环链表可以减少代码的复杂性,提高代码的效率。
循环链表与单链表或双向链表的操作类似,但需要特别注意处理尾部指针的问题。
常规操作包括:
需要注意的是,在处理循环链表时,需要注意头结点和尾部指针的更新,否则容易死循环或遗漏节点。
以下是一个简单的循环链表 Java 实例,其中链表节点包括数据和指向下一个节点的指针。
public class CircularLinkedList {
// 节点类
private static class Node {
int data;
Node next;
Node(int data) {
this.data = data;
}
}
private Node tail; // 尾节点
// 检查链表是否为空
public boolean isEmpty() {
return tail == null;
}
// 返回链表长度
public int size() {
if (isEmpty()) {
return 0;
}
Node current = tail.next;
int count = 1;
while (current != tail) {
count++;
current = current.next;
}
return count;
}
// 在链表头部插入节点
public void addFirst(int data) {
Node node = new Node(data);
if (isEmpty()) {
tail = node;
tail.next = tail;
} else {
node.next = tail.next;
tail.next = node;
}
}
// 在链表末尾插入节点
public void addLast(int data) {
addFirst(data);
tail = tail.next;
}
// 从链表头部删除节点
public int removeFirst() {
if (isEmpty()) {
throw new RuntimeException("链表为空");
}
int data = tail.next.data;
if (tail == tail.next) {
tail = null;
} else {
tail.next = tail.next.next;
}
return data;
}
// 打印链表元素
public void printList() {
if (isEmpty()) {
System.out.println("链表为空");
return;
}
Node current = tail.next;
do {
System.out.print(current.data + " ");
current = current.next;
} while (current != tail.next);
System.out.println();
}
}
可以使用以下代码进行测试:
public class Main {
public static void main(String[] args) {
CircularLinkedList list = new CircularLinkedList();
list.addLast(1);
list.addLast(2);
list.addLast(3);
list.addLast(4);
System.out.println("链表元素个数:" + list.size());
list.printList();
list.addFirst(0);
list.addLast(5);
System.out.println("链表元素个数:" + list.size());
list.printList();
System.out.println("删除头部元素:" + list.removeFirst());
System.out.println("链表元素个数:" + list.size());
list.printList();
}
}
输出结果为:
链表元素个数:4
1 2 3 4
链表元素个数:6
0 1 2 3 4 5
删除头部元素:0
链表元素个数:5
1 2 3 4 5
双向链表是一种特殊链表,每个节点包含两个指针,一个指向前一个节点,一个指向后一个节点。以下是双向链表的常用操作:
以上是双向链表的基本操作,还有一些其他常用操作,比如反转链表、合并链表等,都是基于上述基本操作实现的。
下面是一个简单的双向链表 Java 实现:
public class DoublyLinkedList<E> {
private int size;
private Node<E> head;
private Node<E> tail;
public DoublyLinkedList() {
size = 0;
head = null;
tail = null;
}
public void addFirst(E data) {
Node<E> newNode = new Node<>(data);
if (isEmpty()) {
tail = newNode;
} else {
head.setPrev(newNode);
}
newNode.setNext(head);
head = newNode;
size++;
}
public void addLast(E data) {
Node<E> newNode = new Node<>(data);
if (isEmpty()) {
head = newNode;
} else {
tail.setNext(newNode);
newNode.setPrev(tail);
}
tail = newNode;
size++;
}
public boolean add(int index, E data) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException();
}
if (index == 0) {
addFirst(data);
} else if (index == size) {
addLast(data);
} else {
Node<E> currentNode = getNode(index);
Node<E> newNode = new Node<>(data);
newNode.setPrev(currentNode.getPrev());
newNode.setNext(currentNode);
currentNode.getPrev().setNext(newNode);
currentNode.setPrev(newNode);
size++;
}
return true;
}
public Node<E> removeFirst() {
if (isEmpty()) {
throw new NoSuchElementException();
}
Node<E> removedNode = head;
if (size == 1) {
head = null;
tail = null;
} else {
head = removedNode.getNext();
head.setPrev(null);
}
size--;
return removedNode;
}
public Node<E> removeLast() {
if (isEmpty()) {
throw new NoSuchElementException();
}
Node<E> removedNode = tail;
if (size == 1) {
head = null;
tail = null;
} else {
tail = removedNode.getPrev();
tail.setNext(null);
}
size--;
return removedNode;
}
public Node<E> remove(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}
if (index == 0) {
return removeFirst();
} else if (index == size - 1) {
return removeLast();
} else {
Node<E> removedNode = getNode(index);
removedNode.getPrev().setNext(removedNode.getNext());
removedNode.getNext().setPrev(removedNode.getPrev());
size--;
return removedNode;
}
}
public E get(int index) {
return getNode(index).getData();
}
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
private Node<E> getNode(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}
Node<E> currentNode = head;
for (int i = 0; i < index; i++) {
currentNode = currentNode.getNext();
}
return currentNode;
}
private static class Node<E> {
private E data;
private Node<E> prev;
private Node<E> next;
public Node(E data) {
this.data = data;
this.prev = null;
this.next = null;
}
public E getData() {
return data;
}
public void setData(E data) {
this.data = data;
}
public Node<E> getPrev() {
return prev;
}
public void setPrev(Node<E> prev) {
this.prev = prev;
}
public Node<E> getNext() {
return next;
}
public void setNext(Node<E> next) {
this.next = next;
}
}
}
这个实现中,我们定义了 DoublyLinkedList
类,它包含一个内部类 Node
,它表示链表的节点。每个节点都保存一个 data
值,以及指向前一个节点和后一个节点的引用。双向链表中每个节点都有一个前一个节点和后一个节点,这样可以在链表中向前或向后遍历。我们也定义了 size
、head
和 tail
类变量,size
表示链表大小,head
和 tail
分别表示头节点和尾节点。如果链表为空,head
和 tail
应该为 null
。
DoublyLinkedList
类包含下面这些方法:
addFirst(data)
:在链表的开头添加一个新节点,将 data
作为新节点的 data
值。如果链表为空,新节点将成为唯一的节点,同时将 tail
设为新节点;如果不为空,将现有的头节点 head
的前一个节点指向新节点,并将新节点的后一个节点指向 head
,最后将 head
设为新节点。addLast(data)
:在链表的末尾添加一个新节点,将 data
作为新节点的 data
值。如果链表为空,新节点将成为唯一的节点,同时将 head
设为新节点;如果不为空,将现有的尾节点 tail
的后一个节点指向新节点,并将新节点的前一个节点指向 tail
,最后将 tail
设为新节点。add(index, data)
:在链表的指定位置 index
添加一个新节点,将 data
作为新节点的 data
值。如果 index
在链表的范围之外,则引发 IndexOutOfBoundsException
异常。如果 index
等于 0,则调用 addFirst
方法;如果 index
等于 size
,则调用 addLast
方法;否则,查找位置为 index
的节点并将新节点插入节点列表中。removeFirst()
:从链表的开头删除头节点,并返回被删除的节点。如果链表为空,引发 NoSuchElementException
异常。如果链表不为空,将 head
的值设为 head
的下一个节点,并将 head
的前一个节点设为 null
(如果存在)。最后,将链表大小 size
减 1。removeLast()
:从链表的末尾删除尾节点,并返回被删除的节点。如果链表为空,引发 NoSuchElementException
异常。如果链表不为空,将 tail
的值设为 tail
的前一个节点,并将 tail
的后一个节点设为 null
(如果存在)。最后,将链表大小 size
减 1。remove(index)
:从链表的指定位置 index
删除节点,并返回被删除的节点。如果 index
在链表的范围之外,则引发 IndexOutOfBoundsException
异常。如果 index
等于 0,则调用 removeFirst
方法;如果 index
等于 size - 1
,则调用 removeLast
方法;否则,查找位置为 index
的节点并将其从链表中删除。get(index)
:返回链表中位置为 index
的节点的 data
值。如果 index
在链表的范围之外,则引发 IndexOutOfBoundsException
异常。isEmpty()
:如果链表为空,则返回 true;否则返回 false。size()
:返回链表的大小。在 DoublyLinkedList
类中,我们还定义了一个私有方法 getNode(index)
,它返回链表中位置为 index
的节点。该方法用于实现 add
、remove
和 get
方法。如果 index
在链表的范围之外,则引发 IndexOutOfBoundsException
异常。在 Node
内部类中,我们定义了每个节点的 data
值、前一个节点和后一个节点。因为这些字段都是私有的,我们还定义了 getData
、setData
、getPrev
、setPrev
、getNext
和 setNext
方法以访问它们。