JS–学习链表
一、链表结构
1、单向链表
2、双向链表
3、单向循环链表
4、双向循环链表
5、链表与数组
数组:js中数组中可以存储不同数据类型的数据,查找元素很方便(随机访问),可以根据下标快速查找;但是删除、添加都比较复杂,会是数组中元素移动。
链表:查找数据不如数组方便,但是删除、插入等操作时会比较迅速,因为链表不需要移动多个元素,使用指针直接将元素添加进来或删除掉,但是空间会有部分浪费(使用部分空间存储指针,以空间换时间)
二、封装链表
1、单向链表
class NodeOne {//每一个节点
constructor(element) {
this.data = element;//数据
this.next = null;//指针
}
}
//下标由0开始
class OneLinkList {//单向链表
constructor() {
this.head = null;//头指针
this.length = 0;//链表长度
}
//添加节点
append(element) {
//创建一个新节点
let node = new NodeOne(element);
//this.head == null和this.length == 0都可以判断链表为空
if (this.head == null || this.length == 0) {
//链表为空时直接添加节点
this.head = node;
} else {
//链表部位空时找到尾部节点,在尾部节点后面添加节点
let current = this.head;
//找到next == null,不再往后找
while (current.next != null) {//判断条件可以换为current.next
//next为空就继续往下找
current = current.next;
}
//next为空后就将next指向刚创建的新节点
current.next = node;
}
this.length++;//每添加一个节点就将链表长度+1
}
//在任意位置和它前一个之间插入节点
insert(element, position) {
if (position < 0 || position > this.length || !Number.isInteger(position)) {
return -1;
}
let node = new NodeOne(element);
let current = this.head;
if (position == 0) {
if (this.head == null || this.length == 0) {
this.head = node;
} else {
//链表不为空时插入到头部
current.next = this.head;//先将新的节点连接到头指针指向的节点
this.head = node;//再将头指针指向新的节点
}
} else if (position == this.length) {
this.append(node);
} else {
//方法一:
let current = this.head, index = 0;
while (index++ < position - 1) {
current = current.next;
}
node.next = current.next;
current.next = node;
//方法二:
// let current = this.head;
// let previous = null;
// let index = 0;
// while (index < position) {
// previous = current;
// current = current.next;
// index++;
// }
// node.next = current;
// previous.next = node;
}
this.length++;
}
//便于查看链表内容
toString() {
let listData = '';
let current = this.head;
while (current) {
listData += '-' + current.data;
current = current.next;
}
return listData.slice(1);
}
//根据数据查找节点
indexOf(element) {
let current = this.head;
let index = 0;
while (current) {
if (current.data === element) {
return index;
}
index++;
current = current.next;
}
return -1;
}
//移除特定位置的节点
removeAt(position) {
//不合法的位置返回-1
if (position < 0 || position > this.length || !Number.isInteger(position)) {
return -1;
}
//删除第一个
if (position == 0) {
//只有一个节点
if (this.length == 1) {
this.head = null;
} else {
//有多个节点
this.head = this.head.next;
}
// //删除最后一个
}else if(position == this.length - 1){
let current = this.head;
let index = 0;
while(index++ < position - 1){
current = current.next;
}
current.next = null;
//移除中间部分和尾部合并
} else {
let current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
current.next = current.next.next;
}
this.length--;
}
isEmpty(){
return this.length == 0;
}
size(){
return this.length;
}
remove(element){
return this.removeAt(this.indexOf(element));
}
}
2、双向链表
class DoubleNode {
constructor(element) {
this.data = element;
this.next = null;//下一个
this.previous = null;//前一个
}
}
class DoubleLinkList {
constructor() {
this.head = null;//头指针
this.length = 0;
this.tail = null;//尾指针
}
//添加节点
append(element) {
let node = new DoubleNode(element);
if (this.head == null) {//链表为空时
this.head = node;
this.tail = node;
} else {//不为空时
//前两句可以交换位置
this.tail.next = node;
node.previous = this.tail;
this.tail = node;
}
this.length++;
}
//正向遍历
toAfterString() {
let re = '';
let current = this.head;
while (current) {
re += '-' + current.data;
current = current.next;
}
return re.slice(1);
}
//反向遍历
toBeforString() {
let re = '';
let current = this.tail;
while (current) {
re += '-' + current.data;
current = current.previous;
}
return re.slice(1);
}
//特定位置插入
insert(element, position) {
//数据不合法
if (position < 0 || position > this.length || !Number.isInteger(position)) {
return -1;
}
//创建节点
let node = new DoubleNode(element);
//在头部插入
if (position == 0) {
//链表为空时
if (this.head == null) {
this.head = node;
this.tail = node;
} else {//不为空时
node.next = this.head;
this.head.previous = node;
this.head = node;
}
} else if (position == this.length) {
//在尾部插入相当于添加节点
this.append(element);
} else {//在中间插入
let current = this.head;
let index = 0;
while (index++ < position) {
//找到插入位置
current = current.next;
}
//先将创建的节点连接上去再将之前链表上原有的连接修改
node.next = current;
node.previous = current.previous;
//后面两步尽量不交换位置,交换后将会变得复杂
current.previous.next = node;
current.previous = node;
}
this.length++;
}
//根据数据查找元素位置(正向查找)
indexOf(element) {
let current = this.head;
let index = 0;
while (current) {
if (current.data === element) {
return index;
}
index++;
current = current.next;
}
return -1;
}
//反向查找
lastIndexOf(element) {
let current = this.tail;
let index = this.length - 1;
while (current) {
if (current.data === element) {
return index;
}
index--;
current = current.previous;
}
return -1;
}
//移除指定位置的节点
removeAt(position) {
//数据不合法
if (position < 0 || position > this.length || !Number.isInteger(position)) {
return -1;
}
//头部移除
if (position == 0) {
//链表为空时
if (this.head == null) {
this.head = null;
this.tail = null;
} else {//不为空时
this.head = this.head.next;
this.head.previous = null;
}
//移除尾部
} else if (position == this.length - 1) {
this.tail = this.tail.previous;
this.tail.next = null;
//移除中间节点
} else {
//从头部开始往后找
let current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
//可以交换位置
current.previous.next = current.next;
current.next.previous = current.previous;
}
this.length--;
}
//移除节点
remove(element) {
return this.removeAt(this.indexOf(element));
}
size() {
return this.length;
}
isEmpty() {
return this.length == 0;
}
getHead() {
return this.head.data;
}
getTail() {
return this.tail.data;
}
//将removeAt(position)转换为二分查找的方式
removeAtHalf(position) {
if (position < 0 || position > this.length || !Number.isInteger(position)) {
return -1;
}
if (position == 0) {
//链表为空时
if (this.head == null) {
this.head = null;
this.tail = null;
} else {//不为空时
this.head = this.head.next;
this.head.previous = null;
}
} else if (position == this.length - 1) {
//移除尾部
this.tail = this.tail.previous;
this.tail.next = null;
} else {
let middle = Math.floor(this.length / 2);
if (position < middle) {
//从头部向后查找
let current = this.head;
//这里要从1开始或者while里面小于position - 1
let index = 1;
while (index++ < position) {
current = current.next;
}
//删除position节点
current.previous.next = current.next;
current.next.previous = current.previous;
}
// else if (position >= middle) {
// //从头部开始找到中间位置为当前current
// let current = this.head;
// let index = 0;
// while (index++ < middle) {
// current = current.next;
// }
// //从中间位置开始向后查找,直到找到position的节点
// while (index++ < position) {
// current = current.next;
// }
// //删除position节点
// current.previous.next = current.next;
// current.next.previous = current.previous;
// }
else if(position > middle){
//从尾部向前查找
let current = this.tail;
let index = this.length - 1;
while(index-- > position){
current = current.previous;
}
current.previous.next = current.next;
current.next.previous = current.previous;
}
}
}
}
3、单向循环链表
class Node{
constructor(element){
this.data = element;
this.next = null;
}
}
class OneCycleLinkList{
constructor(){
this.head = null;
this.length = 0;
}
append(element){
let node = new Node(element);
if(this.head == null || this.length == 0){
this.head = node;
node.next = this.head;
}else{
// console.log(this.head.next);
let current = this.head;
while(current.next != this.head){
current = current.next;
}
node.next = this.head;
current.next = node;
}
this.length++;
}
insert(element,position){
if(position < 0 || position > this.length || !Number.isInteger(position)){
return -1;
}
let node = new Node(element);
if(position == 0){
if(this.head == null){
this.head = node;
node.next = this.head;
}else{
node.next = this.head;
let current = this.head;
while(current.next != this.head){
current = current.next;
}
node.next = this.head;
current.next = node;
this.head = node;
}
this.length++;
}else if(position == this.length){
this.append(element);
}else{
let current = this.head;
let index = 0;
while(index++ < position - 1){
current = current.next;
// index++;
}
node.next = current.next;
current.next = node;
this.length++;
}
}
indexOf(element){
let current = this.head;
let index = 0;
while(index < this.length){
if(current.data == element){
return index;
}
current = current.next;
index++;
}
return -1;
}
removeAt(position){
if(position < 0 || position > this.length || !Number.isInteger(position)){
return -1;
}
if(position == 0){
if(this.length == 1){
this.head = null;
}else{
let current = this.head;
while(current.next != this.head){
current = current.next;
}
this.head = this.head.next;
current.next = this.head;
}
}else{
let current = this.head;
let index = 0;
while(index++ < position - 1){
current = current.next;
}
current.next = current.next.next;
}
this.length--;
}
remove(element){
return this.removeAt(this.indexOf(element));
}
toString(){
let result = '';
let current = this.head;
let index = 0;
while(index++ < this.length){
result += '-' + current.data;
current = current.next;
}
return result.slice(1);
}
}
4、双向循环链表
class Node {
constructor(element) {
this.data = element;
this.next = null;
this.previous = null;
}
}
class DoubleCycleLinkList {
constructor() {
this.length = 0;
this.head = null;
this.tail = null;
}
append(element) {
let node = new Node(element);
if (this.length == 0 || this.head == null) {
this.head = node;
this.tail = node;
node.next = this.head;
node.previous = this.tail;
} else {
node.next = this.head;
node.previous = this.tail;
this.head.previous = node;
this.tail.next = node;
this.tail = node;
}
this.length++;
}
insert(element, position) {
if (position < 0 || position > this.length || !Number.isInteger(position)) {
return -1;
}
let node = new Node(element);
if (position == 0) {
if (this.length == 0 || this.head == null) {
this.head = node;
this.tail = node;
node.next = this.head;
node.previous = this.tail;
} else {
node.next = this.head;
node.previous = this.tail;
this.head.previous = node;
this.tail.next = node;
this.head = node;
}
this.length++;
} else if (position == this.length) {
this.append(element);
} else {
let current = this.head, index = 0;
while (index++ < position - 1) {
current = current.next;
}
node.next = current.next;
node.previous = current;
current.next.previous = node;
current.next = node;
this.length++;
}
}
removeAt(position) {
if (position < 0 || position > this.length - 1 || !Number.isInteger(position)) {
return -1;
}
if (position == 0) {
if (this.length == 1) {
this.head = null;
this.tail = null;
} else {
this.tail.next = this.head.next;
this.head.next.previous = this.tail;
this.head = this.head.next;
}
}else if(position == this.length - 1){
this.tail = this.tail.previous;
this.tail.next = this.head;
this.head.previous = this.tail
} else {
let current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
current.previous.next = current.next;
current.next.previous = current.previous;
}
this.length--;
}
indexOf(element) {
let index = 0;
let current = this.head;
while (index < this.length) {
if (current.data === element) {
return index;
}
index++;
current = current.next;
}
return -1;
}
lastIndexOf(element){
let index = this.length - 1;
let current = this.tail;
while(index >= 0){
if(current.data === element){
return index;
}
index--;
current = current.previous;
}
return -1;
}
remove(element) {
return this.removeAt(this.indexOf(element));
}
toAfterString() {
let result = '', index = 0, current = this.head;
while (index++ < this.length) {
result += '-' + current.data;
current = current.next;
}
return result.slice(1);
}
toBeforString() {
let result = '', index = this.length - 1, current = this.tail;
while (index-- >= 0) {
result += '-' + current.data;
current = current.previous;
}
return result.slice(1);
}
//将removeAt方法改成首尾查找
removeAtHalf(position) {
if (position < 0 || position > this.length || !Number.isInteger(position)) {
return -1;
}
if (position == 0) {
if (this.length == 1) {
this.head = null;
this.tail = null;
} else {
this.tail.next = this.head.next;
this.head.next.previous = this.tail;
this.head = this.head.next;
}
} else {
let middle = Math.floor(this.length / 2);
let current,index;
if (position < middle) {
index = 0;
current = this.head;
while (index++ < position) {
current = current.next;
}
}else if(position >= middle){
index = this.length - 1;
current = this.tail;
while(index-- > position){
current = current.previous;
}
}
current.previous.next = current.next;
current.next.previous = current.previous;
}
this.length--;
}
}
三、链表运用
例1、使用单向链表实现约瑟夫环
//单向链表:
function josephRingList(list, num, winNum){
let oneList = new OneLinkList();
//依次放入链表
list.forEach((item)=>{
oneList.append(item);
});
//当链表元素大于两个时游戏还没结束
while(oneList.length > winNum){
for(let i = 0; i < num - 1; i++){
//将链表头结点数据添加在最后(存活下来的)
oneList.append(oneList.head.data);
//添加完后将头部数据移除,保证没有重复的
oneList.removeAt(0);
}
//淘汰的人移除掉
oneList.removeAt(0);
}
return oneList;
}
let arr = [];
((arr, n)=>{
for(let i = 1; i <= n; i++){
arr.push(i);
}
})(arr,41);
console.log(josephRingList(arr,3,2).toString());
例2、使用单向循环链表实现约瑟夫环
//单向循环链表:
function josephRingCycleList(list, num, winNum){
let cycleList = new OneCycleLinkList();
list.forEach((item)=>{
cycleList.append(item);
});
let count = 1;
let current = cycleList.head;
while(cycleList.length > winNum){
if(count == 3){
cycleList.remove(current.data);
current = current.next;
count = 1;
}else{
current = current.next;
count++;
}
}
return cycleList;
}
let arr1 = [];
((arr1, n)=>{
for(let i = 1; i <= n; i++){
arr1.push(i);
}
})(arr1,41);
console.log(josephRingCycleList(arr1,3,2).toString());
例3、单向链表翻转
function reverseList(list) {
let previous = null;
let current = list.head;
while(current){
let temp = current.next;
current.next = previous;
previous = current;
current = temp;
}
list.head = previous;
return list;
}
let list = new OneLinkList();
for (let i = 0; i < 10; i++) {
list.append(i);
}
console.log(list.toString());
console.log(reverseList(list).toString());
例4、单向链表相邻元素交换
function transListEle(list){
let previous = new NodeOne(-1);
let p = previous;
previous.next = this.head;
while(previous.next && previous.next.next){
let temp = previous.next;
let buffer = previous.next.next;
previous.next = buffer;
temp.next = buffer.next;
buffer.next = temp;
previous = temp;
}
list.head = p.next;
return list;
}
let list = new OneLinkList();
for(let i = 1; i <= 10; i++){
list.append(i);
}
console.log(list.toString());
console.log(transListEle(list).toString());
例5、合并单向链表
let list1 = new OneLinkList();
list1.append(1);
list1.append(4);
list1.append(9);
list1.append(13);
console.log('list1:' + list1.toString());
let list2 = new OneLinkList();
list2.append(3);
list2.append(6);
list2.append(10);
list2.append(20);
list2.append(50);
console.log('list2:' + list2.toString());
let newList = mergeList(list1, list2);
function mergeList(list, otherList) {
let mergeHead = new NodeOne("head"),
listHead = list.head,
otherListHead = otherList.head;
let current = mergeHead;
while (listHead && otherListHead) {
if (listHead.data < otherListHead.data) {
current.next = listHead;
listHead = listHead.next;
} else {
current.next = otherListHead;
otherListHead = otherListHead.next;
}
current = current.next;
}
current.next = listHead || otherListHead;
return mergeHead.next;
}
console.log(newList);