1. 链表
可以用来存储一系列元素,不同于数组,链表中的元素在内存中的地址一般是不连续的。相比较于数组可以方便的进行扩容,在插入和删除元素方面相比较于数组具有明显的优势。
2. 优势
- 内存动态管理
- 链表不必在创建的时候就确定其大小,可以无限的延伸下去
- 在插入和删除操作上,时间复杂度可以达到O(1),效率很高
3. 劣势
- 无法使用下标访问元素,必须从头依次查找,直到一个接一个的找到对应的元素。
4. 应用举例
例如:在java中LinkedList就是使用链表实现的。
5. 链表上的操作
1. 在链表的尾部添加一个新元素:append(element)
2. 在链表的特定位置插入一个元素:insert(position, element)
3. 获取对应位置的元素:get(position)
4. 返回某个元素在此链表中的索引,如果没有找到此元素的话就返回-1:indexOf(element)
5. 更新某个位置的元素的值:update(element)
6. 移除链表中的某一项:remove(element)
7. 通过索引移除链表中的某一项:remvoeAt(position)
8. 判断链表元素是否为空:isEmpty()
9. 返回链表中元素的个数:size()
10. 以指定的方式打印链表中的元素:toString(cb)
6. 链表上的属性
1. 链表头:指向链表的第一个元素(head),如果链表中没有元素则指向null;
2. 链表长度:表示当前链表的长度(length),如果链表中没有元素则为0;
3. 内部类:用来创建链表中的元素(_Node),链表自己维护的一个静态属性。
---------内部类
内部类中有两个属性,next和ele,分别表示本元素的下一个元素和本元素中存储的数据。
7. 链表实现
class _Node<T> {
// 内部的节点的next属性在刚开始的时候为null,表示后继没有
// 其后继需要手动指定,如果一直不指定则认为是在链表的尾部
next: null | _Node<T> = null;
constructor(public ele: T){}
}
class LinkedList<T> {
head: null | _Node<T> = null;
length: number = 0;
constructor(private _Node: new (ele: T)=>_Node<T>){}
// 添加一个新的元素
append(ele: T): void {
// 使用内部类创建一个新的元素
const newNode = new this._Node(ele);
// 如果此元素是第一个元素则直接将链表头指向此元素即可
if(this.isEmpty()){
// 表示链表为空
this.head = newNode;
} else {
// 否则的话找到链表中的最后一个元素然后让最后一个元素指向此新元素
let current = this.head!;
while(current.next){
current = current.next;
}
current.next = newNode;
}
// 最后将链表的长度加一
this.length+=1;
}
// 向链表的某个位置插入一个元素
insert(position: number, ele: T): null | T{
// 插入位置越界判断
if(!this.isValid(position)) return null;
// 没有越界的话创建新的内部节点然后插入到对应的位置中
// 不要忘记将链表的长度增加1
const newNode = new this._Node(ele);
// 判断要插入的位置是不是第一个
if(this.isEmpty()){
this.head = newNode;
} else {
let current = this.head;
let index = 0;
// 找到相应的位置,将新创建的node插入进去
while(current){
if(index+1 === position){
// prev和next都是相对于newNode而言的
const prev = current;
const next = current.next;
prev.next = newNode;
newNode.next = next;
break;
}
current = current.next;
index++;
}
}
// 插入完成之后不要忘记修改链表的长度
this.length+=1;
return newNode.ele;
}
// 根据序列号删除对应的链表上的元素
removeAt(position: number): null | T{
if(!this.isValid(position)) return null;
let current = this.head;
let index = 0;
while(current){
if(index+1 === position){
const prev = current;
const target = prev.next;
const next = target?.next || null;
prev.next = next;
// 不要忘了改变链表的长度
this.length--;
return target?.ele || null;
}
current = current.next;
index += 1;
}
return null;
}
// 根据ele的值删除链表中对应的元素
remove(ele: T): null | T{
// 复用已有的方法
const position = this.indexOf(ele);
// 删除对应位置元素
return this.removeAt(position);
}
// 使用序列号更新链表中的某个元素
update(position: number, newEle: any): boolean{
// 对于下标类的一律要判断是否越界
if(!this.isValid(position)) return false;
let current = this.head;
let index = 0;
while(current){
if(index === position){
current.ele = newEle;
break;
}
current =current.next;
index++;
}
// 注意这里返回的是current也就是node上的ele属性,而不是node本身,因为node是链表内部元素
return true;
}
// 根据序列号获取链表上对应的元素
get(position: number): null | T{
// 对于下标类的一律要判断是否越界
if(!this.isValid(position)) return null;
let current = this.head;
let index = 0;
while(current){
if(index === position){
break;
}
current = current.next;
index++;
}
// 注意这里返回的是current也就是node上的ele属性,而不是node本身,因为node是链表内部元素
return current!.ele;
}
// 根据数据找到对应元素的序列号
indexOf(ele: T): number {
let current = this.head;
let index = 0;
while(current){
if(current.ele === ele){
return index;
}
current = current.next;
index += 1;
}
return -1;
}
// 判断链表是否为空
isEmpty(): boolean{
return !this.length;
}
// 返回链表的长度
size(): number {
return this.length;
}
// 判断下表是否越界
isValid(position: number): boolean{
return (position < 0 || position > this.size()) ? false : true;
}
// 打印链表中的值
toString(): string{
let current = this.head;
let listString = "";
while(current){
listString += "".concat(current.ele as unknown as string).concat(" ");
current = current.next;
}
return listString;
}
}
// test
const arr = new LinkedList<string>(_Node);
arr.append('a');
arr.append('b');
arr.append('c');
arr.append('d');
arr.append('e');
console.log(arr.toString()); // a b c d e
arr.insert(2,'x');
console.log(arr.toString()); // a b x c d e
arr.removeAt(3);
console.log(arr.toString()); // a b x d e
arr.update(3, 'y');
console.log(arr.toString()); // a b x y e
console.log(arr.get(3)); // y
arr.remove('b');
console.log(arr.toString()); // a x y e
arr.remove('t');
console.log(arr.toString()); // a x y e
console.log(arr.indexOf('e')); // 3
console.log(arr.size()); // 4
8. 要点总结
// 1. 从头开始遍历链表的时候的初始化值
let current = this.head;
let index = 0;
// 2. current.next驱动挨个查找元素
while(current){
if(){
...;
break'
}
current = current.next;
index += 1;
}
// 3. 找到链表尾部
while(current.next){
...;
current = current.next;
}
// 4. 找到对应位置的前一个节点(找到前一个节点就可以通过next找到后一个节点,这样做是为了在某个位置删除或者插入某个节点)
while(index+1 === position){
if(){
...;
break;
};
current = current.next;
index++;
}
// 5. 找到对应位置的节点(找到本节点就是为了修改本节点的内容或者过去本节点的信息)
while(index === position){
if(){
...;
break;
};
current = current.next;
index++;
}
// 6. 使用下标/索引的时候一定要先进性越界判断
(position < 0 || position > this.size()) ? false : true;
9. 小结一下
在某些场景下,如果频繁的使用到插入和删除操作,而很少使用索引查询某个元素,在此时就应该使用链表代替数组,这样可以极大的提高效率。