链表的概念以及相关基础操作

2023-05-16

前言:

链表是数据结构里面最开始的章节,也是对新手的理解有困难的第一章。笔者大二下学校才开设数据结构,以防自己忘记,遂记录之。

链表的概念:

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

由以上定义我们可知道单链表的组成。单链表由头指针头结点结点组成。头指针是找到一个链表的关键,头结点可以帮助我们更好的操作链表,头结点的指针域指向第一个结点,头结点的数据域可以用来记录表长,但是我习惯于赋0。一个结点分为指针域数据域。通过指针域可以找到下一个结点。最后一个结点的指针域为NULL,这可以作为遍历结束的标志。

链表的优势:

相对于线性表:

①链表对于长度定义不死板,可以随时插入元素,增加表长。

②线性表在插入和删除元素时,要移动大量的元素,而链表插入和删除操作简单。

链表的相关操作:

定义:

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
using namespace std;
// 定义相关返回值
const int  OK = 1;  
const int  ERROR = 0;
// 元素类型与返回状态的别称
typedef int ElemType;
typedef int Status;

// 节点,链表定义
typedef struct Lnode {  
    ElemType data; //数据域
    struct Lnode *next;  //指针域
}Lnode,*LinkList;  // 这里的*LinkList 等价于 Lnode * Lnode是一个结点,LinkList相当于头指针

初始化的操作

这里的初始化便创建了一个带有头结点的链表,之后可以用LinkList L找到这个链表,所以L最好是不要移动或者是改变指向。

//链表的初始化
Status InitList(LinkList & L) { //这里是地址传递,如果要改变L的值就传地址,否则会copy一份操作,这样就不会改变L
    L = new Lnode;  //创建头节点
    L->next = NULL;
    L->data = 0;
    return OK;
}

链表的创建,前插法与尾插法

前插法:

思路:有个头指针永远指向最左边的元素,新建的元素的指针域先接头结点的的指针域(即NULL),然后再将头结点指向该结点。关键的步骤在于s->next = L->next;L->next = s;

值得注意的是:这种创建方法的元素存储顺序是与逻辑顺序相反的!

void CreateList_F(LinkList &L, int n) {
    //创建的一开始都要先初始化
    L = new Lnode;
    L->next = NULL;
    L->data = 0;
    for (int i = n; i > 0; i--) {//这里的n的作用就是用来计数的
        LinkList s = new Lnode; 
        cin >> s->data;
        s->next = L->next;
        L->next = s;
    }
}

尾插法:

思路:初始化该链表的同时用一个尾指针永远指向链表的最后一位,每新建一个结点,新节点的指针域都赋值为NULL,然后连接到r的指针域上去,r指针移动到新节点上,这样r又是指向最后的结点了。关键操作:s->next = NULL;r->next = s; r= s;

//链表的创建 --- 尾插法(与逻辑顺序相同)
void CreateList_R(LinkList& L, int n) {
    L = new Lnode;
    L->next = NULL;
    //尾插法要用尾指针
    LinkList R = L;
    for (int i = 0; i < n; i++) {
        LinkList s;
        s = new Lnode; //这里别忘记了s应指向一个新的结点
        cin >> s->data;
        s->next = NULL;
        R->next = s;
        R = s;
    }
}

链表的打印

这里提供两种方法:第一种是while遍历,第二种是递归的思想。

这里如果是直接传入L而不是&L,也不需要p来充当遍历指针,直接移动头结点就可以了。

在用第二种方法的时候,是要从头结点开始遍历的,所以最好给头结点的data赋值,要不然就是随机分配的数字了。

//使用while
void PrintList(LinkList& L) {
    if (L == NULL) { 
        cout << "链表已空" << endl;
        return;
    }
    LinkList p = L->next;
    while (p) {
        cout << p->data << " ";
        p = p->next;
    }
    cout << endl;
}
//递归
void PrintList(LinkList L){
if(!L) return; 
cout<<L->data<<" ";
PrintList(L->next);
}

链表的插入操作

