线性表——链表(java实现)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/YIXIANG0234/article/details/79949967

一,链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表是顺序表在链式存储结构上的另一种实现。链表一般可分为单链表,双链表和循环列表。

单链表:由结点组成,每个结点包含两部分,数据域和指针域,数据域存储真正需要存储的数据部分,指针域是一个指向其后继结点(或前驱结点)的指针。
双链表:双链表也是由结点组成,不同的是除了数据域以外,它有两个指针域,一个指向其前驱结点,另一个指向其后继结点。
循环列表:循环列表一般指单循环列表,即只有一个指针域的链表,在与单链表不同的是,在单链表中其尾结点的指针通常为null,而循环列表其尾结点的指针指向其头节点,构成一个循环结构。


二, 链表实现

这里我们采用的是双链表,即链表结点包含数据域和两个指针域,一个指向前驱结点,一个指向后继结点。

1,定义一个类MyLinkedList实现接口MyList

MyList的定义见文章https://blog.csdn.net/YIXIANG0234/article/details/79901468

实现MyList接口并支持泛型

public class MyLinkedList<T> implements MyList<T> {}

2,成员变量

private Node<T> head;   //头节点,指向链表中的第一个结点
private Node<T> rear;   //尾结点,指向链表中的最后一个结点
private int length; //链表的长度

3,静态内部类——结点类

//私有的静态内部类,用于构建链表结点
private static class Node<T>
{
    public Node<T> prev;    //指向前驱结点
    public Node<T> next;    //指向后继结点
    public T data;  //存储数据
    public Node(Node<T> prev,Node<T>next,T data)
    {
        this.prev = prev;
        this.next = next;
        this.data = data;
    }
}

4,构造方法

//构造方法,初始化一个带有头尾结点的空链表
public MyLinkedList() {
    head = new Node<T>(null,null,null);
    rear = new Node<T>(head,null,null);
    head.next = rear;
    length = 0;
}

5,getNode方法,私有方法,方便链表操作的实现,查找指定位置的结点

private Node<T> getNode(int index)//获取指定位置的结点
{
    Node<T> node;
    if(index<0 || index>length-1)
        throw new RuntimeException("查找操作失败,下标越界:"+index);
    //分为两部分查找,下标小于length/2在前半部分查找
    if(index<length/2)
    {
        node = head.next;
        for(int i=0;i<index;i++)
            node = node.next;
    }
    else {      //下标大于length/2在后半部分查找
        node = rear.prev;
        for(int i=length-1;i>index;i--)
            node = node.prev;
    }
    return node;    
}

6,get方法,根据下标查找结点数据

@Override
public T get(int index) {   //查找指定位置的数据
    Node<T> node = getNode(index);
    if(node==null)
        return null;
    return node.data;
}

7,indexOf方法,查找某个数据所在结点的下标

@Override
public int indexOf(T data) {
    int index = 0;
    Node<T> node = head.next; //找到链表第一个节点
    while(index<length && node!=null)
    {
        if(node.data.equals(data))//找到即返回
            return index;
        node = node.next;   //循环查找下一个结点
        index++;
    }
    return -1;  //未找到以-1表示
}

8,size方法,返回链表的大小

@Override
public int size() { //返回此链表的长度
    return length;
}

9,isEmpty方法,判断链表是否为空

@Override
public boolean isEmpty() {      //判断链表是否为空
    return length == 0;
}

10,toString方法,给出链表的字符串表示

public String toString()
{
    StringBuilder sb = new StringBuilder();
    sb.append("{");
    for(int i=0;i<length-1;i++)
    {
        String s = get(i).toString();
        sb.append(s+",");
    }
    sb.append(get(length-1).toString()+"}");
    return sb.toString();
}

11,set方法,修改结点的数据

@Override
public boolean set(int index, T data) {
    Node<T> node = getNode(index);  //找到该结点,然后更新数据域
    node.data = data;
    return true;
}

12,remove方法,分别根据下标和数据移除结点

