数据结构与算法--链表(Linked list)--JS

Posted 煜成'Studio

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法--链表(Linked list)--JS相关的知识,希望对你有一定的参考价值。

数据结构与算法–链表(Linked list)–JS

数组的缺点:

1.在创建时通常需要一段连续的内存空间,并且大小是固定的,当数组不满足容量需求时,需要扩容(一般会申请更大的数组,将原数组拷贝过去)
2.在数组开头或者中间位置出入数据时成本很高,需要进行大量元素的移动,

链表:

链表内的元素在内存中不必是连续的空间,链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(指针或连接)组成

链表的优势:

1.内存空间不是必须连续的,可以充分利用计算机内存灵活地内存动态管理;
2.不必在创建时确定大小,大小可以无限延伸下去
3.链表在插入和删除数据时,时间复杂度为O(1),相对数组效率更高,(数组是通过下标值获取或修改元素性能更高)

链表的缺点:

1.访问任何一个元素时,都需要从头开始访问
2.无法直接通过下标访问元素,需要从头一个一个访问,直到找到对应的元素

//封装链表类
function LinkedList() {
    //内部的类:节点类
    function Node(data) {
        this.data = data;
        this.next = null;
    }
    //属性
    this.head = null;
    this.length = 0;

    //append(element):向列表尾部添加一个新的项
    //追加方法
    LinkedList.prototype.append = function (data) {
        //创建新的节点
        var newNode = new Node(data);
        //判断是否添加的是第一个节点
        if (this.length == 0) { //是第一个节点
            this.head = newNode;
        } else { //不是第一个节点
            //找到最后一个节点
            var current = this.head;
            while (current.next) {
                current = current.next;
            }
            //最后节点的next指向新节点
            current.next = newNode;
        }
        //别忘了,长度值加一
        this.length += 1;
    }

    //insert(position, element):向列表的特定位置插入一个新的项
    LinkedList.prototype.insert = function (position, data) {
        //对position进行越界判断
        if (position < 0 || position > this.length) return false;
        //根据data创建newNode
        var newNode = new Node(data);
        //判断插入的位置是否是第一个
        if (position == 0) {
            newNode.next = this.head;
            this.head = newNode;
        } else { //在最后的位置插入的情况也包含在内了
            var index = 0;
            //两个参考的位置,一个是当前位置,一个是当前位置的前一位
            var current = this.head;
            var previous = null;
            while (index++ < position) {
                previous = current;
                current = current.next;
            }
            newNode.next = current;
            previous.next = newNode;
        }
        this.length +=1;
        return true;
    }

    //get(position):获取对应位置的元素
    LinkedList.prototype.get = function (position) {
        //越界判断
        if (position < 0 || position >= this.length) return null;
        //获取对应的data
        var current = this.head;
        var index = 0;
        while (index++ < position) {
            current = current.next;
        }
        return current.data;
    }

    //indexOf(element):返回元素在列表中的索引,如果没有返回-1
    LinkedList.prototype.indexOf = function (data) {
        var current = this.head;
        var index = 0;
        while (current) {
            if (current.data == data) {
                return index;
            }
            current = current.next;
            index +=1;
        }
        return -1;
    }

    //update(position, element):修改某个位置的元素
    LinkedList.prototype.update = function (position, newData) {
        //越界判断
        if (position < 0 || position >= this.length) return false;
        //查找正确的节点
        var current = this.head;
        var index = 0;
        while (index++ < position) {
            current = current.next;
        }
        //将position位置的node的data修改为newData
        current.data = newData;
        return true;
    }

    //removeAt(position):从列表的特定位置移除一项
    LinkedList.prototype.removeAt = function (position) {
        //越界判断
        if (position < 0 || position >= this.length) return null;
        //判断是否删除的是第一个节点
        var current = this.head;
        if (position == 0) {
            this.head = this.head.next;
        } else {
            var index = 0;
            var previous = null;
            while (index++ < position) {
                previous = current;
                current = current.next;
            }
            //前一个节点的next指向current的next即可
            previous.next = current.next;
        }
        //长度值别忘记修改
        this.length -= 1;
        return current.data;
    }
    //remove(element):从列表中移除一项
    LinkedList.prototype.remove = function (data) {
        //获取data在列表中的位置
        var position = this.indexOf(data);
        //根据位置信息,删除节点
        return this.removeAt(position);
    }

    //isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false
    LinkedList.prototype.isEmpty = function () {
        return this.length == 0;
    }

    //size():返回链表包含的元素个数,与数组的length属性类似
    LinkedList.prototype.size = function () {
        return this.length;
    }
    
    //toString():由于列表项使用了Node类,就需要重写继承自javascript对象默认的toString方法,让其只输出元素的值
    LinkedList.prototype.toString = function () {
        //定义变量
        var current = this.head;
        var listString = "";
        //循环获取一个个的节点
        while (current) {
            listString += current.data + " ";
            current = current.next;
        }
        return listString;
    }
}
//测试代码
//创建LinkedList
var list = new LinkedList();
//测试append方法
list.append("abc");
list.append("aba");
alert(list);
// 测试insert
list.insert(1, 'aaa');
alert(list);
//测试get
alert(list.get(0));
//测试indexOf
alert(list.indexOf('abc'));
//测试updata
list.update(0, 'mmm');
alert(list);
//测试removeAt
list.removeAt(0);
alert(list);
//测试remove
list.remove('aaa');
alert(list);
//测试isEmpty和size
alert(list.isEmpty());
alert(list.size());