想插入到ai的位置,必须先找到ai-1的位置。并且插入是可以插入到第一个跟最后一个元素的位置上的。所以这里j从0开始,结束条件是j<i-1。出了循环如果找到了就是i-1的位置。这里p=L,是从头结点开始遍历的,这样可以插到第一个元素。while里面写p,保证插到最后一个元素上。

//链表的插入操作
Status AddList(LinkList& L, int i, ElemType e) {
    LinkList p = L;
    int j = 0;
    //这里是p而不是p->next,这样写可以在最后一位插入数,p->next的话最后一位不能插入,删除是这样写的
    //j<i-1的意思是找到ai-1
    while (p && j < i - 1) {
        p = p->next;
        j++;
    } 
    if (!p || j > i - 1)  return ERROR; // 没找到直接return
    LinkList s = new Lnode; //这里的Linklist 跟Londe *含义一样,但是我们一般使用Linklist,符合定义习惯
    s->data = e;   // s的含义是s指向的节点,s->的含义是指向s的下一个节点
    s->next = p->next;
    p->next = s;
    return OK;
}

链表的删除操作

想删除第ai个元素,必须找到第ai-1个元素的位置。值得注意的是①这里的while中的条件应该是p->next。(最后一个结点是不会作为ai-1的)②删除一个元素要释放空间(delete)

//删除第ai个元素 ,并用e带回返回值
Status DeleteListElem(LinkList &L,int i,ElemType &e){
int j = 0;
LinkList p = L;
while(p->next&&j<i-1){
    p = p->next;
    j++;
}
if(!(p->next)||j>i-1) return ERROR;
Linklist q;
q = p ->next;
e = q ->data;
p->next = q->next;
delete q;
return OK;
}

链表的查询操作

这里提供按序号查询的操作

思想就是遍历,找到了就返回。

//链表的按序号查找 直接找到ai就行了
Status GetElem(LinkList L, int i, ElemType& e) { //找到的元素赋值给e
    LinkList p = L->next; //不要考虑开头与结尾 直接从第一个元素开始即可
    int j = 1; // 这里的j从1开始,因为不要考虑ai-1!但是插入删除要考虑ai的前一个元素,所以从0开始!!
    while (p && j < i) { //j<i如果有结果,那就是j=i
        p = p->next; 
        j++;
    }
    if (!p || j > i) return ERROR; //没有找到return掉
    e = p->data;
    return OK;
}

链表的排序操作

这里可以用冒泡,选择排序等,但是笔者在其他地方看到的一个简单的操作但是可以提高排序的速度。

思路就是先放到顺序表中,然后用sort排序,再放回链表中。这样最快有nlogn的速度。

// 求链表长度
int LengthList(LinkList& L) {
    int n = 0;
    LinkList q = L->next; // 从头节点开始
    while (q) {
        q = q->next;
        n++;
    }
    return n;
}
//前提是知道链表的长度
//链表的排序  先全放到数组中,然后对数组排序,最后再放回链表中,这样做比冒泡要快nlogn <n^2
void SortList(LinkList& L) {
    int length = LengthList(L);
    ElemType *arr = new ElemType[length];  // c++开辟空间的语法 指针 = new 类型[个数];
    //if (Is_Empty(L)) return;
    int i = 0;
    LinkList p = L->next;
    //遍历链表,全放数组里
    while (p) {
        arr[i] = p->data;
        i++;
        p = p->next;
    }
    // 这里一定要重新改变i跟p的指向
    i = 0;
    p = L->next;
    //对数组排序
    sort(arr, arr + length);
    //再全放入链表中
    while (p) {
        p->data = arr[i];
        i++;
        p = p->next;
    }
}

有序链表的合并

生成升序链表

// 两个链式表的合并
// 前提是两个链表是有序的
void mergeList_L(LinkList& La,LinkList &Lb,LinkList &Lc) {
    //开始的时候用pa,pb分别指向LaLb的第一个元素,Lc指向La的头节点
    LinkList pa = La ->next; 
    LinkList pb = Lb->next;
    Lc = La;
    LinkList pc = Lc;
    //当la或者lb一者为空时,结束比较
    //循环条件为pa&&pb
    while (pa && pb) {
        if (pa->data <= pb->data) { //La的元素小
            pc->next = pa; pc = pa; pa = pa->next;
        }
        else {
            pc->next = pb; pc = pb; pb = pb->next;
        }
    }
        pc->next = pa ? pa : pb;
        //用La做表头,所以释放Lb的空间
        delete Lb;
}

