数据结构-链表

链表概述

真正的动态数据结构,也是最简单的动态数据结构,可以更深入的理解引用(或者指针),更深入的理解递归操作。

优点:真正的动态,不需要处理固定容量的问题
缺点:丧失了随机访问的能力

实现链表

添加元素的过程
在这里插入图片描述
在指定位置进行插入元素时,需要辅助节点prev进行维护。

public class LinkedList<E> {
	//节点类
    private class Node {
        public E e;
        public Node next;

        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        public Node(E e) { this(e, null); }

        public Node() { this(null, null); }

        @Override
        public String toString() {
            return e.toString();
        }
    }

    private Node head;//头结点
    private int size;

    public LinkedList() {
        head = null;
        size = 0;
    }

    public int getSize() { return size; }

    public boolean isEmpty() { return size == 0;}

    /**
     * 头插法插入元素
     * @param e
     */
    public void addFirst(E e) {
        // Node node = new Node(e);
        // node.next = head;
        // head = node;
        head = new Node(e, head);
        size++;
    }

    /**
     * 在链表指定位置插入新元素
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("插入位置有误");
        }
        if (index == 0) {//处理从首元素插入,因为首元素没有prev节点
            addFirst(e);
        } else {
            Node prev = head;//prev初识指向head
            for (int i = 0; i < index - 1; i++) {//移动到指定位置
                prev = prev.next;
            }
            //Node newNode = new Node(e);
            //newNode.next=prev.next;
            //prev.next=newNode;
            prev.next = new Node(e, prev.next);
        }
        size++;
    }

    /**
     * 在链表尾部插入元素
     * @param e
     */
    public void addLast(E e) {
        add(size, e);
    }
}

在链表头添加元素和在链表其他位置添加元素的逻辑上有所差别(addFirst不能复用add() ):在给链表添加元素时,需要找到待添加位置之前的元素,但是对于链表头来说,他没有那个节点,所以在逻辑上有点点不同,所以引入一个虚拟头结点dummyHead,让添加操作统一起来,dummyHead对用户来说没有任何意义,类似于在循环队列中有意识的浪费一个空间一样,只是方便编写逻辑。

public class LinkedList<E> {
    private class Node {
        public E e;
        public Node next;

        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        public Node(E e) {   this(e, null);}
  
        public Node() { this(null, null);  }

        @Override
        public String toString() {    return e.toString();  }
    }

    private Node dummyHead;//虚拟头结点
    private int size;

    public LinkedList() {
        //这样看来:初始时链表中有一个节点
        dummyHead = new Node(null, null);
        size = 0;
    }
	
	/**
	* 数组转为链表
	*/
	public LinkedList(E[] arr){
		if(arr.length == 0 && arr == null) {
			throw new IllegalArgumentException("arr can not be empty");
		}
		this.val=arr[0];
		ListNode current = this;
		for(int i = 1;i < arr.length ; i++){
			current.next=new ListNode(arr[i]);
			current=current.next;
		}
	}

    public int getSize() {   return size;  }

    public boolean isEmpty() {     return size == 0; }

    /**
     * 在链表指定位置插入新元素
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("插入位置有误");
        }

        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {//移动到指定位置
            prev = prev.next;
        }
        prev.next = new Node(e, prev.next);
        size++;
    }

    /**
     * 头插法插入元素
     * @param e
     */
    public void addFirst(E e) {   add(0, e);}

    /**
     * 在链表尾部插入元素
     * @param e
     */
    public void addLast(E e) {   add(size, e);  }

    /**
     * 获取指定下标的元素
     * @param index
     * @return
     */
    public E get(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("索引非法!");
        }

        Node current = dummyHead.next;
        for (int i = 0; i < index; i++) {
            current = current.next;
        }
        return current.e;
    }

    public E getFirst() {  return get(0);  }

    public E getLast() {   return get(size); }

    /**
     * 是否包含指定元素
     * @param e
     * @return
     */
    public boolean contains(E e) {
        for (Node node = dummyHead.next; node != null; node = node.next) {
            if (node.e.equals(e)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 给指定位置修改元素值
     * @param index
     * @param e
     */
    public void set(int index, E e) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("索引非法!");
        }
        Node current = dummyHead.next;
        for (int i = 0; i < index; i++) {
            current = current.next;
        }
        current.e = e;
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        Node current = dummyHead.next;
        while (current != null) {
            result.append(current + "->");
            current = current.next;
        }
        result.append("NULL");
        return result.toString();
    }

    /**
     * 删除指定位置的元素
     * @param index
     * @return
     */
    public E remove(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("索引非法!");
        }
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }
        Node retNode = prev.next;
        prev.next = retNode.next;
        retNode.next = null;
        size--;
        return retNode.e;
    }

    public E removeFirst(){ return remove(0);  }
    public E removeLast(){ return remove(size-1); }

    public static void main(String[] args) {
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < 5; i++) {
            linkedList.addFirst(i);
            System.out.println(linkedList);
        }
        linkedList.add(2, 666);
        System.out.println(linkedList);
        linkedList.remove(2);
        System.out.println(linkedList);
    }
}

