單向鏈表
鏈表是由一組節(jié)點組成的集合。每個節(jié)點都使用一個對象的引用指向它的后繼瑞眼。指向另一 個節(jié)點的引用叫做鏈。
實現(xiàn):
class Node {
constructor(val) {
this.val = val // 節(jié)點數(shù)據(jù)
this.next = null // 指向下一個節(jié)點的鏈接
}
}
class LinkedList {
constructor() {
this.head = new Node('head')
this.length = 0 // 鏈表的長度,默認為0妒峦,不算頭節(jié)點
}
get isEmpty() {
return !this.length
}
// 遍歷鏈表,查找指定索引節(jié)點
find(val) {
let curNode = this.head
while(curNode.val !== val) {
curNode = curNode.next
}
return curNode
}
// 查找指定節(jié)點的索引
findIndex(val) {
let curNode = this.head
let index = -1
while(curNode.val !== val) {
curNode = curNode.next
index++
}
if (index == this.length) return -1
return index
}
// 在任意位置插入節(jié)點
insert(position, val) {
if (position < 0 || position > this.length) return false
const newNode = new Node(val)
let curNode = this.head
let index = 0
// 找到插入點的節(jié)點
while(index < position) {
curNode = curNode.next
index++
}
newNode.next = curNode.next
curNode.next = newNode
this.length++
return true
}
// 在鏈表最后插入一個新元素
append(val) {
const newNode = new Node(val)
let curNode = this.head
while(curNode.next !== null) {
curNode = curNode.next
}
curNode.next = newNode
this.length++
}
// 刪除指定位置的節(jié)點
removeAt(position) {
if (position < 0 || position > this.length) return null
let curNode = this.head
let index = 0
let prevNode = null
// 找到待刪除節(jié)點
while(index <= position) {
prevNode = curNode
curNode = curNode.next
index++
}
prevNode.next = curNode.next
this.length--
return curNode.val
}
// 刪除節(jié)點
remove(val) {
// 找到待刪除節(jié)點前面的節(jié)點兵睛,然后修改它的next屬性肯骇,使其不再指向待刪除節(jié)點,而是指向待刪除節(jié)點的下一個節(jié)點
const index = this.findIndex(val)
return this.removeAt(index)
}
toString() {
let curNode = this.head
let str = ''
while(curNode.next !== null) {
str += curNode.next.val + ', '
curNode = curNode.next
}
return str.slice(0, -2)
}
}
測試:
// test
const cities = new LinkedList()
cities.insert(0, 'BeiJing')
cities.insert(1, 'ShangHai')
cities.insert(0, 'ChongQing')
console.log(cities.toString()) // ChongQing, BeiJing, ShangHai
cities.remove('BeiJing')
console.log(cities.toString()) // ChongQing, ShangHai
console.log(cities.length) // 2
cities.removeAt(1)
console.log(cities.toString()) // ChongQing
cities.append('GuangZhou')
console.log(cities.toString()) // ChongQing, GuangZhou
雙向鏈表
與普通列表類似祖很,只是多了一個指向前驅節(jié)點的鏈接笛丙。
實現(xiàn):
class Node {
constructor(val) {
this.val = val // 節(jié)點數(shù)據(jù)
this.next = null // 指向下一個節(jié)點的鏈接
this.prev = null // 指向前一個節(jié)點
}
}
class DbLinkedList {
constructor() {
this.head = new Node('head')
this.current = this.head
this.length = 0 // 鏈表的長度,默認為0假颇,不算頭節(jié)點
}
get isEmpty() {
return !this.length
}
// 遍歷鏈表胚鸯,查找指定索引節(jié)點
find(val) {
let curNode = this.head
while(curNode.val !== val) {
curNode = curNode.next
}
return curNode
}
// 查找最后的節(jié)點
findLast() {
let curNode = this.head
while(curNode.next !== null) {
curNode = curNode.next
}
return curNode
}
// 查找指定節(jié)點的索引
findIndex(val) {
let curNode = this.head
let index = -1
while(curNode.val !== val) {
curNode = curNode.next
index++
}
if (index == this.length) return -1
return index
}
// 在任意位置插入節(jié)點
insert(position, val) {
if (position < 0 || position > this.length) return false
const newNode = new Node(val)
let curNode = this.head
let index = 0
// 找到插入點的節(jié)點
while(index < position) {
curNode = curNode.next
index++
}
newNode.next = curNode.next
newNode.prev = curNode
if (curNode.next && curNode.next.prev) {
curNode.next.prev = newNode
}
curNode.next = newNode
this.length++
return true
}
// 在鏈表最后插入一個新元素
append(val) {
const newNode = new Node(val)
let curNode = this.head
while(curNode.next !== null) {
curNode = curNode.next
}
curNode.next = newNode
newNode.prev = curNode
this.length++
}
// 刪除指定位置的節(jié)點
removeAt(position) {
if (position < 0 || position > this.length) return null
let curNode = this.head
let index = 0
let prevNode = null
// 找到待刪除節(jié)點
while(index <= position) {
prevNode = curNode
curNode = curNode.next
index++
}
prevNode.next = curNode.next
curNode.next.prev = prevNode
curNode.prev = null
curNode.next = null
this.length--
return curNode.val
}
// 刪除節(jié)點
remove(val) {
// 找到待刪除節(jié)點前面的節(jié)點,然后修改它的next屬性笨鸡,使其不再指向待刪除節(jié)點姜钳,而是指向待刪除節(jié)點的下一個節(jié)點
const index = this.findIndex(val)
return this.removeAt(index)
}
toString() {
let curNode = this.head
let str = ''
while(curNode.next !== null) {
str += curNode.next.val + ', '
curNode = curNode.next
}
return str.slice(0, -2)
}
display() {
console.log(this.toString())
}
displayReverse() {
const data = this.toString().split(', ').reverse().join(', ')
console.log(data)
}
// 在鏈表中向前移動 n 個節(jié)點
advance(n) {
while(n > 0 && this.current.next !== null) {
this.current = this.current.next
n--
}
}
// 在雙向鏈表中后退 n 個節(jié)點
back(n) {
while(n > 0 && this.current.val !== 'head') {
this.current = this.current.prev
n--
}
}
// 只顯示當前節(jié)點
show() {
console.log(this.current)
}
}
測試:
const cities = new DbLinkedList()
cities.insert(0, 'BeiJing')
cities.insert(1, 'ShangHai')
cities.insert(0, 'ChongQing')
cities.append('GuangZhou')
cities.append('TianJin')
cities.append('Taiwan')
cities.display() // ChongQing, BeiJing, ShangHai, GuangZhou, TianJin, Taiwan
cities.remove('BeiJing')
cities.display() // ChongQing, ShangHai, GuangZhou, TianJin, Taiwan
cities.removeAt(1)
cities.display() // ChongQing, GuangZhou, TianJin, Taiwan
cities.displayReverse() // Taiwan, TianJin, GuangZhou, ChongQing
cities.show()
cities.advance(2)
cities.show() // Node {val: "GuangZhou", next: Node, prev: Node}
cities.back(1)
cities.show() // Node {val: "ChongQing", next: Node, prev: Node}
循環(huán)鏈表
循環(huán)鏈表和單向鏈表相似,節(jié)點類型都是一樣的形耗。唯一的區(qū)別是哥桥,在創(chuàng)建循環(huán)鏈表時,讓其頭節(jié)點的 next 屬性指向它本身激涤,即:
head.next = head
實現(xiàn):
class Node {
constructor(val) {
this.val = val // 節(jié)點數(shù)據(jù)
this.next = null // 指向下一個節(jié)點的鏈接
}
}
class CircularLinkedList {
constructor() {
this.head = new Node('head')
this.length = 0 // 鏈表的長度拟糕,默認為0,不算頭節(jié)點
this.current = this.head
}
get isEmpty() {
return !this.length
}
// 遍歷鏈表倦踢,查找指定索引節(jié)點
find(val) {
let curNode = this.head
while(curNode.val !== val && curNode.next.val !== 'head') {
curNode = curNode.next
}
return curNode
}
// 查找指定節(jié)點的索引
findIndex(val) {
let curNode = this.head
let index = -1
while(curNode.val !== val && curNode.next.val !== 'head') {
curNode = curNode.next
index++
}
if (index == this.length) return -1
return index
}
// 查找最后的節(jié)點
findLast() {
let curNode = this.head
while(curNode.next !== null && curNode.next.val !== 'head') {
curNode = curNode.next
}
return curNode
}
// 在任意位置插入節(jié)點
insert(position, val) {
if (position < 0 || position > this.length) return false
const newNode = new Node(val)
let curNode = this.head
let index = 0
// 找到插入點的節(jié)點
while(index < position) {
curNode = curNode.next
index++
}
newNode.next = curNode.next
curNode.next = newNode
// 鏈表的尾節(jié)點指向頭節(jié)點
const lastNode = this.findLast()
lastNode.next = this.head
this.length++
return true
}
// 在鏈表最后插入一個新元素
append(val) {
const newNode = new Node(val)
let curNode = this.head
while(curNode.next !== null && curNode.next.val !== 'head') {
curNode = curNode.next
}
curNode.next = newNode
// 鏈表的尾節(jié)點指向頭節(jié)點
newNode.next = this.head
this.length++
}
// 刪除指定位置的節(jié)點
removeAt(position) {
if (position < 0 || position > this.length) return null
let curNode = this.head
let index = 0
let prevNode = null
// 找到待刪除節(jié)點
while(index <= position) {
prevNode = curNode
curNode = curNode.next
index++
}
prevNode.next = curNode.next
this.length--
return curNode.val
}
// 刪除節(jié)點
remove(val) {
// 找到待刪除節(jié)點前面的節(jié)點送滞,然后修改它的next屬性,使其不再指向待刪除節(jié)點硼一,而是指向待刪除節(jié)點的下一個節(jié)點
const index = this.findIndex(val)
return this.removeAt(index)
}
// 在鏈表中向前移動 n 個節(jié)點
advance(n) {
while(n > 0) {
this.current = this.current.next
if (this.current.val === 'head') {
this.current = this.current.next
}
n--
}
}
// 只顯示當前節(jié)點
show() {
console.log(this.current)
}
display() {
console.log(this.toString())
}
displayReverse() {
const data = this.toString().split(', ').reverse().join(', ')
console.log(data)
}
toString() {
let curNode = this.head
let str = ''
while(curNode.next !== null && curNode.next.val !== 'head') {
str += curNode.next.val + ', '
curNode = curNode.next
}
return str.slice(0, -2)
}
}
測試:
const cities = new CircularLinkedList()
cities.insert(0, 'BeiJing')
cities.insert(1, 'ShangHai')
cities.insert(0, 'ChongQing')
cities.append('GuangZhou')
cities.append('TianJin')
cities.append('Taiwan')
cities.display() // ChongQing, BeiJing, ShangHai, GuangZhou, TianJin, Taiwan
cities.remove('BeiJing')
cities.display() // ChongQing, ShangHai, GuangZhou, TianJin, Taiwan
cities.removeAt(1)
cities.display() // ChongQing, GuangZhou, TianJin, Taiwan
cities.displayReverse() // Taiwan, TianJin, GuangZhou, ChongQing
cities.advance(2)
cities.show() // Node {val: "GuangZhou", next: Node}
示例:約瑟夫環(huán)問題
傳說在公元 1 世紀的猶太戰(zhàn)爭中累澡,猶太歷史學家弗拉維奧·約瑟夫斯和他的 40 個同胞 被羅馬士兵包圍。猶太士兵決定寧可自殺也不做俘虜般贼,于是商量出了一個自殺方案愧哟。他 們圍成一個圈,從一個人開始哼蛆,數(shù)到第三個人時將第三個人殺死蕊梧,然后再數(shù),直到殺光 所有人腮介。約瑟夫和另外一個人決定不參加這個瘋狂的游戲肥矢,他們快速地計算出了兩個位 置,站在那里得以幸存。寫一段程序將 n 個人圍成一圈甘改,并且第 m 個人會被殺掉旅东,計算 一圈人中哪兩個人最后會存活。使用循環(huán)鏈表解決該問題十艾。
/**
* @param { Number } n // 總人數(shù)
* @param { Number } m // 間隔人數(shù)
* @return { String }
*/
function game(n, m) {
var links = new CircularLinkedList()
links.insert(0, 1)
var sign = 2
// 生成循環(huán)鏈表
while(sign <= n) {
links.insert(sign - 1, sign)
sign++
}
// 循環(huán)遍歷抵代,直到剩余的人數(shù)少于間隔數(shù)
while(n >= m) {
links.advance(m)
links.remove(links.current.val)
n--
}
links.display()
}
// test
game(40, 3) // 13, 28