双向链表的原理与实现

一、双向链表原理

顾名思义,双向链表跟单链表和循环列表最大的差别,就是同时拥有前驱指针和后驱指针,基于这一个特性,查询某结点的前一个结点,时间复杂度可以达到O(1),非常高效。双向链表的存取时间复杂度是O(n),增删时间复杂度为O(1),跟其他链表没啥区别。

双向链表表示意图:


双向链表.jpg

所以双向链表的结点定义如下:
class Node{
Object data; //元素值
Node pre; //前驱指针
Node next; //后驱指针
}
对于双向链表做增删操作,有一定的顺序要求,顺序不对,很可能会造成空指针异常。

双向链表增加结点示意图:


双向链表增加结点示意图.png

双向链表删除结点示意图:


双向链表删除结点示意图.png

二、双向链表的实现-java

public class DoubleLinkedList {

    /**
     *  单向或者循环链表访问下一个结点的时间复杂度都是O(1),但是访问上个结点的时间复杂度是O(n)
     *  所以设计出了双向链表,相比于单向链表,除了有next指针,还有pre指针,可以实现访问上个结点
     *  和下个结点的时间复杂度都是O(1)
     *
     *  LinkedHashMap底层都是HashMap+双向链表实现
     */

    /**
     * 双向链表一般具有以下功能:
     * 1.InitDoubleLinkedList() 初始化操作,建立一个空的双向链表
     * 2.DoubleLinkedListEmpty() 判断双向链表是否为空,为空则返回true,非空返回false
     * 3.DoubleLinkedListInsert(T elem) 在双向链表末尾插入数据,不需要手动指定位置
     * 4.DoubleLinkedListInsertWithIndex(int index,T elem) 在双向链表脚标为index处插入新元素
     * 5.DoubleLinkedListDelete() 删除双向链表尾部元素,并返回其值
     * 6.DoubleLinkedListDeleteWithIndex(int index) 删除指定位置的元素结点,并返回其值
     * 7.DoubleLinkedListDeleteWithElem(Object elem) 删除指定元素结点,并返回其值
     * 8.DoubleLinkedListLenth() 返回双向链表的长度
     * 9.toString() 打印双向链表的所有元素值,用逗号分隔
     */
    int doubleLinkedNodeLength = 0; //假设头结点不占用大小
    DoubleLinkedNode head = null;

    public DoubleLinkedList(){
        head = new DoubleLinkedNode(); // 创建一个双向链表的头结点
        head.pre = head.next = head; // 初始化一个空的双向链表,头结点的前驱和后继都指向头结点本身
    }

    class DoubleLinkedNode{
        // 定义一个双向链表的结点
        Object nodeData = null;
        DoubleLinkedNode pre = null;
        DoubleLinkedNode next = null;
    }

    //实现DoubleLinkedListEmpty功能
    public Boolean DoubleLinkedListEmpty(){
        return doubleLinkedNodeLength == 0?true:false;
    }


    //实现DoubleLinkedListInsert功能
    public Boolean DoubleLinkedListInsert(Object elem){
        DoubleLinkedNode insertLinkedNode = null;
        if (head.next == head){
            //表示是一个空的双向链表
            insertLinkedNode = new DoubleLinkedNode();
            insertLinkedNode.nodeData = elem;
            insertLinkedNode.pre = head;
            insertLinkedNode.next = head;
            head.next = insertLinkedNode;
            head.pre = insertLinkedNode;
            doubleLinkedNodeLength++;
            return true;
        }
        insertLinkedNode = new DoubleLinkedNode();
        insertLinkedNode.nodeData = elem;
        insertLinkedNode.next = head;
        insertLinkedNode.pre = head.pre;
        head.pre.next = insertLinkedNode;
        head.pre = insertLinkedNode;
        doubleLinkedNodeLength++;
        return true;
    }

