基础数据结构-链表

思维导图

1. 熟悉结构

首先我们要知道链表的结构以及每个节点的结构,这是我们手写链表的第一步,也是学习链表的第一步。我们知道,每个链表时这样表示的:

那每个节点结构是由数据域和指针域组成,数据域是存放数据的,而指针域存放下一结点的地址。

我们可以通过数据域访问到我们要的数据,而通过指针域访问到当前结点以后的结点,那么将这些结点串起来,就是一个链表。

那么用代码怎么来表示呢?
我们通常会用到函数,我们如果将一个函数抽象成一个结点,那么我们给函数添加两个属性,一个属性是存放数据的属性data,另一个属性是存放指向下一个结点的指针属性next。

你可能会问,如果我们有成千上万个结点,要定义成千上万个函数?有一个函数叫做构造函数,想必大家都听说过,声明一个构造函数就可以创造出成千上万个结点实例。

function Node(data){
    this.data = data;
    this.next = null;
}

还有一个方法就是使用类的概念,在JavaScript中,类的概念在ES6才出现,使用 Class 来声明一个类,我们可以为类添加两个属性,同上,一样可以创造出多个结点实例。

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

2. 理清思路

如果你把链表的结构搞得明明白白了,恭喜你,成功的进入第二关,但是你只超越了百分之20的人,继续加油。

既然链表的结构弄明白了,那么我们开始理思路,我们就先拿最简单的单链表开刀,我们要完成两个操作,插入数据和删除数据。

如果我想插入数据,你可能会问,往哪里插呢?有几种插入的方法?

开始想,插入到单链表的头部算一种。

然后插入到单链表的中间算一种。

插入到单链表尾部又算一种。

所有可能的情况就三种。那么删除呢?想必你也想到了,也一共三种,删除头部、删除中间部分、删除尾部。

3. 边界条件

边界条件是这五个步骤最容易犯错的一部分,因为通常考虑的不全面,导致了最后的面试未通过。如果想做好这一部分,也不难,跟着小鹿的方法走。

3.1 输入边界

首先我们先考虑用户输入的参数,比如传入一个链表,我们首先要判断链表是否为空,如果为空我们就不能让它执行下边的程序。再比如插入一个结点到指定结点的后边,那么你也要判断输入的结点是否为空,而且还要判断该结点是否存在该链表中。对于这些输入值的判断,小鹿给他同一起个名字叫做输入边界。

3.2 输入边界

特殊边界考虑到一些特殊情况,比如插入数据,我们插入数据一般考虑到插入尾部,但是突然面试官插入到头部,插入尾部的代码并不适用于插入到头部,所以呢需要考虑这种情况,删除节点也是同样思考。其实特殊边界最主要考虑到一些逻辑上的特殊情况,考察应聘者的考虑的是否全面。小鹿给他起个名字叫做特殊边界。

4. 手写代码

4.1 定义结点

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

4.2 定义结点

就以单链表中部添加数据为例子,分解成每个步骤,每个步骤对应代码如下:
1、保存临时地址(4结点的地址),需要进行遍历查找到3结点,也就是下列代码的currentNode 结点。

//先查找该元素
let currentNode = this.findByValue(element);
// 保存 3 结点的下一结点地址(4 结点的地址)
let pre = currentNode.next

2、创建新结点,将新结点(5结点)的指针指向下一结点指针(4结点地址,已经在上一步骤保存下来了)

let newNode = new Node(value);
newNode.next = pre;

3、将3 的结点地址指向新结点(5结点)

currentNode.next = newNode;

以上步骤分析完毕,剩下的两个在头部插入和在尾部插入同样的分析方式。

4.3 删除节点

删除节点也分为三种,头部、中部、尾部,咱们就删除中间结点为例进行删除,我们详细看步骤操作。
我们先看删除的全部动画,然后再分步拆分。

断开3结点的指针(断开3结点相当于让2结点直接指向4结点)

let currentNode = this.head;
  // 用来记录 3 结点的前一结点
  let preNode = null;
  // 遍历查找 3 结点
  while(currentNode !== null && currentNode.data !== value){
         // 3 结点的前一结点
         preNode = currentNode;
         // 3 结点
         currentNode = currentNode.next;
}

让结点2的指针指向4结点,完成删除。

preNode.next = currentNode.next;

剩下的删除头结点和删除尾结点同样的步骤。
所有代码实现:

