数据结构(2)-链表

填补那些模棱两可的后知后觉

什么是链表

链表是一种用于存储数据集合的数据结构,他是最简单的动态数据结构。上一篇我们虽显然实现了一个简单的动态数组。单这仅仅是面向使用者而言,其实数组的底层还是维护的是一个静态的数组,我们只是简单的通过拷贝的当时实现容量的增减,但是!!!链表 则是真正意义上的动态数据结构。

链表的优点

  • 真正的动态数据结构,不需要处理固定容量的问题
  • 能够在常数时间内扩展容量

链表的缺点

  • 随机访问效率较低
  • 链表中需要维护一份指针引用,相对浪费内存

链表与数组的简单对比

  • 数组最用用于索引有语意的情况,其最大特点就是支持快速的查询和修改
  • 链表不适用于索引有语意的情况,其最大的特点则是动态,能快读插入和删除数据

实现一个自己的链表类

同样,我们和上一篇数组的介绍中,我们来完成一个自已的链表,支持最简单的增删该查功能。

public class LinkedList<E> {

    private class Node{
        private E e;
        private 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();
        size = 0;
    }

    //获取链表中的元素个数
    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("Add failed. Illegal index.");
        }
        //利用虚拟头节点,将添加索引处的前方元素全部前移一位
        Node prve = dummyHead;

        for (int i = 0; i < index; i++) {
            //将原元素的指针指向原来的位置
            prve = prve.next;
        }
        prve.next = new Node(e,prve.next);
        size++;
    }

    /**
     * 新增头节点
     */
    public void addFirst(E e){
//        Node node = new Node(e);
//        node.next = dummyHead;
//        dummyHead = node;

        dummyHead = new Node(e,dummyHead);
    }

    /**
     * 在结尾处增加元素
     * @param e
     */
    public void addLast(E e){
        this.add(size,e);
    }

    public E get(int index){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("Add failed. Illegal index.");
        }

        Node cur = dummyHead.next;
        for (int i = 0; i < size; i++) {
            cur = cur.next;
        }

        return cur.e;
    }

    //获取链表的第一个元素
    public E getFirst(){
        //可以直接返回虚拟头节点的指向的下一个元素
        return dummyHead.next.e;
    }

    //获取链表的最后一个元素
    public E getLast(){
        return this.get(size -1);
    }

    //修改链表中指定位置的元素
    public void set(int index ,E e){
        //思路与get方法类似,找到改元素,直接替换即可
        if(index < 0 || index > size){
            throw new IllegalArgumentException("Add failed. Illegal index.");
        }

        Node cur = dummyHead.next;
        for (int i = 0; i < size; i++) {
            cur = cur.next;
        }
        cur.e = e;
    }

    //查找链表中是否包含某个元素e
    public boolean contains(E e){
        //从第一个元素开始遍历,然后进行匹配
        Node cur = dummyHead.next;
        while (cur != null){
            if(cur.e.equals(e)){
                return true;
            }
            cur = cur.next;
        }

        return false;
    }

    //删除指定位置的元素
    public void remove(int index){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("Add failed. Illegal index.");
        }
        //首先定位此元素,与修改或查询一致,将改元素置空,然后将改元素原来的前后元素进行指向
        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 --;
    }

    //删除链表中的第一个元素
    public void removeFirst(){
        //将虚拟头节点指向第二个元素即可
        dummyHead.next = dummyHead.next.next;
        size--;
    }

    // 从链表中删除最后一个元素, 返回删除的元素
    public void removeLast() {
         remove(size - 1);
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();

        Node cur = dummyHead.next;
        while (cur != null) {
            res.append(cur + "->");
            cur = cur.next;
        }
        res.append("NULL");

        return res.toString();
    }
}

链表中虚拟头节点的作用

因为第一个元素(真头节点)是没有前序节点的。当我们在做操作时都需要对头节点进行单独的判断,这样一来的话,真头节点的逻辑就有空。所以为了方便,我们设置一个虚拟的头节点,来屏蔽真头节点的特殊性。

简单复杂度分析

我们从链表的操作中可以很容易的看出,对于增删改查这几个操作的复杂度都是O(n)的,但是如果我们只是对链表头进行增/删/查的操作的话,那么它的复杂度就是O(1)的,这里也可以看出来我们的链表适合干的事情了..

转载于:https://www.cnblogs.com/wujiwen/p/9595258.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值