数据结构与算法--链表

简介

链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成

节点类设计

按照面向对象的思想,可以设计一个类,来描述结点这个事物,用一个属性描述这个结点存储的元素,用来另外一个属性描述这个结点的下一个结点

public class Node<T>{
    
    /**存储元素*/
    public T item;
    
    /**指向下一个结点*/
    public Node next;
    
    public Node(T t , Node next){
        this.item = t;
        this.next = next;
    }
}

单向链表

简介

单向链表是由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据, 指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点

实现

根据单链表的描述,为该单链表提供获取链表中元素的个数、链尾插入元素、指定位置插入元素、删除指定位置的元素并返回删除结点的值、获取指定位置的数据元素值、提供forEach循环遍历链表的方式

import java.util.Iterator;

public class LinkList<T> implements Iterable<T>{

    /**记录首节点*/
    private final Node head;

    /**记录单链表中的元素个数*/
    private int n;

    public LinkList(){
        this.head = new Node(null,null);
        this.n = 0;
    }

    /**
     * 清空单链表
     */
    public void clear(){
        head.next = null;
        n = 0;
    }

    /**
     * 判断单链表是否为空
     * @return 为空返回true,否则返回false
     */
    public boolean isEmpty(){
        return head.next == null;
    }

    /**
     * 返回单链表中的元素个数
     * @return n
     */
    public int length(){
        return n;
    }

    /**返回单链表中第i个位置的节点的值*/
    public T get(int i) throws Exception {
        if (i < 0 || i > n){
            throw new Exception("位置不合法");
        }

        //获取头节点
        Node n = head;

        //遍历寻找第i个节点
        for (int index = 0; index < i; index++){
            n = n.next;
        }

        //返回第i个节点的值
        return n.item;
    }

    /**先单链表末尾插入一个新结点*/
    public void insert(T t){
        //获取头节点
        Node node = head;

        //遍历循环到后继节点
        while (node.next != null) {
            node = node.next;
        }

        //新建一个节点
        Node newNode = new Node(t,null);

        //插入
        node.next = newNode;

        //元素个数加1
        n++;
    }

    /**在链表的第i个元素之前插入一个值为t的数据元素 */
    public void insert(int i,T t) throws Exception {
        if (i < 0 || i > n) {
            throw new Exception("位置不合法!");
        }

        //获取头节点
        Node node = head;

        //遍历到第i个节点的前一个节点
        for (int index = 0; index <= i-1;index++){
            node = node.next;
        }

        //第i个节点
        Node cur = node.next;

        //新建一个节点,将新建的节点指向第i个节点
        Node newNode = new Node(t,cur);

        //将第i个节点的前一个节点指向新建的节点
        node.next = newNode;

        //元素个数加1
        n++;
    }

    /**删除并返回链表中第i个数据元素*/
    public T remove(int i) throws Exception {

        if (i < 0 || i > n) {
            throw new Exception("位置不合法!");
        }

        //获取头节点
        Node node = head;

        //遍历找到第i个节点的前一个节点
        for (int index = 0; index <= i-1; index++){
            node = node.next;
        }

        //第i个节点
        Node cur = node.next;

        //删除第i个节点
        node.next = cur.next;

        //元素个数减一
        n--;

        //返回删除的第i个节点
        return cur.item;
    }

    /**返回链表中首次出现的指定的数据元素的位序号,若不存在,则返-1*/
    public int indexOf(T t){
        //获取头节点
        Node node = head;

        //遍历寻找
        for (int index = 0; index < n; index++){
            node = node.next;
            if (node.item.equals(t)){
                return index;
            }
        }
        return -1;
    }

    @Override
    public Iterator<T> iterator() {
        return new MyIterator();
    }

    public class MyIterator implements Iterator{

        private Node node;

        public MyIterator(){
            this.node = head;
        }

        @Override
        public boolean hasNext() {
            return node.next!=null;
        }

        @Override
        public Object next() {
            node = node.next;
            return node;
        }
    }

   private class Node{

       /**存储元素*/
       public T item;

       /**记录下一个节点*/
       public Node next;

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

缺点

  1. 待改进的地方:添加一个last结点的成员变量,用于记录单链表的尾结点,方便尾插入时不用再次的全链表的扫描到链表尾部,可以直接在尾部进行插入即可
  2. 查找的方向只能是一个方向

  3. 删除时必须找到待删除结点的前一个结点,即需要靠辅助结点删除,无法自我删除


双向链表

简介

双向链表由多个结点组成,每个结点都由一个数据域和两个指针域组成数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点

节点类的设计

public class Node{

    /**存储数据*/
    T item;

    /**指向下一个节点*/
    Node next;

    /**指向上一个节点*/
    Node pre;

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

实现

根据双向链表的描述,为该链表提供获取链表中元素的个数、链尾插入元素、指定位置插入元素、删除指定位置的元素并返回删除结点的值、获取指定位置的数据元素值、提供forEach循环遍历链表等

 

package linear;

import java.util.Iterator;

public class TowWayLinkList<T> implements Iterable<T>{

    /**首节点*/
    private final Node head;

    /**尾节点*/
    private Node last;

    /**双向链表中元素的个数*/
    private int n;

    public TowWayLinkList(){
        //初始化尾节点和头节点
        this.last = null;
        this.head = new Node(null,null,null);
        //初始化元素个数
        this.n= 0;
    }

    /**清空双向链表*/
    public void clear(){
        this.last = null;
        this.head.pre = null;
        this.head.item = null;
        this.head.next = null;
        this.n = 0;
    }

    /**判断双向链表是否为空*/
    public boolean isEmpty(){
        return this.n == 0;
    }

    /**返回双向链表的长度*/
    public int length(){
        return this.n;
    }

    /**获取第一个节点的值*/
    public T getFirst(){
        if (isEmpty()){
            return null;
        }
        return head.next.item;
    }

    /**获取尾节点的值*/
    public T getLast(){
        if (isEmpty()) {
            return null;
        }
        return last.item;
    }

    /**获取第i个位置的值*/
    public T get(int i) throws Exception {
        if (i < 0 || i >= this.n) {
            throw new Exception("位置错误");
        }
        Node n = head;
        for(int index = 0;index < i;index++){
            n = n.next;
        }
        return n.item;
    }

    /**向链表中插入一个节点*/
    public void insert(T t){
        //如果链表为空
        if (isEmpty()) {
            this.last = new Node(t,head,null);
            this.head.next = last;
        }else {
            //如果链表不为空
            Node oldLast = last;
            Node node = new Node(t, oldLast, null);
            oldLast.next = node;
            this.last = node;
        }
        //元素个数加1
        this.n++;
    }

    /**在链表的第i个元素之前插入一个值为t的数据元素*/
    public void insert(int i,T t) throws Exception {
        if (i < 0 || i > n-1){
            throw new Exception("位置错误");
        }
        Node n = head;
        //寻找位置i前一个节点
        for (int index = 0; index < i; index++){
            n = n.next;
        }
        //第i个位置的节点
        Node curr = n.next;

        //构造新结点
        Node node = new Node(t,n,curr);
        curr.pre = node;
        n.next = node;

        //元素个数加1
        this.n++;
    }

    /**移除位置i的节点*/
    public T remove(int i) throws Exception {
        if (i<0 || i>=n){
            throw new Exception("位置不合法");
        }

        Node n = head;
        //寻找位置i前一个节点
        for(int index = 0; index < i; index++){
            n = n.next;
        }

        //获取第i位置的节点
        Node oldNode = n.next;

        //获取第i位置的节点后的节点
        Node node = oldNode.next;

        n.next = node;
        node.pre = n;

        //元素个数减1
        this.n--;
        return oldNode.item;
    }

    /**返回链表中首次出现的指定的数据元素的位序号*/
    public int indexOf(T t){
        Node node = head;
        for (int index = 0; node.next != null; index++){
            node = node.next;
            if (node.item.equals(t)){
                return index;
            }
        }
        return -1;
    }

    /**循环遍历*/
    @Override
    public Iterator<T> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator{
        private Node node = head;

        @Override
        public boolean hasNext() {
            return node.next!=null;
        }

        @Override
        public Object next() {
            node = node.next;
            return node.item;
        }
    }

    private class Node{

        /**存储数据*/
        T item;

        /**指向下一个节点*/
        Node next;

        /**指向上一个节点*/
        Node pre;

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

链表跟顺序表的比较 

 相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为链表的物理地址是不连续的, 它不需要预先指定存储空间大小,或者在存储过程中涉及到扩容等操作,同时它并没有涉及的元素的交换。 相比较顺序表,链表的查询操作性能会比较低。因此,如果在程序中查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表

posted @ 2022-07-19 22:11  伊文小哥  阅读(45)  评论(0编辑  收藏  举报