js数据结构与算法——单链表实现

193 阅读5分钟

        链表是动态的数据结构,它的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。

       链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。        

        链表是一种常见的数据结构——与数组不同的是:

        1.数组首先需要在定义时声明数组大小,如果像这个数组中加入的元素个数超过了数组的长度时,便不能正确保存所有内容;链表可以根据大小需要进行拓展。 

        2.其次数组是同一数据类型的元素集合,在内存中是按一定顺序连续排列存放的;链表常用malloc等函数动态随机分配空间,用指针相连。

链表结构示意图如下所示:

其实现实中就有一些链表的例子。

第一个就是寻宝的游戏(或破案)。你有一条线索,这条线索是指向寻找下一条线索的地点的指针。你顺着这条链接去下一个地点,得到另一条指向下一处的线索。得到列表中间的线索的唯一办法,就是从起点(第一条线索)顺着列表寻找。

第二个例子是火车。一列火车是由一些车厢组成的。每节车厢都是相互连接。你很容易分离一节车皮,改变它的位置,添加或移除它。每节车厢都是列表的元素,车厢间的连接就是指针。

链表的实现

链表有多种不同的类型,单链表,双向链表,循环链表等。

我们先实现单链表。单链表是一种链式存取的数据结构。

/**
 * 单项链表
 * push(element) 向链表尾部添加元素
 * getElementAt(index)返回链表中下标指向的值
 * removeAt(index)删除只定下标的值
 * indexOf(element)返回元素在链表中的下标
 * remove(elemen)删除只定元素
 * insert(elment,index)往指定下标中插入元素
 * size()链表的长度
 * toString()返回链表中的所有元素的字符串
 */
//学习数据结构 做系统优化用的 项目优化

//链表节点,链表中的项,链表中的节点class Node {//节点
	constructor(element) {
		this.element = element;
		this.next = undefined;
	}
}//链表类
class LinkedList {	constructor() {
		this.head = null;//存储第一个节点的引用
		this.count = 0;//链表项的数量
	}
	push(element) {
		let node = new Node(element)//{a}
		let current;//指针
		if (this.head === null) {//判断链表是否是空链表
			this.head = node
		} else {
			//找到链表中next指向为空的节点
			current = this.head;
			while (current.next != null) {
				current = current.next//让指针移动
			}
			current.next = node
		}
		this.count++
	}
	getElementAt(index) {//返回链表中下标指向的值
		if (index >= 0 && index < this.count) {
			let current = this.head;
			for (let i = 0; i < index; i++) {
				current = current.next
			}
			return current
		}
		return undefined
	}
     /**

     * 移除指定位置的节点元素,并返回移除的项

     * 如果超出了链表的范围,则返回null

     * @param {*} index 链表中的位置

     */        removeAt(index) {//删除指定下标的元素
		if (index >= 0 && index < this.count) {
			let current = this.head;
			if (index === 0) {//删除第一个节点
				this.head = current.next
			} else {
				let previous = this.getElementAt(index - 1)//获取前一个节点
				current = previous.next;
				previous.next = current.next
			}
			this.count--;
			return current.element
		}
		return undefined
	}
        /**       * 向列表特定位置插入一个新的项
       * @param {*} index 插入列表中的位置
       */	insert(element, index) {//往指定下标中插入元素
		if (index >= 0 && index < this.count) {
			let node = new Node(element);// 将新项创建为符合链表结构的列表项

                        if (index = 0) {
				let current = this.head;
				node.next = current;
				this.head = node
			} else {
				const previous = this.getElementAt(index - 1);
				const current = previous.next;
				node.next = current;
				previous.next = node
			}
			this.count++;
			return true
		}
		return false
	}
     /**

     * 返回元素在列表中的索引。如果列表中没有该元素则返回-1

     * @param {*} element 元素

     */       indexOf(element) {//返回元素在链表中的下标
		let current = this.head;
		for (let i = 0; i < this.count && current != null; i++) {
			if (element === current.element) {
				return i
			}
			current = current.next
		}
		return -1
	}
       /**

       * 从列表中移除一项

       * 先找出元素的索引项,再根据索引移除元素

       * @param {*} element 列表中的元素

       */        remove(element) {//删除节点
		let index = this.indexOf(element);
		return this.removeAt(index)
	}
       /**

       * 由于列表项使用了Node类,需要重写toString方法,让其只输出元素的值。

       */        toString() {//返回链表中的所有元素的字符串
		if (this.head === null) {
			return ''
		}
		let objString = `${this.head.element}`;//第一个元素
		let current = this.head.next // 让current指向第二个元素
		for (let i = 0; i < this.count && current != null; i++) {
			objString = `${objString},${current.element}`;
			current = current.next
		}
		return objString;
	}
	middle() {//查找中间节点  如果链表的长度为偶数,返回中间两个节点的任意一个,若为奇数,则返回中间节点
		if (this.head === null) {
			return null
		} else {
			let fast = this.head;//快指针
			let slow = this.head;//慢指针
			while (fast != null && fast.next != null) {
				slow = slow.next;//慢指针一次走一个
				fast = fast.next.next;//快指针一次走两个 快指针走到最后一个时 慢指针刚好走到中间
			}
			return slow
		}
	}
       /**

       * 判断是否为空链表

       * 空链表返回true,非空(链表长度大于0)返回false

       */        isEmpty() {//如果链表中不包含任何元素,返回 true,如果链表⻓度⼤于 0 则返回 falsereturn this.count === 0
	}
       /**

       * 返回链表包含的元素个数。与数组的length属性类似

       */        size() {//返回链表包含的元素个数,与数组的 length 属性类似。
		return this.count
	}
}

let List = new LinkedList()
List.push("a")
List.push("b")
List.push("c")
List.push("d")
List.push("e")
// List.remove("e")
// console.log(List.getElementAt(0))//b
console.log("toString--", List.toString())
console.log("middle---", List.middle())
console.log("size---", List.size())
console.log("empty---", List.isEmpty())
console.log("List---", List)

链表的应用

我们先比较下JavaScript中链表与数组的区别:

  1. 链表中的元素是不连续的,而数组中的元素是连续的。

  2. 链表添加或移除元素的时候不需要移动其他元素,而数组需要。

  3. 链表需要指针,而数组不需要。

  4. 链表需要从表头开始迭代列表直到找到所需要的元素。数组则可以直接访问任何位置的任何元素。

  5. 两者都具有动态性。关于数组动态性。数组在js中是一个可以修改的对象,添加移除元素,它会动态的变化,但是成本很高,需要移动元素。在大多数语言中(比如C和Java),数组的大小是固定的,想添加元素就要创建一个全新的数组,不能简单地往其中添加所需的元素。

要存储多个元素,数组可能是最常用的数据结构。但是,如果要存储数据,并且会有移除或者添加大量数据时候,链表比数组更实用。