JS数据结构第三篇---双向链表和循环链表

一、双向链表

在上文《JS数据结构第二篇---链表》中描述的是单向链表。单向链表是指每个节点都存有指向下一个节点的地址,双向链表则是在单向链表的基础上,给每个节点增加一个指向上一个节点的地址。然后头结点的上一个节点,和尾结点的下一个节点都指向null。同时LinkedList类中再增加一个last内部属性,一直指向链表中最后一个节点。结构模拟如图:

同样对外暴露的方法和单向链表一样,只是内部实现稍有变化

双向链表完整设计代码:

/**
 * 自定义双向链表:对外公开的方法有
 * append(element) 在链表最后追加节点
 * insert(index, element) 根据索引index, 在索引位置插入节点
 * remove(element)  删除节点
 * removeAt(index)  删除指定索引节点
 * removeAll(element) 删除所有匹配的节点
 * set(index, element) 根据索引,修改对应索引的节点值
 * get(index)  根据索引获取节点信息
 * indexOf(element) 获取某个节点的索引位置
 * clear()  清空所有节点
 * length()   返回节点长度
 * print() 打印所有节点信息
 * toString() 打印所有节点信息,同print
 * */
const LinkedListDouble = function(){
    let head = null; //链表中第一个LinkNode
    let last = null;  //链表中最后一个LinkNode
    let size = 0;   //记录链表元素个数

    //Node模型
    function LinkNode(prev, element, next){
        this.prev = prev; //当前节点的上一个node
        this.element = element;  //当前节点的元素
        this.next = next; //当前节点的下一个node
    }

    //元素越界检查, 越界抛出异常
    function outOfBounds(index){
        if (index < 0 || index >= size){
            throw("抱歉,目标位置不存在!");
        }
    }

    //根据索引,获取目标对象
    function node(index){
        outOfBounds(index);

        //判断index是靠近前半部分,还是后半部分,以求最少次数找到目标节点
        if (index <= size>>1){
            //说明从头往后找,次数少一点
            let obj = head;
            for (let i = 0;i < index; i++){
                obj = obj.next;
            }
            return obj;
        }
        else{
            //说明从后往前找,次数少一点
            let obj = last;
            for (let i = size-1; i > index; i--){
                obj = obj.prev;
            }
            return obj;
        }
    }

    //新增一个元素
    function append(element){
        if (size == 0){
            head = new LinkNode(null, element, null);
            last = head;
        }
        else{
            let obj = node(size-1);
            obj.next = new LinkNode(obj, element, null);
            last = obj.next;
        }
        size++;
    }

    //插入一个元素
    function insert(index, element){
        if (index == 0){
            head = new LinkNode(null, element, head);

            if (size == 0){
                last = head;
            }
        }
        else{
            let obj = node(index-1);
            obj.next = new LinkNode(obj, element, obj.next);

            if (index == size){
                last = obj.next;
            }
        }
        size++;
    }

    //修改元素
    function set(index, element){
        let obj = node(index);
        obj.element = element;
    }

    //移除节点(内部使用,不对外暴露)
    function removeNode(node){
        let prev = node.prev, next = node.next; //当前节点的前一个,和后一个

        //判断head临界点
        if (prev == head){
            head = next;
        }
        else{
            prev.next = next;
        }

        //判断last临界点
        if (next == last){
            last = prev;
        }
        else{
            next.prev = prev;
        }
        size--;
        return node.element;
    }

    //根据值移除节点元素
    function remove(element){
        let temp = head;
        while(temp){
            if (temp.element == element){
                return removeNode(temp);
            }
            else{
                temp = temp.next;
            }
        }
        return null;
    }

    //根据索引移除节点
    function removeAt(index){
        return removeNode(node(index));
    }

    //移除链表里面的所有匹配值element的元素
    function removeAll(element){
        let newFirst = new LinkNode(null, 0, head), ele = null;
        let virHead = newFirst;

        while(virHead.next){
            if (virHead.next.element == element){

                ele = element;

                if (virHead.next.next){
                    virHead.next.next.prev = virHead.next.prev;
                }
                else{ //删除了最后一个节点
                    last = virHead.next.prev;
                }
                virHead.next = virHead.next.next;
                size--;
            }
            else{
                virHead = virHead.next;
            }
        }

        //重新赋值
        head = newFirst.next;

        return ele;
    }

    //获取某个元素
    function get(index){
        return node(index).element;
    }

    //获取元素索引
    function indexOf(element){
        let obj = head, index = -1;

        for (let i = 0; i < size; i++){
            if (obj.element == element){
                index = i;
                break;
            }
            obj = obj.next;
        }
        return index;
    }

    //清除所有元素
    function clear(){
        head = null;
        last = null;
        size = 0;
    }

    //属性转字符串
    function getObjString(obj){

        let str = "";

        if (obj instanceof Array){
            str += "[";
            for (let i = 0; i < obj.length; i++){
                str += getObjString(obj[i]);
            }
            str = str.substring(0, str.length - 2);
            str += "], "
        }
        else if (obj instanceof Object){
            str += "{";
            for (var key in obj){
                let item = obj[key];
                str += "\"" + key + "\": " + getObjString(item);
            }
            str = str.substring(0, str.length-2);
            str += "}, "
        }
        else if (typeof obj == "string"){
            str += "\"" + obj + "\"" + ", ";
        }
        else{
            str += obj + ", ";
        }

        return str;
    }
    function toString(){
        let str = "", obj = head;
        for (let i = 0; i < size; i++){
            str += getObjString(obj.element);
            obj = obj.next;
        }
        if (str.length > 0) str = str.substring(0, str.length -2);
        return str;
    }
    //打印所有元素
    function print(){
        console.log(this.toString())
    }

    //对外公开方法
    this.append = append;
    this.insert = insert;
    this.remove = remove;
    this.removeAt = removeAt;
    this.removeAll = removeAll;
    this.set = set;
    this.get = get;
    this.indexOf = indexOf;
    this.length = function(){
        return size;
    }
    this.clear = clear;
    this.print = print;
    this.toString = toString;
}

