链表的含义和代码实现(单向、双向、循环)

很多现在语言都比较灵活,提供了栈、队列等数据结构,这些实质上都是基于数组工作的。数组属于基本数据结构,属于顺序线性表。 它的优势在于数据的随机访问,但在长度的扩展和数据的随机插入、删除时则比较麻烦,这里我们介绍一个特殊的数据结构-链表

单向链表

链表是一组结点组成的集合,每个结点都包含两部分:存储数据元素的数据域和存储下一个结点的指针域。在物理存储上非连续,因此在插入和删除时效率更高。

数据结构查找插入/删除
顺序线性表(数组)O(1)O(n)
链表O(logn)O(logn)

优势:

  1. 链表结构可以充分利用存储空间,不需要预先知道数据的大小。
  2. 插入和删除结点效率高

劣势:

  1. 相比数组,无法进行随机读取
  2. 除了数据,还需要存储指针域,空间开销大

单向链表的操作

链表是一组结点的集合,每个结点都是用一个引用指向后一个结点,这个引用就是链,整个结构就是链表。

链表会包含一个特殊的头结点,用于整个链表的引用。链表的尾元素指向null结点,表示链表的结束。

向链表插入一个结点非常简单,就是将前结点的引用指向新结点,而将新结点指向原前结点的引用结点。

删除链表的一个结点,只需要将前结点的引用指向待删除结点的引用,而将待删除结点指向null即可。

实现一个单向链表(JS版)

// 一个结点类
class Node {
    constructor(data) {
        this.data = data;
        this.next = null;
    }
}