单向链表的缺点:

能轻松到达下一点,但是很难回到前一个节点

双向链表:一个节点既有向前连接的引用,也有一个向后连接的引用

优点:既可以从头遍历到尾,也可以从尾遍历到头
缺点:每次在插入或删除某个节点时,需要处理四个引用,而不是两个,相对于单向链表,占用内存空间更大

双向链表的特点:

1.可以使用一个head和一个tail分别指向头部和尾部的节点
2.每个节点都由三部分组成:前一个节点的指针(prev)、保存的元素(item)、后一个节点的指针(next)
3.双向链表的第一个节点的prev是null
4.双向链表的最后的节点的next是null

//封装双向链表
function DoublyLinkedList() {
    //内部类:节点类
    function Node(data) {
        this.data = data;
        this.prev = null;
        this.next = null;
    }
    //属性
    this.head = null;
    this.tail = null;
    this.length = 0;
    //常见的操作:方法
    
    //追加方法append
    DoublyLinkedList.prototype.append = function (data) {
        //创建新的节点
        var newNode = new Node(data);
        //判断是否添加的是第一个节点
        if (this.length == 0) { //是第一个节点
            this.head = newNode;
            this.tail = newNode;
        } else { //不是第一个节点
            newNode.prev = this.tail;
            this.tail.next = newNode;
            this.tail = newNode;
        }
        //长度加一
        this.length += 1;
    }

    //将链表转成字符串形式
    //1.toString方法
    DoublyLinkedList.prototype.toString = function () {
        return this.backwardString();
    }

    //2.forwardString方法,向前遍历
    DoublyLinkedList.prototype.forwardString = function () {
        //定义变量
        var current = this.tail;
        var resultString = "";
        //依次向前遍历,获取每一个节点
        while (current) {
            resultString += current.data + " ";
            current = current.prev;
        }
        return resultString;
    }

    //3.backwardString方法,向后遍历
    DoublyLinkedList.prototype.backwardString = function () {
        //定义变量
        var current = this.head;
        var resultString = "";
        //依次向后遍历,获取每一个节点
        while (current) {
            resultString += current.data + " ";
            current = current.next;
        }
        return resultString;
    }

    //insert方法
    DoublyLinkedList.prototype.insert = function (position, data) {
        //越界判断
        if (position < 0 || position > this.length) return false;
        //根据data创建新的节点
        var newNode = new Node(data);
        //判断原来的列表是否是空
        if (this.length == 0) {
            this.head = newNode;
            this.tail = newNode;
        } else {
            //判断position是否为0
            if (position == 0) { //插入位置在最开始
                this.head.prev = newNode;
                newNode.next = this.head;
                this.head = newNode;
            } else if (position == this.length) { //插入位置在最后
                newNode.prev = this.tail;
                this.tail.next = newNode;
                this.tail = newNode;
            } else { //插入位置既不是最开始也不是最后
                var current = this.head;
                var index = 0;
                while (index++ < position) {
                    current = current.next;
                }
                //修改指针
                newNode.next = current;
                newNode.prev = current.prev;
                current.prev.next = newNode;
                current.prev = newNode;
            }
        }
        //length
        this.length += 1;
        return true;
    }

    //get方法
    DoublyLinkedList.prototype.get = function (position) {
        //越界判断
        if (position < 0 || position >= this.length) return null;
        //获取元素
        //判断所求位置离前面还是离后面更近
        if (this.length / 2 < position) {
            var current = this.head;
            var index = 0;
            while (index++ < position) {
                current = current.next;
            }
            return current.data;
        } else {
            var current = this.tail;
            var index = this.length - 1;
            while (index-- > position) {
                current = current.prev;
            }
            return current.data;
        }
    }

    //indexOf方法
    DoublyLinkedList.prototype.indexOf = function (data) {
        //定义变量
        var current = this.head;
        var index = 0;
        //查找和data相同的节点
        while (current) {
            if (current.data == data) {
                return index;
            }
            current = current.next;
            index += 1;
        }
        return -1;
    }

    //updata方法
    DoublyLinkedList.prototype.updata = function (position, newData) {
        //越界判断
        if (position < 0 || position >= this.length) return false;
        //寻找正确的节点
        //前后的判断
        if (position < this.length / 2) {
            var current = this.head;
            var index = 0;
            while (index++ < position) {
                current = current.next;
            }
        } else {
            var current = this.tail;
            var index = this.length - 1;
            //这里判断要注意
            while (index-- > position) {
                //current的指向别忘记写了
                current = current.prev;
            }
        }
        //修改找到节点的data的信息
        current.data = newData;
        return true;
    }

    //removeAt方法
    DoublyLinkedList.prototype.removeAt = function (position) {
        //越界判断
        if (position < 0 || position >= this.length) return null;
        //判断是否只有一个节点
        var current = this.head;
        if (this.length == 1) {
            this.head = null;
            this.tail = null;
        } else {
            //一个节点如果没有指向它的引用,它就会被回收,
            //不用管它是否有指向其他地方的引用
            if (position == 0) { //判断是否删除的是第一个节点
                this.head.next.prev = null;
                this.head = this.head.next;
            } else if (position == this.length - 1) { //最后一个节点
                current = this.tail;
                this.tail.prev.next = null;
                this.tail = this.tail.prev;
            } else {
                //判断前后
                if (position < this.length / 2) {
                    var index = 0;
                    while (index++ < position) {
                        current = current.next;
                    }
                    
                } else {
                    //current的指向别忘记写了
                    var current = this.tail;
                    var index = this.length - 1;
                    while (index-- > position) {
                        current = current.prev;
                    } 
                }
                current.prev.next = current.next;
                current.next.prev = current.prev;
            }
        }
        //长度
        this.length -= 1;
        return current.data;
    }

    //remove方法
    DoublyLinkedList.prototype.remove = function (data) {
        //根据data获取下标值
        var index = this.indexOf(data);
        //根据index删除对应位置的节点
        return this.removeAt(index);
    }

    //isEmpty方法
    DoublyLinkedList.prototype.isEmpty = function () {
        return this.length == 0;
    }

    //size方法
    DoublyLinkedList.prototype.size = function () {
        return this.length;
    }

    //获取链表的第一个元素
    DoublyLinkedList.prototype.getHead = function () {
        return this.head.data;
    }

    //获取链表的最后一个元素
    DoublyLinkedList.prototype.getTail = function () {
        return this.tail.data;
    }
}