////测试
// let obj = new LinkedListDouble();
// let obj1 = { title: "全明星比赛", stores: [{name: "张飞vs岳飞", store: "2:3"}, { name: "关羽vs秦琼", store: "5:5"}]};
// obj.append(99);
// obj.append("hello")
// obj.append(true)
// obj.insert(3, obj1);
// obj.insert(0, [12, false, "Good", 81]);
// obj.print();
// console.log("obj1.index: ", obj.indexOf(obj1));
// obj.remove(0);
// obj.removeAll(obj1);
// obj.print();

////测试2
console.log("\n\n......test2.....")
var obj2 = new LinkedListDouble();
obj2.append(8); obj2.insert(1, 99); obj2.append(‘abc‘); obj2.append(8); obj2.append(false);
obj2.append(12); obj2.append(8); obj2.append(‘123‘); obj2.append(8);
obj2.print();
obj2.removeAll(8); //删除所有8
obj2.print();

二、循环链表

在链表的基础上,再稍稍修改一下,让链表中的尾结点和头节点链接起来,形成一个循环生生不息。单向循环链表是尾结点的下一个地址指向头结点,而不是null;双向循环链表则是last节点的next指向head节点,head节点的prev指向last节点。结构模拟如下两个图:

对于单个节点的循环链表,头结点和尾节点为同一个节点,则自己指向自己。结构模拟如下图:

循环链表的代码这里就不贴出来了,代码放在Github那里,有兴趣可以点进去看看。

三、循环链表的应用---约瑟夫问题模拟

据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

这里我做了一个效果图,模拟下约瑟夫问题:

接下来我们如何用链表来模拟约瑟夫问题。

在上面循环链表的基础上,我们给LinkedList再添加一个内部属性current,指向当前节点,默认指向头结点;再增加三个对外方法next()、removeCurrent()、reset(),分别表示当前节点指向下一个节点,移除当前节点,重置当前节点为头结点。修改后,新的LinkedList对外方法有:

新的循环双向链表完整设计代码:

/**
 * 在循环双向链表的基础上,增加1个属性,3个方法(属性内部使用,方法对外开放),让循环链表发挥更大的效果:
 * current: 指向当前节点,默认指向首节点head
 * next():让current每次移动一次,移上下一个节点, 返回元素
 * removeCurrent(): 每次删除当前current指向的节点,删除后,current指向下一个节点
 * reset(): 重置current指向首节点head
 *
 * 自定义双向循环链表:对外公开的方法有
 * append(element) 在链表最后追加节点
 * insert(index, element) 根据索引index, 在索引位置插入节点
 * remove(element)  删除节点
 * removeAt(index)  删除指定索引节点
 * removeAll(element) 删除所有匹配的节点
 * set(index, element) 根据索引,修改对应索引的节点值
 * get(index)  根据索引获取节点信息
 * indexOf(element) 获取某个节点的索引位置
 * clear()  清空所有节点
 * length()   返回节点长度
 * print() 打印所有节点信息
 * toString() 打印所有节点信息,同print
 * */