/**
 * 2019/11/14
 * 功能:单链表的插入、删除、查找
 * 【插入】:插入到指定元素后方
 * 1、查找该元素是否存在?
 * 2、没有找到返回 -1
 * 3、找到进行创建结点并插入链表。
 *
 * 【查找】:按值查找/按索引查找
 * 1、判断当前结点是否等于null,且是否等于给定值?
 * 2、判断是否可以找到该值?
 * 3、没有找到返回 -1;
 * 4、找到该值返回结点;
 *
 * 【删除】:按值删除
 * 1、判断是否找到该值?
 * 2、找到记录前结点,进行删除;
 * 3、找不到直接返回-1;
 */
//定义结点
class Node{
    constructor(data){
        this.data = data;
        this.next = null;
    }
}

//定义链表
class LinkList{
    constructor(){
        //初始化头结点
        this.head = new Node('head');
    }

    //根据 value 查找结点
    findByValue = (value) =>{
        let currentNode = this.head;
        while(currentNode !== null && currentNode.data !== value){
            currentNode = currentNode.next;
        }
        //判断该结点是否找到
        console.log(currentNode)
        return currentNode === null ? -1 : currentNode;
    }

    //根据 index 查找结点
    findByIndex = (index) =>{
        let pos = 0;
        let currentNode = this.head;
        while(currentNode !== null && pos !== index){
            currentNode = currentNode.next;
            pos++;
        }
        //判断是否找到该索引
        console.log(currentNode)
        return currentNode === null ? -1 : currentNode;
    }

    //插入元素(指定元素向后插入)
    insert = (value,element) =>{
        //先查找该元素
        let currentNode = this.findByValue(element);
        //如果没有找到
        if(currentNode == -1){
            console.log("未找到插入位置!")
            return;
        }
        let newNode = new Node(value);
        newNode.next = currentNode.next;
        currentNode.next = newNode;
    }

    //根据值删除结点
    delete = (value) =>{
        let currentNode = this.head;
        let preNode = null;
        while(currentNode !== null && currentNode.data !== value){
            preNode = currentNode;
            currentNode = currentNode.next;
        }
        if(currentNode == null) return -1;
        preNode.next = currentNode.next;
    }

     //遍历所有结点
    print = () =>{
        let currentNode = this.head
        //如果结点不为空
        while(currentNode !== null){
            console.log(currentNode.data)
            currentNode = currentNode.next;
        }
    }
}

//测试
const list = new LinkList()
list.insert('xiao','head');
list.insert('lu','xiao');
list.insert('ni','head');
list.insert('hellow','head');
list.print()
console.log('-------------删除元素------------')
list.delete('ni')
list.delete('xiao')
list.print()
console.log('-------------按值查找------------')
list.findByValue('lu')
console.log('-------------按索引查找------------')
list.print()

5. 测试用例

其实这里的测试用例主要用于判断我们写的程序到底对不对,我们一般都会输入一个自己认为的情况进行测试,这是错误的做法。程序最容易出错的还是边界情况考虑不全面导致的,所以,我们为了能够测试程序的全面性,所以我们要按照以下步骤进行全面性的测试。

5.1 普通测试
普通测试就是输入一个正常的值,比如单链表中插入数据。

5.2 特殊测试
特殊输入可以参照上边边界条件中的特殊边界进行测试,比如在头部插入数据,在尾部插入数据等特殊情况的测试。

5.3 输入测试
对于输入测试的内容参考上边边界条件中的输入边界,比如:输入一个空链表测试一下程序能否正常的运行。

6. 小结

做一个小结。今天的手写链表主要从五部分下手,从前到后依次为熟悉结构、理清思路、手写代码、测试用例。以后无论手写什么代码,有五步走。

通过总结链表的方法,不用刻意去背,只要把思路理清楚,边界条件考虑全面,就不用去背,重复的练习。今天的内容就到这里了,非常感谢。

参考:
https://mp.weixin.qq.com/s/hKjkITbCRcnZBafpjiwWJA

原文地址:https://www.cnblogs.com/wufeiboyuan/p/12012608.html

时间: 2024-10-21 02:31:55

基础数据结构-链表的相关文章

基础数据结构 链表、栈、队列

数据结构是程序设计中一个非常重要的部分,基本的数据结构包括链表.栈和队列,当然高级一点的还有树.图等,实际上链表.栈和队列都是线性表,只是在操作和表示方式上有所不同,线性表用顺序结构表示就是顺序表,用链结构表示就是链表,如果对线性表的操作加以限制,只能有在表尾进行插入和删除元素,这就变成栈了,如果只能允许元素从表尾插入,表头删除,这就变成队列了. 链表 /* * 数据结构 链表 * 链式结构 */ #include <iostream> using namespace std; enum St

java 基础数据结构

