数据结构(二)
说明:本文基于哔哩哔哩视频【JavaScript数据结构与算法】整理
链表(linked List)
-
类似于数组(数组在有些语言叫链表)
-
插入删除性能高
-
相较于数组的优点: 内存不必要是一整块的内存空间(无序,没有下标),大小可以无限延伸
-
链表内的元素由 元素本身和指向下一个元素的指针【引用或者连接】组成;
-
相当于 火车,火车头+车厢+连接,每一节车厢和连接处相当链表中的元素
-
常见的操作
-
append(element):向列表尾部添加一个新的项
-
insert(position,element):向列表的特定位置插入一个新的项。
-
remove(element):从列表中移除一项。
-
indexOf(element):返回元素在列表中的索引。如果列表中没有该元素则返回-1。
-
removeAt(position):从列表的特定位置移除一项。
-
isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。
-
size():返回链表包含的元素个数。与数组的length属性类似。
-
toString():由于列表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值。
-
操作的代码实现
// 链表的封装(类似数组)
function linkedList() {
// node 保存节点信息
function Node(ele) {
this.element = ele;
this.next = null;
}
// 属性
this.head = null;
this.length = 0;
// 方法
linkedList.prototype.append = function(data) {
var newNode = new Node(data);
if (this.head == null) {
this.head = newNode;
} else {
var current = this.head;
while (current.next) {
current = current.next;
}
current.next = newNode;
}
this.length += 1; // ++ 大多数语言无法识别
};
// 转化为字符串
linkedList.prototype.toString = function() {
var current = this.head;
var listString = "";
while (current) {
listString += " " + current.element;
current = current.next;
}
return listString.slice(1);
};
// 插入
linkedList.prototype.insert = function(position, data) {
// 越界判断 界限:>0,length
if (position < 0 || position > this.length) return false;
var newnode = new Node(data);
var current = this.head;
index = 0;
if (position == 0) {
newnode.next = current;
this.head = newnode;
} else {
// 运算符的优先级,先比较再计算, 相当于while 的最后一步,index++
while (index++ < position) {
privious = current;
current = current.next;
}
// 指针
newnode.next = current;
privious.next = newnode;
}
this.length += 1;
return true;
};
// 获取某个位置的值
linkedList.prototype.get = function(position) {
if (position < 0 || position >= this.length) return null;
var current = this.head;
var index = 0;
// 找到特定的位置
while (index++ < position) {
current = current.next;
}
return current.element;
};
// 获取索引
linkedList.prototype.indexOf = function(ele) {
var current = this.head;
index = 0;
while (current) {
if (current.element == ele) {
return index;
}
index += 1;
current = current.next;
}
return -1;
};
// 修改数据
linkedList.prototype.update = function(position, data) {
if (position < 0 || position >= this.length) return null;
var current = this.head;
var index = 0;
while (index++ < position) {
current = current.next;
}
current.element = data;
};
// 删除某个位置的值
linkedList.prototype.removeAt = function(position) {
if (position < 0 || position >= this.length) return null;
var previous = null;
var current = this.head;
var previous = null;
if (position == 0) {
this.head = current.next;
} else {
var index = 0;
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = current.next;
}
this.length -= 1
return current.element
};
// 删除某个值
linkedList.prototype.remove = function(data) {
const index = this.indexOf(data)
return this.removeAt(index)
};
// 判断是否为空
linkedList.prototype.isEmpty = function(data) {
return this.length == 0
}
// 获取链表的长度
linkedList.prototype.size = function(data) {
return this.length
}
}
// 测试
var link = new linkedList();
link.append("sdfg");
link.append("sdf是g");
link.append("sd出生地fg");
link.toString(); // linkedList {head: Node, length: 1}head: Node {node: "sdfg", next: null}length: 1
console.log(link, "link");
link.insert(1, "123");
link.insert(3, "456");
// console.log(link.get(0), "get");
console.log(link.indexOf("123"), "indexof");
link.update(2, "mk");
console.log(link, "up");
console.log(link.removeAt(2), "removeat");
console.log(link.remove('123'), "remove");
console.log(link.isEmpty('123'), "isEmpty");
console.log(link.size('123'), "size");
双向链表(使用较多)
-
可以从两个方向遍历的链表
-
缺点:内存空间占用较大,需要操作的指针有四个,相对复杂
-
操作:同 单向链表,不同的如下
-
forwardString()正向遍历 从后往前
-
backwardString()反向遍历
-
相关代码的实现
// 双向链表
function DoublyLinkedList() {
// 内部类:相当于火车的车厢
function Node(data) {
this.data = data;
this.pre = null;
this.next = null;
}
// 大类 双向链表类 的属性
this.head = null;
this.tail = null;
this.length = 0;
// 双向链表 操作
DoublyLinkedList.prototype.append = function(data) {
// 思路
// 先以传入的元素为基础创建节点,获取这个节点之前的节点pre,让 pre的 next指针指向 这个节点
const newnode = new Node(data);
if (this.length == 0) {
this.head = newnode;
this.tail = newnode;
} else {
newnode.pre = this.tail;
this.tail.next = newnode;
this.tail = newnode;
}
this.length += 1;
};
DoublyLinkedList.prototype.forwardString = function() {
var current = this.tail;
var res = "";
while (current) {
res += current.data + " ";
current = current.pre;
}
return res;
};
DoublyLinkedList.prototype.backwardString = function() {
// 向后遍历
var current = this.head;
var res = "";
while (current) {
res += current.data + " ";
current = current.next;
}
return res;
};
DoublyLinkedList.prototype.toString = function() {
this.forwardString();
};
DoublyLinkedList.prototype.insert = function(position, data) {
// 越界判断
if (position < 0 || position > this.length) return false;
// 创建节点
var newnode = new Node(data);
if (this.length == 0) {
this.head = newnode;
this.tail = newnode;
} else {
if (position == 0) {
this.head.pre = newnode;
newnode.next = this.head;
this, (head = newnode);
} else if (position == this.length) {
newnode.pre = this.tail;
this.tail.next = newnode;
this.tail = newnode;
} else {
var current = this.head;
index = 0;
// 找位置
while (index++ < position) {
current = current.next;
}
// 找到位置
// 修改指针
newnode.next = current;
newnode.pre = current.pre;
// current.pre.next 拿到的是 原来 的pre,不是变成newnode之后的 pre
current.pre.next = newnode;
current.pre = newnode;
}
}
this.length += 1;
};
DoublyLinkedList.prototype.get = function(position) {
if (position < 0 || position >= this.length) return false;
// 较高效率的做法是 二分之后作对比,然后决定顺序还是倒序遍历
var current = this.head;
index = 0;
while (index++ < position) {
current = current.next;
}
return current.data;
};
DoublyLinkedList.prototype.indexOf = function(data) {
var current = this.head;
index = 0;
while (current) {
if (current.data == data) {
return index;
}
current = current.next;
index++;
}
return -1;
};
DoublyLinkedList.prototype.update = function(position, data) {
if (position < 0 || position >= this.length) return false;
var current = this.head;
var newData = new Node(data);
index = 0;
while (index++ < position) {
current = current.next;
}
current.data = newData;
return true;
};
DoublyLinkedList.prototype.removeAt = function(position) {
if (position < 0 || position >= this.length) return null;
var current = this.head;
if (this.length == 1) {
this.head = thia.tail = null;
} else {
if (position == 0) {
this.head.next.pre = null; // 如果删除1 位置的,则,让head 指向为空,并且,重新赋值,指向为删除后的下一个元素
this.head = this.head.next;
} 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;
}
this.length -= 1;
return current.data;
}
};
DoublyLinkedList.prototype.remove = function(data) {
var pos = this.indexOf(data);
return this.removeAt(pos);
};
DoublyLinkedList.prototype.isEmpty = function(data) {
return this.length == 0;
};
DoublyLinkedList.prototype.size = function(data) {
return this.length;
};
}
const doublel = new DoublyLinkedList();
doublel.append("123");
doublel.append("456");
doublel.append("789");
console.log(doublel, "append");
console.log(doublel.forwardString(), "forwardString");// 789 456 123 forwardString
console.log(doublel.backwardString(), "backwardString");// 178 123 456 789 backwardString
doublel.insert(1, "aaa");
console.log(doublel, "insert");
console.log(doublel.get(0), "get");
console.log(doublel.indexOf("123"), "indexOf");
doublel.update(0, "mmm");
console.log(doublel, "up");
console.log(doublel.removeAt(0), "remove at");
doublel.remove("456");
console.log(doublel, "remove ");
console.log(doublel.isEmpty(), "isEmpty ");
console.log(doublel.size(), "size ");
其他数据结构可访问以下地址: