JavaScript中常见数据结构

Posted 圆圆测试日记

tags:

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

数据结构

  • :一种遵从先进后出 (LIFO) 原则的有序集合;新添加的或待删除的元素都保存在栈的末尾,称作栈顶,另一端为栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。
  • 队列:与上相反,一种遵循先进先出 (FIFO / First In First Out) 原则的一组有序的项;队列在尾部添加新元素,并从头部移除元素。最新添加的元素必须排在队列的末尾。
  • 链表:存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的;每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(指针/链接)组成。
  • 集合:由一组无序且唯一(即不能重复)的项组成;这个数据结构使用了与有限集合相同的数学概念,但应用在计算机科学的数据结构中。
  • 字典:以 [键,值] 对为数据形态的数据结构,其中键名用来查询特定元素,类似于 javascript 中的Object
  • 散列:根据关键码值(Key value)直接进行访问的数据结构;它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度;这个映射函数叫做散列函数,存放记录的数组叫做散列表。
  • :由 n(n>=1)个有限节点组成一个具有层次关系的集合;把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的,基本呈一对多关系,树也可以看做是图的特殊形式。
  • :图是网络结构的抽象模型;图是一组由边连接的节点(顶点);任何二元关系都可以用图来表示,常见的比如:道路图、关系图,呈多对多关系。

 

 

生活中常见的Stack的例子比如一摞书,你最后放上去的那本你之后会最先拿走;又比如浏览器的访问历史,当点击返回按钮,最后访问的网站最先从历史记录中弹出。

Stack一般具备以下方法:

  1. push:将一个元素推入栈顶
  2. pop:移除栈顶元素,并返回被移除的元素
  3. peek:返回栈顶元素
  4. isEmpty(): 栈中是否有元素
  5. clear(): 移除栈中所有元素
  6. size:返回栈中元素的个数

以对象形式实现栈

    <script type="text/javascript">
        function Stack() {
            this.count = 0;
            this.storage = {};
            //将一个元素推入栈顶
            this.push = function (value) {
                this.storage[this.count] = value;
                this.count++;
            }
            //移除栈顶元素,并返回被移除的元素
            this.pop = function () {
                if (this.count === 0) {
                    return undefined;
                }
                this.count--;
                var result = this.storage[this.count];
                delete this.storage[this.count];
                return result;
            }
            //返回栈顶元素
            this.peek = function () {
                return this.storage[this.count - 1];
            }
            //栈中是否有元素
            this.isEmpty = function () {
                //使用es6语法判断对象中属性长度
                //return Object.keys(this.storage).length==0;
                return this.count==0;
            }
            //移除栈中所有元素
            this.clear = function () {
                this.count = 0
                //return this.storage={};
            }
            //返回栈中元素的个数
            this.size = function () {
                return this.count;
            }
        }


        var newStack = new Stack();
        newStack.push("第一个元素");
        newStack.push("第二个元素");
        newStack.push("第三个元素");
        console.log("打印栈中元素个数:" + newStack.size());
        console.log("打印栈中栈顶元素:" + newStack.peek());
        console.log("打印栈中移除元素:" + newStack.pop());
        console.log("移除栈顶元素后再次打印栈中栈顶元素:" + newStack.peek());
        console.log("判断栈中是否有元素:" + newStack.isEmpty());
        console.log("移除栈中所有元素:" + newStack.clear());
        console.log("移除后判断栈中是否有元素:" + newStack.isEmpty());
        console.log("打印栈中移除元素:" + newStack.pop());
    </script>

以数组形式实现栈

    <script type="text/javascript">
        function Stack() {
            //保存栈内元素的数组
            this.dataStore = [];
            //top用来记录栈顶位置,初始化为0        
            this.top = 0;

            this.push = function (element) {
                this.dataStore[this.top++] = element;   // 先在top位置加入元素,之后top加1
            }
            this.pop = function () {
                // top先减1,然后返回top位置的元素
                return this.dataStore[--this.top];
            }
            this.peek = function peek() {
                return this.dataStore[this.top - 1];
            }

            
            this.isEmpty = function clear() {
                return this.top ==0;
            }

            this.clear = function clear() {
                this.top = 0;
            }

            this.length = function length() {
                return this.top;
            }
        }
        var newStack = new Stack();
        newStack.push("第一个元素");
        newStack.push("第二个元素");
        newStack.push("第三个元素");
        console.log("打印栈中元素个数:" + newStack.length());
        console.log("打印栈中栈顶元素:" + newStack.peek());
        console.log("打印栈中移除元素:" + newStack.pop());
        console.log("移除栈顶元素后再次打印栈中栈顶元素:" + newStack.peek());
        console.log("判断栈中是否有元素:" + newStack.isEmpty());
        console.log("移除栈中所有元素:" + newStack.clear());
        console.log("移除后判断栈中是否有元素:" + newStack.isEmpty());
    </script>

 Queue(队列)

 

