五. 链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
链表的特点
1.插入、删除数据效率高O(1)级别(只需更改指针指向即可),随机访问效率低O(n)级别(需要从链头至链尾进行遍历)。
2.和数组相比,内存空间消耗更大,因为每个存储数据的节点都需要额外的空间存储后继指针。
1. 单链表
每个节点只包含一个指针,即后继指针。
class Node {
constructor(element){
this.element = element // 链表节点中存放的内容
this.next = null // 用于指向下一个节点的指针
}
}
class LinkedList {
constructor(){
this.head = null // 链表的表头
this.count = 0 // 链表的节点数量
}
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++ // 完成添加操作后,令count++
}
removeAt(index){ // 用于删除第index个元素
if(index>=0&&index<this.count){
let current = this.head
if(index===0){
this.head = current.next // 如果只有一个元素,则直接将表头指向下一个(空)节点
}else{
let previous
for(let i =0;i<index;i++){ // 找出要删除的节点current 和 该节点的上一个节点previous
previous = current
current = current.next
}
previous.next = current.next // 令上一个节点的指针指向该节点的下一个节点
}
this.count-- // 完成删除操作后,令count--
return current.element // 返回被删除节点的内容
}
return // 如果index越界,则直接返回
}
getNodeAt(index){ // 获得第index个节点
if(index>=0&&index<this.count){
let node = this.head
for(let i=0;i<index;i++){
node= node.next
}
return node
}
return // 如果index越界,则直接返回
}
removeAt2(index){ // 利用getNodeAt()优化后的删除节点方法
if(index>=0&&index<this.count){
let current = this.head
if(index===0){
this.head = current.next
}else{
const previous = this.getNodeAt(index-1)
current = previous.next
previous.next = current.next
}
this.count--
return current.element
}
return
}
equalFn(a,b){ // 比较两个数据是否相同
return JSON.stringify(a)===JSON.stringify(b) // 仍然存在隐患,无法解决复杂数据类型,对于对象类型而言,如果对象初始化时赋值的顺序不同(例如{a:1,b:2} 和 {b:2,a:1} 这两个对象,本质上他们相同,可是根据JSON的处理生成的字符串不同),结果依然是false
}
indexOf(element){ // 根据值(内容)来获取索引(index表示第几个)
let current = this.head
for(let i=0;i<this.count;i++){
if(this.equalFn(element,current.element)){
return i
}
current = current.next
}
return -1
}
remove(element){ // 根据值删除节点
const index = this.indexOf(element) // 根据值来获取'索引'
return this.removeAt(index) // 调用根据'索引'删除节点的方法
}
insert(element,index){ // 在指定位置处插入节点的方法
if(index>=0&& index<=this.count){
const node = new Node(element) // 创建将要插入的节点
if(index===0){ //在首位插入节点
const current = this.head
node.next = current // 让该节点指向head节点
this.head = node // 改变head指向(指向第一个节点也就是该节点)
}else{ // 不在首位插入节点
const previous = this.getNodeAt(index-1)
const current = previous.next
node.next = current
previous.next = node
}
this.count++ // 完成添加操作后令count++
return true
}
return false
}
isEmpty(){
return this.size()===0
}
size(){
return this.count
}
getHead(){
return this.head
}
}
2.利用链表解决十进制转二进制 击鼓传花 回文判断
<1>十进制转二进制:
function docToBinl(num) {
let transList = new linkedList()
while (num > 0) {
let remainder = num % 2
num = Math.floor(num / 2)
transList.push(remainder)
}
return transList.toString().split('').reverse().join('')
}
<2> 击鼓传花
function gamel(list, num) {
let q = new linkedList()
for (let i = 0; i < list.length;i++) {
q.push(list[i])
}
console.log(list);
while (q.size() > 1) {
for (let k = 1; k <= num; k++){
q.push(q.removeAt(0))
}
console.log(`${q.getHead().element}出局`)
q.removeAt(0)
}
console.log(`winner: ${q.getHead().element}!!!`);
}
// gamel(['apple','rice','green','pencil','dollar','mirror'],5)
<3>判断回文:
function isPalindromel(str) {
// 处理字符串
let list = str.split(' ').join('').toLowerCase()
// console.log(list);
let deq1 = new linkedList()
// 初始化队列
for (let i = 0; i < list.length; i++) {
// console.log(`list[i]:${list[i]}`)
deq1.push(list[i])
}
let deq2 = new linkedList()
// 初始化队列
for (let i = 0; i < list.length; i++) {
// console.log(`list[${i}]:${list[i]}`);
deq2.insert(list[i],0)
}
// console.log(deq1.toString());
// console.log(deq2.toString());
return deq1.toString() === deq2.toString()
}
// let strl = '我D bab d 我 '
// console.log(isPalindromel(strl));
3. 双向链表
节点除了存储数据外,还有两个指针分别指向前一个节点地址(前驱指针prev)和下一个节点地址(后继指针next)。
class DoublyNode extends Node {
constructor(element) {
super(element);
this.prev = null; // 新增前驱指针
}
}
class DoublyLinkedList extends LinkedList {
constructor() {
super();
this.tail = null; // 新增后继指针
}
push(element) {
const node = new DoublyNode(element);
if (this.head == null) { // 若链表为空,则让链表的头和尾都指向新增节点
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
node.prev = this.tail;
this.tail = node;
}
this.count++;
}
insert(element, index) {
if (index >= 0 && index <= this.count) {
const node = new DoublyNode(element);
let current = this.head;
if (index === 0) {
if (this.head == null) {
this.head = node;
this.tail = node;
} else {
node.next = this.head;
this.head.prev = node;
this.head = node;
}
} else if (index === this.count) {
current = this.tail;
current.next = node;
node.prev = current;
this.tail = node;
} else {
const previous = this.getNodeAt(index - 1);
current = previous.next;
node.next = current;
previous.next = node;
current.prev = node;
node.prev = previous;
}
this.count++;
return true;
}
return false;
}
removeAt(index) {
if (index >= 0 && index < this.count) {
let current = this.head;
if (index === 0) {
this.head = this.head.next;
if (this.count === 1) {
// {2}
this.tail = undefined;
} else {
this.head.prev = undefined;
}
} else if (index === this.count - 1) {
current = this.tail;
this.tail = current.prev;
this.tail.next = undefined;
} else {
current = this.getNodeAt(index);
const previous = current.prev;
previous.next = current.next;
current.next.prev = previous; // NEW
}
this.count--;
return current.element;
}
return undefined;
}
getHead() {
return this.head;
}
getTail() {
return this.tail;
}
}
4. 循环链表
循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用 undefined,而是指向第一个元素(head)
class CircularLinkedList extends LinkedList {
constructor() {
super();
}
push(element) {
const node = new Node(element);
let current;
if (this.head == null) {
this.head = node;
} else {
current = this.getNodeAt(this.size() - 1);
current.next = node;
}
node.next = this.head;
this.count++;
}
insert(element, index) {
if (index >= 0 && index <= this.count) {
const node = new Node(element);
let current = this.head;
if (index === 0) {
if (this.head == null) {
// if no node in list
this.head = node;
node.next = this.head;
} else {
node.next = current;
current = this.getNodeAt(this.size() - 1);
// update last element
this.head = node;
current.next = this.head;
}
} else {
const previous = this.getNodeAt(index - 1);
node.next = previous.next;
previous.next = node;
}
this.count++;
return true;
}
return false;
}
removeAt(index) {
if (index >= 0 && index < this.count) {
let current = this.head;
if (index === 0) {
if (this.size() === 1) {
this.head = undefined;
} else {
let last = this.getNodeAt(this.size() - 1);
this.head = this.head.next;
last.next = this.head;
}
} else {
const previous = this.getNodeAt(index - 1);
current = previous.next;
previous.next = current.next;// 如果删的是最后一个也不怕,current.next 是head
}
this.count--;
return current.element;
}
return undefined;
}
}