热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

LinkedList与链表(数据结构系列5)

目录前言:1.链表的概念以及分类1.1链表的概念1.2分类1.2.1单向和双向1.2.2循环和非循环1.2.3带头和不带头2.无头单链表的模拟实现3.

目录

前言:

1.链表的概念以及分类

1.1链表的概念

1.2分类

1.2.1单向和双向

1.2.2循环和非循环

1.2.3带头和不带头

2.无头单链表的模拟实现

3.双向链表的模拟实现

4.LinkedList的简单介绍

5.LinkedList的遍历

5.1直接打印

5.2for-each遍历

5.3迭代器遍历

6.LinkedList与ArrayList的区别

结束语:



前言:

我们之前学习了线性结构中的顺序存储,那么在之前也与大家一起实现了ArrayList的基本的方法,我们会发现这种结构的优点是当我们在给定下标查询的时候速度会非常的快,时间复杂度可以达到O(1),但是当我们在插入元素、删除元素和扩容的时候你会发现时间会很慢,我们必须通过挪动元素才可以达到我们想要的效果,在扩容的时候也会非常的浪费空间,所以这种顺序表适合随机查找某个元素,那么为了解决我们上述的一些问题,接下来我们就需要学习另一种线性结构——链式结构。

1.链表的概念以及分类

1.1链表的概念

链表是一种物理结构上非连续存储结构,数据元素以链式存储的方式存储在链表中,是一种物理上非连续,逻辑上通过链表中的引用链接次序实现的。

一个结点存在两个域:数据域地址域

数据域用来存储当前结点的数据,地址域用来存储下一个结点的地址,这样我们就可以实现逻辑上连续了。

1.2分类

链表的分类一共有八种:带头和不带头、循环和非循环、单向和双向,那么接下来我们来分别看一下都是怎么构成的。

1.2.1单向和双向

单向:

双向:

  

1.2.2循环和非循环

非循环:

循环:

1.2.3带头和不带头

带头:


不带头: 

上面的这四类八种组合中接下来我们主要学习就是单向不带头非循环双向不带头非循环。 

2.无头单链表的模拟实现

主要核心代码如下所示:

