数据结构—单链表的概述与应用、顺序表与链表的比较(上)

简介: 数据结构—单链表的概述与应用、顺序表与链表的比较

第二章:线性

(一) 单链表

       1.定义

采用链式存储方式存储的线性表称为链表。


            链表中每一个结点包含存放数据元素值的数据域和存放逻辑上相邻节点的指针域。


                  数据域 data:用于存放数据元素的值。


                  指针域 next:用于存放后继结点的地址。

56dd66c51fe84344a837c58ea48c6061.png

2.术语

            非空表

fe9229758c96405599f44daec52acdc6.png

空表:若线性表为空,则头结点的指针域为空

a0b0f80a89124311ada984d624a43e23.png

3. 结点类的描述

             单链表是由若干个结点连接而成。

             数据域 data:用于存放数据元素的值。

             指针域 next:用于存放后继结点的地址

package data.linear_table.linked_list;
//单链表:结点类
public class Node {
    public Object data ;  //存放结点值 : 数据域
    public Node next ;    // 后继结点的引用 : 指针域
    //无参时的构造函数
    public Node() {
        this(null,null);
    }
    //带一个参数时的构造函数
    public Node(Object data) {
        this(data,null);
    }
    //带两个参数时的构造函数
    public Node(Object data , Node next) {
        this.data = data ;
        this.next = next ;
    }
}

4. 单链表类的描述

               只需一个头指针就能唯一标识它。所以单链表类只需设置一个头指针即可。

package data.linear_table.linked_list;
import data.linear_table.IList;
//单链表类
//实现线性表IList接口
public class LinkList  implements IList {
    public Node head ;     //单链表的头指针
    //单链表的构造函数
    public LinkList(){
        //初始化头结点
        head = new Node();
    }
    //构造一个长度为n的单链表
    public LinkList(int n ,boolean order) {
        //初始化结点
        this();
        if (order){
            //用尾插法顺序建立单链表
            create1(n);
        }else {
            //用头插法逆位序建立单链表
            create2(n);
        }
    }
    //用尾插法顺序建立单链表,其中n为单链表中的结点个数
    public void create1(int n ) {
        //{...}
    }
    //用头插法逆位序建立单链表,其中n为单链表中的结点个数
    public void create2(int n ) {
        //{...}
    }
    //设为空链表
    @Override
    public void clear() {
        head.data = null ;
        head.next = null ;
    }
    //判断带头结点的单链表是否为空
    @Override
    public boolean isEmpty() {
        return head.next == null ;
    }
    //输出单链表中所有的结点
    @Override
    public void display() {
        //取出带头结点的单链表中的首结点
        Node node = head.next;
        while (node != null) {
            //输出结点的值
            System.out.println(node.data+" ");
            //取出下一个结点
            node = node.next ;
        }
        //换行
        System.out.println();
    }
    //求带头结点的单链表长度
    @Override
    public int length() {
        //{...}
        return 0 ;
    }
    @Override
    public Object get(int i) throws Exception {
        //{...}
        return null;
    }
    @Override
    public void insert(int i, Object x) throws Exception {
        //{...}
    }
    @Override
    public void remove(int i) throws Exception {
        //{...}
    }
    @Override
    public int indexOf(Object x) {
        //{...}
        return 0;
    }
}

注:单链表类中的“//{...}”中的代码将在后续的具体操作中实现


-----------------------------------------------------------------------------------------------


    5. 单链表的具体操作实现

               单链表的长度

public int length() {
    Node p = head.next;         // 获得第一个结点
    int length = 0;           // 定义一个变量记录长度
    while(p != null) {
        length ++;            //计数
        p = p.next;           //p指向下一个结点
    }
    return length;
}

单链表的查找操作

                               按位序号查找操作:i 结点的限制长度为[0,n-1]之间(n为单链表的当前长度)

public Object get(int i) {
    Node p = head.next;       //初始化,p指向头结点
    int j = 0;            //计数器
//链表没有下一个 && 处理有效部分【从首结点开始向后查找,直到p指向第i个结点】
    while(p != null && j < i) {   
        //指向后继结点
        p = p.next;
        j++;           
    }
    //i 小于0 或者i 大于表长度减一时,即不合法
    //解析 j > i :  j = 0 时—— j > i 即 0 > i 即 i < 0 
   //当j遍历累加到 i-1 有效部分之后,即 j < i 即要使位置不合法,则需 j > i 
    if(j > i || p == null) {
        throw new Exception("元素不存在");
    }
    return p.data;          //返回数据
}

