Java数据结构之链表实现(单向、双向链表及链表反转)

前言

之前学习的顺序表查询非常快,时间复杂度为O(1),但是增删改效率非常低,因为每一次增删改都会元素的移动。可以使用另一种存储方式-链式存储结构。

链表是一种物理存储单元上非连续、非顺序的存储结构。链表由一序列的结点(链表中的每一个元素成为结点)组成。

结点API设计

类名 Node
构造方法 Node(T t,Node next) 创建Node对象
成员变量
T item:存储数据

Node next :指向下一个结点

结点类

public class Node<T>{
    Node next;
    private T item;

    public Node(Node next, T item) {
        this.next = next;
        this.item = item;
    }
}

生成链表

public class TestNode {
    public static void main(String[] args) {
        // 生成结点
        Node<Integer> one = new Node<Integer>(null,12);
        Node<Integer> two = new Node<Integer>(null,16);
        Node<Integer> three = new Node<Integer>(null,11);
        Node<Integer> four = new Node<Integer>(null,10);
        //生成链表
        one.next = two;
        two.next = three;
        three.next =four;
    }
}

1、单链表

单向链表是链表的一种,它有多个结点组成每个结点都由一个数据域一个指针组成,数据域用来存储数据指针域用来指向其后结点

链表的头结点数据域不存储数据,指针域指向第一个真正存储数据的结点。

单向链表API设计

类名 LinkList
构造方法 LinkList() :创建LinkList对象
成员变量
private Node head;记录首结点

private int N; 记录链表的长度

成员内部类 private class Node;结点类
成员方法

public void clear() 清空链表
public boolean isEmpty() 判断链表是否为空,是返回true
public int length() 获取链表中的元素个数
public T get(int i) 读取并返回链表中的第i个元素的值

public void insert(T t)

往链表中插入一个元素
public void insert(T t,int i) 在链表的第i位置插入一个值为t的数据元素
public T remove(int i) 删除并返回删除链表中的第i个数据元素
public int indexof(T t) 返回链表中首次出现的指定的数据元素的序号,如不存在,则返回-1

单向链表Java代码实现

package com.ycy.Algorithm.LinkList;

import java.util.Iterator;

/**
 * 链表的head是不可以动的
 * @param <T>
 */
public class LinkList<T> implements Iterable<T>{

    private Node head;//头结点,链表的head是不可以动的
    private int N;//结点个数
    public LinkList() {
        this.head = new Node(null,null);
        N = 0;
    }
    /**
     * 结点内部类
     */
    private class Node{
        //存储数据
        T item;
        //下一个结点
        Node next;
        public Node(T item,Node next) {
            this.item = item;
            this.next = next;
        }

    }
    /**
     * 清空链表
     */
    public void clear(){
        head.item=null;
        head.next=null;// 头节点next为null就是空链表
        this.N=0;
    }
    /**
     * 判断链表是否为空
     */
    public boolean isEmpty(){
        return this.N==0;
    }
    /**
     * 获取链表的长度
     */
    public int length(){
        return this.N;
    }
    /**
     * 读取链表第i位置的元素值并返回
     */
    public T get(int i){
        //非法性检查
        if(i<0 || i > this.N){
            throw new RuntimeException("位置不合法");
        }
        // n也是指向头结点
        Node n = head;
        for(int index=0; index<i; index++){
            n = n.next;
        }
        return n.item;
    }
    /**
     * 往链表中插入数据t
     */
    public void insert(T t){
        // head不可以移动,不然就无法在找到链表
        // 定义一个临时的Node也指向head的指针就可以通过移动该指针就可以
        Node n = head;
        // 获取尾节点
        while(true){
            // 当刚好就一个节点时(头节点)
            if(n.next == null){
                break;
            }
            n = n.next;
        }
        //当为空表时,就可以插入
        Node node = new Node(t, null);
        n.next =node;
        this.N ++;
    }

    /**
     * 在第i位置上插入数据t
     */
    public void insert(T t,int i){
        // 非法性检查
        if(i < 0 || i > this.N){
            throw  new RuntimeException("插入位置❌");
        }
        Node pre = head;
        for(int index=0;index <= i-1; index++){
            pre = pre.next;
        }
        Node current = pre.next;
        //先链接后面结点
        Node newNode = new Node(t,null);
        pre.next = newNode;
        newNode.next = current;
        this.N++;
    }
    /**
     * 移除并返回第i位置的元素值
     */
    public  T remove(int i){
        // 非法性检查
        if(i < 0 || i >this.N){
            throw  new RuntimeException("删除位置有误");
        }
        Node n =head;
        for(int index=0;index <= i-1;index ++){
             n = n.next;
        }
        //要删除的节点
        Node curr = n.next;
        n.next = curr.next;
        this.N--;//结点个数减一
        return curr.item;
    }
    //查找元素t在链表中第一次出现的位置
    public int indexof(T t){
        Node n = head;
        for(int i = 0; n.next != null;i++){
            n =n.next;
            if(n.item.equals(t)){
                return i;
            }
        }
        return -1;
    }

