JavaScript数据结构与算法(2)链表、集合、字典

111 阅读5分钟

数据结构与算法

1、链表

特点:

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

优点:

  • 1、内存空间不是必须连续的,可以充分利用计算机的内存,实现灵活的内存动态管理
  • 2、链表不必创建时就确定大小,并且大小可以无限的延伸下去
  • 3、链表在插入和删除数据时,时间复杂度可以达到O(1),相对数组效率高很多 缺点:
  • 1、链表访问任何一个位置的元素时,都需要从头开始访问(无法跳过第一个元素访问任何元素)
  • 2、无法通过下标直接访问元素,需要从头一个个访问,直到找到元素

单向链表图解

image.png

链表的常用操作

  • 1、append(element):向列表尾部添加一个新的项
  • 2、insert(position,element):向链表特定位置插入一个新的项
  • 3、get(position):获取对应位置的元素
  • 4、indexOf(element):返回元素在列表中的索引,如果列表中没有该元素则返回-1
  • 5、update(position,element):修改某个位置的元素
  • 6、removeAt(position):删除列表的特定位置的一项
  • 7、remove(element):从列表中移除一项
  • 8、isEmpty():如果链表中不包含任何元素,返回true,否则返回false
  • 9、size():返回链表中包含元素的个数,与数组的length相似
  • 10、toString():由于链表中使用Node类,就需要重写继承自JavaScript的toString方法,让其只输出元素的值
  • 11、forwardString():返回正确遍历的节点字符串形式(双向链表
  • 12、backwordString():返回反向遍历的节点字符串形式(双向链表

单向链表的封装代码

function linkedList(){
	// 内部类:节点类
	function Node(data){
		this.data = data
		this.next = null
	}
	//属性
	this.head = null
	this.length = 0

	// 1、追加方法
	linkedList.prototype.append = function(data){
		// 1、创建新节点
		let newNode = new Node(data)

		// 2、判断添加的是否为第一个节点
		if(!this.length){			//2.1是第一个节点
			this.head = newNode 
		}else{						//2.2不是第一个节点
			//找到最后一个节点
			let current = this.head
			while (current.next) {
				current = current.next
			}
			// 最后的节点指向新的节点
			current.next = newNode
		}
		// 3、length+1
		this.length += 1
	}

	// 2、toString方法
	linkedList.prototype.toString = function(){
		// 1、定义一个变量
		let current = this.head
		let listString = ''
		// 2、循环获取一个个节点
		while (current) {
			listString += current.data + ' '
			current = current.next
		}
		return listString
	}

	// 3、insert方法
	linkedList.prototype.insert = function(position,data){
		// 1、对position进行越界判断 例如是负数 超过链表长度
		if(position < 0 || position > this.length){
			return false
		}

		// 2、根据data创建newNode
		let newNode = new Node(data)

		// 3、判断插入的位置是否为第一个
		if(position == 0){
			newNode.next = this.head
			this.head = newNode
		}else{
			let index = 0
			let current = this.head
			let pervious = null
			while (index++ < position) {
				pervious = current
				current = current.next
			}
			newNode.next = current
			pervious.next = newNode
		}

		// 4、length + 1
		this.length += 1
		return true
	}

	// 4、get方法
	linkedList.prototype.get =function(position){
		// 1、对position进行越界判断 例如是负数 超过链表长度
		if(position < 0 || position >= this.length){
			return null
		}
		// 2、获取对应的data
		let current = this.head
		let index = 0
		while (index++ < position) {
			current = current.next
		}
		return current.data
	}

	// 5、indexOf方法
	linkedList.prototype.indexOf =function(data){
		// 1、定义变量
		let current = this.head
		let index = 0

		//  2、开始查找
		while (current) {
			if(current.data == data){
				return index
			}
			current = current.next
			index += 1
		}

		//3、最后没找到返回-1
		return -1
	}

	// 6、update方法
	linkedList.prototype.update = function(position,newData){
		// 1、对position进行越界判断 例如是负数 超过链表长度
		if(position < 0 || position >= this.length){
			return false
		}
		// 2、先查找正确的节点
		let current = this.head
		let index = 0
		while (index++ < position) {
			current = current.next
		}

		current.data = newData
		return true
	}

	// 7、removeAt方法
	linkedList.prototype.removeAt = function(position){
		// 1、对position进行越界判断 例如是负数 超过链表长度
		if(position < 0 || position >= this.length){
			return false
		}
		let current = this.head
		//2、判断删除的是否为第一个节点
		if(position == 0){
			this.head = this.head.next
		}else{
			let index = 0
			let pervious = null
			while (index++ < position) {
				pervious = current
				current = current.next
			}
			//前一个节点的next指向current的next即可
			pervious.next = current.next
		}

		// 3、length-1
		this.length -= 1

		return current.data
	}

	//8、remove方法
	linkedList.prototype.remove = function(data){
		// 1、根据data获取在列表中的位置
		let position = this.indexOf(data)

		//2、根据位置信息删除节点
		return this.removeAt(position)
	}

	// 9、isEmpty方法
	linkedList.prototype.isEmpty = function(){
		return this.length == 0
	}
	// 10、size方法查看链表中元素个数
	linkedList.prototype.size = function(){
		return this.length
	}
}

单向链表的缺点

  • 1、只能从头到尾遍历,或者从尾到头遍历
  • 2、我们可以轻松的到达下一个节点,但是回到前一个节点是很难的,但是,在实际开发中,经常会遇到需要回到上一个节点的情况

双向链表的优缺点

优点

  • 1、即可从头到尾遍历,也可从尾到头遍历
  • 2、一个节点既有向前引用,也有向后引用 缺点
  • 1、每次在插入或者删除时,需要处理四个引用,而不是两个,实现起来困难一些
  • 2、相比于单向链表,必然占用更大的内存空间

双向链表图解

image.png

双向链表常用方法代码封装

function DoublyLinkedList(){
    //内部类:节点类
    function Node(data){
        this.data = data
        this.prev = null
        this.next = null
    }

    //属性
    this.head = null
    this.tail = null
    this.length = 0

    //常见的操作
    // 1、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
    }

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

    //2.2 forwardString方法
    DoublyLinkedList.prototype.forwardString = function(){
        let current = this.tail
        let resultString = ''

        // 2、依次向前遍历,获取每一个节点
        while(current){
            resultString += current.data + ' '
            current = current.prev
        }
        return resultString
    }

    //2.3 backwardString方法
    DoublyLinkedList.prototype.backwardString = function(){
        // 1、定义变量
        let current = this.head
        let resultString = ''

        // 2、依次向后遍历,获取每一个节点
        while(current){
            resultString += current.data + ' '
            current = current.next
        }

        return resultString

    }

    // 3、insert方法
    DoublyLinkedList.prototype.insert = function(position,data){
        // 1、越界判断
        if(position < 0 || position > this.length){
            return false
        }

        // 2、根据data创建新的节点
        let newNode = new Node(data)

        //3、判断链表是否为空
        if(this.length == 0){
            this.head = newNode
            this.tail = newNode 
        }else{
            if(position == 0){                      // 3.1判断position是否为0
                this.head.prev = newNode
                newNode.next = this.head
                this.head = newNode
            }else if(position == this.length){ // 3.2判断position是否为length
                newNode.prev = this.tail
                this.tail.next = newNode
                this.tail = newNode
            }else{
                let index = 0
                let current = this.head
                while(index++ < position){
                    current = current.next
                }
                //修改指针
                newNode.next = current
                newNode.prev = current.prev
                current.prev.next = newNode
                current.prev = newNode
            }
        }

        // 4、length + 1
        this.length += 1

    }

    // 4、get方法
	DoublyLinkedList.prototype.get =function(position){
		// 1、对position进行越界判断 例如是负数 超过链表长度
		if(position < 0 || position >= this.length){
			return null
		}
        let current = {}
		let index = 0
        if(this.length / 2 > position){ // this.length / 2 > position:从头往后遍历
            current = this.head
            while (index++ < position) {
                current = current.next
            }
        }else{       // this.length / 2 <= position:从后往前遍历
            current = this.tail
            index = this.length-1
            while (index-- > position) {
                current = current.prev
            }
        }
		// 2、获取对应的data
		return current.data
	}

    // 5、indexOf方法
	DoublyLinkedList.prototype.indexOf =function(data){
		// 1、定义变量
		let current = this.head
		let index = 0

		//  2、开始查找
		while (current) {
			if(current.data == data){
				return index
			}
			current = current.next
			index += 1
		}

		//3、最后没找到返回-1
		return -1
	}

    // 6、update方法
    DoublyLinkedList.prototype.update =function(position,newData){
		// 1、对position进行越界判断 例如是负数 超过链表长度
		if(position < 0 || position >= this.length){
			return null
		}
        let current = {}
		let index = 0
        if(this.length / 2 > position){ // this.length / 2 > position:从头往后遍历
            current = this.head
            while (index++ < position) {
                current = current.next
            }
        }else{       // this.length / 2 <= position:从后往前遍历
            current = this.tail
            index = this.length-1
            while (index-- > position) {
                current = current.prev
            }
        }
        current.data = newData
		// 2、获取对应的data
		return true
	}

    // 7、removeAt方法
    DoublyLinkedList.prototype.removeAt =function(position){
		// 1、对position进行越界判断 例如是负数 超过链表长度
		if(position < 0 || position >= this.length){
			return null
		}
        // 2、判断链表是否只有一个节点
        let 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){ //最后节点
                this.tail.prev.next = null
                this.tail = this.tail.prev
            }else {
                index = 0
                while(index++ < position){
                    current = current.next
                }
                current.prev.next = current.next
                current.next.prev = current.prev
            }
        }

        //3、length-1
        this.length -= 1

        return current.data
    }

    // 8、remove方法
    DoublyLinkedList.prototype.remove =function(data){
        // 1、根据data获取下标
        let index = this.indexOf(data)

        // 2、根据index删除对应位置节点
        return this.removeAt(index)
    }

    // 9、isEmpty方法
	DoublyLinkedList.prototype.isEmpty = function(){
		return this.length == 0
	}
	// 10、size方法查看链表中元素个数
	DoublyLinkedList.prototype.size = function(){
		return this.length
	}

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

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

