在上一篇文章中,我们说到了栈与队列,这章中,我们来看看另一个基础的知识,链表
链表
链表是存储的有序元素的集合,但是不同于数组连续存储方式,链表在存储中并不是采用连续放置的方式,而是每一个元素由节点和指向下一个元素的引用(其他语言中的指针)。所以我们可以得知链表的具体的形式应该是![链表]那么我们将上图进行拆分,我们可以很明确的得到一个链表应该有的数据结构,如下图:
从上图可以得知,一个链表我们可以将其拆分List类,和node类。所以我们可以先实现这两个类
Node类
/**
* @export
* @class LinkNode
*/
export default class LinkNode {
[x: string]: undefined;
public node:any;
public next: undefined;
constructor(node:any) {
this.node = node;
this.next = undefined;
}
}
List类
*
* @export
* @class LinkList
*/
import LinkNode from './LinkNode'
export default class LinkList {
public count: number;
public head: any | undefined;
constructor() {
this.count = 0;
this.head = undefined;
}
}
那么一个链表应该需要哪些方法呢?从个人理解中,同队列和栈一样需要以下方法
方法名 | 描述 |
---|---|
push(item) | 添加一个元素到链表中 |
insert(item,index) | 添加一个元素到链表指定位置中 |
getItemAt(index) | 获取链表指定位置元素 |
remove() | 移除最后一项 |
clear() | 清除链表 |
size() | 返回链表大小 |
removeAt(index) | 移除指定位置元素并返回 |
indexOf(item) | 获取指定元素所在的位置 |
下面我们来一一分析上面方法的具体实现
Push
向链尾添加元素。那么有两种情况,一种是最初始状态下添加,根据我们前面的list类,我们可以看出,当前数据格式为,也就是一个head指向的是undefined。那么我们添加一个数据后,我们的数据格式应该为什么样呢,第二种情况为在已有的数据格式的基础下添加,那么我们可以思考下怎么实现呢?
我们来分析下,也就是当前最后一个变成了倒数第二个,push进来的node为最后一个,所以只需要将当前最后一个的next指向当前传入的即可。如图
因此Push方法就此实现
/**
*尾部插入元素
*
* @param {*} item
* @memberof LinkList
*/
public push(item: any) {
let node = new LinkNode(item);
// 将节点的head设置成node
if (!this.head) {
this.head = node;
} else {
// 如果不是空,则需要找到链表最后一个node,然后将最后一个节点的next设置成当前传入的node
let currentNode: any = this.getItemAt(this.count - 1);
currentNode.next = node;
}
this.count++;
}
removeAt(index)
从指定位置移除元素。我们可以先来分析下,移除有三种情况
第二种移除尾部,类比移除头部,只需要将倒数第二个节点的next指向为undefined即可,如图
第三种就是删除中间位置,同理只需要将index所处的节点前一个节点的next指向index所处节点的next节点即可
具体实现如下
/**
* 移除指定位置的节点
* @param {number} index
* @returns {LinkNode}
* @memberof LinkList
*/
public removeAt(index: number): LinkNode | undefined {
if (index >= 0 && index <= this.count) {
let cuttentNode: LinkNode = this.getItemAt(index);
if (index === this.count - 1) {
let pretNode: LinkNode = this.getItemAt(index - 1);
pretNode.next = undefined;
} else if (index === 0) {
let headNode: LinkNode = this.getItemAt(0);
headNode.head = undefined;
} else {
let preNode: LinkNode = this.getItemAt(index - 1);
let currentNode: LinkNode = this.getItemAt(index);
preNode.next = currentNode.next;
}
this.count--;
return cuttentNode;
}
return undefined;
}
insert(item,index)
在指定位置插入元素。我们来分析分析,我们选择插入元素,那么同移除有三个位置,头部,尾部以及中间
第二种情况,从尾部插入,也就是将当前尾部元素的next指向我们插入的元素,我们插入的元素的next指向原有元素next指向的位置
第三种情况插入中间位置,那么也就是说假设我们将数据插入第二个位置,我们只需要将第一个位置节点的next指向插入的元素,插入的元素的next指向当前元素即可。如图
这个时候我们再回顾下从尾部插入,我们是否也可以跟中间节点一样,将最后一个节点的前一个节点的next赋值给当前节点的next,前一个节点的next指向插入的节点,貌似也行,那么代码实现为。
/**
* 任意位置插入元素
* @param {*} item
* @param {number} index
* @returns {boolean}
* @memberof LinkList
*/
public insert(item: any, index: number): boolean {
if (index >= 0 && index <= this.count) {
let node = new LinkNode(item);
if (index === 0) {
let currentNode = this.head;
node.next = currentNode;
this.head = node;
} else {
let preNode = this.getItemAt(index - 1);
node.next = preNode.next;
preNode.next = node;
}
this.count++;
return true;
}
return false;
}
至此一个普通链表的最重要的三个方法讲完了,下面附上一个链表的具体的实现.下一章我们进入另一个链表-双向链表
/**
* push
* insert
* getItemAt
* remove
* indexOf
* removeAt
* isEmpty
* size
*
*
* @export
* @class LinkList
*/
import LinkNode from './LinkNode'
export default class LinkList {
public count: number;
public head: any | undefined;
constructor() {
this.count = 0;
this.head = undefined;
}
/**
*尾部插入元素
*
* @param {*} item
* @memberof LinkList
*/
public push(item: any) {
let node = new LinkNode(item);
// 将节点的head设置成node
if (!this.head) {
this.head = node;
} else {
// 如果不是空,则需要找到链表最后一个node,然后将最后一个节点的next设置成当前传入的node
let currentNode: any = this.getItemAt(this.count - 1);
currentNode.next = node;
}
this.count++;
}
/**
* 任意位置插入元素
* @param {*} item
* @param {number} index
* @returns {boolean}
* @memberof LinkList
*/
public insert(item: any, index: number): boolean {
if (index >= 0 && index <= this.count) {
let node = new LinkNode(item);
if (index === 0) {
let currentNode = this.head;
node.next = currentNode;
this.head = node;
} else {
let preNode = this.getItemAt(index - 1);
node.next = preNode.next;
preNode.next = node;
}
this.count++;
return true;
}
return false;
}
/**
* 移除指定位置的节点
* @param {number} index
* @returns {LinkNode}
* @memberof LinkList
*/
public removeAt(index: number): LinkNode | undefined {
if (index >= 0 && index <= this.count) {
let cuttentNode: LinkNode = this.getItemAt(index);
if (index === this.count - 1) {
let pretNode: LinkNode = this.getItemAt(index - 1);
pretNode.next = undefined;
} else if (index === 0) {
let headNode: LinkNode = this.getItemAt(0);
headNode.head = undefined;
} else {
let preNode: LinkNode = this.getItemAt(index - 1);
let currentNode: LinkNode = this.getItemAt(index);
preNode.next = currentNode.next;
}
this.count--;
return cuttentNode;
}
return undefined;
}
/**
* 获取指定位置的节点
* @param {number} index
* @returns {(LinkNode|undefined)}
* @memberof LinkList
*/
public getItemAt(index: number): LinkNode | undefined | any {
if (index >= 0) {
if (index > this.count) {
return undefined;
} else if (index === 0) {
return this.head;
} else {
let currentNode = this.head;
while (index && currentNode.next) {
currentNode = currentNode.next;
index--;
}
return currentNode;
}
}
}
/**
* 返回item所在的位置
* @param {*} item
* @returns {number}
* @memberof LinkList
*/
public indexOf(item: any): number {
let currentNode = this.head;
let hasFind: boolean = false;
let index: number = -1;
while (!hasFind && currentNode) {
index++;
if (this.judgeObjectSame(item, currentNode.node)) {
hasFind = true;
} else {
currentNode = currentNode.next;
}
}
if (hasFind) {
return index;
} else {
return -1;
}
}
/**
* 移除指定元素
* @param {*} item
* @memberof LinkList
*/
public remove(item: any) {
let index = this.indexOf(item);
this.removeAt(index);
}
/**
* 获取链表size
* @returns {number}
* @memberof LinkList
*/
public size(): number {
return this.count;
}
/**
* 判断是否为空
* @returns
* @memberof LinkList
*/
public isEmpty() {
return this.size() === 0;
}
/**
* 判断两个对象是否相同
*
* @param {*} firstObj
* @param {*} secondObj
* @returns
* @memberof StackObject
*/
public judgeObjectSame(firstObj: any, secondObj: any) {
const aProps = Object.getOwnPropertyNames(JSON.parse(JSON.stringify(firstObj)));
const bProps = Object.getOwnPropertyNames(JSON.parse(JSON.stringify(secondObj)));
if (aProps.length !== bProps.length) {
return false;
}
for (let i = 0; i < aProps.length; i++) {
const propName = aProps[i]
const propA = firstObj[propName]
const propB = secondObj[propName]
if ((typeof (propA) === 'object') && propA !== null) {
if (this.judgeObjectSame(propA, propB)) {
} else {
return false;
}
} else if (propA !== propB) {
return false;
} else { }
}
return true;
}
}