数据结构, 需要考虑两个方面: 1. 每个元素具体的存储方法 (java中是一个对象) 2. 元素之间的关系如何实现存储 (java中也是一个对象) 另外在java中, 已经可以把跟数据结构有关的一些方法写到一个类里了. 线性表 顺序表 c语言: 借助数组实现 #define INIT_SIZE 100; typedef struct { int elem[INIT_SIZE]; // 用来存储数组元素 int length; // 当前顺序表的长度 } SqList; // 元素之间的关系隐含

redis 基础数据结构实现

参考文献 redis数据结构分析 Skip List(跳跃表)原理详解 redis 源码分析之内存布局 Redis 基础数据结构与对象 Redis设计与实现-第7章-压缩列表 在redis中构建了自己的底层数据结构:动态字符,双端链表,字典,压缩列表,整数集合和跳跃表等.通过这些数据结构,redis构造出字符串对象,列表对象,哈希对象,集合对象和有序集合对象这5种我们常用的数据结构.接下来将从底层数据结构开始,一步步介绍redis的数据结构的实现 动态字符串 在redis中并没有使用c语言原生的

基础数据结构

这周,研究了一下基础数据结构,感觉挺难的.啥都懂,但做题就难了. 好,言归正传,下面就对基础数据结构进行一个总结. 一.什么事数据结构 数据结构是计算机存储.组织数据的一种方式. 二.为什么要学数据结构 首先,一个程序不能没有数据结构,一个程序可以说是算法和数据结构构成的. 其次,程序设计其实就是对问题的设计,所以代码实现的速度.质量等,都与选用的数据结构有关系. 三.数据结构有哪些 常见的数据结构有栈.队列.树.链表等. 其中,结构体是比较特殊的一种数据结构,数组是最重要的数据结构. 最后,数

线段树基础知识----(基础数据结构)--(一)

1.定义 引入:为什么要使用线段树而不用数组模拟呢? answer:因为有些题用数组来做就要超时,用线段树的O(log(n))的时间复杂度刚好可以求解 毫无疑问线段树是一种数据结构,但是它实际是一个类似树状的链表结构(个人认为) ///还是要正经一点(照搬教科书)----------- / ////////////////////////////////////////////////////////////////////// 线段树定义:线段树是一种二叉搜索树,与区间树相似,它将一个区间划分

2.【Redis系列】Redis基础数据结构

原文:2.[Redis系列]Redis基础数据结构 千里之行始于足下,我们先来看看redis的基础知识. Redis有5中基本数据类型:字符串(string).列表(list).集合(set).有序集合(zset).字典(hash).熟练掌握这5种基本数据结构也是最基本最重要的部分. String(字符串) 字符串是redis中最简单的数据结构,Redis所有的数据结构都是以唯一key作为名称,然后通过唯一的key来获取相应的redis数据.不同类型的数据结构的差异就在于value的结构不一样.

排序与基础数据结构

6大排序与6大基础数据结构 本文从冒泡排序撩起,对选择.插入.希尔.归并.快排6种经典的数组排序进行了深入分析,并详解其间的关联,让你深刻理解其中的关键点:同时对经典的数据结构Vector.Stack.Queue.树.Map.Set做了归纳总结,对其底层的实现做了解析,分享给大家,作为每一个中高级程序员应该懂得的算法与排序,祝大家早上走上自己的"成金之路". 目录: 1.排序算法 2.数据结构 3.资料参考 1.排序算法: a.起源: 计算机从诞生起,就在模拟人这种智能生物的行为,而排

【UOJ#228】基础数据结构练习题 线段树

#228. 基础数据结构练习题 题目链接:http://uoj.ac/problem/228 Solution 这题由于有区间+操作,所以和花神还是不一样的. 花神那道题,我们可以考虑每个数最多开根几次就会成1,而这个必须利用开根的性质 我们维护区间最大.最小.和.区间加操作可以直接做. 区间开方操作需要特殊考虑. 首先对于一个区间,如果这个区间的所有数取$x=\left \lfloor \sqrt{x} \right \rfloor$值一样,那么就可以直接区间覆盖. 分析上述过程,一个区间可以

Flink内存管理源码解读之基础数据结构

概述 在分布式实时计算领域,如何让框架/引擎足够高效地在内存中存取.处理海量数据是一个非常棘手的问题.在应对这一问题上Flink无疑是做得非常杰出的,Flink的自主内存管理设计也许比它自身的知名度更高一些.正好最近在研读Flink的源码,所以开两篇文章来谈谈Flink的内存管理设计. Flink的内存管理的亮点体现在作为以Java为主的(部分功能用Scala实现,也是一种遵循JVM规范并依赖JVM解释执行的函数式编程语言)的程序却自主实现内存的管理而不完全依赖于JVM的内存管理机制.它的优势在