Queue和Stack有一些类似,不同的是Stack是先进后出,而Queue是先进先出。Queue在生活中的例子比如排队上公交,排在第一个的总是最先上车;又比如打印机的打印队列,排在前面的最先打印。

Queue一般具有以下常见方法:

  1. enqueue:入列,向队列尾部增加一个元素
  2. dequeue:出列,移除队列头部的一个元素并返回被移除的元素
  3. front:获取队列的第一个元素
  4. isEmpty:判断队列是否为空
  5. size:获取队列中元素的个数

Javascript中的Array已经具备了Queue的一些特性,所以我们可以借助Array实现一个Queue类型:

    <script type="text/javascript">
        function Queue() {
            var collection = [];
            //返回队列所有元素
            this.print = function () {
                return collection;
            }
            //入列,向队列尾部增加一个元素
            this.enqueue = function (element) {
                collection.push(element);
            }
            //出列,移除队列头部的一个元素并返回被移除的元素
            this.dequeue = function () {
                return collection.shift();
            }
            //获取队列的第一个元素
            this.front = function () {
                return collection[0];
            }
            //判断队列是否为空
            this.isEmpty = function () {
                return collection.length === 0;
            }
            //清除队列
            this.clear = function () {
                items = [];
            };
            //获取队列中元素的个数
            this.size = function () {
                return collection.length;
            }
        }
        var newQueue= new Queue();
        newQueue.enqueue("第一个元素");
        newQueue.enqueue("第二个元素");
        newQueue.enqueue("第三个元素");
        console.log("打印队列中元素个数:" + newQueue.size());
        console.log("打印队列中第一个元素:" + newQueue.front());
        console.log("打印队列中所有元素:" + newQueue.print());
        console.log("打印队列中移除元素:" + newQueue.dequeue());
        console.log("移除队列元素后再次打印队列中所有元素:" + newQueue.print());
        console.log("判断队列中是否有元素:" + newQueue.isEmpty());
        console.log("移除队列中所有元素:" + newQueue.clear());
        console.log("移除后判断队列中是否有元素:" + newQueue.isEmpty());
    </script>

 Priority Queue(优先队列)