public class MySingleList {
class Node{
public int val;//存储数据
public Node next;//存储下一个结点的地址
public Node(int val) {
this.val = val;
}
}
public Node head = null;//头结点
//接下来我们直接手动创建一个链表
public void createList() {
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
//关联上面的四个结点
node1.next = node2;
node2.next = node3;
node3.next = node4;
//让头结点引用结点
head = node1;
}
/**
下面我们来实现一些方法
*/
//遍历链表
public void display() {
Node cur = head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
}
//查找是否包含关键字key在链表中
public boolean contains(int key) {
Node cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
//得链表的长度
public int size() {
int count = 0;
Node cur = head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
//链表的插入只是修改指向
//头插法
//时间复杂度O(1)
public void addFirst(int data){
//先得到一个结点
Node node = new Node(data);
//先绑定后面
node.next = head;
head = node;
}
//尾插法
//时间复杂度O(N)
public void addLast(int data) {
//先得到一个结点
Node node = new Node(data);
if (head == null) {
head = node;
return;
}
//找到最后一个结点
Node cur = head;
while (cur.next != null) {
cur = cur.next;
}
//绑定
cur.next = node;
}
//任意位置插入
public void addIndex(int index, int data) throws IndexOutOfBoundsException{
Node node = new Node(data);
//查下标的合法性
checkIndex(index);
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
//先要找到index - 1的位置
Node cur = findIndexSubOne(index);
//先绑定后面的
node.next = cur.next;
cur.next = node;
}
//找到index - 1位置结点的位置
private Node findIndexSubOne(int index) throws IndexOutOfBoundsException{
Node cur = head;
int count = 0;
while (count != index - 1) {
cur = cur.next;
count++;
}
return cur;
}
//检查下标的合法性
private void checkIndex(int index) throws IndexOutOfBoundsException{
if (index <0 || index > size()) {
throw new IndexOutOfException("该下标不合法");
}
}
//删除第一次出现关键字为key的结点
//时间复杂度O(N)
public void remove(int key) {
if (head.val &#61;&#61; key) {
head &#61; head.next;
return;
}
//先找到前key个结点
Node cur &#61; searchPrev(key);
if (cur &#61;&#61; null) {
return;
}
cur.next &#61; cur.next.next;
}
//找到key的前一个结点
private Node searchPrev(int key) {
if (head &#61;&#61; null) {
return null;
}
Node cur &#61; head;
while (cur.next !&#61; null) {
if (cur.next.val &#61;&#61; key){
return cur;
}
cur &#61; cur.next;
}
return null;
}
//删除所有key的结点
public void removeAll(int key) {
if (head &#61;&#61; null) {
return;
}
Node prev &#61; head;
Node cur &#61; head.next;
while (cur !&#61; null) {
if (cur.val &#61;&#61; key) {
prev.next &#61; cur.next;
cur &#61; cur.next;
}else {
cur &#61; cur.next;
prev &#61; prev.next;
}
}
//处理头部是关键字的情况
if (head.val &#61;&#61; key) {
head &#61; head.next;
}
}
//清除
public void clear() {
head &#61; null;
}
}

异常代码如下所示&#xff1a;

public class IndexOutOfException extends RuntimeException{
public IndexOutOfException() {
}
public IndexOutOfException(String mag) {
super(mag);
}
}

 

测试代码如下所示&#xff1a;

public class Test {
public static void main(String[] args) {
MySingleList mySingleList &#61; new MySingleList();
mySingleList.createList();
mySingleList.display();
System.out.println();
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;1.查找是否包含关键字测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
System.out.println(mySingleList.contains(2));
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;2.长度测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
System.out.println(mySingleList.size());
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;3.头插发测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
mySingleList.addFirst(88);
mySingleList.display();
System.out.println();
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;4.尾插发测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
mySingleList.addLast(88);
mySingleList.display();
System.out.println();
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;5.任意位置插入测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
try {
mySingleList.addIndex(0,1);
}catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
mySingleList.display();
System.out.println();
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;6.删除第一次出现的关键字测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
mySingleList.remove(1);
mySingleList.display();
System.out.println();
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;7.删除出现的所有关键字测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
mySingleList.removeAll(88);
mySingleList.display();
System.out.println();
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;8.清除测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
mySingleList.clear();
mySingleList.display();
}
}

结果如下所示&#xff1a;

 

3.双向链表的模拟实现

核心代码&#xff1a;

public class MyLinkedList {
static class ListNode{
public int val;
public ListNode prev;//前驱
public ListNode next;//后继
public ListNode(int val) {
this.val &#61; val;
}
}
public ListNode head;
public ListNode last;
//打印
public void display() {
ListNode cur &#61; head;
while (cur !&#61; null) {
System.out.print(cur.val &#43; " ");
cur &#61; cur.next;
}
}
//求长度
public int size() {
ListNode cur &#61; head;
int count &#61; 0;
while (cur !&#61; null) {
cur &#61; cur.next;
count&#43;&#43;;
}
return count;
}
//判断是否包含
public boolean contains(int key) {
ListNode cur &#61; head;
if (head.val &#61;&#61; key) {
return true;
}
while (cur !&#61; null) {
if (cur.val &#61;&#61; key) {
return true;
}
cur &#61; cur.next;
}
return false;
}
//头插
//时间复杂度O(1)
public void addFirst(int data) {
ListNode node &#61; new ListNode(data);
if (head &#61;&#61; null) {
head &#61; node;
last &#61; node;
}else {
node.next &#61; head;//先绑定前面的
head.prev &#61; node;//修改刚刚head的前驱
head &#61; node;//修改头部
}
}
//尾插法
//时间复杂度O(1)
public void addLast(int data) {
ListNode node &#61; new ListNode(data);
if (head &#61;&#61; null) {
head &#61; node.next;
last &#61; node.next;
}else {
last.next &#61; node;
node.prev &#61; last;
last &#61; node;
}
}
//任意位置插入
public void addIndex(int index,int data) {
//处理合法性
checkIndex(index);
if (index &#61;&#61; 0) {
addFirst(data);
return;
}
if (index &#61;&#61; size()) {
addLast(data);
return;
}
ListNode node &#61; new ListNode(data);
//找到index - 1的位置
ListNode cur &#61; findIndex(index);
//注意下面修改地址的顺序
node.next &#61; cur;
cur.prev.next &#61; node;
node.prev &#61; cur.prev;
cur.prev &#61; node;
}
private void checkIndex(int index) {
if (index <0 || index > size()) {
throw new IndexOutOfException("下标不合法&#xff01;");
}
}
//找到index的位置
private ListNode findIndex(int index) {
ListNode cur &#61; head;
while (index !&#61; 0) {
cur &#61; cur.next;
index--;
}
return cur;
}
//删除
public void remove(int key) {
ListNode cur &#61; head;
while (cur !&#61; null) {
if (cur.val &#61;&#61; key) {
//删除的是头结点
if (cur &#61;&#61; head) {
head &#61; head.next;
//只有一个结点的时候
if (head !&#61; null) {
head.prev &#61; null;
}
}else {
//中间或者是尾部
cur.prev.next &#61; cur.next;
if (cur.next !&#61; null) {
//不是尾巴结点
cur.next.prev &#61; cur.prev;
}else {
//是尾部结点
last &#61; last.prev;
}
}
return;
}
cur &#61; cur.next;
}
}
//删除所有
public void removeAll(int key) {
ListNode cur &#61; head;
while (cur !&#61; null) {
if (cur.val &#61;&#61; key) {
//删除的是头结点
if (cur &#61;&#61; head) {
head &#61; head.next;
//只有一个结点的时候
if (head !&#61; null) {
head.prev &#61; null;
}
}else {
//中间或者是尾部
cur.prev.next &#61; cur.next;
if (cur.next !&#61; null) {
//不是尾巴结点
cur.next.prev &#61; cur.prev;
}else {
//是尾部结点
last &#61; last.prev;
}
}
}
cur &#61; cur.next;
}
}
//清空
public void clear() {
//遍历双向链表的每一个结点&#xff0c;把里面的后继和前驱都置为空
ListNode cur &#61; head;
while (cur !&#61; null) {
ListNode curNext &#61; cur.next;
cur.prev &#61; null;
cur.next &#61; null;
cur &#61; curNext;
}
head &#61; null;
last &#61; null;
}
}


测试代码&#xff1a;

public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList &#61; new MyLinkedList();
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;1.头插法测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
myLinkedList.addFirst(1);
myLinkedList.addFirst(1);
myLinkedList.addFirst(1);
myLinkedList.addFirst(1);
myLinkedList.addFirst(5);
myLinkedList.display();
System.out.println();
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;2.尾插法测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
myLinkedList.addLast(11);
myLinkedList.addLast(12);
myLinkedList.addLast(13);
myLinkedList.addLast(14);
myLinkedList.addLast(15);
myLinkedList.display();
System.out.println();
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;3.任意位置插入测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
try {
myLinkedList.addIndex(2,99);
}catch (IndexOutOfException e){
e.printStackTrace();
}
myLinkedList.display();
System.out.println();
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;4.删除测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
myLinkedList.remove(99);
myLinkedList.display();
System.out.println();
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;4.删除所有测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
myLinkedList.removeAll(1);
myLinkedList.display();
System.out.println();
System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;5.清除测试&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;");
myLinkedList.clear();
myLinkedList.display();
}
}

异常处理代码&#xff1a;

public class IndexOutOfException extends RuntimeException{
public IndexOutOfException() {
}
public IndexOutOfException(String mag) {
super(mag);
}
}

结果截图&#xff1a;

 

 

4.LinkedList的简单介绍

LinkedList是一个双向链表&#xff0c;也就是在一个结点中包含三个域&#xff08;数值域、前驱、后继&#xff09;如下所示&#xff1a;

使用如下所示&#xff1a;

import java.util.LinkedList;
public class Test6 {
public static void main(String[] args) {
LinkedList linkedList &#61; new LinkedList<>();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
linkedList.add(4);
System.out.println(linkedList);
}
}


结果如下所示&#xff1a;

 

 

5.LinkedList的遍历

5.1直接打印

代码如下所示&#xff1a;

import java.util.LinkedList;
public class Test2 {
public static void main(String[] args) {
LinkedList linkedList &#61; new LinkedList<>();
linkedList.add("hello");
linkedList.add("world");
linkedList.add("!!!");
System.out.println(linkedList);
}
}


结果截图如下所示&#xff1a;

 

5.2for-each遍历

代码如下所示&#xff1a;

import java.util.LinkedList;
public class Test3 {
public static void main(String[] args) {
LinkedList linkedList &#61; new LinkedList<>();
linkedList.add("hello");
linkedList.add("world");
linkedList.add("!!!");
for (String x:linkedList) {
System.out.print(x &#43; " ");
}
}
}


结果截图如下所示&#xff1a;

 

5.3迭代器遍历

正向打印&#xff1a;

代码如下所示&#xff1a;

import java.util.LinkedList;
import java.util.ListIterator;
public class Test4 {
public static void main(String[] args) {
LinkedList linkedList &#61; new LinkedList<>();
linkedList.add("hello");
linkedList.add("world");
linkedList.add("!!!");
//正向打印
ListIterator listIterator &#61; linkedList.listIterator();
while (listIterator.hasNext()) {
System.out.print(listIterator.next() &#43; " ");
}
}
}


结果截图如下所示&#xff1a;

反向打印&#xff1a;

代码如下所示&#xff1a;

import java.util.LinkedList;
import java.util.ListIterator;
public class Test5 {
public static void main(String[] args) {
LinkedList linkedList &#61; new LinkedList<>();
linkedList.add("hello");
linkedList.add("world");
linkedList.add("!!!");
//反向遍历
ListIterator listIterator &#61; linkedList.listIterator(linkedList.size());
while (listIterator.hasPrevious()) {
System.out.print(listIterator.previous() &#43;" ");
}
}
}


结果截图如下所示&#xff1a; 

6.LinkedList与ArrayList的区别

顺序表在物理上和逻辑上都是连续的&#xff0c;但是链表只是在逻辑上是连续的&#xff0c;在物地址上的不连续的。

随机访问上ArrayList比LinkedList要快很多&#xff0c;ArrayList随机访问的时间复杂度是O(1)&#xff0c;LinkedList随机访问的时间复杂度是O(n)。

在插入的时候ArrayList中会有扩容的概念&#xff0c;而LinkedList中是没有这个概念的。

应用场景&#xff1a;ArrayList中适合高效存储和数据的频繁访问&#xff0c;LinkedList适合任意位置的插入和频繁的删除。

结束语&#xff1a;

好了这节小编带着大家简单的认识了什么是链表以及自己模拟实现了一个链表&#xff0c;那么下一次小编将会和大家分享一些链的练习题巩固一下我们这节的知识点&#xff0c;大家记得查看哦&#xff01;大家继续跟紧小编的步伐一起往下走吧&#xff01;&#xff01;&#xff01;希望对大家有所帮助&#xff0c;想要学习的同学记得关注小编和小编一起学习吧&#xff01;如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津&#xff08;在此小编先谢过各位大佬啦&#xff01;&#xff09;


推荐阅读
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • Java 架构:深入理解 JDK 动态代理机制
    代理模式是 Java 中常用的设计模式之一,其核心在于代理类与委托类共享相同的接口。代理类主要用于为委托类提供预处理、过滤、转发及后处理等功能,以增强或改变原有功能的行为。 ... [详细]
  • MainActivityimportandroid.app.Activity;importandroid.os.Bundle;importandroid.os.Handler;im ... [详细]
  • 开发笔记:9.八大排序
    开发笔记:9.八大排序 ... [详细]
  • 实用正则表达式有哪些
    小编给大家分享一下实用正则表达式有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下 ... [详细]
  • 1Authenticator简介1.1层次结构图1.2作用职责是验证用户帐号,是ShiroAPI中身份验证核心的入口点;接口中声明的authenticate方法就是用来实现认证逻辑 ... [详细]
  • Java EE CDI:解决依赖关系冲突的实例
    在本教程中,我们将探讨如何在Java EE的CDI(上下文和依赖注入)框架中有效解决依赖关系的冲突问题。通过学习如何使用限定符,您将能够为应用程序的不同客户端提供多种接口实现,并确保每个客户端都能正确调用其所需的实现。 ... [详细]
  • 本文探讨了在C++中如何有效地清空输入缓冲区,确保程序只处理最近的输入并丢弃多余的输入。我们将介绍一种不阻塞的方法,并提供一个具体的实现方案。 ... [详细]
  • 异常要理解Java异常处理是如何工作的,需要掌握一下三种异常类型:检查性异常:最具代表性的检查性异常是用户错误或问题引起的异常ÿ ... [详细]
  • 本文将探讨Java编程语言中对象和类的核心概念,帮助读者更好地理解和应用面向对象编程的思想。通过实际例子和代码演示,我们将揭示如何在Java中定义、创建和使用对象。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • Java Servlet中获取客户端IP与MAC地址的方法
    本文介绍了一种在Java Servlet应用中获取客户端IP地址及MAC地址的技术实现方法,通过示例代码详细解析了获取过程中的关键步骤和技术点。 ... [详细]
  • 本文详细探讨了HTML表单中GET和POST请求的区别,包括它们的工作原理、数据传输方式、安全性及适用场景。同时,通过实例展示了如何在Servlet中处理这两种请求。 ... [详细]
  • 任务,栈, ... [详细]
author-avatar
此号我已不再用
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有