    @Override
    public Iterator iterator() {
        return new Iterator() {
            Node n =head;
            @Override
            public boolean hasNext() {
                return n.next !=null;
            }
            @Override
            public Object next() {
                //下移一个指针
                n = n.next;
                return n.item;
            }
        };
    }
}

补充一点链表的赋值给新的链表后,两个链表是会相会影响的,说白了就是把地址赋值给它了,他们操作是同一块内存的同一个对象。Node n = head;把head赋值给n,现在对n操作也是会影响head的

其中提供遍历方式,实现Iterable接口。

测试代码:

public class test {
    public static void main(String[] args) {
        LinkList<Integer>linkList = new LinkList<>();
        linkList.insert(1);
        linkList.insert(2);
        linkList.insert(4);
        linkList.insert(3);
        linkList.insert(1,2);
        for (Integer i : linkList) {
            System.out.print(i+" ");
        }
    }
}

运行结果:

D:\Java\jdk-12.0.2\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2019.1\lib\idea_rt.jar=3542:D:\IDEA\IntelliJ IDEA 2019.1
1 2 1 4 3

学习完链表还是需要加以练习的,可以在LeetCode上刷题加深理解。

2、双向链表

头插法:新增节点总是插在头部

便于理解可以画图表示

头插法:原图,node是待插入的结点.

插入后图:

关键性代码:

尾插法:新增节点总是插在尾部

原图如下:

插入结点后图如下:

关键性代码:

中间任意位置插入

插入之前的原图:

插入到链表中间位置:

关键性代码:

代码演示:

class MyLinkedList {
    Node head;//定义双向链表的头节点
    Node last;//定义双向链表的尾节点

    //打印双向链表
    public void disPlay() {
        Node cur = this.head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

    //求双向链表的长度(之后addIndex代码会用到)
    public int size() {
        int count = 0;
        Node cur = this.head;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }

    //头插法
    public void addFirst(int data) {
        Node node = new Node(data);//定义一个用作插入的节点
        //1.假设双向链表为空
        if (this.head == null) {
            this.head = node;
            this.last = node;
        } else {
            //2.双向链表不为空的情况下
            //不懂请看上面的图解,就很简单了
            node.next = this.head;
            this.head.prev = node;
            this.head = node;
        }
    }

    //尾插法(与头插法类似)
    public void addLast(int data) {
        //定义一个用作插入的节点
        Node node = new Node(data);
        //1.假设双向链表为空
        if (this.head == null) {
            this.head = node;
            this.last = node;
        } else {
            //2.双向链表不为空的情况下
            //不懂请看上面的图解,就很简单了
            last.next = node;
            node.prev = last;
            last = node;
        }
    }

    //在任意位置插入
    public void addIndex(int index, int data) {//形参index为插入元素位置,data为插入的数值
        //定义一个用作插入的节点
        Node node = new Node(data);
        Node cur = this.head;//定义一个cur用作遍历双向链表
        //1、判断插入位置的合法性
        if (index < 0 || index > size()) {
            System.out.println("插入位置不合法!!!");
            return;
        }
        //2、假设插入位置为头结点的情况下,即就是头插法
        if (index == 0) {
            addFirst(data);//调用头插法代码
            return;
        }
        //3、假设插入位置为尾结点的情况下,即就是尾插法
        if (index == size()) {
            addLast(data);//调用尾插法代码
            return;
        }
        //4、在中间任意位置的情况下
        while (index != 0) {
            cur = cur.next;
            index--;
        }
        //不懂请看上面的图解,就很简单了
        //核心代码
        node.next = cur;
        node.prev = cur.prev;
        cur.prev = node;
        node.prev.next = node;
    }
}

//双向链表的节点结构
class Node {
    int val;
    Node prev;
    Node next;

    Node(int val) {
        this.val = val;
    }
}

3、链表反转

public void reverse(){
        if(N==0){
        //当前是空链表,不需要反转
            return;
        }
        reverse(head.next);
}
    /**
     * @param curr 当前遍历的结点
     * @return 反转后当前结点上一个结点
     */
public Node reverse(Node curr){
        //已经到了最后一个元素
        if(curr.next==null){
        //反转后,头结点应该指向原链表中的最后一个元素
          head.next=curr;
          return curr;
        }
       //当前结点的上一个结点
       Node pre=reverse(curr.next);
       pre.next=curr;
       //当前结点的下一个结点设为null
       curr.next=null;
       //返回当前结点
       return curr;
}

总结

到此这篇关于Java数据结构之链表实现的文章就介绍到这了,更多相关Java数据结构链表内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java模拟单链表和双端链表数据结构的实例讲解

    模拟单链表 线性表: 线性表(亦作顺序表)是最基本.最简单.也是最常用的一种数据结构. 线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的. 线性表的逻辑结构简单,便于实现和操作. 在实际应用中,线性表都是以栈.队列.字符串等特殊线性表的形式来使用的. 线性结构的基本特征为: 1.集合中必存在唯一的一个"第一元素": 2.集合中必存在唯一的一个 "最后元素" : 3.除最后一个元素之外,均有 唯一的后继(后件):

