数据结构(二):单向链表、双向链表

news2024/9/29 19:26:20

数据结构(二)

  • 一、什么是链表
    • 1.数组的缺点
    • 2.链表的优点
    • 3.链表的缺点
    • 4.链表和数组的区别
  • 二、封装单向链表
    • 1. append方法:向尾部插入节点
    • 2. toString方法:链表元素转字符串
    • 3. insert方法:在任意位置插入数据
    • 4.get获取某个位置的元素
    • 5.indexOf根据元素值返回元素位置
    • 6.update更新某个位置的元素
    • 7.removeAt删除某个位置的节点
    • 8.remove删除指定data的元素
    • 9.判断是否为空、输出长度
  • 三、封装双向链表
    • 1.什么是双向链表
    • 2.封装双向链表:结构搭建
    • 3.append向尾部插入元素
      • (1)链表为空,添加的是第一个节点
      • (2)添加的不是第一个节点,那么改变相关指向
    • 4.toString链表数据转换为字符串
    • 5.insert向任意位置插入节点
    • 6.get获取某个位置的元素值
    • 7.indexOf根据某个元素值获取该节点位置
    • 8.update更新某个元素
    • 9.removeAt删除某个位置的节点
    • 10.remove删除某个元素
    • 11.其他方法

一、什么是链表

链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有的语言称为指针或连接)组成。类似于火车头,一节车厢载着乘客(数据),通过节点连接另一节车厢。
在这里插入图片描述
在这里插入图片描述

  • head属性指向链表的第一个节点;
  • 链表中的最后一个节点指向null;
  • 当链表中一个节点也没有的时候,head直接指向null;

1.数组的缺点

1、数组的创建通常需要申请一段连续的内存空间(一整块内存),并且大小是固定的。所以当原数组不能满足容量需求时,需要扩容(一般情况下是申请一个更大的数组,比如2倍,然后将原数组中的元素复制过去)。
2、在数组的开头或中间位置插入数据的成本很高,需要进行大量元素的位移。

2.链表的优点

1、链表中的元素在内存中不必是连续的空间,可以充分利用计算机的内存,实现灵活的内存动态管理。
2、链表不必在创建时就确定大小,并且大小可以无限地延伸下去。
3、链表在插入和删除数据时,时间复杂度可以达到O(1),相对数组效率高很多。

3.链表的缺点

1、链表访问任何一个位置的元素时,都需要从头开始访问,需要顺着指针一个一个找(无法跳过第一个元素访问任何一个元素)。
2、无法通过下标值直接访问元素,需要从头开始一个个数组内和指针访问,直到找到对应的元素,这也是和数组最明显的区别。
3、虽然可以轻松地到达下一个节点,但是回到前一个节点是很难的

4.链表和数组的区别

二、封装单向链表

首先定义一个节点类,用来存储新声明的节点

//定义一个节点类,用来声明新的节点
class Node {
    constructor(data) {
        this.data = data; //数据
        this.next = null; //指针
    }
}

开始封装,声明两个属性,分别是头部指针和长度。

1. append方法:向尾部插入节点

1、先声明一个新的节点,判断链表是否有数据。如果没有数据,直接head指向新节点。
2、如果有数据,那么就要声明一个变量current来存储当前的指针。这个指针第一次肯定指向第一个元素(看代码理解),然后循环看一下current.next是否有指向,如果有,就改变currentcurrent.next,直到current.next为空,说明已经到链表尾部了。
3、最后,将current指针指向新节点,搞定。
在这里插入图片描述

//封装一个单向链表
class LinkedList {
    constructor() {
        this.head = null;
        this.length = 0;
    }

    //将元素插入链表尾部的方法
    append(data) {
        //1.声明一个新的节点
        let newNode = new Node(data);
        if(this.length == 0) {
            //2.1如果链表为空,那么head直接指向新节点
            this.head = newNode;
        }else {
            //2.2如果链表不为空,那么循环遍历,直到指针指向最后一个节点
            let current = this.head; //这里每次都是指向第一个元素
            while(current.next) {
                current = current.next;
            }
            current.next = newNode;
        }
        //3.添加完之后length+1
        this.length++;
    }
}

2. toString方法:链表元素转字符串

