最简单的数据结构与算法(三)链表

469 阅读6分钟

链表-单向链表?

单向链表结构图:

557295227-5c273f96b5146_fix732.png

有结构图中可以看出,单项链表是用一组任意的内存空间去存储数据元素,由头指针开始,它的指针指向下一个节点,并且最后一个指针指向null,这其中每个节点都有数据本身和一个指向下一个节点的指针构成的一种数据结构

分析完了结构让我们用js代码来实现下吧

1:初始化一个节点信息,用来生成每个节点

// 初始化节点信息
// 1:指针指向空
// 2:数据指向存储数据
class Node {
    constructor(key){
        this.next = null;
        this.key = key;
    }
}

2:初始化单向链表

class List {
    constructor(){
        this.head = new Node('head');
    }
}

我们用构造函数的方式来实现我们列表,我这里直接生成了链表的头节点,便于我们后边的插入和删除

3:插入新节点

我们知道链表中每个节点都有一个指针指向下一个节点,那么我们想插入一个新节点该怎么办呢?其实只需要改变指针的指向就可以了,我们将插入节点的指针改为当前节点的指针,并且将当前节点的指针指向我们需要插入的节点就可以了

我们首先需要一个查找函数,用来确定我们要将节点插入到哪里

// 查找节点
    find(item){
        let currentNode = this.head;
        while (currentNode.key!==item) {
            if(currentNode.next === null){
                return '没找到'
            }
            currentNode = currentNode.next;
        }
        return currentNode
    }

插入节点实现

// 向某一个元素后面插入新的节点
    insert(newKey,item){
        // 新元素
        let newNode = new Node(newKey);
        let current = this.find(item);
        // 改变指针指向
        if(current === '没找到'){
            return
        }
        newNode.next=current.next;
        current.next=newNode;
    }

删除节点

删除节点同插入节点一样,我们只需要改变节点的指针指向新的节点即可,但是因为删除的时候我们需要改变父节点的指针指向当前节点指针指向的节点,所有我们首先需要查到删除节点的父节点,那么怎么实现呢,其实很简单,我们只需要知道节点的指针指向的节点的属性等于我们需要查找的值就可以了,这时候的节点即为我们想要父节点

实现查找父节点

    // 查找Key相等
    findUpNode(newKey){
        let currentNode = this.head;
        while (currentNode.next!==null && currentNode.next.key!==newKey) {
            currentNode = currentNode.next
        }
        return currentNode
    }

实现删除节点

    // 删除某个节点,其实就是改变next的指向,将当前节点next指向下一个节点next的next
    deleteNode(newKey){
        let upNode = this.findUpNode(newKey);
        if(upNode.next!==null){
            upNode.next = upNode.next.next
        }
    }

OK!!!到这里我们就实现了单向链表的完整功能,我这里增加了一个view方法用来查看我们链表中的元素,下面附上完整代码

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

class List {
    constructor(){
        this.head = new Node('head');
    }
    // 查找节点
    find(item){
        let currentNode = this.head;
        while (currentNode.key!==item) {
            if(currentNode.next === null){
                return '没找到'
            }
            currentNode = currentNode.next;
        }
        return currentNode
    }
    // 向某一个元素后面插入新的节点
    insert(newKey,item){
        let newNode = new Node(newKey);
        let current = this.find(item);
        if(current === '没找到'){
            return
        }
        newNode.next=current.next;
        current.next=newNode;
    }
    // 查找某个节点的前一个节点
    findUpNode(newKey){
        let currentNode = this.head;
        while (currentNode.next!==null && currentNode.next.key!==newKey) {
            currentNode = currentNode.next
        }
        return currentNode
    }
    // 删除某个节点
    deleteNode(newKey){
        let upNode = this.findUpNode(newKey);
        if(upNode.next!==null){
            upNode.next = upNode.next.next
        }
    }
    // 查看当前的链表,并且打印出每个节点
    view(){
        let currentNode = this.head;
        while (currentNode.next!==null  ) {
            console.log(currentNode.next.key)
            currentNode=currentNode.next
        }
    }
}

let list = new List();
list.insert('张三','head')
list.insert('李四','张三')
list.findUpNode('李四')
list.deleteNode('李四')
console.log(list.findUpNode('李四'))
console.log(list.view())

链表-双向链表

WX20210927-152142@2x.png

学过了单向链表我们再看下双向链表吧,由上图分析可知相对于单向链表而言双向链表增加了一个prev的指针,这个指针指向了上一个节点,并且双向链表头节点的prev指向了null,尾结点的next指向了null,对比单向链表,我们来实现下双向链表吧! ps:这里的prev和next都是指针的意思

初始化一个节点信息用来生成每个节点

class Node {
    constructor(key){
        this.prev = null;
        this.key = key;
        this.next = null;
    }
}

ps:对比单向链表我们多初始化了一个prev的指针

初始化双向链表

