链表–线性表
一、线性表定义
零个或多个数据元素的有限序列
1. 抽象数据类型定义:
ADT list
Data
集合{a1,a2,a2..an},有限,每个元素的数据类型都为DataType,其中,除第一个元素a1外,每个元素有且只有一个直接前驱元素,除了最后一个元素an外,每个元素有且只有一个直接后继元素。数据元素间是一对一的关系!
Operation
InitList(*L)初始化操作,建立一个空的线性表L
ListEmpty(L)若线性表为空,返回了true,否则返回false
ClearList(*L)清空线性表
GetElem(L,i,*e) 将线性表L的第i个位置元素值返回给e
LocateElem(L,e) 在线性表L查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号,失败则返回0
ListInsert(*L,i,e)在线性表L的第i个位置,插入新元素e
ListDelete(*L,i,e)删除线性表L中第i位置元素,并用e返回其值
ListLength(L) 返回线性表L的元素个数
endADT
在实际开发中,更为复杂的操作,都可使用以上组合实现!!!
二、顺序存储结构–数组
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
在线性表的某个位置存入或者取出数据,时间复杂度:O(1)
插入和删除:时间复杂度:O(n)
1. 数组插入
2. 数组删除
3.数组优缺点
三、链式存储结构
使用链表的原因:
- 数组创建需要一段连续的空间(一整块内存),并且大小是固定的。
- 在数组的开头或中间插入数据的成本很高,需要进行大量元素的位移!
- 在 JavaScript 中的 Array 类方法背后原理依旧如此!
链表的优势:
- 内存空间不必连续,可以充分利用计算机内存,实现灵活的内存动态管理
- 创建时,不必确定大小,并且可以无限延伸下去
- 插入和删除数据时,时间复杂度可以达到 O ( 1 ) O(1) O(1) ,对比数组高效很多
链表的缺点:
- 访问任何一个位置的元素,都必须从开头开始访问(无法跳过第一个元素访问任何一个元素)
- 无法通过下标直接访问元素,必须一个个寻找!
瑕不掩瑜–链表很有必要:
- 链式存储是解决顺序存储的解决方案!各有优劣,结合实际开发使用!
头指针:指的是第一个结点的物理地址!如果有头结点,指向的便是头结点的物理地址!
头结点:方便对第一个元素统一操作的预设结点,数据域一般不含数据(可放公共信息)
1.单链表
-
基本思路
head–头指针
Node–节点
- data next
// 单链表封装 function LinkedList() { // 内部类:节点类,暂时不传下一节点的指向地址 function Node(data) { this.data = data this.next = null } // 属性 this.head = null //对应第一个元素 this.length = 0 }
- 常见操作
-
实现代码
// 单链表封装 function LinkedList() { // 内部类:节点类,暂时不传下一节点的指向地址 function Node(data) { this.data = data this.next = null } // 属性 this.head = null this.length = 0 // 1.追加方法: LinkedList.prototype.append = function (data) { // 1.创建新的节点 var newNode = new Node(data) // 2.判断是否添加的是第一个节点 if (this.length == 0) { // 2.1 是第一个节点 this.head = newNode } else { // 2.2不是第一个节点,找到最后一个节点 var current = this.head while (current.next) { current = current.next } // 最后节点的next指向新的节点 current.next = newNode } //3.链表长度加 1 this.length += 1 } // 2. toString 方法 LinkedList.prototype.toString = function () { // 1.定义变量 var current = this.head var listString = "" // 2. 循环获取一个个的节点数据 while (current) { listString += current.data + " " current = current.next } return listString } // 3. insert方法 position 插入的位置,data插入的数据 LinkedList.prototype.insert = function (position,data) { // 1. 做越界判断 0 -- length if (position < 0 || position > this.length) { return false } // 2.根据 data 创建newNode var newNode = new Node(data) // 3.判断插入的位置是否是第一个 if (position == 0) { newNode.next = this.head this.head = newNode } else { // 不是第一个位置 // 找到目标位置,循环,需要两个变量,一个 current 一个 previous var current = this.head var previous = null for (var i = 0; i < position; i++) { previous = current current = current.next } newNode.next = current previous.next = newNode } // 4. length + 1 this.length += 1 return true } // 4. get方法 LinkedList.prototype.get = function (position) { // 1.越界判断 if (position <= 0 || position > this.length) { return null } // 2.获取对应的data var current = this.head var index = 1 while (index++ < position) { current = current.next } return current.data } // 5.indexOf 返回下标的索引 LinkedList.prototype.indexOf = function (data) { // 1.定义变量 // this.head 就是 第一个元素第一个节点 var current = this.head var 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.越界判断 if (position < 0 || position >= this.length) return false // 2. 查找正确的节点 var current = this.head var index = 0 while (index++ < position) { current =current.next } // 3. 将 position 位置的 node 的 data 修改成 newData current.data = newData return true } // 7. removeAt LinkedList.prototype.removeAt = function (position) { // 1. 越界判断 if (position < 0 || position >= this.length) return null // 2. 判断是否删除的是第一个结点 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 } // 3. length - 1 this.length -= 1 return current.data } // 8.remove 方法 LinkedList.prototype.remove = function (data) { // 1. 获取 data 在列表的位置 var 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 } }
// 测试代码 var list = new LinkedList() //2. 测试 append 方法 list.append('abc1') list.append('cba3') list.append('bca2') // list.append('acb8') alert(list) // 3.测试 insert 方法 list.insert(0,'aaa') list.insert(3,'bbb') list.insert(5,'ccc') alert(list) // 4.测试 get 方法 aaa abc1 cba3 bbb bca2 ccc // 目前是从 0 开始计算的 应该从1才对 // alert(list.get(1)) // alert(list.get(5)) // alert(list.get(6)) // // // 5. 测试 indexOf // alert(list.indexOf('aaa')) // alert(list.indexOf('bbb')) // alert(list.indexOf('ccc')) // 6. 测试 update 方法,根据下标的索引修改 // list.update(0,'mmm') // list.update(2,'nnn') // alert(list) // 7.removeAt 按照索引下标删除 元素 // list.removeAt(5) // alert(list) // list.removeAt(0) // alert(list) // list.removeAt(2) // alert(list) // 8.测试 remove list.remove('aaa') alert(list) list.remove('ccc') alert(list) // 9. 10. 判断是否为空,获取长度 alert("长度:"+list.size()) alert("是否为空:"+list.isEmpty())
-
单链表分析
单向链表
-
只能从头到尾遍历或者尾到头
-
整个链表的相连过程是单向的,上一个链表中有下一个指向的引用
-
可以轻松到达下一个节点,但是回到前一个结点是很难的
双向链表
- 可从头遍历到尾,又可以从尾到头遍历
- 相连过程是双向过程
- 具有向前和向后连接的引用
- 插入或删除某节点时,需要处理四个引用,相对单向链表,占用内存空间更大
-
2. 双向链表
既有直接前驱指针,又有直接后继指针,中间放数据域
图示:
-
基本思路
有 前后指针,数据三部分组成,头指针 head、尾指针 tail
- 其实和单向链表差不多,只是内部的实现不太一样
常见操作
-
实现代码
// 封装双向链表 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) { // 1.根据 data 创建节点 var newNode = new Node(data) // 2. 判断添加的是否是第一个节点 if (this.length == 0) { this.head = newNode this.tail = newNode } else { newNode.prev = this.tail this.tail.next = newNode this.tail = newNode } // 3. length + 1 this.length += 1 } // 2. 将链表转成字符串形式 // 2.1 toString DoublyLinkedList.prototype.toString = function () { return this.backwardString() } // 2.2 forwardString 方法 DoublyLinkedList.prototype.forwardString = function () { // 向前遍历 // 1. 定义变量 var current = this.tail var resultString = "" // 2. 依次向前遍历,获取每一个节点 while (current) { resultString += current.data + " " current = current.prev } return resultString } // 2.3 backwardString DoublyLinkedList.prototype.backwardString = function () { // 向后遍历 // 1. 定义变量 var current = this.head var 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 创建新的节点 var newNode = new Node(data) // 3.判断原来的列表是否为空 if (this.length == 0) { this.head = newNode this.tail = newNode } else { // 3.1 判断 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 } } this.length += 1 return true } // 4.get 方法 DoublyLinkedList.prototype.get = function (position) { // 1.越界判断 if (position < 0 || position >= this.length) return null // 双向链表的查找 if (this.length / 2 > position) { // 从头向后遍历 // 2.获取元素 var current = this.head var index = 0 while (index++ < position) { current = current.next } } else if (this.length / 2 < position) { // 从尾到前,遍历 // 2.获取元素 var current = this.tail var index = this.length -1 while (index-- > position) { current = current.next } } return current.data } // 5. indexOf 方法 DoublyLinkedList.prototype.indexOf = function (data) { // 1.定义变量 var current = this.head var index = 0 // 2.查找和 data 相同的节点 while (current) { if (current.data == data) { return index } current = current.next index += 1 } return -1 } // 6.update 方法 DoublyLinkedList.prototype.update = function (position,newData) { // 1.越界的判断 if (position < 0 || position >= this.length) return false if (this.length / 2 > position) { // 从头向后遍历 // 2.获取元素 // 2. 寻找正确的节点 var current = this.head var index = 0 while (index++ < position) { current = current.next } // 3. 修改找到的节点 data 信息 current.data = newData return true } else if (this.length / 2 < position) { // 从尾到前,遍历 // 2.获取元素 // 2. 寻找正确的节点 var current = this.tail var index = this.length - 1 while (index-- > position) { current = current.next } // 3. 修改找到的节点 data 信息 current.data = newData return true } } // 7. removeAt 方法 DoublyLinkedList.prototype.removeAt = function (position) { // 1.越界判断 if (position < 0 || position >= this.length) return null // 2.判断是否只有一个节点 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 { var 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 获取下标值 var index = this.indexOf(data) // 2.根据 index 删除对应位置的节点 return this.removeAt(index) } // 9. 判断是否为空 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 } }
// 测试代码 var list = new DoublyLinkedList() //1.测试 append 方法 list.append('1a') list.append('2b') list.append('3c') // 2. 测试转成字符串的方法 alert(list) // alert(list.backwardString()) // alert(list.forwardString()) // 3. 测试 insert 方法 list.insert(0,'0g') list.insert(4,'4r') list.insert(2,'22') alert(list) // 4.测试 get 0g 1a 22 2b 3c 4r // alert(list.get(0)) // alert(list.get(2)) // alert(list.get(5)) // 5. 测试 indexOf // alert(list.indexOf('0g')) // alert(list.indexOf('2b')) // alert(list.indexOf('22')) // 6.测试 update 方法 list.update(0,'ttt') list.update(1,'lll') list.update(5,'520') alert(list) // 7.测试 removeAt // alert(list.removeAt(2)) // alert(list) // alert(list.removeAt(4)) // alert(list) // 8. 测试 remove 方法 // alert(list.remove('2b')) // alert(list) // alert(list.remove('520')) // alert(list) // alert(list.remove('ttt')) // alert(list) // 9测试其他方法 alert(list.isEmpty()) alert(list.size()) alert(list.getHead()) alert(list.getTail())
-
双向链表分析
3. 循环链表
在单链表的基础上,将终端结点的指针由空指针改为指向头结点。即可!
-
基本思路
- 操作:
-
代码实现