    //实现DoubleLinkedListInsertWithIndex功能
    public Boolean DoubleLinkedListInsertWithIndex(int index,Object elem){
        DoubleLinkedNode insertLinkedNode = null;
        if (index <= 0 || index > doubleLinkedNodeLength+1){
            return false;
        }
        if (index == 1){
            //表示要插入头结点后面的位置
            if (doubleLinkedNodeLength == 0){
                //表示双向链表为空
                insertLinkedNode = new DoubleLinkedNode();
                insertLinkedNode.nodeData = elem;
                insertLinkedNode.pre = head;
                insertLinkedNode.next = head;
                head.next = insertLinkedNode;
                head.pre = insertLinkedNode;
                doubleLinkedNodeLength++;
                return true;
            }else{
                //表示双向链表非空
                insertLinkedNode = new DoubleLinkedNode();
                insertLinkedNode.nodeData = elem;
                insertLinkedNode.next = head.next;
                insertLinkedNode.pre = head;
                head.next.pre = insertLinkedNode;
                head.next = insertLinkedNode;
                doubleLinkedNodeLength++;
                return true;
            }
        }
        if (index == doubleLinkedNodeLength+1){
            // 表示需要插入到双向链表的最后一个位置,需要修改head.pre
            insertLinkedNode = new DoubleLinkedNode();
            insertLinkedNode.nodeData = elem;
            insertLinkedNode.next = head;
            insertLinkedNode.pre = head.pre;
            head.pre.next = insertLinkedNode;
            head.pre = insertLinkedNode;
            doubleLinkedNodeLength++;
            return true;
        }
        int currIndex = 1;
        DoubleLinkedNode currLinkedNode = head.next;
        while (currIndex != index){
            currIndex++;
            currLinkedNode = currLinkedNode.next;
        }
        insertLinkedNode = new DoubleLinkedNode();
        insertLinkedNode.nodeData = elem;
        insertLinkedNode.next = currLinkedNode;
        insertLinkedNode.pre = currLinkedNode.pre;
        currLinkedNode.pre.next = insertLinkedNode;
        currLinkedNode.pre = insertLinkedNode;
        doubleLinkedNodeLength++;
        return true;
    }

    //实现DoubleLinkedListDelete功能
    public Object DoubleLinkedListDelete(){
        DoubleLinkedNode rearLinkedNode = null; //待删除的尾部结点
        Object elem = null;
        if (doubleLinkedNodeLength == 0){return -1;}
        rearLinkedNode = head.pre;
        elem = rearLinkedNode.nodeData;
        rearLinkedNode.pre.next = rearLinkedNode.next;
        rearLinkedNode.next.pre = rearLinkedNode.pre;
        rearLinkedNode.pre = rearLinkedNode.next = null;
        doubleLinkedNodeLength--;
        return elem;
    }

    //实现DoubleLinkedListDeleteWithIndex功能
    public Object DoubleLinkedListDeleteWithIndex(int index){
        DoubleLinkedNode LinkedNode = null;
        Object elem = null;
        if (doubleLinkedNodeLength == 0){return -1;}
        if (index <= 0 || index > doubleLinkedNodeLength){return -1;}

        int currindex = 1;
        DoubleLinkedNode currLinkedNode = head.next;
        while (currindex != index){
            currindex++;
            currLinkedNode = currLinkedNode.next;
        }
        elem = currLinkedNode.nodeData;
        currLinkedNode.pre.next = currLinkedNode.next;
        currLinkedNode.next.pre = currLinkedNode.pre;
        currLinkedNode.pre = currLinkedNode.next = null;
        doubleLinkedNodeLength--;
        return elem;
    }
    //实现DoubleLinkedListDeleteWithElem功能
    public Object DoubleLinkedListDeleteWithElem(Object elem){
        DoubleLinkedNode LinkedNode = head.next;
        Object deleteElem = null;
        if (doubleLinkedNodeLength == 0){return -1;}
        while (LinkedNode != head){
            if (!(LinkedNode.nodeData.equals(elem))){
                LinkedNode = LinkedNode.next;
                continue;
            }else{
                break;
            }
        }
        if (LinkedNode == head){return -1;} //表示未找到对应的元素结点
        deleteElem = LinkedNode.nodeData;
        LinkedNode.pre.next = LinkedNode.next;
        LinkedNode.next.pre = LinkedNode.pre;
        LinkedNode.pre = LinkedNode.next = null;
        doubleLinkedNodeLength--;
        return deleteElem;
    }