生成降序

void mergeList(LinkList l1, LinkList l2, LinkList &l3) {  // 要用到头插法 因为头插法的插入顺序是与逻辑顺序相反的
    LinkList p1 = l1->next;
    LinkList p2 = l2->next;
    l3 = l1;
    l3->next = NULL;  // 使用头插法
    LinkList s;  // 工作指针指向插入节点
    while (p1 && p2) {
        if (p1->data < p2->data) {  // p2比较大的话
            s = p1;
            p1 = p1->next;
            s->next = l3->next;
            l3->next = s;
        
        }
        else {
            s = p2;
            p2 = p2->next;
            s->next = l3->next;
            l3->next = s;
        }
    }
    if (!p1) p1 = p2;
    while (p1) {  //全部头插法到l3
        s = p1;
        p1 = p1->next;
        s->next = l3->next;
        l3->next = s;
    } 
}

链表的反转

三指针法

int inverse(LinkList& L) { //三指针法
    if (L->next->next == NULL) return -1;
    LinkList r = L;
    LinkList p = L->next;
    LinkList q = L->next->next;
    while (q) {
        p->next = r;
        r = p;
        p = q;
        q = q->next;
    }
    p->next = r;
    L->next->next = NULL;
    L->next = p;
    return 1;
}

链表的题目还是要多画图,这样好理解。

算法初学者且第一次发帖,如果有错请告知!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

链表的概念以及相关基础操作 的相关文章