  • Java 数据结构链表操作实现代码

    链表是一种复杂的数据结构,其数据之间的相互关系使链表分成三种:单链表.循环链表.双向链表,下面将逐一介绍.链表在数据结构中是基础,也是重要的知识点,这里讲下Java 中链表的实现, JAVA 链表操作:单链表和双链表 主要讲述几点: 一.链表的简介 二.链表实现原理和必要性 三.单链表示例 四.双链表示例 一.链表的简介 链表是一种比较常用的数据结构,链表虽然保存比较复杂,但是在查询时候比较便捷,在多种计算机语言都相应的应用,链表有多种类别,文章针对单链表和双链表进行分析.链表中数据就像被一个链

  • 详解java数据结构与算法之双链表设计与实现

    在单链表分析中,我们可以知道每个结点只有一个指向后继结点的next域,倘若此时已知当前结点p,需要查找其前驱结点,那么就必须从head头指针遍历至p的前驱结点,操作的效率很低,因此如果p有一个指向前驱结点的next域,那效率就高多了,对于这种一个结点中分别包含了前驱结点域pre和后继结点域next的链表,称之为双链表.本篇我们将从以下结点来分析双链表 双链表的设计与实现 双链表的主要优点是对于任意给的结点,都可以很轻易的获取其前驱结点或者后继结点,而主要缺点是每个结点需要添加额外的next域,因

  • java数据结构之实现双向链表的示例

    复制代码 代码如下: /** * 双向链表的实现 * @author Skip * @version 1.0 */public class DoubleNodeList<T> { //节点类 private static class Node<T>{  Node<T> perv;  //前节点  Node<T> next;  //后节点  T data;    //数据 public Node(T t){   this.data = t;  } } priv

  • Java数据结构之简单链表的定义与实现方法示例

    本文实例讲述了Java数据结构之简单链表的定义与实现方法.分享给大家供大家参考,具体如下: 一.概述: 1.原理: 只有一个数据项(链接点Link),每个数据插入时都是对第一个数据的引用. 2.插入数据说明: 当链表没有数据时,插入的值就是第一个数据,如果链表里有数据,就把当前的数据的next指针指向第一个数据. 3.插入数据图: 4.特点:先进后出 5.实现功能: 数据插入,指定位置插入,显示,查询,删除等 6.删除原理 7.插入头节点原理 二.实现: 1.创建节点 /** * @描述 节点

  • JAVA 数据结构链表操作循环链表

    JAVA 链表操作:循环链表 主要分析示例: 一.单链表循环链表 二.双链表循环链表 其中单链表节点和双链表节点类和接口ICommOperate<T>与上篇一致,这里不在赘述.参考:JAVA链表操作:单链表和双链表http://www.jb51.net/article/95113.htm 一.单链表循环链表 package LinkListTest; import java.util.HashMap; import java.util.Map; public class SingleCycle

  • java 数据结构之删除链表中的元素实例代码

    java 删除链表中的元素 以下实例演示了使用 Clear() 方法来删除链表中的元素: import java.util.*; public class Main { public static void main(String[] args) { LinkedList<String> lList = new LinkedList<String>(); lList.add("1"); lList.add("8"); lList.add(&q

  • java实现数据结构单链表示例(java单链表)

    复制代码 代码如下: /** * 单向链表 * */public class NodeList<E> { private static class Node<E> { // 节点类  E data; // 节点上的数据  Node<E> next; // 指向下一个节点 Node(E e) {   this.data = e;   this.next = null;  } } private Node<E> head; // 链表的头节点 private N

  • Java描述数据结构学习之链表的增删改查详解

    前言 链表是一种常见的基础数据结构,它是一种线性表,但在内存中它并不是顺序存储的,它是以链式进行存储的,每一个节点里存放的是下一个节点的"指针".在Java中的数据分为引用数据类型和基础数据类型,在Java中不存在指针的概念,但是对于链表而言的指针,指的就是引用数据类型的地址. 链表和数组都是线性的数据结构,对于数组而言其长度是固定的,由于在内存中其是连续的,因此更适合做查找与遍历,而链表在内存中是并不是顺序存储的,但是由于其是通过"指针"构成的,因此在插入.删除时

  • Java模拟有序链表数据结构的示例

    有序链表: 按关键值排序.删除链头时,就删除最小(/最大)的值,插入时,搜索插入的位置. 插入时需要比较O(N),平均O(N/2),删除最小(/最大)的在链头的数据时效率为O(1), 如果一个应用需要频繁的存取(插入/查找/删除)最小(/最大)的数据项,那么有序链表是一个不错的选择 优先级队列 可以使用有序链表来实现 有序链表的插入排序: 对一个无序数组,用有序链表来排序,比较的时间级还是O(N^2) 复制时间级为O(2*N),因为复制的次数较少,第一次放进链表数据移动N次,再从链表复制到数组,

随机推荐