//转字符串的方法
toString() {
    let result = '';
    let current = this.head;
    while (current) {
        result += current.data + ' '; //如果有就拼接
        current = current.next; //指针指向后面的元素
    }

    return result;
}

顺便测试一下append方法好不好使

let list = new LinkedList();
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
console.log(list.toString()); //DantinZhang zzy 元素 

3. insert方法:在任意位置插入数据

情况1:在position为0的位置插入数据,那么这样的话就要

1、先让插入的新元素指向原来的第一个元素
2、然后再让head指向插入的新元素。

在这里插入图片描述

情况2:往其他位置插入

1、首先应该拿到该位置的那个元素
2、让新节点指向该元素
3、让该位置前边的元素指向新节点

在这里插入图片描述
代码(仔细看注释):

//任意位置插入元素
insert(position, data) {
    //1.对position进行越界判断
    //position==this.length说明是往最后插,如果比它大就不行了
    if(position < 0 || position > this.length) return false;
    //2.生成新节点
    let newNode = new Node(data);
    //3.对position的不同情况进行判断
    //3.1如果插入的位置是第一个(position=0)
    if(position == 0) {
        newNode.next = this.head; //先保存原来的第一个元素
        this.head = newNode; //再把新节点给head
        //注意:上面这两步顺序不能反,不然就找不到原来的第一个元素了
    }else {
        //如果是往其他位置插入元素
        let current = this.head;  //拿到第一个元素
        let previous = null; //声明变量存储position前边的元素
        //循环查找,直到找到这个位置的元素
        for(let i = 0; i < position; i++) {
            previous = current; //保存上一个位置
            current = current.next;  //保存当前位置
        }
        //循环结束后current已经是position位置的元素
        newNode.next = current; //让新节点指向当前位置节点(挤到后边去)
        previous.next = newNode; //让前一个节点指向新节点
    }
    
    //4.长度加1
    this.length++;
    return true;
}

测试代码:

let list = new LinkedList();
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
list.insert(2,'ht');
console.log(list.toString()); //DantinZhang zzy ht 元素 

4.get获取某个位置的元素