const CircleLinkedListDouble = function(){
    let head = null; //链表中第一个LinkNode
    let last = null;  //链表中最后一个LinkNode
    let size = 0;   //记录链表元素个数
    let current = null; //当前指向的节点

    //Node模型
    function LinkNode(prev, element, next){
        this.prev = prev; //当前节点的上一个node
        this.element = element;  //当前节点的元素
        this.next = next; //当前节点的下一个node
    }

    //元素越界检查, 越界抛出异常
    function outOfBounds(index){
        if (index < 0 || index >= size){
            throw("抱歉,目标位置不存在!");
        }
    }

    //根据索引,获取目标对象
    function node(index){
        outOfBounds(index);

        //判断index是靠近前半部分,还是后半部分,以求最少次数找到目标节点
        if (index <= size>>1){
            //说明从头往后找,次数少一点
            let obj = head;
            for (let i = 0;i < index; i++){
                obj = obj.next;
            }
            return obj;
        }
        else{
            //说明从后往前找,次数少一点
            let obj = last;
            for (let i = size-1; i > index; i--){
                obj = obj.prev;
            }
            return obj;
        }
    }

    //新增一个元素
    function append(element){
        if (size == 0){
            head = new LinkNode(null, element, null);
            head.next = head;
            head.prev = head;
            last = head;
            current = head;
        }
        else{
            let obj = node(size-1);
            obj.next = new LinkNode(obj, element, head);
            last = obj.next;
            head.prev = last;
        }
        size++;
    }

    //插入一个元素
    function insert(index, element){
        //表示插入到第一个
        if (index == 0){

            let last = null;
            if (size > 0) last = node(size-1);

            head = new LinkNode(last, element, head);

            if (size < 1){
                head.next = head;
                head.prev = head;
                last = head;
                current = head;
            }
            else{
                last.prev = head;
            }
        }
        else{
            let prev = node(index-1);
            prev.next = new LinkNode(prev, element, prev.next);

            //表示插入到最后一个
            if (index == size){
                last = prev.next;
                head.prev = last;
            }
        }
        size++;
    }

    //修改元素
    function set(index, element){
        let obj = node(index);
        obj.element = element;
    }

    //移除节点(内部使用,不对外暴露)
    function removeNode(node){

        if (size == 1){
            current = head = last = null;
        }
        else{
            let prev = node.prev, next = node.next; //当前节点的前一个,和后一个

            //判断head临界点
            if (prev == last){
                head = next;
                head.prev = last;
                last.next = head;
            }
            else{
                prev.next = next;
            }

            //判断last临界点
            if (next == head){
                last = prev;
                last.next = head;
                head.prev = last;
            }
            else{
                next.prev = prev;
            }

            if (current == node){
                current = next;
            }
        }

        size--;
        return node.element;
    }

    //根据值移除节点元素
    function remove(element){
        let temp = head;
        while(temp){
            if (temp.element == element){
                return removeNode(temp);
            }
            else{
                temp = temp.next;
            }
        }
        return null;
    }

    //根据索引移除节点
    function removeAt(index){
        return removeNode(node(index));
    }

    //移除链表里面的所有匹配值element的元素
    function removeAll(element){
        let newFirst = new LinkNode(null, 0, head), delNode = null;
        let virHead = newFirst;

        //为了避免无限循环,先把循环链表断开
        head.prev = null, last.next = null;

        while(virHead.next){
            if (virHead.next.element == element){

                delNode = virHead.next;

                if (virHead.next.next){
                    virHead.next.next.prev = virHead.next.prev;
                }
                else{
                    last = virHead.next.prev;
                }

                if (virHead.next == current){
                    current = current.next;
                    virHead.next = current;
                }
                else{
                    virHead.next = virHead.next.next;
                }

                size--;
            }
            else{
                virHead = virHead.next;
            }
        }

        //重新赋值
        head = newFirst.next;
        last = size > 0 ? node(size-1) : null;
        if (size > 0){
            last.next = head;
            head.prev = last;
        }
        else{
            current = head = last = null;
        }

        return delNode.element;
    }

    //获取某个元素
    function get(index){
        return node(index).element;
    }

    //获取元素索引
    function indexOf(element){
        let obj = head, index = -1;

        for (let i = 0; i < size; i++){
            if (obj.element == element){
                index = i;
                break;
            }
            obj = obj.next;
        }
        return index;
    }

    //清除所有元素
    function clear(){
        current = head = last = null;
        size = 0;
    }

    //这次新增加的三个方法next, removeCurrent, reset
    //调用重置current指向head节点
    function reset(){
        current = head;
    }
    function next(){
        if (!current) return null;

        current = current.next;
        return current.element;
    }

    //每调用一次,删除current指向的节点
    function removeCurrent(){
        if (size < 1) return null;
        return removeNode(current);
    }

    //属性转字符串
    function getObjString(obj){

        let str = "";

        if (obj instanceof Array){
            str += "[";
            for (let i = 0; i < obj.length; i++){
                str += getObjString(obj[i]);
            }
            str = str.substring(0, str.length - 2);
            str += "], "
        }
        else if (obj instanceof Object){
            str += "{";
            for (var key in obj){
                let item = obj[key];
                str += "\"" + key + "\": " + getObjString(item);
            }
            str = str.substring(0, str.length-2);
            str += "}, "
        }
        else if (typeof obj == "string"){
            str += "\"" + obj + "\"" + ", ";
        }
        else{
            str += obj + ", ";
        }

        return str;
    }
    function toString(){
        let str = "", obj = head;
        for (let i = 0; i < size; i++){
            str += getObjString(obj.element);
            obj = obj.next;
        }
        if (str.length > 0) str = str.substring(0, str.length -2);
        return str;
    }
    //打印所有元素
    function print(){
        console.log(this.toString())
    }

    //对外公开方法
    this.append = append;
    this.insert = insert;
    this.remove = remove;
    this.removeAt = removeAt;
    this.removeAll = removeAll;
    this.set = set;
    this.get = get;
    this.indexOf = indexOf;
    this.length = function(){
        return size;
    }
    this.clear = clear;
    this.print = print;
    this.toString = toString;

    //新增加的方法
    this.next = next;
    this.removeCurrent = removeCurrent;
    this.reset = reset;
}