// 一个链表
class List {
    constructor() {
        this.head = new Node('head');
    }
    // 展示链表中所有的值
    display() {
        let cur = this.head;
        while (cur.next !== null) {
            console.log(cur.next.data);
            cur = cur.next;
        }
    }
    // 查找结点在链表中的位置
    find(value) {
        let cur = this.head;
        while (cur.data !== value) {
            if (cur.next === null) {
                return null;
            }
            cur = cur.next;
        }
        return cur;
    }
    // 查找结点在链表中的前一个结点
    findPrev(value) {
        let cur = this.head;
        while (cur.next !== null) {
            if (cur.next.data === value) {
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }
    // 在指定结点后插入一个结点
    insert(newValue, value) {
        let node = new Node(newValue);
        let cur = this.find(value);
        if (cur === null) {
            return false;
        }
        node.next = cur.next;
        cur.next = node;
    }
    // 删除一个结点
    remove(value) {
        let prev = this.findPrev(value);
        if (prev !== null) {
            if (prev.next.next === null) {
                prev.next = null;
            } else {
                prev.next = prev.next.next;
            }
        }
    }
}

// 单向链表的使用
let list = new List();
list.insert('a', 'head');
list.insert('b', 'a');
list.display(); // a, b
list.insert('c', 'b');
list.remove('b');
list.display(); // a, c
复制代码

双向链表

双向链表与单向链表的区别就是除了有next结点的引用,还提供prev结点的引用,这样能够解决单向列表难以逆序遍历。

实现一个双向链表(JS版)

// 一个结点类
class Node {
    constructor(data) {
        this.data = data;
        this.next = null;
        this.prev = null;
    }
}

// 一个双向链表(double direction list)
class ddList {
    constructor() {
        this.head = new Node('head');
    }
    // 展示链表中所有的值
    display() {
        let cur = this.head;
        while (cur.next !== null) {
            console.log(cur.next.data);
            cur = cur.next;
        }
    }
    // 反向展示链表中的所有值
    displayReverse() {
        let cur = this.findLast();
        while (cur.prev !== null) {
            console.log(cur.data);
            cur = cur.prev;
        }
    }
    // 查找结点在链表中的位置
    find(value) {
        let cur = this.head;
        while (cur.data !== value) {
            if (cur.next === null) {
                return null;
            }
            cur = cur.next;
        }
        return cur;
    }
    // 查找最后一个结点
    findLast() {
        let cur = this.head;
        while (cur.next !== null) {
            cur = cur.next;
        }
        return cur;
    }
    // 在指定结点后插入一个结点
    insert(newValue, value) {
        let node = new Node(newValue);
        let cur = this.find(value);
        if (cur === null) {
            return false;
        }
        node.next = cur.next;
        node.prev = cur;
        cur.next = node;
    }
    // 删除一个结点
    remove(value) {
        let cur = this.find(value);
        if (cur === null) {
            return false;
        }
        if (cur.next === null) {
            cur.prev.next = null;
        } else {
            cur.prev.next = cur.next;
            cur.next.prev = cur.prev;
        }
        cur.next = null;
        cur.prev = null;
    }
}

// 双向链表的使用
let list = new ddList();
list.insert('a', 'head');
list.insert('b', 'a');
list.insert('c', 'b');
list.display(); // a, b, c
list.displayReverse(); // c, b, a
list.remove('b');
list.display(); // a, c
复制代码

双向循环链表(JS版)

循环链表的含义很简单,就是头尾相连,实现的独特之处,就是头部结点在初始化时,后继结点指向自身,这样在每次插入时都会继承这个设定。

// 一个结点类
class Node {
    constructor(data) {
        this.data = data;
        this.next = null;
        this.prev = null;
    }
}

// 一个双向循环链表(double direction round list)
class roundList {
    constructor() {
        this.head = new Node('head');
        this.head.next = this.head;
        this.head.prev = this.head;
    }
    // 展示链表中所有的值
    display() {
        let cur = this.head.next;
        while (cur !== this.head) {
            console.log(cur.data);
            cur = cur.next;
        }
    }
    // 反向展示链表中的所有值
    displayReverse() {
        let cur = this.head.prev;
        while (cur !== this.head) {
            console.log(cur.data);
            cur = cur.prev;
        }
    }
    // 查找结点在链表中的位置
    find(value) {
        let cur = this.head.next;
        while (cur !== this.head && cur.data !== value) {
            cur = cur.next;
        }
        if (cur === this.head) {
            return null;
        }
        return cur;
    }
    // 在指定结点后插入一个结点, 如果第2个参数为空,则在尾部插入
    insert(newValue, value) {
        let node = new Node(newValue);
        let cur = null;
        if (value === undefined) {
            cur = this.head.prev;
        } else {
            cur = this.find(value);
            if (cur === null) {
                return false;
            }
        }
        node.next = cur.next;
        cur.next.prev = node;
        node.prev = cur;
        cur.next = node;
    }
    // 删除一个结点
    remove(value) {
        let cur = this.find(value);
        if (cur === null) {
            return false;
        }
        cur.prev.next = cur.next;
        cur.next.prev = cur.prev;
        cur.next = null;
        cur.prev = null;
    }
}

// 双向循环链表的使用
let list = new roundList();
list.insert('a');
list.insert('b');
list.insert('c');
list.display(); // a, b, c
list.insert('d', 'b');
list.display(); // a, b, d, c
list.displayReverse(); // c, d, b, a
list.remove('b');
list.display(); // a, d, c
复制代码

双向循环链表(PHP版)

<?php 
class Node {
    public $data = null;
    public $prev = null;
    public $next = null;
    public function __construct($value) {
        $this->data = $value;
    }
}

class RoundList {
    public $head = null;
    public function __construct() {
        $this->head = new Node('head');
        $this->head->prev = $this->head;
        $this->head->next = $this->head;
    }
    public function display() {
        $cur = $this->head->next;
        while ($cur !== $this->head) {
            echo $cur->data . '<br/>';
            $cur = $cur->next;
        }
    }
    public function displayReverse() {
        $cur = $this->head->prev;
        while ($cur !== $this->head) {
            echo $cur->data . '<br/>';
            $cur = $cur->prev;
        }
    }
    public function find($value) {
        $cur = $this->head->next;
        while ($cur !== $this->head) {
            if ($cur->data === $value) {
                return $cur;
            }
            $cur = $cur->next;
        }
        return false;
    }
    public function insert($value, $beforeValue = null) {
        if ($beforeValue === null) {
            $cur = $this->head->prev;
        } else {
            $cur = $this->find($beforeValue);
            if ($cur === false) {
                return false;
            }
        }
        $node = new Node($value);
        $node->next = $cur->next;
        $cur->next->prev = $node;
        $node->prev = $cur;
        $cur->next = $node;
        return $node;
    }
    public function remove($value) {
        $cur = $this->find($value);
        if ($cur === false) {
            return false;
        }
        $cur->prev->next = $cur->next;
        $cur->next->prev = $cur->prev;
        $cur->prev = null;
        $cur->next = null;
        return $cur;
    }
}

$list = new RoundList();
$list->insert('a');
$list->insert('b');
$list->insert('c');
$list->display(); // a, b, c
$list->insert('d', 'b');
$list->display(); // a, d, d, c
$list->displayReverse();  // c, d, b, a
$list->remove('b');
$list->display();  // a, d, c
return;
?>
复制代码

总结

链表的基础知识基本就这么多了,虽然我都已经理解,但在写上面示例代码的时候还是出现了那么几次bug,所以自己亲自动手写写还是很有必要的,可以有效避免眼高手低的坏毛病。

参考资料

  1. JS中的算法与数据结构——链表(Linked-list):juejin.im/entry/59cb7…
  2. 链表:baike.baidu.com/item/链表

转载于:https://juejin.im/post/5c90fb9ce51d4543ce6bf0f5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值