传入位置返回当前位置元素。
主要的思路就是从head找,通过循环依次改变指针的指向,直到指针指向当前位置,那么就把当前位置返回,(如果用户输入0,那么直接返回this.head

//获取某个位置的元素
get(position) {
    //1.越界判断,如果获取最后一个位置后面,那么是没有滴,所以是>=length
    if (position < 0 || position >= this.length) return false;
    //2.从头往后找,直到找到该位置元素
    let current = this.head;
    for (let i = 0; i < position; i++) {
        current = current.next;
    }
    return current;
}

测试代码:

let list = new LinkedList();
//1.测试尾部插入
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
console.log(list.toString()); //DantinZhang zzy 元素
//2.测试任意位置插入
list.insert(2, 'ht');
console.log(list.toString()); //DantinZhang zzy ht 元素
//3.测试获取某个位置元素 
console.log(list.get(2)); //Node {data: 'ht', next: Node}

5.indexOf根据元素值返回元素位置

主要的思路是从head依次往后查找(需要定义指针current),如果当前指针不为空,就拿传入的元素值和当前current指针的元素值比较,如果相等说明是我们要找的,直接return,不相等就要依次往后指,并且index+1

   indexOf(data) {
        let current = this.head;
        let index = 0; //定义一个索引
        //从头开始一个一个对比,如果相等了就返回index的值
        while(current) {
            if(current.data == data) {
                return index;
            }else {
                //当前节点不是我要找到节点,那么就往后指
                current = current.next;
                index++;
            }
        }
        //如果所有都对比完了都没找到,说明没有,就返回-1
        return -1;
    }

测试代码:

let list = new LinkedList();
//1.测试尾部插入
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
console.log(list.toString()); //DantinZhang zzy 元素
//2.测试任意位置插入
list.insert(2, 'ht');
console.log(list.toString()); //DantinZhang zzy ht 元素
//3.测试获取某个位置元素 
console.log(list.get(2)); //Node {data: 'ht', next: Node}
//4.测试获取某个元素索引
console.log(list.indexOf('zzy')); //1

6.update更新某个位置的元素

主要思路:
1、通过声明current指针,拿到这个位置的元素
2、改变该元素内部的data的值,不需要改变前后指针(因为对象地址不会变)

//更新某个位置的元素值
update(position, data) {
    //1.越界判断,positon==length是刚好在最后一个元素的后面那个空位
    if(position < 0 || position >= this.length ) return false;
    //2.拿到这个位置节点
    let current = this.head;
    let index = 0;
    while(index++ < postion) {
        current = current.next;
    }
    //3.修改当前节点的元素值
    current.data = data;
    return current;
}

测试更新:

let list = new LinkedList();
//1.测试尾部插入
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
console.log(list.toString()); //DantinZhang zzy 元素
//2.测试任意位置插入
list.insert(2, 'ht');
console.log(list.toString()); //DantinZhang zzy ht 元素
//3.测试获取某个元素索引
console.log(list.indexOf('zzy')); //1
//4.测试获取某个位置元素 
console.log(list.get(2)); //Node {data: 'ht', next: Node}
//5.测试修改元素
list.update(2, '修改元素');  
console.log(list.toString()); //DantinZhang zzy 修改元素 元素 

7.removeAt删除某个位置的节点

1、如果删除的是第一个位置,那么head直接指向第二个节点,其他不用动(被删除的第一个节点由于没有指针指向它,会被垃圾回收机制回收)

在这里插入图片描述

2、如果删除的是其他的位置,就要让被删除节点的上一个节点指向被删除节点的下一个节点

在这里插入图片描述
代码如下:

//删除指定位置的节点
removeAt(position) {
    //1.越界判断
    if(position < 0 || position >= this.length) return false;
    let current = this.head;
    //2.1如果删除的是第一个节点,前边没东西,只有head
    if(position == 0) {
        this.head = current.next;
    }else {
        //2.2如果删除的是其他位置的节点
        let index = 0;
        let previous = null;
        //2.2.1先找到前边的节点,删除节点,后边的节点
        while(index++ < position) {
            previous = current;
            current = current.next;
        }
        //2.2.2将前边的节点指向后边的节点
        previous.next = current.next;
    }
    //3.长度-1
    this.length--;
    //4.返回被删除的元素
    return current;
}

测试代码:

let list = new LinkedList();
//1.测试尾部插入
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
console.log(list.toString()); //DantinZhang zzy 元素
//2.测试任意位置插入
list.insert(2, 'ht');
console.log(list.toString()); //DantinZhang zzy ht 元素
//3.测试获取某个元素索引
console.log(list.indexOf('zzy')); //1
//4.测试获取某个位置元素 
console.log(list.get(2)); //Node {data: 'ht', next: Node}
//5.测试修改元素
list.update(2, '修改元素');  
console.log(list.toString()); //DantinZhang zzy 修改元素 元素
//6.测试删除指定位置元素
list.removeAt(2);
console.log(list.toString()); //DantinZhang zzy 元素 

8.remove删除指定data的元素

这里的思路就是先找到data对应的索引,然后删除,直接调用就行了

//删除指定data所在的节点
remove(data) {
    //1.先找到data所在的位置
    let index = this.indexOf(data);
    //2.删除该位置的元素
    return this.removeAt(index);
}

9.判断是否为空、输出长度

//判断链表是否为空
isEmpty() {
    return this.length == 0;
}

//判断链表的长度
size() {
    return this.length;
}

全部测试代码:

let list = new LinkedList();
//1.测试尾部插入
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
console.log(list.toString()); //DantinZhang zzy 元素
//2.测试任意位置插入
list.insert(2, 'ht');
console.log(list.toString()); //DantinZhang zzy ht 元素
//3.测试获取某个元素索引
console.log(list.indexOf('zzy')); //1
//4.测试获取某个位置元素 
console.log(list.get(2)); //Node {data: 'ht', next: Node}
//5.测试修改元素
list.update(2, '修改元素');  
console.log(list.toString()); //DantinZhang zzy 修改元素 元素
//6.测试删除指定位置元素
list.removeAt(2);
console.log(list.toString()); //DantinZhang zzy 元素 
//7.测试删除指定data的元素
list.remove('元素');
console.log(list.toString()); //DantinZhang zzy
//8.测试是否为空和输出长度
console.log(list.isEmpty()); //false
console.log(list.size()); //2

三、封装双向链表

1.什么是双向链表

双向链表:既可以从头遍历到尾,又可以从尾遍历到头。也就是说链表连接的过程是双向的,它的实现原理是:一个节点既有向前连接的引用,也有一个向后连接的引用。

双向链表的缺点:
1、每次在插入或删除某个节点时,都需要处理四个引用,而不是两个,实现起来会困难些;
2、 相对于单向链表,所占内存空间更大一些;
3、但是,相对于双向链表的便利性而言,这些缺点微不足道。
也就是说,双向链表就是用空间换时间

在这里插入图片描述

  • 双向链表不仅有head指针指向第一个节点,而且有tail指针指向最后一个节点;
  • 每一个节点由三部分组成:item储存数据、prev指向前一个节点、next指向后一个节点点;
  • 双向链表的第一个节点的prev指向null;
  • 双向链表的最后一个节点的next指向null;

2.封装双向链表:结构搭建

class Node {
    constructor(data) {
        this.pre = null;
        this.data = data;
        this.next = null;
    }
}

class DoublyLinedList {
    constructor() {
        this.head = null;
        this.tail = null;
        this.length = 0;
    }
}

3.append向尾部插入元素

双向链表向尾部插入元素和单向不一样,这里不需要再遍历元素,直接拿到tail操作就可以了。在插入的时候有两种情况:1、链表为空时 2、链表不为空时

(1)链表为空,添加的是第一个节点

我们只需要让head和tail都指向这个节点就可以了
在这里插入图片描述

(2)添加的不是第一个节点,那么改变相关指向

改变指向分为两步,首先改变下图1、2指向,此时tail指向的是原来的最后一个节点。

newNode.pre = this.tail; //先让新节点pre指向之前的尾部节点
this.tail.next = newNode; //之前尾部节点next指向新节点

在这里插入图片描述
第二步,改变指向3,让tail指向新插入的最后一个节点。

this.tail = newNode; //tail指向新节点

在这里插入图片描述

//向尾部插入节点
append(data) {
    //1.先生成一个节点
    const newNode = new Node(data);
    //2.添加的是第一个节点,只需要让head和tail都指向新节点
    if(this.length == 0) {
        this.head = newNode;
        this.tail = newNode;
    } else {
        //3.添加的不是第一个节点,直接找到尾部(不用遍历)
        newNode.pre = this.tail; //先让新节点pre指向之前的尾部节点
        this.tail.next = newNode; //之前尾部节点next指向新节点
        this.tail = newNode; //tail指向新节点
        console.log(newNode.next); //最后一个指向null
    }
    //4.长度加一
    this.length++;
}

测试代码:

const list = new DoublyLinedList();
list.append('DantinZhang');
list.append('努力的但丁');
console.log(list); 

4.toString链表数据转换为字符串

这里有两种转字符串的方法,分别是顺序和逆序。

主要原理都一样,就是定义current变量记录当前指向的节点。首先让current指向第一个(最后一个)节点,然后通过 current = current.next 依次向后(向前)遍历。在while循环(或for循环)中以(current)作为条件遍历链表,只要current != null就一直遍历,由此可获取链表所有节点的数据。
在这里插入图片描述
代码:

//1.链表数据转换为字符串的两种遍历(顺序、逆序)
toString() {
    return this.forwardString();
}

//2.顺序遍历转字符串
forwardString() {
    let result = '';
    let current = this.head;
    while (current) {
        result += current.data + ' ';
        current = current.next;
    }
    return result;
}

//3.逆序遍历转字符串
backwardString() {
    let result = '';
    let current = this.tail;
    for (let i = 0; i < this.length; i++) {
        result += current.data + ' ';
        current = current.pre;
    }
    return result;
}

测试代码:

const list = new DoublyLinedList();
list.append('DantinZhang');
list.append('努力的但丁');
list.append('zzy');
console.log(list.toString()); //DantinZhang 努力的但丁 zzy 
console.log(list.forwardString()); //DantinZhang 努力的但丁 zzy 
console.log(list.backwardString()); //zzy 努力的但丁 DantinZhang 

5.insert向任意位置插入节点

这里代码看着比较复杂,不过实现的思路不难。
具体可以去看这个人的博客,写的真的是非常详细:Javascript实现双向链表

//向任意位置插入节点
insert(position, data) {
    let newNode = new Node(data);
    //1.越界判断,其中length位置是最后一个节点的后面那个空位
    if (position < 0 || position > this.length) return false;
    //2.1如果链表为空
    if (this.length == 0) {
        this.head = newNode;
        this.tail = newNode;
    } else {
        //2.2如果链表不为空
        //2.2.1如果位置是1
        if (position == 0) {
            // this.head此时指的是第一个节点
            this.head.pre = newNode;
            newNode.next = this.head;
            this.head = newNode;
        }
        //2.2.2.如果在最后一个节点的后面插入
        else if (position == this.length) {
            // this.tail此时指的是最后一个节点
            newNode.pre = this.tail;
            this.tail.next = newNode;
            this.tail = newNode;
        }
        //2.2.3.如果在其他位置插入
        else {
            //先找到这个位置
            let index = 0;
            let current = this.head;
            let previous = null;
            while (index++ < position) {
                previous = current;
                current = current.next;
            }
            //插入新节点,到它们俩中间儿
            previous.next = newNode;
            newNode.pre = previous;
            newNode.next = current;
            current.pre = newNode;
        }
    }
    //3.长度别忘了+1
    this.length++;
    return true;
}

测试代码:

//测试代码
    //1.创建双向链表
    let list = new DoubleLinklist()

	//2.测试insert方法
    list.insert(0, '插入链表的第一个元素')
    list.insert(0, '在链表首部插入元素')
    list.insert(1, '在链表中间插入元素')
    list.insert(3, '在链表尾部插入元素')
    console.log(list);
    alert(list)

在这里插入图片描述

6.get获取某个位置的元素值

整体思路比较简单,为了提高效率,可以使用二分查找。判断位置是在前半部分还是在后半部分,如果在前半部分,就从head开始找;如果在后半部分,就从tail开始找。

//获取某个位置的元素值
get(position) {
    //1.越界判断
    if (position < 0 || position >= this.length) return false;
    //2.1如果该元素在前半部分,从head开始找
    if (position < this.length / 2) {
        let current = this.head;
        for(let i = 0; i < position; i++) {
            current = current.next;
        }
        return current.data;
    } else {
        //2.2如果该元素在后半部分,从tail倒着找
        let current = this.tail;
        let index = this.length - 1;
        while(index-- > position) {
            current = current.pre;
        }
        return current.data;
    }
}

测试代码:

const list = new DoublyLinedList();
list.append('DantinZhang');
list.append('努力的但丁');
list.append('zzy');
list.append('ht');
list.insert(2, '第三个位置插入本元素');
console.log(list.forwardString());
console.log(list.backwardString());
console.log(list.get(2)); //第三个位置插入本元素
console.log(list.get(3)); //zzy

7.indexOf根据某个元素值获取该节点位置

这个和单向链表一样,就是从头找,找不到就返回-1,找到了就对比并返回索引

//根据元素值获取节点位置
indexOf(data) {
    let current = this.head;
    let index = 0;
    while(current) {
        if(current.data == data) {
            return index;
        }else {
            current = current.next;
            index++;
        }
    }
    return -1
}

测试代码:

const list = new DoublyLinedList();
list.append('DantinZhang');
list.append('努力的但丁');
list.append('zzy');
list.append('ht');
list.insert(2, 'No.3');
console.log(list.forwardString());//DantinZhang 努力的但丁 No.3 zzy ht
console.log(list.backwardString());
console.log(list.get(2)); //第三个位置插入本元素
console.log(list.get(3)); //zzy
console.log(list.indexOf('zzy'));//3

8.update更新某个元素

主要思路还是先查找,然后把数据改了就行。查找的时候采用二分查找。

//更新某个元素
update(position, data) {
    //1.生成节点
    let newNode = new Node(data);
    //2.越界判断
    if(position < 0 || position >= this.length) return false;
    //3.寻找位置,改变元素值,二分查找
    let current = null;
    if(position < this.length / 2) {
        current = this.head;
        let index = 0;
        while(index++ < position) {
            current = current.next;
        }
    }else {
        current = this.tail;
        for(let i = this.length-1; i > position; i--) {
           current = current.pre; 
        }
    }
    current.data = data;
    return current.data;
}

测试代码:

const list = new DoublyLinedList();
list.append('DantinZhang');
list.append('努力的但丁');
list.append('zzy');
list.append('ht');
list.insert(2, 'No.3');
console.log(list.forwardString());//DantinZhang 努力的但丁 No.3 zzy ht
console.log(list.backwardString());
console.log(list.get(2)); //第三个位置插入本元素
console.log(list.get(3)); //zzy
console.log(list.indexOf('zzy'));//3
//测试更新
list.update(2,'DJ');
console.log(list.toString()); //DantinZhang 努力的但丁 DJ zzy ht 

9.removeAt删除某个位置的节点

这里的主要思路是:
1、首先判断是否只有一个节点,如果只有一个节点,删除后head和tail都要置空.
2、如果有多个节点,要看删除的是第一个、最后一个、其他位置
3、第一个位置,先清空第二节节点指向被删除元素的pre指针(清空所有指向它的指针,这样该节点在内存中没用途,会被自动回收),然后直接让head指向第二个节点就行了。
4、最后一个位置,同理,删除倒数第二个节点指向被删除节点的next,然后让tail直接指向倒数第二个节点。
5、删除完长度-1

//删除某个位置的元素
removeAt(position) {
    //1.越界判断
    if(position < 0 || position >= this.length) return false;
    let current = this.head; //定义在最上面方便各种情况返回数据
    //2.判断是否只有一个节点
    if(this.length == 1) {
        //2.1如果只有一个节点,那么删除时head和tail都为空
        this.head = null;
        this.tail = null;
    }else {
        //2.2如果有多个节点,那么就要进行位置判断
        //2.2.1删除第一个节点
        if(position == 0) {
            //删除前head指向的是第一个节点
            this.head.next.pre = null; //所有指向它的指针都清掉
            this.head = this.head.next;
        }
        //2.2.2删除最后一个节点
        else if(position == this.length-1) {
            //删除前tail指向的是最后一个节点
            current = this.tail;
            this.tail.pre.next = null; //所有指向它的指针都清掉
            this.tail = this.tail.pre;
        }
        //2.2.3删除其他的节点
        else {
            //先找到位置
            let index = 0;
            while(index++ < position) {
                current = current.next;
            }
            current.pre.next = current.next;
            current.next.pre = current.pre;
        }
    }
    //3.删除完-1
    this.length -= 1;
    return current.data;
}

代码测试:

const list = new DoublyLinedList();
list.append('DantinZhang');
list.append('努力的但丁');
list.append('zzy');
list.append('ht');
list.insert(2, 'No.3');
console.log(list.forwardString());//DantinZhang 努力的但丁 No.3 zzy ht
console.log(list.backwardString());
console.log(list.get(2)); //第三个位置插入本元素
console.log(list.get(3)); //zzy
console.log(list.indexOf('zzy'));//3
//测试更新
list.update(2,'DJ');
console.log(list.toString()); //DantinZhang 努力的但丁 DJ zzy ht 
list.removeAt(2);
//测试删除
console.log(list.toString()); //DantinZhang 努力的但丁 zzy ht 

10.remove删除某个元素

//删除某个元素
remove(data) {
    return this.removeAt(this.indexOf(data));
}

测试代码:

   const list = new DoublyLinedList();
    list.append('DantinZhang');
    list.append('努力的但丁');
    list.append('zzy');
    list.append('ht');
    list.insert(2, 'No.3');
    console.log(list.forwardString());//DantinZhang 努力的但丁 No.3 zzy ht
    console.log(list.backwardString());
    console.log(list.get(2)); //第三个位置插入本元素
    console.log(list.get(3)); //zzy
    console.log(list.indexOf('zzy'));//3
    //测试更新
    list.update(2,'DJ');
    console.log(list.toString()); //DantinZhang 努力的但丁 DJ zzy ht 
    //测试删除
    list.removeAt(2);
    list.remove('努力的但丁');
    console.log(list.toString()); //DantinZhang zzy ht 

11.其他方法

//删除某个元素
remove(data) {
    return this.removeAt(this.indexOf(data));
}

//测试是否为空
isEmpty() {
    return this.length == 0;
}

//输出长度
size() {
    return this.length;
}

//获取链表第一个元素值
getHead() {
    return this.head.data;
}

//获取链表最后一个元素值
getTail() {
    return this.tail.data;
}

更详细的图解参考:大佬的博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/338855.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RNN神经网络初探

目录1. 神经网络与未来智能2. 回顾数据维度和神经网络1. 神经网络与未来智能 2. 回顾数据维度和神经网络 循环神经网络&#xff0c;主要用来处理时序的数据&#xff0c;它对每个词的顺序是有要求的。 循环神经网络如何保存记忆功能&#xff1f; 当前样本只有 3 个特征&#x…

git基本概念图示【学习】

基本概念工作区&#xff08;Working Directory&#xff09;就是你在电脑里能看到的目录&#xff0c;比如名字为 gafish.github.com 的文件夹就是一个工作区本地版本库&#xff08;Local Repository&#xff09;工作区有一个隐藏目录 .git&#xff0c;这个不算工作区&#xff0c…

新方案:从错误中学习,点云分割中的自我规范化层次语义表示

前言 LiDAR 语义分割通过直接作用于传感器提供的原始内容来完成细粒度的场景理解而受到关注。最近的解决方案展示了如何使用不同的学习技术来提高模型的性能&#xff0c;而无需更改任何架构或数据集。遵循这一趋势&#xff0c;论文提出了一个从粗到精的设置&#xff0c;该设置从…

查找与排序 练习题

1、下列排序算法中&#xff0c;▁▁B▁▁ 是稳定的。 A.简单选择排序 B.冒泡排序 C.希尔排序 D.快速排序 解析&#xff1a;稳定排序是每次排序得到的结果是唯一的&#xff0c;不稳定排序得到的结果不唯一。 稳定&#xff1a;冒泡排序、归并排序、基数排序 不稳定&#x…

DolphinSchedule基于事件驱动的高性能并发编程

文章目录前言前置知识异步编程基于时间驱动的异步编程模式&#xff08;EAP Event-based Asynchronous Pattern &#xff09;实现EAPDolphinSchedule结合Netty实现Master与Worker之间的高性能处理能力的设计方案设计代码实现总结前言 研究DolphinSchedule的内因在于对调度系统并…

内存访问局部性特征

分享一道360的C语言笔试题。x是一个行列均为1000的二维数组&#xff0c;下面代码运行效率最高的是哪个&#xff1f; 二维数组大家都很熟悉&#xff0c;正常人遍历二维数组都是一行一行来的&#xff0c;为什么很少有人按列去遍历&#xff1f; 这道笔试题其实考察的就是遍历效率…

#车载基础软件——AUTOSAR AP技术形态

车载基础软件——AUTOSAR AP技术形态 我是穿拖鞋的汉子! 今天是2023年2月11日,时间好快,疫情解封已好几个月,生活节奏也在逐渐恢复到三年前的节奏。可能是感觉疫情与自己距离变远了,大家也开始慢慢的不再恐惧! 老规矩分享一段喜欢的文字,避免自己成为高知识低文化的工…

【安全】nginx反向代理+负载均衡上传webshell

目录 一、负载均衡反向代理下上传webshell Ⅰ、环境搭建 ①下载蚁剑&#xff0c;于github获取官方版&#xff1a; ②下载docker&docker-compose ③结合前面启动环境 ④验证 负载均衡下webshell上传 一、负载均衡反向代理下上传webshell 什么是反向代理&#xff1f; 通常的代…

大数据框架之Hadoop:入门(三)Hadoop运行环境搭建(开发重点)

3.1虚拟机环境准备 详见&#xff1a;yiluohan1234/vagrant_bigdata_cluster: 利用virtualbox快速搭建大数据测试环境 (github.com) 单纯只是安装虚拟机的话&#xff0c;注释掉40到115行。按照教程安装即可。 在 /opt 目录下创建 module、 software 文件夹 [roothdp101 ~]# m…

一、Java并发编程之线程、synchronized

黑马课程 文章目录1. Java线程1.1 创建和运行线程方法一&#xff1a;Thread方法二&#xff1a;Runnable&#xff08;推荐&#xff09;lambda精简Thread和runnable原理方法三&#xff1a;FutureTask配合Thread1.2 查看进程和线程的方法1.3 线程运行原理栈与栈帧线程上下文切换1.…

1. SpringMVC 简介

文章目录1. SpringMVC 概述2. SpringMVC 入门案例2.1 入门案例2.2 入门案例工作流程3. bean 加载控制4. PostMan 工具1. SpringMVC 概述 SpringMVC 与 Servlet 功能等同&#xff0c;均属于 Web 层开发技术。SpringMVC 是 Spring 框架的一部分。 对于 SpringMVC&#xff0c;主…

Python导入模块的3种方式

很多初学者经常遇到这样的问题&#xff0c;即自定义 Python 模板后&#xff0c;在其它文件中用 import&#xff08;或 from...import&#xff09; 语句引入该文件时&#xff0c;Python 解释器同时如下错误&#xff1a;ModuleNotFoundError: No module named 模块名意思是 Pytho…

45.在ROS中实现global planner(1)

前文move_base介绍&#xff08;4&#xff09;简单介绍move_base的全局路径规划配置&#xff0c;接下来我们自己实现一个全局的路径规划 1. move_base规划配置 ROS1的move_base可以配置选取不同的global planner和local planner&#xff0c; 默认move_base.cpp#L70中可以看到是…

Vue3电商项目实战-分类模块1【01-顶级类目-面包屑组件-初级、02-顶级类目-面包屑组件-高级】

文章目录01-顶级类目-面包屑组件-初级02-顶级类目-面包屑组件-高级01-顶级类目-面包屑组件-初级 目的&#xff1a; 封装一个简易的面包屑组件&#xff0c;适用于两级场景。 大致步骤&#xff1a; 准备静态的 xtx-bread.vue 组件定义 props 暴露 parentPath parentName 属性&am…

[oeasy]python0081_ANSI序列由来_终端机_VT100_DEC_VT选项_终端控制序列

更多颜色 回忆上次内容 上次 首先了解了RGB颜色设置可以把一些抽象的色彩名字 落实到具体的 RGB颜色 计算机所做的一切 其实就是量化、编码把生活的一切都进行数字化 标准 是ANSI制定的 这个ANSI 又是 怎么来的 呢&#xff1f;&#xff1f;&#x1f914; 由来 ANSI 听起…

【c++设计模式】——模板方法模式

模板方法模式的定义 定义一个操作中的算法对象的骨架&#xff08;稳定&#xff09;&#xff0c;而将一些步骤延迟到子类&#xff08;定义一个虚函数&#xff0c;让子类去实现&#xff09;&#xff0c;template method使得子类可以不改变&#xff08;复用&#xff09;一个算法结…

can协议介绍

目录 1 can协议介绍 1.1can协议 1.2 CAN协议特点 2.CAN FD 2.1 CAN FD协议简介 2.2 CAN FD协议特点 3.LIN 3.1 LIN总线简介 3.2 LIN总线特点 4. FlexRay 4.1 FlexRay简介 4.2 FlexRay特点 5. MOST 6.Ethernet 7 总结&#xff1a; 1 can协议介绍 1.1can协议 CAN…

Linux---Linux是什么

Linux 便成立的核心网站&#xff1a; http://www.kernel.org Linux是什么 Linux 就是一套操作系统 Linux 就是核心与系统呼叫接口那两层 软件移植&#xff1a;如果能够参考硬件的功能函数并据以修改你的操作系统程序代码&#xff0c; 那经过改版后的操作系统就能够在另一个硬…

Spring Boot 整合定时任务完成 从0 到1

Java 定时任务学习 定时任务概述 > 定时任务的应用场景非常广泛, 如果说 我们想要在某时某地去尝试的做某件事 就需要用到定时任务来通知我们 &#xff0c;大家可以看下面例子 如果需要明天 早起&#xff0c;哪我们一般会去定一个闹钟去通知我们, 而在编程中 有许许多多的…

ssm高校功能教室预约系统java idea maven

本网站所实现的是一个高校功能教室预约系统&#xff0c;该系统严格按照需求分析制作相关模块&#xff0c;并利用所学知识尽力完成&#xff0c;但是本人由于学识浅薄&#xff0c;无法真正做到让该程序可以投入市场使用&#xff0c;仅仅简单实现部分功能&#xff0c;希望日后还能…