//测试代码
var list = new DoublyLinkedList();
//测试append方法
list.append('abc');
list.append('bdc');
list.append('cba');
//测试转成字符串的方法
// alert(list);
// alert(list.backwardString());
// alert(list.forwardString());
//测试insert方法
list.insert(0, '000');
list.insert(4, '111');
list.insert(2, '333');
// alert(list);
//测试get方法
// alert(list.get(0));
// alert(list.get(3));
//测试indexOf
// alert(list.indexOf('000'));
//测试update
// alert(list);
list.updata(0, 'haha');
list.updata(4, 'haha');
// alert(list);
//测试removeAt
list.removeAt(0);
// alert(list);
list.removeAt(4);
// alert(list);
list.removeAt(2);
alert(list);
//测试remove
list.remove('333');
alert(list);
//测试isEmpty和size
alert(list.isEmpty());
alert(list.size());
//测试getHead和getTail
alert(list.getHead());
alert(list.getTail());

以上是关于数据结构与算法--链表(Linked list)--JS的主要内容,如果未能解决你的问题,请参考以下文章

算法:翻转链表206. Reverse Linked List

算法: 翻转链表 206. Reverse Linked List

链表的实现(Linked List)

算法分析如何理解快慢指针?判断linked list中是否有环找到环的起始节点位置。以Leetcode 141. Linked List Cycle, 142. Linked List Cycl(代码

Chapter six Linked List & Array(链表与数组)

九章算法系列(#5 Linked List)-课堂笔记