////测试
// let obj = new CircleLinkedListDouble();
// let obj1 = { title: "全明星比赛", stores: [{name: "张飞vs岳飞", store: "2:3"}, { name: "关羽vs秦琼", store: "5:5"}]};
//
// obj.append(99);
// obj.append("hello")
// obj.append(true)
// obj.insert(3, obj1);
// obj.insert(0, [12, false, "Good", 81]);
// obj.print();
// console.log("obj1.index: ", obj.indexOf(obj1));
// obj.remove(0);
// obj.removeAll(obj1);
// obj.print();

////测试2
// console.log("\n\n......test2.....")
// var obj2 = new CircleLinkedListDouble();
// obj2.append(8); obj2.append(99); obj2.append(‘abc‘); obj2.append(8); obj2.append(false);
// obj2.append(12); obj2.append(8); obj2.append(‘123‘); obj2.append(8);
// obj2.print();
// obj2.removeAll(8); //删除所有8
// obj2.print();

/**
 * 测试3,来做一个有意思的题目:约瑟夫题目
 据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,
 39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,
 由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。
 然而Josephus 和他的朋友并不想遵从。
 首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。
 接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。
 问题是,给定了和,一开始要站在什么地方才能避免被处决?
 Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

 用循环链表来模拟这个游戏
 */
console.log("\n\n........test3约瑟夫题目。。。....");
var obj3 = new CircleLinkedListDouble();
for (let i = 0; i < 41; i++){
    obj3.append(i+1);
}
obj3.print();
console.log("*************约瑟夫游戏开始**********")
for (let i = 0; i < 39; i++){
    obj3.next(); obj3.next(); //移动两次
    obj3.removeCurrent(); //删除当前节点
    console.log("第", (i+1), "次,剩余的为:", obj3.toString())
}
console.log("***************game over***************")
console.log("最后生存下来的是:", obj3.toString());

看下用链表模拟约瑟夫问题过程的效果:

其余单向循环链表、单向循环链表增强、双向循环链表等代码Demo见github地址:https://github.com/xiaotanit/Tan_DataStruct

原文地址:https://www.cnblogs.com/tandaxia/p/11085985.html

时间: 2024-11-05 16:33:11

JS数据结构第三篇---双向链表和循环链表的相关文章

数据结构与算法之六 双向链表和循环链表

在本章中,你将学习: 执行双链接列表 执行循环链接列表 应用链接列表以解决编程问题 现在,考虑一个示例,您需要以降序的方式显示这些数字. 如何解决此问题? 每一个节点链接到序列中的下一个节点,这意味着您只能以正向遍历列表,这样的链接列表称为单链接列表.要以降序的方式显示数字,您需要反转此链接列表. 运用算法以反转单链接列表. 1.声明三个变量/ 指针, ptr1. ptr2和 ptr 3. 2. 2.如果列表中仅有一个节点: 3. a.退出. 3. 3.使列表中的第一个节点为 ptr1. 4.

数据结构第三篇——线性表的链式存储之单链表