class List {
    constructor(){
        this.head = new Node('head');
    }
}

仍然直接声明一个新的head节点

插入新节点

插入的原理同单向链表我们不再赘述了,直接上代码实现

// 查找节点
    find(item){
        let currentNode = this.head;
        while (currentNode.key!==item) {
            if(currentNode.next === null){
                return '没找到'
            }
            currentNode = currentNode.next;
        }
        return currentNode
    }
    // 插入节点
    insert(newKey,item){
        // 新元素
        let newNode = new Node(newKey);
        let currentNode = this.find(item);
        // 改变next指向,新节点的next指向当前节点的next,当前节点的next指向新节点
        if(currentNode === '没找到'){
            return '无法找到插入节点'
        }
        if(currentNode.next!==null){
            newNode.next = currentNode.next;
            newNode.prev = currentNode;
            newNode.next.prev = newNode;
            currentNode.next = newNode;
        }else{
            newNode.next = null;
            newNode.prev = currentNode;
            currentNode.next = newNode;
        }
    }

特别提醒,双向链表在插入时需要去判断是不是最后一个节点,并且我们要改变节点的prev和next两个指针的指向

删除节点

删除节点比单向链表要简单一些,我们不需要去查找节点的父节点,直接去改变prev指针的指向即可,但是我们需要判断删除的节点是不是头尾节点并进行对应的操作,只是改变节点指针的指向,特别注意,我们需要将删除节点的指针指向null,否则双向链表就错乱了

deleteNode(item){
    let currentNode = this.find(item)
    if(currentNode === '没找到'){
        return '要删除的节点不存在'
    }
    // 这里需要判断节点是不是头尾节点
    if(currentNode.next!==null && currentNode.prev!==null){
        // 不是头也不是尾节点
        currentNode.prev.next = currentNode.next;
        currentNode.next.prev = currentNode.prev;
        currentNode.next = null;
        currentNode.prev = null; 
    } else if(currentNode.next === null){
        // 尾节点
        currentNode.prev.next = null;
        currentNode.prev = null;
    } else if(currentNode.prev === null){
        // 头结点
        currentNode.next.prev = null;
        currentNode.next = null;
    }
}

下面给出双向链表的完整代码,仍然增加了一个view函数查看我们的链表

// 初始化节点信息
// 1:指针指向空
// 2:数据指向存储数据
class Node {
    constructor(key){
        this.prev = null;
        this.key = key;
        this.next = null;
    }
}
// 初始化双向链表
class List {
    constructor(){
        this.head = new Node('head');
    }
    // 查找节点
    find(item){
        let currentNode = this.head;
        while (currentNode.key!==item) {
            if(currentNode.next === null){
                return '没找到'
            }
            currentNode = currentNode.next;
        }
        return currentNode
    }
    // 插入节点
    insert(newKey,item){
        // 新元素
        let newNode = new Node(newKey);
        let currentNode = this.find(item);
        // 改变next指向,新节点的next指向当前节点的next,当前节点的next指向新节点
        if(currentNode === '没找到'){
            return '无法找到插入节点'
        }
        if(currentNode.next!==null){
            newNode.next = currentNode.next;
            newNode.prev = currentNode;
            newNode.next.prev = newNode;
            currentNode.next = newNode;
        }else{
            newNode.next = null;
            newNode.prev = currentNode;
            currentNode.next = newNode;
        }
    }
    // 删除节点,比单项链表简单些因为不需要查找上一个节点直接改变prev指向就可以了
    deleteNode(item){
        let currentNode = this.find(item)
        if(currentNode === '没找到'){
            return '要删除的节点不存在'
        }
        // 这里需要判断节点是不是头尾节点
        if(currentNode.next!==null && currentNode.prev!==null){
            // 不是头也不是尾节点
            currentNode.prev.next = currentNode.next;
            currentNode.next.prev = currentNode.prev;
            currentNode.next = null;
            currentNode.prev = null; 
        } else if(currentNode.next === null){
            // 尾节点
            currentNode.prev.next = null;
            currentNode.prev = null;
        } else if(currentNode.prev === null){
            // 头结点
            currentNode.next.prev = null;
            currentNode.next = null;
        }
    }
    // 查看
    view(){
        let currentNode = this.head;        
        while (currentNode.next !== null) {
            console.log(currentNode.next.key)
            currentNode=currentNode.next
        }        
    }
}
let list = new List();
list.insert('张三','head')
list.insert('李四','张三')
list.insert('王五','李四')
list.deleteNode('李四')
console.log(list.view())

OK,通过这两节链表的学习我们是不是充分掌握了链表的知识呢,也许在我们的开发中用不到,但是不妨碍我们去探索,毕竟你面试的时候也许会被问到,如何判断一个链表是不是双向链表,亦或者怎么判断链表是循环链表(即,链表是首尾相连的),ok,留个小尾巴等您去探索吧!

😄😄😄看完了别忘点赞额😄😄😄