链表
链表是一种物理存储单元上非线性、非连续性的数据结构(它在数据逻辑上是线性的),它的每个节点由两个域组成:数据域和指针域。数据域中存储实际数据,指针域则存储着指针信息,指向链表中的下一个元素或者上一个元素。正是由于指针的存在,链表的存储在物理单元是非连续性的。入口节点称为链表头节点也就是head。
链表的优点和缺点同样明显。和线性表相比,链表在添加和删除节点上的效率更高,因为其只需要修改指针信息即可完成操作,而不像线性表(数组)那样需要移动元素。同样的,链表的长度在理论上也是无限的(在存储器容量范围内),并可以动态变化长度,相比线性表优势很大。 相应的,由于线性表无法随机访问节点,只能通过指针顺着链表进行遍历查询来访问,故其访问数据元素的效率比较低。
- 存储空间不固定,可灵活扩充
- 插入、删除不需要移动元素,效率较高
单向链表
单链表的指针域只能指向节点的下一个节点。
单向链表的方法
- getHead() 获取头元素
- append(element) 在链表最后追加节点
- insert(index, element) 根据索引index, 在索引位置插入节点
- get(index) 根据索引获取节点信息
- indexOf(element) 获取某个节点的索引位置
- update(position, element) 修改某个位置的元素
- removeAt(index) 删除指定索引节点
- remove(element) 删除节点
- isEmpty() 判空
- size() 返回节点长度
- toString() 返回正向遍历节点字符串形式
单向链表的实现
// 节点类
class Node {
// data:数据;next:指向的下一个节点
constructor(data) {
this.data = data;
this.next = null;
}
}
// 封装链表类
class LinkedList {
constructor() {
// 属性
this.head = null; // 默认情况下head是null
this.tail = null; // 默认情况下尾节点tail是null
this.length = 0; // 记录链表的长度
}
// 获取头元素
getHead() {
return this.head;
}
// 获取尾元素
getTail() {
return this.tail;
}
// 向列表尾部添加一个新的项
append(data) {
let newNode = new Node(data);
// 判断是否添加的是第一个节点
if (this.length === 0) {
// 是第一个节点:直接把新元素加在后面
this.head = newNode;
this.tail = newNode;
} else {
// 不是第一个节点:找到最后一个节点,让最后一个节点的next指向新的节点
this.tail.next = newNode;
this.tail = newNode;
}
// 添加新节点后长度加1
this.length += 1;
}
// 任意位置插入
insert(position, data) {
// 对position进行越界判断:不能是负数;长度不能超过现有元素个数+1(即length=元素个数)
if (position < 0 || position > this.length) {
return false;
}
// 根据data创建newNode
let newNode = new Node(data);
// 原链表为空
if (this.length === 0) {
this.head = newNode;
this.tail = newNode;
} else {
// 原链表不为空
if (position === 0) {
// 情况1:插入的位置是第一个时,头节点指向节点改变,尾节点指向节点不变
newNode.next = this.head;
this.head = newNode;
} else if (position === this.length) {
// 情况2:插入的位置是最后一个节点的后面,头节点指向节点不变,尾节点指向节点改变
this.tail.next = newNode;
this.tail = newNode;
} else {
//情况3:插入的位置既不是第一个也不是最后一个的后面,头节点和尾节点指向节点都不变
// 索引位置距离头节点近就从头节点向后遍历,否则就从尾节点向前遍历
let current = this.head; // 指向第一个节点
let preNode = null; //上一个节点
let index = 0; // index指向的节点位置就是要插入的位置
// index++ < position是先比较大小后index加一
while (index++ < position) {
preNode = current;
current = current.next; // 指向要插入的位置的节点
}
newNode.next = current;
preNode.next = newNode;
}
}
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;
}
return current.data;
}
// 返回元素在列表中的索引
indexOf(data) {
let current = this.head;
let index = 0;
while (current) {
if (current.data === data) {
return index;
}
current = current.next;
index += 1;
}
return -1;
}
// 修改某个位置的元素
update(position, newData) {
// 越界判断
if (position < 0 || position >= this.length) {
return false;
}
// 查找正确的节点
let current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
current.data = newData;
return true;
}
// 从列表的特定位置移除一项
removeAt(position) {
// 越界判断
if (position < 0 || position >= this.length) {
return null;
}
let current = this.head;
// 原链表只有一个节点
if (this.length === 1) {
this.head = null;
this.tail = null;
} else {
// 原链表有多个节点
// 删除第一个节点
if (position === 0) {
this.head = this.head.next;
} else {
// 删除中间节点
let preNode = null; // 上一个节点
let index = 0;
while (index++ < position) {
preNode = current;
current = current.next;
}
preNode.next = current.next;
if (position === this.length - 1) {
this.tail = preNode;
}
}
}
// 长度-1
this.length -= 1;
return current.data;
}
// 从列表中移除一项
remove(data) {
// 获取data在列表中的位置
let position = this.indexOf(data);
// 根据位置信息删除节点
return this.removeAt(position);
}
// 判空
isEmpty() {
return this.length === 0;
}
// 节点个数
size() {
return this.length;
}
// 转化为字符串
toString() {
if (this.isEmpty()) {
return "";
}
let temp = [];
let current = this.head;
temp.push(current.data);
while (current.next) {
current = current.next;
temp.push(current.data);
}
return temp.toString();
}
}
双向链表
每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。 双链表即可以向前查询,也可以向后查询。
双向链表有头节点和尾节点,如果根据索引位置查找时,索引位置距离头节点近就从头节点向后遍历,否则就从尾节点向前遍历。
双向链表的方法
- getHead() 获取头元素
- getTail() 获取尾元素
- append(element) 在链表最后追加节点
- insert(index, element) 根据索引index, 在索引位置插入节点
- get(index) 根据索引获取节点信息
- indexOf(element) 获取某个节点的索引位置
- update(position, element) 修改某个位置的元素
- removeAt(index) 删除指定索引节点
- remove(element) 删除节点
- isEmpty() 判空
- size() 返回节点长度
- toString() 返回正向遍历节点字符串形式
- forwardString() 返回正向遍历节点字符串形式
- backwordString() 返回反向遍历的节点的字符串形式
双向链表的实现
// 节点类
class Node {
constructor(data) {
// 节点数据
this.data = data;
// 指向上一个节点
this.pre = null
// 指向下一个节点
this.next = null;
}
}
// 封装链表类
class LinkedList {
constructor() {
// 属性
this.head = null; // 默认情况下头节点head是null
this.tail = null; // 默认情况下尾节点tail是null
this.length = 0; // 记录链表的长度
}
// 获取头元素
getHead() {
return this.head;
}
// 获取尾元素
getTail() {
return this.tail;
}
// 向列表尾部添加一个新的项
append(data) {
let newNode = new Node(data);
// 判断是否添加的是第一个节点
if (this.length === 0) {
// 添加的是第一个节点
this.head = newNode;
this.tail = newNode
} else {
// 添加的不是第一个节点
// 注意改变变量指向的顺序,最后修改tail指向,这样未修改前tail始终指向原链表的最后一个节点。
newNode.pre = this.tail;
this.tail.next = newNode;
this.tail = newNode;
}
// 添加新节点后长度加1
this.length += 1;
}
// 任意位置插入
insert(position, data) {
// 对position进行越界判断:不能是负数;长度不能超过现有元素个数+1(即length=元素个数)
if (position < 0 || position > this.length) {
return false;
}
// 根据data创建newNode
let newNode = new Node(data);
// 原链表为空
if (this.length === 0) {
this.head = newNode;
this.tail = newNode;
} else {
// 原链表不为空
if (position === 0) {
// 情况1:插入的位置是第一个时,头节点指向节点改变,尾节点指向节点不变
newNode.next = this.head;
this.head.pre = newNode;
this.head = newNode;
} else if (position === this.length) {
// 情况2:插入的位置是最后一个节点的后面,头节点指向节点不变,尾节点指向节点改变
newNode.pre = this.tail;
this.tail.next = newNode;
this.tail = newNode;
} else {
//情况3:插入的位置既不是第一个也不是最后一个的后面,头节点和尾节点指向节点都不变
// 索引位置距离头节点近就从头节点向后遍历,否则就从尾节点向前遍历
let current = null;
if (((this.length - 1) / 2) >= position) {
let index = 0; // index指向的节点位置就是要插入的位置
current = this.head; // 指向第一个节点
// index++ < position是先比较大小后index加一
while (index++ < position) {
current = current.next; // 指向要插入的位置的节点
}
} else {
let index = this.length - 1; // index指向的节点位置就是要插入的位置
current = this.tail; // 指向第一个节点
while (index-- > position) {
current = current.pre; // 指向要插入的位置的节点
}
}
newNode.pre = current.pre;
newNode.next = current;
current.pre.next = newNode;
current.pre = newNode;
}
}
// length+1
this.length += 1;
return true;
}
// 获取对应位置的元素
get(position) {
// 越界判断
if (position < 0 || position >= this.length) {
return null;
}
let current = null;
// 双向链表可以向前遍历,也可向后遍历
// 索引位置距离头节点近就从头节点向后遍历,否则就从尾节点向前遍历
if (((this.length - 1) / 2) >= position) {
// 获取对应数据
current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
} else {
// 获取对应数据
current = this.tail;
let index = this.length - 1;
while (index-- > position) {
current = current.pre;
}
}
return current.data;
}
// 返回元素在列表中的索引
indexOf(data) {
let current = this.head;
let index = 0;
while (current) {
if (current.data === data) {
return index;
}
current = current.next;
index += 1;
}
return -1;
}
// 修改某个位置的元素
update(position, newData) {
// 越界判断
if (position < 0 || position >= this.length) {
return false;
}
if (((this.length - 1) / 2) >= position) {
// 查找正确的节点
let current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
current.data = newData;
} else {
// 查找正确的节点
let current = this.tail;
let index = this.length - 1;
while (index-- > position) {
current = current.pre;
}
current.data = newData;
}
return true;
}
// 从列表的特定位置移除一项
removeAt(position) {
// 越界判断
if (position < 0 || position >= this.length) {
return null;
}
let current = null;
// 原链表只有一个节点
if (this.length === 1) {
current = this.head;
this.head = null;
this.tail = null;
} else {
// 原链表有多个节点
// 删除第一个节点
if (position === 0) {
current = this.head;
this.head.next.pre = null;
this.head = this.head.next;
} else if (position === this.length - 1) { // 删除最后一个节点
current = this.tail;
this.tail.pre.next = null;
this.tail = this.tail.pre;
} else {
if (((this.length - 1) / 2) >= position) {
// 删除中间节点
let index = 0;
while (index++ < position) {
current = current.next;
}
} else {
// 删除中间节点
let index = this.length - 1;
while (index-- > position) {
current = current.pre;
}
}
current.pre.next = current.next;
current.next.pre = current.pre;
}
}
// 长度-1
this.length -= 1;
return current.data;
}
// 从列表中移除一项
remove(data) {
// 获取data在列表中的位置
let position = this.indexOf(data);
// 根据位置信息删除节点
return this.removeAt(position);
}
// 判空
isEmpty() {
return this.length === 0;
}
// 节点个数
size() {
return this.length;
}
// 转化为字符串
toString() {
if (this.isEmpty()) {
return "";
}
let temp = [];
let current = this.head;
temp.push(current.data);
while (current.next) {
current = current.next;
temp.push(current.data);
}
return temp.toString();
}
// 返回正向遍历节点字符串形式
forwardString() {
return this.toString();
}
// 返回反向遍历的节点的字符串形式
backwordString() {
if (this.isEmpty()) {
return "";
}
let temp = [];
let current = this.tail;
temp.push(current.data);
while (current.pre) {
current = current.pre;
temp.push(current.data);
}
return temp.toString();
}
}
循环链表
头节点与尾节点相连的,就构成循环链表。其中,单向链表首尾相连构成单向循环链表,双向链表首尾相连构成双向循环链表。
单向循环列表
单向循环链表与单向链表的区别在于尾节点的下一个元素指向头节点。
单向循环列表实现
// 节点类
class Node {
// data:数据;next:指向的下一个节点
constructor(data) {
this.data = data;
this.next = null;
}
}
// 封装链表类
class LinkedList {
constructor() {
// 属性
this.head = null; // 默认情况下head是null
this.tail = null; // 默认情况下尾节点tail是null
this.length = 0; // 记录链表的长度
}
// 获取头元素
getHead() {
return this.head;
}
// 获取尾元素
getTail() {
return this.tail;
}
// 向列表尾部添加一个新的项
append(data) {
let newNode = new Node(data);
// 判断是否添加的是第一个节点
if (this.length === 0) {
// 是第一个节点:直接把新元素加在后面
this.head = newNode;
this.tail = newNode;
this.tail.next = this.head;
} else {
// 不是第一个节点:找到最后一个节点,让最后一个节点的next指向新的节点
this.tail.next = newNode;
this.tail = newNode;
this.tail.next = this.head;
}
// 添加新节点后长度加1
this.length += 1;
}
// 任意位置插入
insert(position, data) {
// 对position进行越界判断:不能是负数;长度不能超过现有元素个数+1(即length=元素个数)
if (position < 0 || position > this.length) {
return false;
}
// 根据data创建newNode
let newNode = new Node(data);
// 原链表为空
if (this.length === 0) {
this.head = newNode;
this.tail = newNode;
this.tail.next = this.head;
} else {
// 原链表不为空
if (position === 0) {
// 情况1:插入的位置是第一个时,头节点指向节点改变,尾节点指向节点不变
newNode.next = this.head;
this.head = newNode;
} else if (position === this.length) {
// 情况2:插入的位置是最后一个节点的后面,头节点指向节点不变,尾节点指向节点改变
this.tail.next = newNode;
this.tail = newNode;
this.tail.next = this.head;
} else {
//情况3:插入的位置既不是第一个也不是最后一个的后面,头节点和尾节点指向节点都不变
// 索引位置距离头节点近就从头节点向后遍历,否则就从尾节点向前遍历
let current = this.head; // 指向第一个节点
let preNode = null; //上一个节点
let index = 0; // index指向的节点位置就是要插入的位置
// index++ < position是先比较大小后index加一
while (index++ < position) {
preNode = current;
current = current.next; // 指向要插入的位置的节点
}
newNode.next = current;
preNode.next = newNode;
}
}
// length+1
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;
}
return current.data;
}
// 返回元素在列表中的索引
indexOf(data) {
let current = this.head;
let index = 0;
while (current) {
if (current.data === data) {
return index;
}
current = current.next;
index += 1;
}
return -1;
}
// 修改某个位置的元素
update(position, newData) {
// 越界判断
if (position < 0 || position >= this.length) {
return false;
}
// 查找正确的节点
let current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
current.data = newData;
return true;
}
// 从列表的特定位置移除一项
removeAt(position) {
// 越界判断
if (position < 0 || position >= this.length) {
return null;
}
let current = this.head;
// 原链表只有一个节点
if (this.length === 1) {
this.head = null;
this.tail = null;
} else {
// 原链表有多个节点
// 删除第一个节点
if (position === 0) {
this.head = this.head.next;
} else {
// 删除中间节点
let preNode = null; // 上一个节点
let index = 0;
while (index++ < position) {
preNode = current;
current = current.next;
}
preNode.next = current.next;
if (position === this.length - 1) {
this.tail = preNode;
this.tail.next = this.head;
}
}
}
// 长度-1
this.length -= 1;
return current.data;
}
// 从列表中移除一项
remove(data) {
// 获取data在列表中的位置
let position = this.indexOf(data);
// 根据位置信息删除节点
return this.removeAt(position);
}
// 判空
isEmpty() {
return this.length === 0;
}
// 节点个数
size() {
return this.length;
}
// 转化为字符串
toString() {
if (this.isEmpty()) {
return "";
}
let index = 0;
let temp = [];
let current = this.head;
temp.push(current.data);
while (current.next && temp.length < this.size()) {
current = current.next;
temp.push(current.data);
}
return temp.toString();
}
}
双向循环列表
双向循环链表与双向链表的区别在于尾节点的下一个元素指向头节点,头节点的上一个元素指向尾节点。
双向循环列表实现
// 节点类
class Node {
constructor(data) {
// 节点数据
this.data = data;
// 指向上一个节点
this.pre = null
// 指向下一个节点
this.next = null;
}
}
// 封装链表类
class LinkedList {
constructor() {
// 属性
this.head = null; // 默认情况下头节点head是null
this.tail = null; // 默认情况下尾节点tail是null
this.length = 0; // 记录链表的长度
}
// 获取头元素
getHead() {
return this.head;
}
// 获取尾元素
getTail() {
return this.tail;
}
// 向列表尾部添加一个新的项
append(data) {
let newNode = new Node(data);
// 判断是否添加的是第一个节点
if (this.length === 0) {
// 添加的是第一个节点
this.head = newNode;
this.tail = newNode;
this.head.pre = this.tail;
this.tail.next = this.head;
} else {
// 添加的不是第一个节点
// 注意改变变量指向的顺序,最后修改tail指向,这样未修改前tail始终指向原链表的最后一个节点。
newNode.pre = this.tail;
this.tail.next = newNode;
this.tail = newNode;
this.head.pre = this.tail;
this.tail.next = this.head;
}
// 添加新节点后长度加1
this.length += 1;
}
// 任意位置插入
insert(position, data) {
// 对position进行越界判断:不能是负数;长度不能超过现有元素个数+1(即length=元素个数)
if (position < 0 || position > this.length) {
return false;
}
// 根据data创建newNode
let newNode = new Node(data);
// 原链表为空
if (this.length === 0) {
this.head = newNode;
this.tail = newNode;
this.head.pre = this.tail;
this.tail.next = this.head;
} else {
// 原链表不为空
if (position === 0) {
// 情况1:插入的位置是第一个时,头节点指向节点改变,尾节点指向节点不变
newNode.next = this.head;
this.head.pre = newNode;
this.head = newNode;
this.head.pre = this.tail;
this.tail.next = this.head;
} else if (position === this.length) {
// 情况2:插入的位置是最后一个节点的后面,头节点指向节点不变,尾节点指向节点改变
newNode.pre = this.tail;
this.tail.next = newNode;
this.tail = newNode;
this.head.pre = this.tail;
this.tail.next = this.head;
} else {
//情况3:插入的位置既不是第一个也不是最后一个的后面,头节点和尾节点指向节点都不变
// 索引位置距离头节点近就从头节点向后遍历,否则就从尾节点向前遍历
let current = null;
if (((this.length - 1) / 2) >= position) {
let index = 0; // index指向的节点位置就是要插入的位置
current = this.head; // 指向第一个节点
// index++ < position是先比较大小后index加一
while (index++ < position) {
current = current.next; // 指向要插入的位置的节点
}
} else {
let index = this.length - 1; // index指向的节点位置就是要插入的位置
current = this.tail; // 指向第一个节点
while (index-- > position) {
current = current.pre; // 指向要插入的位置的节点
}
}
newNode.pre = current.pre;
newNode.next = current;
current.pre.next = newNode;
current.pre = newNode;
}
}
// length+1
this.length += 1;
return true;
}
// 获取对应位置的元素
get(position) {
// 越界判断
if (position < 0 || position >= this.length) {
return null;
}
let current = null;
// 双向链表可以向前遍历,也可向后遍历
// 索引位置距离头节点近就从头节点向后遍历,否则就从尾节点向前遍历
if (((this.length - 1) / 2) >= position) {
// 获取对应数据
current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
} else {
// 获取对应数据
current = this.tail;
let index = this.length - 1;
while (index-- > position) {
current = current.pre;
}
}
return current.data;
}
// 返回元素在列表中的索引
indexOf(data) {
let current = this.head;
let index = 0;
while (current) {
if (current.data === data) {
return index;
}
current = current.next;
index += 1;
}
return -1;
}
// 修改某个位置的元素
update(position, newData) {
// 越界判断
if (position < 0 || position >= this.length) {
return false;
}
if (((this.length - 1) / 2) >= position) {
// 查找正确的节点
let current = this.head;
let index = 0;
while (index++ < position) {
current = current.next;
}
current.data = newData;
} else {
// 查找正确的节点
let current = this.tail;
let index = this.length - 1;
while (index-- > position) {
current = current.pre;
}
current.data = newData;
}
return true;
}
// 从列表的特定位置移除一项
removeAt(position) {
// 越界判断
if (position < 0 || position >= this.length) {
return null;
}
let current = null;
// 原链表只有一个节点
if (this.length === 1) {
current = this.head;
this.head = null;
this.tail = null;
} else {
// 原链表有多个节点
// 删除第一个节点
if (position === 0) {
current = this.head;
this.head.next.pre = null;
this.head = this.head.next;
} else if (position === this.length - 1) { // 删除最后一个节点
current = this.tail;
this.tail.pre.next = null;
this.tail = this.tail.pre;
this.head.pre = this.tail;
this.tail.next = this.head;
} else {
if (((this.length - 1) / 2) >= position) {
// 删除中间节点
let index = 0;
while (index++ < position) {
current = current.next;
}
} else {
// 删除中间节点
let index = this.length - 1;
while (index-- > position) {
current = current.pre;
}
}
current.pre.next = current.next;
current.next.pre = current.pre;
}
}
// 长度-1
this.length -= 1;
return current.data;
}
// 从列表中移除一项
remove(data) {
// 获取data在列表中的位置
let position = this.indexOf(data);
// 根据位置信息删除节点
return this.removeAt(position);
}
// 判空
isEmpty() {
return this.length === 0;
}
// 节点个数
size() {
return this.length;
}
// 转化为字符串
toString() {
if (this.isEmpty()) {
return "";
}
let temp = [];
let current = this.head;
temp.push(current.data);
while (current.next && temp.length < this.size()) {
current = current.next;
temp.push(current.data);
}
return temp.toString();
}
// 返回正向遍历节点字符串形式
forwardString() {
return this.toString();
}
// 返回反向遍历的节点的字符串形式
backwordString() {
if (this.isEmpty()) {
return "";
}
let temp = [];
let current = this.tail;
temp.push(current.data);
while (current.pre && temp.length < this.size()) {
current = current.pre;
temp.push(current.data);
}
return temp.toString();
}
}
约瑟夫环问题
已知 n 个人(以编号1,2,3…n分别表示)围成一圈,首先第 1 个人从 1 开始顺序报数,报到第 m 个人,令其出列,然后再从下一个人开始从 1 开始报数,报到第 m 个人,再令其出列,依此规律重复下去,直到只剩下一个人为止,求出列顺序。
循环链表解决约瑟夫环问题
function getDequeueList(n, m) {
if (n < 1 || m < 1) {
return "";
}
let list = new LinkedList();
for (let index = 0; index < n; index++) {
list.append(index + 1);
}
// 出列数组
let dequeueList = [];
// 初始报数
let _m = 1;
// 指向当前报数人的位置
let position = 0;
while (list.size() > 1) {
if (_m === m) {
dequeueList.push(list.removeAt(position));
_m = 1;
// 当前报数人被移出后,索引已经指向最新的报数人
position = position % list.size();
} else {
_m++;
position = (position + 1) % list.size();
}
}
return dequeueList;
}