按值查找索引号: 根据结点的数据域值进行对比查找,返回下标索引

//查询给定值的索引号,如果没有返回-1
public int indexOf(Object x) {
    Node p = head.next;     // 初始化,p指向头结点
    int j = 0;          // 不匹配元素的个数,计数器
    while(p != null && !p.data.equals(x)) { // 循环依次找【不匹配】元素
        p = p.next;
        j++;
    }
    // 返回匹配索引,如果没有返回-1
    if(p != null) {
        return j;
    } else {
        return -1;
    }
}

单链表的插入操作

                       需求:向索引i前面插入一个新结点s

                       思路:获得i的前驱,结点P

—— 带头结点的单链表上插入操作算法

9ceab938e68f47b3a3a0874ceffa5033.png

    @Override
    //在带头结点的单链表的第i个结点之前插入一个数据域值为x的新结点
    public void insert(int i, Object x) throws Exception {
        //获得i前驱,结点p
        Node p = head ;  // 从头结点head开始移动
        int count = -1 ;  // 使用 -1 表达头结点的索引
        //请用 i= -1/0/1 去测试
        //循环条件: 结点不为null , 并且 计数长度小于 i-1前驱的下标。
        while (p != null && count < i -1 ) {
            p = p.next ;
            count++ ;
        }
        //判断位置是否不合理
        if (count < 0 || p == null ){
            throw new Exception("位置非法");
        }
        //创建一个结点,存入数据x
        Node s = new Node(x);
        //插入位置为表的中间或表尾时
        s.next = p.next ;
        p.next = s ;
    }

注意:指向结点的顺序不可变


//创建一个结点,存入数据x

       Node s = new Node(x);

       //插入位置为表的中间或表尾时

       s.next = p.next ;                            // 第1步

       p.next = s ;                                   // 第2 步


两步的顺序不可调换


原因:


       如若调换位置:如下图

e57b802732e744acaa42415742f4c1a7.png

若位置顺序不变:如下图

78c94307788d4b8a99059ccb73b8aaed.png


—— 不带头结点的单链表上插入操作算法:考虑到在表aca9e728e0c64b2aab69b4f10f08b793.png

@Override
    //在不带头结点的单链表的第i个结点之前插入一个数据域值为x的新结点
    public void insert(int i, Object x) throws Exception {
        //获得i前驱,结点p
        Node p = head ;  // 从头结点head开始移动
        int count = -1 ;  // 使用 -1 表达头结点的索引
        //请用 i= -1/0/1 去测试
        //循环条件: 结点不为null , 并且 计数长度小于 i-1前驱的下标。
        while (p != null && count < i -1 ) {
            p = p.next ;
            count++ ;
        }
        //判断位置是否不合理
        if (count > i || p == null ){
            throw new Exception("位置非法");
        }
        //创建一个结点,存入数据x
        Node s = new Node(x);
        //判断: 插入位置为表头时
        if( i == 0 ){
            s.next = head ;
            head = s ;
        }else {
            //插入位置为表的中间或表尾时
            s.next = p.next ;
            p.next = s ;
        }
    }

头插入节点的情况


相关文章
|
8月前
|
消息中间件 缓存 NoSQL
Redis各类数据结构详细介绍及其在Go语言Gin框架下实践应用
这只是利用Go语言和Gin框架与Redis交互最基础部分展示;根据具体业务需求可能需要更复杂查询、事务处理或订阅发布功能实现更多高级特性应用场景。
435 86
|
10月前
|
存储 监控 安全
企业上网监控系统中红黑树数据结构的 Python 算法实现与应用研究
企业上网监控系统需高效处理海量数据,传统数据结构存在性能瓶颈。红黑树通过自平衡机制,确保查找、插入、删除操作的时间复杂度稳定在 O(log n),适用于网络记录存储、设备信息维护及安全事件排序等场景。本文分析红黑树的理论基础、应用场景及 Python 实现,并探讨其在企业监控系统中的实践价值,提升系统性能与稳定性。
603 1
|
10月前
|
存储 监控 算法
公司员工泄密防护体系中跳表数据结构及其 Go 语言算法的应用研究
在数字化办公中,企业面临员工泄密风险。本文探讨使用跳表(Skip List)数据结构优化泄密防护系统,提升敏感数据监测效率。跳表以其高效的动态数据处理能力,为企业信息安全管理提供了可靠技术支持。
202 0
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
531 30
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
792 25
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
641 5
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
396 59
|
11月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
228 0
栈区的非法访问导致的死循环(x64)