前言
- 线性表:指的是0个或者多个数据元素有限序列;
- 他的物理的存储结构有两种:
1. 顺序存储
:用一段连续的存储单元依次存储线性表的数据元素
2. 链式存储
:内存地址可以是连续的,也可以是不连续的,把数据元素存放在任意的存储单元里,指针来存放数据元素的地址
链表
回顾数组:
- 数组的创建通常需要申请一段连续的内存空间(一整块内存),并且大小是固定的
- 但是js数组并不是真正意义是的数组,它不是存在一段连续的内存中
- 查询修改:数组性能好
比如:int array[4]
假设它占用了从1201位置开始存储区域
当我尝试读array[2]的时候,它只要简单的计算就可以得出array[2]的位置
1201+ (2*4) = 1209 // 4表示一个int 4 个字节
- 插入删除:数组性能低
向数组开头或中间位置插入数据的时候,需要重新申请内存,进行大量元素的位移,所以性能很低。
那有没有一种数据结构,能够快速的实现插入和删除?这是就要提到链表了
-
链表重点在于元素在内存中不必是连续的空间
-
链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用,俗称指针
-
链表的特点
1. 链表没有大小限制,支持动态扩容,因为链表的每个节点第需要存储前驱、后驱节点的指针。
2. 缺点就是内存消耗会翻倍
- js 实现一个普通链表结构:
// 节点类
class Node {
constructor(element){
this.element = element;
this.next = null;
}
}
// 链表
class LinkedList {
constructor(element){
// 链表头
this.head = null;
// 链表长度
this.length = 0;
this.element = element;
this.next = null;
}
// 1. 链表的尾部追加元素
append(element){
// 创建节点
let node = new Node(element);
// 如果链表是空的
if(this.length === 0){
// 指向第一个节点
this.head = node;
}else{
// 通过head找到后面的节点
let current = this.head;
// 遍历是否是最后一个节点,next为空就是最后一个节点
while(current.next){
current = current.next;
}
// while 执行完毕后,current就已经是最后一个节点了
current.next = node;
}
// length 长度加 1
this.length += 1;
}
// 2. 获取链表的头
getHead(){
return this.head;
}
// 3. toString方法
toSting(){
let current = this.head;
let linkSting = '';
while(current){
linkSting += ','+current.element;
current = current.next;
}
// 返回的最终结果 [,a,] 从第一个位置开始
return linkSting.slice(1)
}
// 在任意位置插入元素
insert(element,position){
// 位置不能是负数
if(position<0 || position >this.length){
return false;
}
// postion > 1
let index = 0;
let current = this.head;
// 上一个节点
let privious = null;
// 创建元素
let node = new Node(element)
// 判断插入的是不是第一个
if(position === 0){
// 新结点next指向原来的head节点
node.next = this.head;
this.head = node;
}else{
while(index<position){
privious = current;
current = current.next;
index++;
}
node.next = current;
privious.next = node;
}
this.length += 1;
return true
}
// 获取对应位置的元素
get(position){
// 越界判断
if(position<0 || position >this.length){
return null;
}
// 获取对应的节点
let current = this.head;
let index = 0;
while(index<position){
current = current.next;
index++;
}
return current.element;
}
// 根据元素位置删除节点
removeAt(position){
if(position<0 || position >this.length){
return null;
}
// 定义一个变量,保存信息
let index = 0;
let current = this.head;
let pribious = null;
// 判断删除的是否是第一个节点
if(position === 0){
this.head = this.head.next;
}else{
while(index<position){
privious = current;
current = curent.next;
index++;
}
privious.next = current.next;
}
this.length -= 1;
// 返回移除的元素
return current.element;
}
}
const linkedList = new LinkedList()
linkedList.append('a')
linkedList.append('b')
linkedList.append('c')
console.log(linkedList.get(0))
linkedList.insert('d', 3)
console.log(linkedList.toSting())
console.log(linkedList)
- 运行结果:
链表还有一种双向链表,双向链表和普通链表的区别在于:
- 在链表中,一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向前一个元素