这样一来在添加元素时,就统一起来,addFirst()和addLast()都能复用add()方法了。
在这里插入图片描述
在这里插入图片描述

链表实现栈

由于链表在头部操作时时间复杂度为O(1),所以符合栈(只在一端进行操作)的结构。
栈接口

public interface Stack<E> {
    int getSize();
    boolean isEmpty();
    void push(E e);
    E pop();
    E peek();
}

这种栈的底层是一个链表,链表的头部为栈顶,所以操作链头部即可

public class LinkedListStack<E> implements Stack<E> {
    private LinkedList<E> list;
    public LinkedListStack() {  list = new LinkedList<E>(); }
    public LinkedListStack(LinkedList<E> list) {  this.list=list;  }
    
    @Override
    public int getSize() {    return list.getSize();  }
    @Override
    public boolean isEmpty() {    return list.isEmpty(); }
    @Override
    public void push(E e) {    list.addFirst(e);  }
    @Override
    public E pop() {   return list.removeFirst();   }
    @Override
    public E peek() {   return list.getFirst();   }
    @Override
    public String toString(){
        StringBuilder res=new StringBuilder();
        res.append("Stack : top ");
        res.append(list);
        return res.toString();
    }
    public static void main(String[] args) {
        LinkedListStack<Integer> linkedListStack=new LinkedListStack<>();
        for (int i=0;i<5;i++){
            linkedListStack.push(i);
            System.out.println(linkedListStack);
        }
        linkedListStack.pop();
        System.out.println(linkedListStack);
    }
}

链表实现队列

回顾之前使用数组实现队列时,在出队操作时要进行数组元素的移位导致复杂度为O(n),所以引入了循环数组解决此问题。

链表的时间复杂度如下,可以看出如果只对链表头部操作是时间复杂度为O(1)的,而队列则是对链表头和尾都需要进行操作,所以不能像实现栈那样只对对首元素操作,所以要对链表结构做细微的改变。
在这里插入图片描述

我们的希望是在链表的尾部操作也是O(1)级别的,只需设立一个类似head的节点tail进行维护链表尾部即可,tail节点即为待添加元素的前一个节点,此时在tail和head节点处添加节点都是很容易的。但是删除元素时,我们知道需要找到待删除元素的前一个节点,但是在删除队尾元素(出队操作)时,就算引入了tail,还是需要从头遍历一遍链表,也无法通过O(1)的复杂度找到待删除元素的前一个节点,
在这里插入图片描述

public interface Queue<E> {
    int getSize();
    boolean isEmpty();
    void enqueue(E e);
    E dequeue();
    E getFront();
}

public class LinkedListQueue<E> implements Queue<E> {

    class Node {
        public E e;
        public Node next;

        public Node() {      this(null, null);   }

        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        public Node(E e) {   this(e, null);    }

        @Override
        public String toString() {     return e.toString();   }
    }

    private int size;//队列元素个数
    private Node tail, head;//队首、队尾

    public LinkedListQueue() {
        size = 0;
        head = null;
        tail = null;
    }

    @Override
    public int getSize() {    return size;   }

    @Override
    public boolean isEmpty() {    return size == 0;}

    /**
     * 入队(尾插法)
     * @param e
     */
    @Override
    public void enqueue(E e) {
        if (tail == null) {//队列没有元素时(但凡队列中有元素那么tail就不为null)
            tail = new Node(e);
            head = tail;
        } else {
            tail.next = new Node(e);
            tail = tail.next;
        }
        size++;
    }

    /**
     * 出队(头出法)
     * @return
     */
    @Override
    public E dequeue() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空!");
        }

        Node retNode = head;
        head = head.next;
        retNode.next = null;
        if (head == null) {
            tail = null;
        }
        size--;
        return retNode.e;
    }

    @Override
    public E getFront() {   return head.e;  }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        result.append("Queue front: ");

        Node current = head;
        while (current != null) {
            result.append(current + "->");
            current = current.next;
        }

        result.append("null tail ");
        return result.toString();
    }


    public static void main(String[] args) {
        LinkedListQueue<Integer> queue = new LinkedListQueue<>();
        for (int i = 0; i < 5; i++) {
            queue.enqueue(i);
            System.out.println(queue.toString());
        }
        queue.dequeue();
        System.out.println(queue.toString());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值