链表以及数组的缺点
数组:
-
需要申请一段连续的内存空间
-
大小固定,当数组不满足容量需求时,需要扩容(js不需要)(申请一个更大的数组,将原数组的元素复制过去)
链表的优势:
-
可以存储多个元素
-
不同于数组。链表中的元素在内存里面不用是连续的空间,可以实现灵活的内存动态管理
-
链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用组成(指针)
-
链表不必在创建时就确定大小,并且大小可以无限地延申下去
-
链表在插入和删除数据时,时间复杂度可以达到O(1),相对数组效率高很多
链表的缺点:
-
当他要访问任何一个元素的时候,必须要从头开始访问(因为他是一个一个连接的)
-
不能通过下标直接访问元素,需要从头一个个访问,直到找到对应的元素
链表结构的封装
链表常见的操作
属性 head(第一个节点) node节点(数据data+引用next)
增
-
append(ele) 在链表尾部添加一个新的项
-
insert(position,element) 向列表的特定位置插入一个新的项
删
-
removeAt(position)通过链表的位置信息删除一项
-
remove(ele)通过元素信息,从列表中移除一项
改
-
update(position) 修改某个位置的元素
查
-
get(position):获取对应位置的元素
-
indexOf(ele) 返回元素在列表中的索引,如果列表中没有元素返回-1
其他
//判空
isEmpty()
//返回元素的个数
size()
//输出字符串
toString()
append()
// 1.添加方法
LinkedList.prototype.append = function (data) {
var newnode = new Node(data)
// 判断是不是第一个节点
if (this.length == 0) {
this.head = newnode
} else {
// 找到最后一个节点 给他的next赋值上新节点
// current 用来作为一个游标的 从头往后滑
var current = this.head
// 如果当前游标指的节点的next不为空就 把下一个节点赋值给游标 直到找到空next
while (current.next) {
current = current.next
}
//退出循环后说明游标已经指向最后一个节点
// 将新节点赋值给最后一个指针
current.next = newnode
}
// 链表节点加一
this.length += 1;
}
insert()
情况1:放在开头的地方
情况2:放在中间(两种方式)
-
声明要插入位置的pre节点和next节点
-
只用找到当前位置的前一个节点,previous
-
while(index++<position-1){ previous=previous.next}
-
将该节点保存的下一个节点等于新节点的下一个节点,前一个节点的下一个节点保存为新节点
previous.next=newNode.next previous.next=newNode
-
这里先写get(index) 先找到 prevNode 比较好 直接 newNode.next = prevNode.next; prevNode.next = newNode
情况3:放在后面
// 封装链表类
function LinkedList() {
// 封装一个内部的节点类 放数据和指针
function Node(data) {
this.data = data
//next指向未知
this.next = null
}
//头部属性
this.head = null
// 记录当前的长度
this.length = 0
// 1.添加方法
LinkedList.prototype.append = function (data) {
var newnode = new Node(data)
// 判断是不是第一个节点
if (this.length == 0) {
this.head = newnode
} else {
// 找到最后一个节点 给他的next赋值上新节点
// current 用来作为一个游标的 从头往后滑
var current = this.head
// 如果当前游标指的节点的next不为空就 把下一个节点赋值给游标 直到找到空next
while (current.next) {
current = current.next
}
//退出循环后说明游标已经指向最后一个节点
// 将新节点赋值给最后一个指针
current.next = newnode
}
// 链表节点加一
this.length += 1;
}
//toString
LinkedList.prototype.toString = function () {
var str = ''
//定义一个游标,这个游标从头到尾指一遍
var current = this.head
// 当游标有节点就循环
while (current) {
// 拼接每个节点的数据
str += current.data + ' '
// 当指完上一个节点之后,游标要往后一个节点移动
current = current.next
}
return str
}
//insert方法
LinkedList.prototype.insert = function (position, data) {
//需要判断插入的位置是不是越界 位置不可是负数和超过链表的长度
if (position < 0 || position > this.length) return false
//2.新建一个节点
var newNode = new Node()
newNode.data = 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
index++
}
newNode.next = current
previous.next = newNode
}
//链表长度加一
this.length += 1
return true
}
//get()根据位置信息获取对应的元素
LinkedList.prototype.get = function (position) {
//1.越界判断
if (position < 0 || position >= this.length) return null
// 2.获取对应的data
var current = this.head
var index = 0
while (index < position) {
current = current.next
index++
}
return current.data
}
// indexOf返回再列表中的索引,如果列表中没有该元素就返回-1
LinkedList.prototype.indexOf = function (ele) {
var index = 0
var current = this.head
//这里不能等于this.length 因为下标是从0开始的
while (index < this.length) {
if (ele === current.data) {
return index
break
}
//往后走一步
current = current.next
index++
}
return -1
}
//update(position) 修改某个位置的元素
LinkedList.prototype.update = function (position, ele) {
if (position < 0 || position >= this.length) return false
var index = 0;
var current = this.head
while (index++ < position) {
current = current.next
}
// 将position位置的元素修改成新的ele
current.data = ele
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;
}
previous.next = current.next
}
this.length -= 1
return current.data
}
//remove(ele) 从特定列表移除一项
LinkedList.prototype.remove = function (ele) {
// var index = 0
// var current = this.head
// var previous = null
// while (current) {
// if (ele === current.data) {
// if (index == 0) {
// this.head = this.head.next
// break
// }
// previous.next = current.next
// break
// }
// previous = current
// current = current.next
// index++
// }
// this.length -= 1
// return true
var position = this.indexOf(ele)
return this.removeAt(position)
}
//isEmpty
LinkedList.prototype.isEmpty = function () {
return this.length == 0
}
//size()
LinkedList.prototype.size = function () {
return this.length
}
}
双向链表
-
单向链表,只能从头到尾或者从尾到头
-
双向链表,每一个节点上面有两个引用,既可以从头到尾,也可以从尾到头
双向链表的缺点:
-
插入和删除节点要处理四个引用
-
占用的内存空间更大
双向链表的特点
-
使用head指向头部,使用tail指向尾部
-
每个节点都由三部分组成,prev(指向前一个节点)/item(保存的元素)/next(指向后一个节点)
-
双向链表的第一个节点的prev是null
-
双向链表的最后一个节点的next是null
常见操作
实现代码
function DoublyLinkedList() {
function Node(data) {
this.data = data
//next指向未知
this.next = null
this.pre = null
}
//属性
this.head = null
// 尾部
this.tail = null
this.length = 0
DoublyLinkedList.prototype.append = function (data) {
// 这里记得要加new
var newNode = new Node(data)
console.log(newNode)
if (this.head === null) {
this.head = newNode
this.tail = newNode
console.log(this.head)
console.log(this.tail)
} else {
this.tail.next = newNode
newNode.pre = this.tail
this.tail = newNode
}
return this.length += 1
}
// 2.将链表转换成字符串
DoublyLinkedList.prototype.toString = function () {
return this.backwardString()
}
// 向前遍历
DoublyLinkedList.prototype.forwardString = function () {
var current = this.tail
var str = ''
while (current) {
str += current.data + ' '
current = current.pre
}
return str
}
// 向后遍历
DoublyLinkedList.prototype.backwardString = function () {
var current = this.head
var str = ''
while (current) {
str += current.data + ' '
current = current.next
}
return str
}
//插入方法 insert 向列表插入特定位置插入一个新的项
DoublyLinkedList.prototype.insert = function (position, data) {
// 越界判断
if (position < 0 || position > this.length) return false
//2.根据data创建新节点
var newNode = new Node(data)
// 插入的情况
// 判断是否为空
if (this.length == 0) {
this.head = newNode
this.tail = newNode
} else {
//从第一个节点插 就是由节点的情况下
if (position == 0) {
//原来的第一个节点pre指向新的节点
this.head.pre = newNode
newNode.next = this.head
this.head = newNode
} else if (position == this.length) { //从最后一个添加新节点
this.tail.next = newNode
newNode.pre = this.tail
this.tail = newNode
} else {//插入到中间的位置
var index = 0
var current = this.head
while (index++ < position) {
current = current.next
}
//先把新节点的前后指针给赋值
newNode.next = current
newNode.pre = current.pre
// 前一个节点的next指向新节点 前一个节点由之前的节点的pre得到
current.pre.next = newNode
//再把后一个节点的pre改成新节点
current.pre = newNode
}
this.length += 1
return true
}
}
//get方法元素 根据位置返回元素
DoublyLinkedList.prototype.get = function (position) {
// 1.越界判断
if (position < 0 || position >= this.length) return null
var index
var current
//使用二分查找提高效率
if (this.length / 2 < position) {
index = 0
current = this.head
while (index++ < position) {
current = current.next
}
} else {
index = this.length - 1
current = this.tail
while (index-- > position) {
current = current.pre
}
}
return current.data
}
//根据元素返回下标,没有返回-1
DoublyLinkedList.prototype.indexOf = function (data) {
var index = 0
var current = this.head
while (current) {
if (data === current.data) {
return index
}
current = current.next
index++
}
return -1
}
//更新方法
DoublyLinkedList.prototype.update = function (position, ele) {
// 越界判断
if (position < 0 || position > this.length) return false
var current
// 二分查找法
if (position / 2 < position) {
index = 0
current = this.head
while (index++ < position) {
current = current.next
}
current.data = ele
} else {
index = this.length - 1
current = this.tail
while (index-- > position) {
current = current.pre
}
current.data = ele
}
return true
}
// 从列表移除一项
DoublyLinkedList.prototype.removeAt = function (position) {
// 越界判断
if (position < 0 || position >= this.length) return null
var index
var current = this.head
// 判断是否只有一个节点
if (this.length == 1) {
this.head = null
this.tail = null
} else {
if (position == 0) {
this.head.next.pre = null
this.head = this.head.next
// this.head.pre = null
} else if (position == this.length - 1) {
this.tail.pre.next = null
this.tail = this.tail.pre
} else {
var index = 0
while (index++ < position) {
current = current.next
}
current.pre.next = current.next
current.next.pre = current.pre
}
}
// if (position / 2 < position) {
// //移除第一个
// if (position == 0) {
// this.head.next.pre = null
// this.head = this.head.next
// // this.head.pre = null
// } else {
// // index = 0
// current = this.head
// while (index++ < position) {
// current = current.next
// }
// current.next.pre = current.pre
// current.pre.next = current.next
// }
// } else {
// //移除最后一个
// if (position = this.length - 1) {
// this.tail.pre.next = null
// this.tail = this.tail.pre
// } else {
// index = this.length - 1
// current = this.tail
// while (index-- > position) {
// current = current.pre
// }
// current.next.pre = current.pre
// current.pre.next = current.next
// }
// }
this.length--
return current.data
}
//remove移除列表中的一项
DoublyLinkedList.prototype.remove = function (ele) {
var index = this.indexOf(ele)
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()
list.append('abc')
list.append('cba')
list.append('nba')
//2.测试转成字符串的方法
// alert(list)
// alert(list.backwardString())
// alert(list.forwardString())
// 3.测试insert方法
list.insert(0, 'aaa')
list.insert(2, '99')
// alert(list)
// alert(list.get(2))
// alert(list.indexOf('99'))
list.update(2, '55')
// alert(list)
// list.removeAt(0)
alert(list)
alert(list.remove('cba'))
alert(list.remove('nba'))
alert(list.remove('55'))
alert(list)
alert(list.size())
alert(list.getHead())
alert(list.isEmpty())