链表
本篇博客的所有代码都整理到自己的github库中,以便大家查看及学习。
本篇文章主要内容有:
- 链表数据结构
- 向链表添加元素
- 从链表移除元素
- 使用
linkList
类 - 双向链表
- 循环链表
- 排序链表
- 通过链表实现栈
1. 链表的数据结构
链表存储有序的元素集合,链表中的元素在内存中并不是连续放置的,每个元素都是有一个存储元素本身的节点和一个指向下一个元素的引用(这个引用也称为指针或链接)组成。
通过这幅图,我们可以更清晰理解链表的数据结构。也从这个图中可以看到,当添加或者移除元素的时候,不需要移动其他的元素,可以改变指针的指向。需要注意的是,链表的表头(head
)指向链表第一个元素。
现实生活中链表的例子也比较常见,最常见的就是火车。
2. 创建链表
接下来,我们将会创建一个链表的骨架
function defaultEquals(a, b) {
return a === b;
}
class LinkList{
constructor(equalsFn = defaultEquals){
this.count = 0
this.head = undefined
this.equalsFn = equalsFn
}
}
// 创建链表中的节点
class Node {
constructor(element){
this.element = element
this.next = undefined
}
}
接下来我们会实现以下方法
push(elements)
: 向链表尾部添加数据insert(element,position)
: 向链表中特定位置插入数据getElementAt(index)
: 返回指定位置的元素remove(element)
: 从链表中移除某一元素indexOf(element)
: 返回元素在链表中的索引值,如果没有找到,则返回-1removeAt(position)
: 移除链表特定位置的值isEmpty()
: 返回链表是否为空size()
: 返回链表元素的个数toString()
: 返回表示整个链表的字符串,由于链表中使用了Node类,需要重新定义指输出value值的元素方法
这里需要注意的是: 链表的最后一个节点的下一个元素始终都是
undefined
或null
function defaultEquals(a, b) {
return a === b;
}
class LinkList{
constructor(equalsFn = defaultEquals){
this.count = 0
this.head = undefined
this.equalsFn = equalsFn
}
push(element){
const node = new Node(element) // 创建一个节点
let current
if (this.head == null) { // 判断链表是否为空 ,如果链表为空,则链表表头指针指向新的节点
this.head = node
} else { // 否则,通过遍历的方式将
current = this.head
while (current.next != null) {
current = current.next
}
current.next = node
}
this.count++
}
insert(element,position){
const node = new Node(element)
// 向链表尾部插入值
if (position == this.count) {
this.push(element)
} else {
// 向链表头部插入值
if (position == 0) {
const current = this.head
this.head = node
this.head.next = current
this.count++
}
// 向链表中间插入值
if (position > 0 && position < this.count) {
let prev
let current = this.head
for (let i = 0; i < position; i++) {
prev = current
current = current.next
}
prev.next = node
node.next = current
this.count++
}
}
}
getElementAt(index){
if (index <= this.size() && index >= 0) {
let current = this.head
for (let i = 0; i < index && current.next != null; i++) {
current = current.next
}
return current.element
}
return undefined
}
remove(element){
const index = this.indexOf(element)
this.removeAt(index)
}
removeAt(index){
// 检查越界值
if (index >= 0 && index < this.count) { // 合法值
let current = this.head
if (index == 0) {
this.head = current.next
} else {
let prev // 定义一个值 将下一项值的next
for (let i = 0; i < index; i++) {
prev = current
current = current.next
}
prev.next = current.next
}
this.count --
return current.element
}
return undefined
}
indexOf(element){
let current = this.head
for (let i = 0; i < this.size() && current != null; i++) {
if (this.equalsFn(element,current.element)) {
return i
}
current = current.next
}
return -1
}
size(){
return this.count
}
toString(){
let resultString = ''
let current = this.head
if (this.head != null) {
resultString = current.element
while (current.next != null) {
resultString = `${resultString},${current.next.element}`
current = current.next
}
}
return resultString
}
isEmpty(){
return this.size() === 0
}
}
// 创建链表中的节点
class Node {
constructor(element){
this.element = element
this.next = undefined
}
}
var linkList = new LinkList()
linkList.push(1)
linkList.push(2)
linkList.push(3)
linkList.push(4)
/**
LinkList {
count: 4,
head: Node { element: 1, next: Node { element: 2, next: [Node] } },
equalsFn: [Function: defaultEquals]
}
**/
console.log(linkList.toString()) // 1,2,3,4
console.log(linkList.indexOf(2)) // 1
console.log(linkList.indexOf(5)) // -1
linkList.remove(2)
console.log(linkList.toString()) // 1,3,4
console.log(linkList.size()) // 3
linkList.insert(2,0)
console.log(linkList)
console.log(linkList.toString()) // 2,1,3,4
linkList.insert(5,2)
console.log(linkList.toString()) // 2,1,5,3,4
linkList.insert(6,5)
console.log(linkList.toString()) // 2,1,5,3,4,6
这里的重点是理解代码中的添加和移除元素
3. 双向链表
双向链表和普通链表的区别在于在链表中一个节点只有链向下一个节点的链接,而双向链表中,链接是双向的,一个链接指向下一个节点的链接,一个链接指向上一个节点的链接。如下图所示
我们这次在链表的基础上扩展一下双向链表的方法
class DoubleLinkList extends LinkList {
constructor(equalsFn = defaultEquals){
super(equalsFn)
this.tail = undefined
}
}
这里的defaultEquals方法在创建链表时有定义
双向链表中的DoublyNode也是在链表中Node的基础上继承扩展的
class DoublyNode {
constructor(element){
super(element,next)
this.prev = undefined
}
}
4.双向链表的方法
创建的双向链表的类中的注释方法可以理解为上述方法的第二种方法。双向链表中重点掌握在任意位置插入和移除元素就可以了。
class DoubleLinkList extends LinkList {
constructor(equalsFn = defaultEquals){
super(equalsFn)
this.tail = undefined
}
push(element){
const node = new DoublyNode(element)
if (this.head == null) {
this.head = node
this.tail = node
} else {
node.prev = this.tail
this.tail.next = node
this.tail = node
}
}
insert(element,index){
if (index == this.count) { // 在链表尾部追加
this.push(element)
} else {
const node = new DoublyNode(element)
// 在链表头部追加
if (index == 0) {
if (this.head == null) { // 链表为空
this.head = node
this.tail = node
} else {
const firstNode = this.head
this.head = node
firstNode.prev = node
node.next = firstNode
}
}
// 在链表中间插入值,
const prevItem = this.getElementAt(index -1) // 拿到的前一项
let current = prevItem.next
prevItem.next = node
node.next = current
node.prev = prevItem
current.prev = node
this.count++
return true
// let current = this.head
// let prev
// for (let i = 0; i < index && index < this.count; i++) {
// prev = current
// current = current.next
// }
// prev.next = node
// node.next = current
}
return false
}
indexOf(element){
if (this.head == null) {
return -1
}
let current = this.head
let index = 0
while (current.next != null) {
if (this.equalsFn(current.element , element)) {
return index
}
index ++
current = current.next
}
}
// getElementAt(index){ //查找所在位置的元素,和链表中的方法一样
// if (this.head == null) {
// return undefined
// }
// let current = this.head
// let prev
// for (let i = 0; i < index; i++) {
// current = current.next
// }
// return current
// }
removeAt(index){ // 从任意位置移除元素
if (index >= 0 && index < this.count) {
let current
if (index == 0) { // 移除双向链表头部的元素
current = this.head
this.head = current.next
} else if (index == this.count - 1) { // 移除双向链表尾部的元素
current = this.tail
this.tail = current.prev
this.tail.next = undefined
} else { // 移除链表中间的元素,可用getElementAt()的方法
let current = this.getElementAt(index )
let prevItem = current.prev
prevItem.next = current.next
current.next.prev = prevItem
// let prev
// for (let i = 0; i < index ; i++) {
// prev = current
// current = current.next
// }
// prev.next = current.next
// current.next.prev = prev
}
this.count --
return current.element
}
return undefined
}
getHead(){
return this.head
}
getTail(){
return this.tail
}
clear(){
super.clear()
this.tail = undefined
}
toString(){}
inverseToString(){}
}
5. 循环链表
循环链表和链表唯一的区别就在于 最后一个元素指向下一个元素的指针,最后一个元素的tail.next
指向的是第一个元素(head),如下图所示,看图理解会更容易一些
接下来我们创建一个循环链表的类circleLinkList
,circleLinkList
类仍是继承基础链表的某些方法
import LinkList from './Linklist'
import DoublyNode from './DoublyNode'
import { defaultEquals } from '../utils/index'
class CircleLinkList extends LinkList{
constructor(equals = defaultEquals){
super(equals)
}
push(element){
const node = new DoublyNode(element)
if (this.head == null) {
this.head = node
} else {
let current = this.getElementAt(this.size() - 1)
current.next = node
this.tail = node
node.next = this.head
}
}
insert(element,index){
const node = new DoublyNode(element)
if (index > 0 && index <= this.count) {
if (this.head == null) {
this.head = node
node.prev = this.head
} else {
if (index == this.count) { // 最后一个元素
let lastItem = this.getElementAt(this.count - 1)
lastItem.next = node
node.prev = this.head
node.next = undefined
} else {
// 插入到链表中间位置
let prevItem = this.getElementAt(index - 1)
let current = prevItem.next
prevItem.next = node
node.prev = prevItem
node.next = current
}
}
this.count ++
return true
}
return false
}
removeAt(index){
if (this.head = null) {
return undefined
}
let prevItem = this.getElementAt(index -1) // 前一个元素
const result = prevItem.next
prevItem.next = result.next
result.prev = prevItem
return result
}
}
6. 有序链表
有序链表是值保持元素有序的链表结构,除了使用排序的算法外,还可以将元素插入到正确的位置确保链表的有序性
这里不再创建有序链表类了,有需要的可以自己尝试创建一下。
7. 通过链表表示栈
用双向链表更容易表示栈的数据结构,双向数据链表可以直接获取头尾元素,减少过程的消耗,他的时间复杂度和原始的Stack类实现相同,为O(1)
本文来自博客园,作者:前端加油站,转载请注明原文链接:https://www.cnblogs.com/bllx/p/15967202.html