线性表的链式存储结构的特点是用一组任意的存储单元来存储线性表的数据元素,这些单元可以分散在内存中的任意位置上,其在物理上可以是连续的,也可以是不连续的.具有链式存储结构的线性表称为线性链表. 为了表示出每个数据元素与其后继之间的关系,除了存储数据元素本身的信息之外,还需存储指示其直接后继的信息.这可以用一个结点(node)来完整的表示. 将节点中存储数据元素本身信息的域称为数据域:存储其直接后继位置的域称为指针域.指针域中存储的信息称作指针或链. 一般情况下,链表中每个结点可以包含若干个数据域和

JS数据结构第六篇 --- 二叉树力扣练习题

1.第226题:翻转二叉树 递归+迭代两种实现方式: /** 反转二叉树 * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ /** * @param {TreeNode} root * @return {TreeNode} * 第一种方式迭代 * 执行用时 :72 ms, 在所有 JavaScript 提

jquery jtemplates.js模板渲染引擎的详细用法第三篇

jquery jtemplates.js模板渲染引擎的详细用法第三篇 <span style="font-family:Microsoft YaHei;font-size:14px;"><!doctype html> <html lang="zh-CN"> <head> <meta http-equiv="Content-Type" content="text/html; chars

数据结构8: 双向链表(双向循环链表)的建立及C语言实现

之前接触到的链表都只有一个指针,指向直接后继,整个链表只能单方向从表头访问到表尾,这种结构的链表统称为 “单向链表”或“单链表”. 如果算法中需要频繁地找某结点的前趋结点,单链表的解决方式是遍历整个链表,增加算法的时间复杂度,影响整体效率.为了快速便捷地解决这类问题,在单向链表的基础上,给各个结点额外配备一个指针变量,用于指向每个结点的直接前趋元素.这样的链表被称为“双向链表”或者“双链表”. 双链表中的结点 双向链表中的结点有两个指针域,一个指向直接前趋,一个指向直接后继.(链表中第一个结点的

数据结构(三):非线性逻辑结构-特殊的二叉树结构:堆、哈夫曼树、二叉搜索树、平衡二叉搜索树、红黑树、线索二叉树

在上一篇数据结构的博文<数据结构(三):非线性逻辑结构-二叉树>中已经对二叉树的概念.遍历等基本的概念和操作进行了介绍.本篇博文主要介绍几个特殊的二叉树,堆.哈夫曼树.二叉搜索树.平衡二叉搜索树.红黑树.线索二叉树,它们在解决实际问题中有着非常重要的应用.本文主要从概念和一些基本操作上进行分类和总结. 一.概念总揽 (1) 堆 堆(heap order)是一种特殊的表,如果将它看做是一颗完全二叉树的层次序列,那么它具有如下的性质:每个节点的值都不大于其孩子的值,或每个节点的值都不小于其孩子的值

我的屌丝giser成长记-研三篇

进入研三以来,基本都是自己的自由时间了,从导师的项目抽离出来,慢慢的都交给师弟他们来负责.研三的核心任务就是找工作以及写毕业论文,因为有导师科研基金项目成果作为支撑,所以自己的论文没什么可担心,一切都是水到渠成.研二假期时候,自己有在猪八戒网或者其他渠道,接过一些小的gis单子来做,当是生活补贴也好,进入研三以来,就寻思着怎么组成一个gis开发团队,通过团队接一些大一点的gis项目,还有就是磨练团队的合作意识以及默契,长远的来说,要是拓展业务开了话,慢慢的往工作室甚至开gis公司的方向发展. 第

Django框架之第三篇模板语法

Django框架之第三篇模板语法(重要!!!) 一.什么是模板? 只要是在html里面有模板语法就不是html文件了,这样的文件就叫做模板. 二.模板语法分类 一.模板语法之变量:语法为 {{ }}: 在 Django 模板中遍历复杂数据结构的关键是句点字符  .(也就是点) views.py def index(request): name = "hello haiyan" i = 200 l = [11,22,33,44,55] d = {"name":&quo

3. 蛤蟆的数据结构进阶三静态查询之折半查询

3. 蛤蟆的数据结构进阶三静态查询之折半查询 本篇名言:"但是话不行,要紧的是做. --鲁迅" 继续来看静态查询的折半查询. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/47211637 1.  折半查找 折半查找要求查找表用顺序存储结构存放且各数据元素按关键字有序(升序或隆序)排列,也就是说折半查找只适用于对有序顺序表进行查找. 折半查找的基本思想是:首先以整个查找表作为查找范围,用查找条件中给定值k与中间位置