随机推荐

  • 速腾Helios-16P使用Lego-loam实时构建点云地图(三)——使用雷达实时运行lego-loam

    当确定好雷达正常运行 lego loam正常运行后 xff0c 就可以开始使用雷达实时运行lego loam了 步骤一 xff1a 连接硬件 步骤二 xff1a 修改电脑IP地址 用完雷达记得将设置中有线地址选择之前的默认地址 xff0c
  • 串口通信协议【I2C、SPI、UART、RS232、RS422、RS485、CAN、TTL、USB】

    xff08 1 xff09 I2C 集成电路互连总线接口 Inter IC xff1a 同步串行半双工传输总线 xff0c 连接嵌入式处理器及其外围器件 支持器件 xff1a LCD驱动器 Flash存储器 特点 有两根传输线 xff08
  • Pyhton request模拟浏览器登录(过程、我踩过的坑及解决方式)

    过程 xff08 后表示代码中的操作 xff09 f12 gt network gt ctrl 43 R 登录 gt 输入账号 密码 gt 确定 在network中找到login 需要它的 请求标头 和 url xff09 准备 sessi
  • SpringSecurity用户密码验证过程

    SpringSecurity过滤链当中的UsernamePasswordAuthenticationFilter负责登陆密码验证 AbstractAuthenticationProcessingFilter是UsernamePassword
  • 项目实战:基于 TCP 的局域网内高性能文件传输系统设计与实现

    0 写在前面1 系统设计目标2 系统整体设计思路 2 1 网络传输协议的选择与通信协议的设计2 2 数据库设计 3 上传 下载文件的设计方案4 断点续传的原理及设计5 秒传的原理及设计6 数据库设计 API 编程与 shell 脚本的结合7
  • 进程与线程学习心得

    一 进程与线程的区别 1 进程是操作系统进行资源调度和分配的基本单位 线程是操作系统可执行的最小调度和分配单位 2 一个线程属于一个进程 一个进程可以有多个线程 3 一个进程崩溃不影响其他进程 但是一个线程崩溃会让进程崩溃 4 进程在执行过
  • 《网络编程》C语言 创建TCP服务器(三种)

    一 循环服务器 伪代码 xff1a sfd 61 socket bind listen while 1 newfd 61 accept while 1 recv send close newfd
  • 《网络编程》C语言 使用select函数搭建TCP客户端和服务器

    IO 多路复用概念 1 允许同时多个 IO 进行操作 xff0c 内核一旦发现进程执行一个或多个 IO 事件 xff0c 就会通知该进程 2 应用程序中同时需要处理多路输入输出流 select 功能 xff1a 让内核监听指定集合中的文件描
  • ubuntu下切换python版本(python2与python3之间的切换,python3与python3之间的切换)

    目录 1 问题2 重装python3 83 配置 python3 8 为系统默认 python34 切换回系统自带的python3 1 问题 有点无语 xff0c python3 8明明下载安装好 但是设置python默认版本为python
  • Ubuntu22.04中解决mNetassist无法打开问题

    因为本人在安装mNetassist遇到很多问题 xff0c 也是查找了很多资料 xff0c 最终解决了问题 因为每个人遇到的问题会有所不同 xff0c 故把本人的安装经历记录下来 xff0c 以供参考 大家可参考的文章是这篇 xff0c 但
  • C++中常用的库函数 (自用)

    常用的库函数 一 前言二 内容1 sort 题目 2 upper bound 与lower bound 题目 3 to string 4 string 内嵌的 find 函数 注 xff1a vector无find 函数5 大小写转换 to
  • 四轴飞行器PID调参建议

    在动态控制中 xff0c 我们通过调整PID三个参数来获得动力 xff0c 同时消除振荡 xff0c 找到对你当前的飞行场景来说更优的手感 P xff08 Propotional xff09 是比例的简称 P 单元控制着控制系统的所有动力
  • C语言string库strcpy、strcmp、strcat函数详解

    strcpy 即string copy 语法格式为strcpy str1 str2 作用是将str2赋值给str1 使用方法类似于 char str1 10 str2 61 34 abc 34 strcpy str1 34 bcd 34 s
  • VScode常用快捷键、

    VScode常用快捷键 xff1a 英文 按回车enter xff1a 会快速打出html 后缀名 自行填写 shift xff0b alt xff08 鼠标放在复制行代码区 xff0c 或者鼠标选择区域 xff09 按控制 向下 键 xf
  • 深入了解运行时栈(C语言)

    文章目录 运行时栈函数的栈帧寄存器与机器指令寄存器 xff1a 机器指令 程序计数器控制转移数据传送参数的传递返回值的传递 举例 xff1a 函数栈帧创建和销毁的全过程小结 运行时栈 栈是一种数据结构 xff1a 我们可以向这种结构中存入数
  • 小四轴调试记录

    从准备理论到实际动手调试大约耗时半年吧 xff0c 期间看了很多理论知识 xff0c 惯性导航方面的文章 至于为什么选择从小四轴入手 xff0c 当时的理由很简单 xff1a 1 便宜 xff0c 2 空心杯电机虽然有刷会坏但便宜 xff0
  • 登录 账号密码验证

    lt DOCTYPE html gt lt html lang 61 34 en 34 gt lt head gt lt meta charset 61 34 UTF 8 34 gt lt meta http equiv 61 34 X U
  • 关于C++变量重复定义

    本人是刚入学的大一计算机类学生 xff0c 最近在学习C 43 43 xff0c 在回顾这个代码时候发现 xff0c 这个重复定义i和j会导致之前定义的全局变量i和j并不能起作用 xff0c 现在还不太清楚为什么 xff0c 请小伙伴们注意
  • OpenMV——色块识别

    OpenMV有很多示例代码 xff0c 下面是我学习过程中有关知识的总结 目录 前言 一 阈值选择 二 代码 前言 函数RGB 255 0 0 表示的是红色 RGB 255 0 0 含义 xff1a 红色值 Red 61 255 xff1b
  • 链表的概念以及相关基础操作

    前言 xff1a 链表是数据结构里面最开始的章节 xff0c 也是对新手的理解有困难的第一章 笔者大二下学校才开设数据结构 xff0c 以防自己忘记 xff0c 遂记录之 链表的概念 xff1a 链表是一种物理存储单元上非连续 非顺序的存储