    //实现DoubleLinkedListLenth功能
    public int DoubleLinkedListLenth(){
        return doubleLinkedNodeLength;
    }

    //实现toString功能
    public String toString(){
        if (doubleLinkedNodeLength == 0){return "";}
        DoubleLinkedNode doubleLinkedNode = head.next;
        StringBuffer stringBuffer = new StringBuffer();
        for (;doubleLinkedNode != head;doubleLinkedNode = doubleLinkedNode.next){
            if (doubleLinkedNode.next != head){
                stringBuffer.append(doubleLinkedNode.nodeData);
                stringBuffer.append(",");
            }else{
                stringBuffer.append(doubleLinkedNode.nodeData);
            }
        }
        return stringBuffer.toString();
    }

    public static void main(String[] args) {
    }
}

三、用双向链表来实现LRU算法

3.1LRU(最近最少使用)算法

将不常被访问的数据进行淘汰,来保证有限空间的使用,在计算机cache当中广为应用,因为cache的大小有限,为了尽可能多的命中热数据,就可以将冷数据进行淘汰,充分利用内存空间。

3.2LRU核心思想

-》put进数据时,将其放于链尾,因为链尾的数据最不容易被淘汰,并且插入之前需要判断一下空间是否已满,如果满了,就需要将链头的数据淘汰掉。
-》get数据时,如果未在cache中命中,就返回-1,来模拟cache未命中的现象,如果命中,将该数据从当前位置删除,并移至链尾。

3.3使用双向链表来实现以下LRU算法

public class LeastRecentlyUsed {
    /**
     * 使用双向链表来实现LRU
     * -》put方法
     * -》get方法
     */
    private DoubleLinkedList  doubleLinkedList  = null;
    private int maxSize; // 内存大小
    private int currSize; // 当前双向链表的大小
    public LeastRecentlyUsed(int maxSize){
        // 初始化LRU算法类的时候,生成一个双向链表
        doubleLinkedList = new DoubleLinkedList();
        this.maxSize = maxSize;
    }

    //实现put方法
    public Boolean put(Object elem){
        // get the doubleLinkedList currently size
        currSize = doubleLinkedList.DoubleLinkedListLenth();

        if (currSize == maxSize){
            // 表示cache目前已处于待溢出状态
            //先删除头结点后面的第一个结点数据,在插入数据
            doubleLinkedList.DoubleLinkedListDeleteWithIndex(1);
            //在双向链表的尾部添加数据
            return doubleLinkedList.DoubleLinkedListInsert(elem);
        }else{
            //在双向链表的尾部添加数据
            return doubleLinkedList.DoubleLinkedListInsert(elem);
        }

    }

    //实现get方法
    public Boolean get(Object elem){
        // 直接删除该元素结点,如果存在返回该元素值,如果不存在返回-1
        doubleLinkedList.DoubleLinkedListDeleteWithElem(elem);
        // 在链表尾部插入该数据
        doubleLinkedList.DoubleLinkedListInsert(elem);
        return true;
    }

    //实现toString功能,方便进行测试使用
    public String toString(){
        return doubleLinkedList.toString();
    }

    public static void main(String[] args) {
    }
}

3.4使用双向链表实现LRU算法复杂度分析

之前也提到过,双向链表同其他链表一样,存取时间复杂度都是O(n),因为都是需要遍历链表才行,增删操作的时间复杂度都是O(1)。实现LRU的过程,如果是put操作,那么针对双向链表的操作只有删除第一个结点,然后添加尾结点,时间复杂度为O(1),如果是get操作,需要先遍历查找到对应的结点,然后在进行增删操作,前者时间复杂度为O(n),后者时间复杂度为O(1),所以加起来还是O(n)。

后续为大家介绍一种实现LRU算法,并且时间复杂度为O(1)的实现方式。
LRU算法的原理与实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349