@Override
public T remove(int index) {
    Node<T> node = getNode(index);  //查找到该结点
    node.prev.next = node.next;     //调整结点间的前驱和后继关系
    node.next.prev = node.prev;
    T data = node.data;     //保存数据域
    node = null;        //将该节点的引用置为空,存储空间的处理由垃圾回收机制管理
    length--;
    return data;    //返回数据域
}

@Override
public T remove(T data) {
    int index = indexOf(data);      //查找到该数据在链表中的位置
    Node<T> node = getNode(index);  //找到表示该数据的结点
    if(node == null)
        return null;
    node.prev.next = node.next;     //调整前驱和后继关系
    node.next.prev = node.prev;
    T nodeData = node.data;
    node  = null;
    length--;
    return nodeData;    //返回数据
}

13,add方法,分别在末尾和指定位置插入结点

@Override
public boolean add(T data) {    //在链表结尾插入
    Node<T> newNode = new Node<T>(rear.prev,rear,data);
    newNode.prev.next = newNode;
    rear.prev = newNode;
    length++;
    return true;
}

@Override
public boolean add(int index, T data) { //在指定的位置插入结点
    Node<T> node = getNode(index);  //获取指定位置的结点
    if(node == null)
        return false;
    //在指定位置的结点前加入新节点,即调整各结点间的前驱和后继关系
    Node<T> newNode = new Node<T>(node.prev,node,data);
    newNode.prev.next = newNode;
    node.prev = newNode;
    length++;
    return true;
}

14,说明:
方法getNode,add,remove,indexOf,get等是链表实现的核心算法


三,测试

针对MyLinkedList类中的方法编写测试类TestMyLinkedList

import static java.lang.System.*;
public class TestMyLinkedList {
public static void main(String[] args) {
    MyLinkedList<String> list = new MyLinkedList<String>();//测试构造方法
    list.add("常州"); //测试add方法,在链表末尾添加
    list.add("北京");
    list.add("China");
    list.add("SuZhou");
    out.println("添加插入操作:");
    out.println(list.toString());//测试头String方法
    list.add(1,"南京");   //测试ad方法,在指定位置添加
    out.println(list.toString());
    out.println("--------------------------------------------------");
    list.remove(1); //测试remove方法,删除指定位置元素
    out.println("删除操作:");
    out.println(list.toString());
    list.remove("China");
    out.println(list.toString());
    out.println("--------------------------------------------------");
    out.println("其他操作:");
    list.set(2, "苏州");
    String data = list.get(2);
    out.println(data);
    out.println(list.toString());
    int index = list.indexOf("北京");
    out.println("北京的下标是:"+index);
    out.println("链表list的长度为:"+list.size());
    out.println("list是否为空:"+list.isEmpty());
}
}

程序运行结果:

添加插入操作:
{常州,北京,China,SuZhou}
{常州,南京,北京,China,SuZhou}
--------------------------------------------------
删除操作:
{常州,北京,China,SuZhou}
{常州,北京,SuZhou}
--------------------------------------------------
其他操作:
苏州
{常州,北京,苏州}
北京的下标是:1
链表list的长度为:3
list是否为空:false

从运行结果可以看出,我们的链表得到了较好的实现,如有bug,望各位斧正。


四,顺序表和链表的优缺点

顺序表:
1,优点:

  • 方法简单,存储结构是顺序结构,可以采用数组实现
  • 无须为表示结点间的逻辑关系而增加额外的存储空间
  • 顺序表具有按元素随机访问的特点

2,缺点:

  • 在顺序表中进行插入删除操作时,由于数据在内存中顺序存储,所以需要移动元素,平均移动表中大约一般的元素,n较大时效率低。
  • 需要预先分配足够大的存储空间,若估计过大造成内存浪费,若分配过小会溢出,造成顺序表内部实现对数组元素的移动和复制。

链表:
与顺序表相反,对元素的插入删除效率高,而不具有对元素随机访问的特点,所以访问元素的效率很低。

顺序表和链表的选择
如何选择顺序表和链表呢?结合以上的优缺点可以看出,如果对数据基本不存在改动的操作,即很少对数据进行增删操作,反而常做查询的操作,那么选用顺序表。如果需要对数据进行频繁的增删改,查询操作较少,那么选择链表。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页