数据结构-----2.链表:

1.概念:链表不需要在内存中有连续的内存空间,他是将零散的内存块通过 “指针” 的形式串联在一起,每个节点中需要保存下一个节点所在的地址。

链表与数组的区别是:数组在内存中需要一块连续的空间来存储数据,如果数组需要100MB的存储空间,虽然内存中剩余的存储空间大于100MB,但是连续的存储空间小于100MB,那么该数组同样也会创建失败。

2.常见的三种链表结构:单向链表,双向链表,循环链表

(1)单向链表:

         在单向链表中,通常把第一个节点称之为 “头节点”,头节点中不存放数据,头节点用来记录整条链表的基地址,把最后一个节点称之为 “尾节点”。

根据头节点就可以便利整条链表。尾节点的指针,指向一个NULL,代表这是链表上的最后一个节点。

         在链表中的某一个位置,删除一个节点数据时,只需要改变相邻节点的指针即可,所以它的时间复杂度为O(1),但是如果在链表中随机访问某一个元素,因为他在内存中不是连续存储的,所以无法像数组那样,可以根据首地址和下标索引,根据寻址公式计算出当前元素的地址,只能通过便利链表的形式来查找,所以它的时间复杂度为O(n)。

(2)双向链表:

         双向链表在实际开发中也是最为常用的,它存在两个指针,(1)前驱指针(2)后继指针,所以在某些特殊的情况下,它的插入,删除的效率要比单向链表高。在存储相同的数据时,双向链表比单向链表更占内存空间。

        实际开发中,从链表中删除一个数据时,最常见的两种情况:(1)根据指定的值去删除(2)根据指定的指针去删除

其实说单向链表中,插入和删除的时间复杂度是O(1),指的是单纯的插入和删除操作,没有算上之前的遍历运算。

在第一种情况下,无论是单向链表还是双向链表,都需要遍历链表,然后找到对应的值去删除,时间复杂度都为O(n),

第二种情况下,因为单向链表没有前驱指针,即使知道要删除哪一个节点,还是需要找到它的前驱节点,所以还需要遍历链表,根据时间复杂度的加法法则,复杂度还是O(n)。但是双向链表就会不一样,它存在前驱指针,所以在知道确定删除哪一个节点之后,就会找到它的前驱和后继,进行指针的变换,所以复杂度为O(1).

同理:在某一个节点的前面插入节点时,也会出现该现象。

在实际开发中,即使双向链表消耗更多的内存,也使用的比较多,是因为里面有 “用空间换时间的思想”,对于执行较慢的程序,可以采用,用空间换时间。对于内存比较紧张的情况下,可以采用,以时间换空间的思想。LinkedHashMap。

(3)循环链表:

          循环链表与单向链表的区别在于,单向链表的末尾指针,指向NULL,但是循环链表的末尾指针,指向当前链表的头节点,如同一个环形结构,比如著名的 “约瑟夫问题” 就可以使用循环链表来解决。

   

 3.基于链表实现缓存淘汰算法:

我的思路是这样的:我们维护一个有序单链表,越靠近链表尾部的结点是越早之前访问的。当有一个新的数据被访问时,我们从链表头开始顺序遍历链表。

1.如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。

2.如果此数据没有在缓存链表中,又可以分为两种情况:

  • 如果此时缓存未满,则将此结点直接插入到链表的头部;

  • 如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。

这样我们就用链表实现了一个LRU缓存,

public class LRUCacheLinkedList<T> {

    /*
    * 缓存链表的默认容量*/
    private int DEFAULT_CAPACITY = 10;

    /*
    * 缓存链表头节点*/
    private CacheNode headNode;

    /*
    * 缓存链表长度*/
    private int length;

    /*
    * 缓存链表容量*/
    private int capacity;

    public LRUCacheLinkedList(int capacity) {
        this.headNode = new CacheNode();
        this.capacity = capacity;
        this.length = 0;
    }

    public LRUCacheLinkedList() {
        this.length = 0;
        this.capacity = DEFAULT_CAPACITY;
        this.headNode = new CacheNode();
    }

    /*
    * 遍历缓存链表,判断在链表中是否能查找到指定的数据,如果能查到,那么返回当前节点的前驱节点,
    * 如果查不到,那么返回null
    */
    public CacheNode findPreNode(T data) {
        CacheNode node = headNode;
        while(node.nextNode != null) {
            if(node.nextNode.getValue().equals(data)) {
                return node;
            }
            node = node.nextNode;
        }
        return null;
    }

    /*
    * 增加节点*/
    public void lruAdd(T data) {
        //增加节点时,需要先判断该数据在缓存链表中,是否存在,如果存在,需要先删除,然后加到头节点
        CacheNode node = findPreNode(data);
        if(node != null) {
           lruRemove(node);
           insertByHead(data);
        }else {
            if(length >= this.capacity) {
                removeEndNode();
            }
            insertByHead(data);
        }
    }

    /*
    * 将数据添加到头节点*/
    public void insertByHead(T data) {
        CacheNode node = headNode.getNextNode();
        headNode.setNextNode(new CacheNode(data,node));
        length++;
    }

    /*
    * 删除节点*/
    public void lruRemove(CacheNode preNode) {
        CacheNode node = preNode.getNextNode();
        preNode.setNextNode(node.getNextNode());
        node = null;
        //因为删除节点了,所以长度需要减一
        length--;
    }

    /*
    * 删除缓存链表中尾部节点*/
    public void removeEndNode() {
        CacheNode node = headNode;
        //如果是空链表,那么直接返回
        if(node.getNextNode() == null) {
            return;
        }
        //获得缓存链表的倒数第二个节点
        while ( node.getNextNode().getNextNode()!= null) {
            node = node.getNextNode();
        }
        CacheNode temp = node.getNextNode();
        node.setNextNode(null);
        temp = null;
        length--;
    }

    /*
    * 打印缓存链表中的元素*/
    public void printAll() {
        CacheNode node = headNode.getNextNode();
        while (node != null) {
            System.out.print(node.getValue()+", ");
            node = node.getNextNode();
        }
        System.out.println();
        System.out.println("打印完毕!");
    }

    /*
    * 内部类,缓存链表中的节点元素*/
    private class CacheNode<T> {
        private T value;
        private CacheNode nextNode;
        /*
        * 该构造方法在链表头部添加节点时,需要用到*/
        public CacheNode(T value,CacheNode nextNode) {
            this.value = value;
            this.nextNode = nextNode;
        }

        public CacheNode(T value) {
            this.value = value;
        }

        public CacheNode() {
            this.nextNode = null;
        }

        public T getValue() {
            return value;
        }

        public void setValue(T value) {
            this.value = value;
        }

        public CacheNode getNextNode() {
            return nextNode;
        }

        public void setNextNode(CacheNode nextNode) {
            this.nextNode = nextNode;
        }
    }

    public static void main(String[] args) {
        LRUCacheLinkedList lru = new LRUCacheLinkedList();
        Scanner scanner = new Scanner(System.in);
        while(true) {
            lru.lruAdd(scanner.nextInt());
            lru.printAll();
        }
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值