Queue还有个升级版本,给每个元素赋予优先级,优先级高的元素入列时将排到低优先级元素之前。区别主要是enqueue方法的实现:

    <script type="text/javascript">
        function PriorityQueue() {
            var collection = [];
            //返回队列所有元素
            this.print = function () {
                return collection;
            }
            //入列,向队列尾部增加一个元素
            this.enqueue = function (element) {
                collection.push(element);
            }
            //出列,移除队列头部的一个元素并返回被移除的元素
            this.dequeue = function () {
                return collection.shift();
            }
            //获取队列的第一个元素
            this.front = function () {
                return collection[0];
            }
            //判断队列是否为空
            this.isEmpty = function () {
                return collection.length === 0;
            }
            //清除队列
            this.clear = function () {
                items = [];
            };
            //获取队列中元素的个数
            this.size = function () {
                return collection.length;
            }
            //优先队列
            this.priorityEnqueue = function (element) {
                if (this.isEmpty()) {
                    collection.push(element);
                } else {
                    var added = false;
                    for (var i = 0; i < collection.length; i++) {
                        if (element[1] < collection[i][1]) {
                            collection.splice(i, 0, element);
                            added = true;
                            break;
                        }
                    }
                    if (!added) {
                        collection.push(element);
                    }
                }
            }
        }
        var newQueue = new PriorityQueue();
        newQueue.enqueue("第一个元素");
        newQueue.enqueue("第二个元素");
        newQueue.enqueue("第三个元素");
        console.log("打印队列中元素个数:" + newQueue.size());
        console.log("打印队列中第一个元素:" + newQueue.front());
        console.log("打印队列中所有元素:" + newQueue.print());
        console.log("打印队列中移除元素:" + newQueue.dequeue());
        console.log("移除队列元素后再次打印队列中所有元素:" + newQueue.print());
        console.log("判断队列中是否有元素:" + newQueue.isEmpty());
        console.log("移除队列中所有元素:" + newQueue.clear());
        console.log("移除后判断队列中是否有元素:" + newQueue.isEmpty());

        newQueue.priorityEnqueue([\'gannicus\', 3]);
        newQueue.priorityEnqueue([\'spartacus\', 1]);
        newQueue.priorityEnqueue([\'crixus\', 2]);
        newQueue.priorityEnqueue([\'oenomaus\', 4]);

        console.log("优先队列中所有元素:" + newQueue.print());

    </script>

 Linked List(链表)

链表是一种链式数据结构,链上的每个节点包含两种信息:节点本身的数据和指向下一个节点的指针。链表和传统的数组都是线性的数据结构,存储的都是一个序列的数据,但也有很多区别,如下表:

比较维度数组链表
内存分配 静态内存分配,编译时分配且连续 动态内存分配,运行时分配且不连续
元素获取 通过Index获取,速度较快 通过遍历顺序访问,速度较慢
添加删除元素 因为内存位置连续且固定,速度较慢 因为内存分配灵活,只有一个开销步骤,速度更快
空间结构 可以是一维或者多维数组 可以是单向、双向或者循环链表

一个单向链表通常具有以下方法:

  1. size:返回链表中节点的个数
  2. head:返回链表中的头部元素
  3. add:向链表尾部增加一个节点
  4. remove:删除某个节点
  5. indexOf:返回某个节点的index
  6. elementAt:返回某个index处的节点
  7. addAt:在某个index处插入一个节点
  8. removeAt:删除某个index处的节点

单向链表的Javascript实现:

    <script type="text/javascript">
        /**
     * 链表中的节点 
     */
        function Node(element) {
            // 节点中的数据
            this.element = element;
            // 指向下一个节点的指针
            this.next = null;
        }

        function LinkedList() {
            var length = 0;
            var head = null;
            //返回链表中节点的个数
            this.size = function () {
                return length;
            }
            //返回链表中的头部元素
            this.head = function () {
                return head;
            }
            //向链表尾部增加一个节点
            this.add = function (element) {
                var node = new Node(element);
                if (head == null) {
                    head = node;
                } else {
                    var currentNode = head;

                    while (currentNode.next) {
                        currentNode = currentNode.next;
                    }

                    currentNode.next = node;
                }
                length++;
            }
            //删除某个节点
            this.remove = function (element) {
                var currentNode = head;
                var previousNode;
                if (currentNode.element === element) {
                    head = currentNode.next;
                } else {
                    while (currentNode.element !== element) {
                        previousNode = currentNode;
                        currentNode = currentNode.next;
                    }
                    previousNode.next = currentNode.next;
                }
                length--;
            }

            this.isEmpty = function () {
                return length === 0;
            }

            //返回链表所有节点
            this.value = function () {
                var currentNode = head;
                var nodeList = [];
                if (currentNode==null) {
                    return undefined
                } else {
                    while (currentNode.next) {
                        nodeList.push(currentNode.element);
                        currentNode = currentNode.next;
                    }
                    nodeList.push(currentNode.element);
                }
                return nodeList;
            }

            //返回某个节点的index
            this.indexOf = function (element) {
                var currentNode = head;
                var index = -1;
                while (currentNode) {
                    index++;
                    if (currentNode.element === element) {
                        return index;
                    }
                    currentNode = currentNode.next;
                }

                return -1;
            }
            //返回某个index处的节点
            this.elementAt = function (index) {
                var currentNode = head;
                var count = 0;
                while (count < index) {
                    count++;
                    currentNode = currentNode.next;
                }
                return currentNode.element;
            }
            //在某个index处插入一个节点
            this.addAt = function (index, element) {
                var node = new Node(element);
                var currentNode = head;
                var previousNode;
                var currentIndex = 0;

                if (index > length) {
                    return false;
                }

                if (index === 0) {
                    node.next = currentNode;
                    head = node;
                } else {
                    while (currentIndex < index) {
                        currentIndex++;
                        previousNode = currentNode;
                        currentNode = currentNode.next;
                    }
                    node.next = currentNode;
                    previousNode.next = node;
                }
                length++;
            }
            //删除某个index处的节点
            this.removeAt = function (index) {
                var currentNode = head;
                var previousNode;
                var currentIndex = 0;
                if (index < 0 || index >= length) {
                    return null;
                }
                if (index === 0) {
                    head = currentIndex.next;
                } else {
                    while (currentIndex < index) {
                        currentIndex++;
                        previousNode = currentNode;
                        currentNode = currentNode.next;
                    }
                    previousNode.next = currentNode.next;
                }
                length--;
                return currentNode.element;
            }
        }

        var newLinkedList = new LinkedList();
        newLinkedList.indexOf("第一个节点");
        newLinkedList.add("第一个节点");
        newLinkedList.add("第二个节点");
        newLinkedList.add("第三个节点");
        newLinkedList.add("第四个节点");
        newLinkedList.add("第五个节点");
        newLinkedList.add("第六个节点");
        newLinkedList.add("第七个节点");
        newLinkedList.add("第八个节点");
        console.log("打印链表中所有元素:" + newLinkedList.value());
        newLinkedList.remove("第一个节点");
        newLinkedList.remove("第三个节点");
        console.log("打印删除后链表中所有元素:" + newLinkedList.value());
    </script>

单链表

talk is easy, show the code, 下面用 JavaScript 实现一个相对完整的单链表,并提供以下方法供调用:

  1. push(element) // 链表尾部插入节点
  2. pop() // 链表尾部删除节点
  3. shift() // 删除头部节点、
  4. unshift(element) // 插入头部节点
  5. find(index) // 查找指定位置节点
  6. insert(element, index) // 指定位置插入节点
  7. edit(element, index) // 修改指定位置节点
  8. delete(index) // 链表删除指定位置节点
  9. cycle() // 使链表首尾成环
function initList() {
    class Node {
        constructor(item) {
            this.element = item
        }
    }
    class List {
        constructor() {
            this.head = null
            this.size = 0
            this.last = null
        }
        /**
        * 链表查找元素
        * @param index 查找的位置
        */
        find(index) {
            let current = this.head
            for (let i = 0; i < index; i++) {
                current = current.next
            }
            return current
        }
        /**
        * 链表尾部插入元素
        * @param element 插入的元素
        */
        push(element) {
            let newNode = new Node(element)
            if (this.size === 0) {
                this.head = newNode
                this.head.next = null
                this.last = this.head
            } else {
                this.last.next = newNode
                this.last = newNode
                newNode.next = null
            }
            this.size += 1
        }
        /**
        * 链表尾部删除元素
        * @param element 删除的位置
        */
        pop(element) {
            this.last.next = null
        }
        /**
        * 链表头部删除元素
        */
        shift() {
            if (this.size === 0)
                return
            this.head = this.head.next
            if (this.size === 1)
                this.last = null
            this.size -= 1
        }
        /**
        * 链表头部插入元素
        * @param element 插入的元素
        */
        unshift(element) {
            let newNode = new Node(element)
            newNode.next = this.head
            this.head = newNode
            if (this.size === 0)
                this.last = this.head
            this.size += 1
        }
        /**
        * 链表插入元素
        * @param element 插入的位置, index 插入的位置
        */
        insert(element, index) {
            if (index < 0 || index > this.size) {
                console.error(\'超出链表节点范围\')
                return
            }
            let newNode = new Node(element)
            if (this.size === 0) {
                // 空链表
                newNode.next = null
                this.head = newNode
                this.last = newNode
            } else if (index === 0) {
                // 插入头部
                newNode.next = this.head
                this.head = newNode
            } else if (index == this.size) {
                //插入尾部
                newNode.next = null
                this.last.next = newNode
                this.last = newNode
            } else {
                // 中间插入
                let preNode = this.find(index - 1)
                newNode.next = preNode.next
                preNode.next = newNode
            }
            this.size += 1
        }
        /*
        *链表编辑元素
        * @param element 编辑的元素,index 元素位置
        */
        edit(element, index) {
            let current = this.find(index)
            current.element = element
        }
        /*
        *链表删除元素
        * @param index 删除元素位置
        */
        delete(index) {
            let current = this.find(index)
            if (index === 0) {
                // 删除头节点
                this.head = this.head.next
            } else if (index === ((this.size) - 1)) {
                // 删除尾节点
                let preNode = this.find(index - 1)
                preNode.next = null
            } else {
                // 删除中间节点
                let preNode = this.find(index - 1)
                let nextNode = preNode.next.next
                let removeNode = preNode.next
                preNode.next = nextNode
            }
            this.size -= 1
        }
        /*
        *链表使首尾成环
        */
        cycle() {
            this.last.next = this.head
        }
    }
    return new List()
}

let list = initList()

双向链表

双向链表的特点就是添加了指向上一个节点的指针(prev),比较单链表来说,稍微复杂一些,也更强大,这里把上面的单链表修改一下。

function initList() {
    class Node {
        constructor(item) {
            this.element = item
            this.next = null
            this.prev = null
        }
    }
    class List {
        constructor() {
            this.head = null
            this.size = 0
            this.last = null
        }
        /**
        * 链表查找元素
        * @param index 查找的位置
        */
        find(index) {
            let current = this.head
            for (let i = 0; i < index; i++) {
                current = current.next
            }
            return current
        }
        /**
        * 链表尾部插入元素
        * @param element 插入的元素
        */
        push(element) {
            let newNode = new Node(element)
            if (this.size === 0) {
                this.head = newNode
                this.head.next = null
                this.last = this.head
            } else {
                this.last.next = newNode
                newNode.next = null
                newNode.prev = this.last
                this.last = newNode
            }
            this.size += 1
        }
        /*以上是关于JavaScript中常见数据结构的主要内容,如果未能解决你的问题,请参考以下文章

你可能不知道的JavaScript代码片段和技巧(下)

你可能不知道的JavaScript代码片段和技巧(上)

代码片段使用复杂的 JavaScript 在 UIWebView 中插入 HTML?

常见的代码片段

16个必备的JavaScript代码片段

Node.js JavaScript 片段中的跳过代码