2、集合

特点

  • 1、特殊之处在于里面的元素没有顺序,也不能重复
  • 2、没有顺序意味着不能通过下标值进行访问,不能重复意味着相同的对象在集合中只会存在一份

集合的常用操作封装代码

function Set(){
    //属性
    this.items = {}

    //方法
    //1、add方法
    Set.prototype.add = function(value){
      //判断当前集合是否已经包含该元素
      if(this.has(value)){
        return false
      }
      //将元素添加到集合中
      this.item[value] = value
      return true
    }

    // 2、has方法
    Set.prototype.has = function(value){
      return this.items.hasOwnProperty(value)
    }

    //3、remove方法
    Set.prototype.remove = function(value){
      //判断当前集合是否已经包含该元素
      if(!this.has(value)){
        return false
      }
      //将元素从集合中删除
      delete this.items[value]
      return true
    }

    //clear方法
    Set.prototype.clear = function(){
      this.items = {}
    }

    //size方法
    Set.prototype.size = function(){
      return Object.keys(this.items).length
    }

    //获取集合中所有的值
    Set.prototype.values = function(){
      return Object.keys(this.items)
    }
}

集合之间的操作

  • 1、并集:对于给定两个集合,返回一个包含两个集合中所有元素的新集合
  • 2、交集:对于给定两个集合,返回一个包含两个集合中共有元素的新集合
  • 3、差集:对于给定两个集合,返回一个包含所有存在于一个集合且不存在于第二个集合的新集合
  • 4、子集:验证一个给定的集合是否是另一个集合的子集

image.png

3、字典

特点

  • 1、字典的主要特点就是通过键值对进行保存,键值对是一一对应关系
  • 2、字典的key是不可以重复的,而value可以重复,字典中的key是无序的