【C语言版】数据结构教程(二)线性表

news2024/11/23 6:30:58

 【内容简介】本文整理数据结构(C语言版)相关内容的复习笔记,供各位朋友借鉴学习。本章内容更偏于记忆和理解,请读者们耐心阅读。同时,这里提醒各位读者,尽管书本上说本书用的是 C 语言版,但是中间用到的许多操作都是需要在 C++ 环境下才可以运行的。

2.1 线性表的类型定义

        线性表(linear_list)是最常用且是最简单的一种数据结构。在介绍它之前,我们先来了解什么是线性结构。

        什么是线性结构?线性结构的定义是:在数据元素的非空有限集中,(1)存在唯一的一个被称为“第一个”的数据元素;(2)存在唯一的一个被称为“最后一个”的数据元素;(3)除第一个之外,集合中的每个数据元素均只有一个前驱;(4)除最后一个之外,集合中每个数据元素均只有一个后继。

        由此,实际上,线性表就是一个线性结构,其中由 n 个数据元素排列而成。比如:(A, B, C, ... , Z),这个由26个英文字母组成的字母表就是一个线性表。但对于一些比较复杂的线性表,一个数据元素往往由若干个数据项组成。这时,我们称由若干个数据项组成的数据元素为记录(record),而含有大量记录的线性表被称为文件(file)

【例1】下面这张学生健康登记表也是一个线性表。其中每个学生的情况都是一个数据元素,它由姓名、学号、性别、年龄、班级和健康状况等 6 个数据项组成。

                从上面的例子可见,线性表中数据元素可以是任意类型,但同一个线性表中的所有元素必定具有相同属性,也就是说属于同一数据对象。一般情况下,我们可以将线性表记为:

(a_1, ... , a_i, a_{ i +1}, ... , a_n)

        线性表中元素的个数 n 定义为线性表的长度,n = 0 时称为空表。其中 a_i 是第 i 个数据元素,称 i 为数据元素 a_i 在线性表中的位序。

        线性表中还有许多其他的操作,以下是抽象数据类型线性表的定义

         对于上述定义的抽象数据类型线性表,还可进行一些更复杂的操作,例如:将两个及以上的线性表合并成一个线性表;把一个线性表拆成两个或两个以上的线性表;复制一个线性表等等。在上图中有一些比较常用的操作,这些在之后的算法书写中都是可以直接使用的,因此需要牢记!

        在数据结构的学习过程中,算法的书写必定是重中之重,这里我们先来看一个例子:

【例2】已知线性表 A 和 B 中的数据元素按值非递减有序排列,现要求将 A 和 B 归并为一个新的线性表 C,并且 C 中的数据元素仍按照非递减有序排列。试编写算法满足上述要求。

例如:A = { 1, 2, 4, 8 };B = { 3, 7, 10, 11, 12 },则 C = { 1, 2, 3, 4, 7, 8, 10, 11, 12 }。

【算法思路】由于两个线性表 A, B 都已经按顺序排好了,只需要先设 C 是空表,将 A, B 中的元素按大小顺序依次放入 C 中即可。具体可以设两个指针分别指向 A 和 B 中元素,再按插入顺序后移即可。(请注意,这里书写时用的是类 C 语言,并不是真正的 C 语言!!!)

void MergeList(List A, List B, List &C) {
    // 已知线性表A,B,C中数据元素按值非递减排列。
    // 归并A,B得到的新的线性表C,C的数据元素也按值非递减排列
    InitList(C);
    i = j = 1; k = 0;
    A_len = ListLength(A); B_len = ListLength(B);
    while((i <= A_len) && (j <= B_len)) { // A,B均非空
        GetElem(A, i, ai); GetElem(B, j, bj);
        if(ai <= bj) {ListInsert(C, ++k, ai); ++i;}
        else {ListInsert(C, ++k, bj); ++j;}
    } 
    while(i <= A) {
        GetElem(A, i++, ai); ListInsert(C, ++k, ai);
    }
    while(j <= B) {
        GetElem(B, j++, bj); ListInsert(C, ++k, bj);
    }
}// MergeList

【时间复杂度分析】该算法的时间复杂度为 O(ListLength(A) + ListLength(B))。后面两个循环最终只会执行一个。 

2.2 线性表的顺序表示和实现

2.2.1 线性表的顺序存储表示

        线性表的顺序表示指的是表中的数据元素不仅逻辑上相邻,其物理次序也相邻。有时,我们也称这种存储结构的线性表为顺序表

        由于线性表的长度可变,且所需最大存储空间随问题的不同而不同,因此在 C 语言中可以使用动态分配的一维数组来表示线性表,描述如下:

PS: 这里请注意,之后初始化时的动态分配是 C++ 特有的,而不是 C 语言所有的写法!

#define MAX 100
typedef struct {
    ElemType *elem;
    int length;
} SqList;

2.2.2 顺序表中基本操作的实现

         这里我们主要针对上面提到的一些线性表的操作,用算法来实现一下。

【1】初始化:顺序表的初始化操作就是构造一个空的顺序表。

// 动态分配线性表的存储区域可以更有效地利用系统的资源,当不需要时可以销毁操作
int InitList(SqList &L) {
    // 构造一个空的线性表
    L.elem = new ElemType[MAX];  // 为顺序表分配空间
    if (!L.elem) exit(OVERFLOW); // 存储分配失败退出
    L.length = 0;                // 空表长度为0
    return 1;
}

【2】取值和查找:在顺序表中取某一个元素的值和查找某一个元素分别为复杂度 O(1) 和 O(n) 的算法。

int GetElem(SqList L, int i, ElemType &e) {
    if (i < 1 || i > L.length) return 0;
    
    e = L.elem[i - 1]; // elem[i-1]单元存储第 i 个数据元素

    return 1;
}
int LocateElem(SqList L, ElemType e) {
    // 在顺序表中查找值为e的数据元素,返回其序号
    for (int i = 0; i < L.length; i++) {
        if (L.elem[i] == e) return i + 1;
    }
    return 0;
}

【3】插入:插入时主要的时间消耗在于需要将插入位置之后的其它元素统一向后移动。

int InsertList(SqList &L, int i, ElemType e) {
    if ((i < 1) || (i > L.length + 1)) return 0;
    if (L.length == MAX) return 0;
    for (int j = L.length - 1; j >= i - 1; j--) {
        L.elem[j + 1] = L.elem[j];
    }
    L.elem[i - 1] = e;
    ++L.length;
    return 1;
}

【4】删除:这个和插入差不多,只不过需要将删除之后的其它元素统一向前移动。这里就不写代码示例了,留给读者自行尝试完成,应该比较简单(原谅我说这么恶毒的话)

2.3 线性表的链式表示和实现

2.3.1 单链表的定义和表示

        线性表的链式表示中最突出的特点就是相邻的结点在内存中的存储并不一定是连续的,也就意味着我们不能用下标进行访问链表中的某一个结点。而为了能表示每个数据元素 a_i 与其直接相邻的数据元素 a_{i+1} 之间的逻辑关系,我们使用指针来串联整个链表。又由于此链表中每个结点都只包含一个指针域,故又称线性链表或单链表

        由于整个单链表线性相连,我们不难意识到整个链表的逻辑关系都可以由头指针唯一确定。其余的所有结点只需要和头指针或头指针之后已经连接上的结点相连即可。

        以下是结构体链表的结点的一般创建形式,其中 int num; 可以由任意需要保存的数据代替。

struct LNode {
    int num;            // 结点中保留的数据
    struct LNode *next; // 指针指向下一个结点
};

        一般情况下,一个单链表的第一个存储了数据的结点之前会放置一个不存储数据的头结点。当然头结点也可以存储一些完整的数据,比如:当数据元素为整数型时,头结点的数据域中可以存储该线性表的长度。

2.3.2 单链表中基本操作的实现

        这里我们主要针对上面提到的一些单链表的基本操作,用算法来实现一下。首先这里的结点我们使用的是:

#include<stdio.h>
#include<stdlib.h>

typedef struct Node {
    int data;
    struct Node* next;
} Node;

【1】初始化:生成一个新结点作为头结点,并将其头结点的指针域置空(指向 NULL)。

Node* initList() {
    Node* head = (Node*) malloc (sizeof(Node));
    if (!head) {
        exit(1);    // 内存分配失败,结束程序
    }
    head->next = NULL;  // 初始化头节点指针域为空
    return head;
}

【2】取值:由于链表中无法通过下标访问结点,因此我们如果想要获取某结点的数据域,我们需要顺着链表依次查找。这个过程比较简单,这里就不展开了,读者可以自行尝试。

【3】插入:链表的插入有两种,分别是头插法和尾插法。顾名思义,就是每次在头部插入新的结点或是每次在尾部插入新的结点。

// 尾插法
void insertNodeInEnd(Node* head, int data) {
    Node* newNode = (Node*) malloc (sizeof(Node));
    if (!newNode) {
        exit(1);    // 内存分配失败,结束程序
    }
    newNode->data = data;
    newNode->next = NULL;

    // 找到链表的最后一个节点
    Node* curr = head;
    while (curr->next != NULL) {
        curr = curr->next;
    }

    // 将新节点插入链表
    curr->next = newNode;
}
// 头插法
void insertNodeInHead(Node* head, int data) {
    Node* newNode = (Node*) malloc (sizeof(Node));
    if (!newNode) {
        exit(1);    // 内存分配失败,结束程序
    }
    newNode->data = data;
    newNode->next = head->next;
    // 将head前移一位
    head->next = newNode;
}

【4】删除结点:

void deleteNode(Node* head, int data) {
    if (head == NULL || head->next == NULL) {   // 空链表或只有一个节点的链表
        return;
    }
    // 如果要删除的节点是头节点
    if (head->next->data == data) {
        Node* temp = head->next;    // 暂存要删除的节点
        head->next = temp->next;    // 修改头节点的指针域,跳过要删除的节点
        free(temp); // 释放temp的内存空间
        return;
    }
    // 如果要删除的节点不是头节点
    Node* curr = head;
    while (curr->next != NULL && curr->next->data != data) {    // 找到要删除的节点的前一个节点
        curr = curr->next;
    }
    if (curr->next == NULL) {   // 如果没有找到该节点
        return;
    }
    Node* temp = curr->next;    // 暂存要删除的节点
    curr->next = temp->next;    // 修改该节点的指针域,跳过要删除的节点
    free(temp); // 释放temp的内存空间
}

2.3.3 循环链表

        循环链表是一种特殊的单链表,它和一般的单链表不同之处在于,表中最后一个结点的指针域指向头结点,整个链表形成一个环。这种链表对于某些特殊的问题还是有好处的,不过这需要看情况讨论了。

2.3.4 双向链表

         对于单链表中的某个结点而言,如果我们想知道它的下一个结点的数据,我们可以通过指针直接得到,执行时间为 O(1),但如果我们想知道它的上一个结点的数据,我们必须从头开始查找,执行时间为 O(n)。为了克服这种缺点,我们可以利用双向链表。

        顾名思义,双向链表的结点中有两个指针域,一个直接指向下一个结点,一个直接指向上一个结点。大体如下:

struct DulNode {
    int num;
    struct DulNode *prior; // 指向前驱
    struct DulNode *next;  // 指向后驱
};

        这里我们展示一下双向链表的插入和删除的一般算法,仅供参考。

#include <cstdio>
#include <cstdlib>
using namespace std;
typedef struct Node {
    int num;
    struct Node *prior;
    struct Node *next;
} Node;

bool ListInsert(Node* head, int i, int number) {
    Node *p = head;
    while (i--) {
        if (p == NULL) return 0;
        p = p -> next;
    }
    Node *s = new Node;
    s -> num = number;
    s -> prior = p -> prior;
    p -> prior -> next = s;
    s -> next = p;
    p -> prior = s;
    return 1;
}

bool ListDelete(Node* head, int i) {
    Node *p = head;
    while (i--) {
        if (p == NULL) return 0;
        p = p -> next;
    }
    p -> prior -> next = p -> next;
    p -> next -> prior = p -> prior;
    free(p);
    return 1;
}

2.4 总结

        以上便是我们第二章线性表的内容,接下来我们将要学习的是栈和队列,请同学们做好准备哦。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2115281.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于javaweb的茶园茶农文化交流平台的设计与实现(源码+L文+ppt)

springboot基于javaweb的茶园茶农文化交流平台的设计与实现&#xff08;源码L文ppt&#xff09;4-20 系统功能结构 系统结构图可以把杂乱无章的模块按照设计者的思维方式进行调整排序&#xff0c;可以让设计者在之后的添加&#xff0c;修改程序内容的过程中有一个很明显的思维…

使用 WebStorm 导入已有的 Vue 项目并运行的步骤与注意事项

目录 1. 引言2. WebStorm 环境准备2.1 安装 WebStorm2.2 配置 Node.js 和 npm2.3 使用 nvm 管理 Node.js 和 npm 版本2.4 npm 版本与 Vue 版本对应关系 3. 导入已有的 Vue 项目3.1 打开 Vue 项目3.2 安装项目依赖3.3 使用 nvm 控制 Node.js 和 npm 版本 4. 运行 Vue 项目4.1 启…

STM32双轮平衡小车(基于STM32F103C8T6HAL库)

STM32双轮平衡小车参考教程 这个项目是跟做以上UP的STM32双轮平衡小车&#xff0c;主要是为了学习电机驱动和PID控制。这篇我就不提供源码了&#xff0c;我也是跟学的&#xff0c;原作者也提供了源码&#xff0c;我记录一下自己的理解。 1 PID原理 1.1 PID简介 1.2 PID演示 …

打破AI壁垒-降低AI入门门槛

AI和AGI AI&#xff08;人工智能-Artificial Intelligence&#xff09;&#xff1a; 先说说AI&#xff0c;这个大家可能都不陌生。AI&#xff0c;就是人工智能&#xff0c;它涵盖了各种技术和领域&#xff0c;目的是让计算机模仿、延伸甚至超越人类智能。想象一下&#xff0c;…

图像分割分析效果2

这次加了结构化损失 # 训练集dice: 0.9219 - iou: 0.8611 - loss: 0.0318 - mae: 0.0220 - total: 0.8915 # dropout后&#xff1a;dice: 0.9143 - iou: 0.8488 - loss: 0.0335 - mae: 0.0236 - total: 0.8816 # 加了结构化损失后:avg_score: 0.8917 - dice: 0.9228 - iou: 0.…

如何做服务迁移、重构?

思维导图 0. 前言 本文意在提供服务迁移的完整思路&#xff0c;将思考题变成填空题&#xff0c;只需要按照本文提供的思路填空&#xff0c;服务迁移至少可以做到 80 分。 本文的服务迁移指&#xff1a;将老服务的代码迁移至新服务。 1. 服务资源梳理 服务资源&#xff0c;我…

AI学习记录 - 旋转位置编码

创作不易&#xff0c;有用点赞&#xff0c;写作有利于锻炼一门新的技能&#xff0c;有很大一部分是我自己总结的新视角 1、前置条件&#xff1a;要理解旋转位置编码前&#xff0c;要熟悉自注意力机制&#xff0c;否则很难看得懂&#xff0c;在我的系列文章中有对自注意力机制的…

Win32函数调用约定(Calling Convention)

平常我们在C#中使用DllImportAttribute引入函数时&#xff0c;不指明函数调用约定(CallingConvention)这个参数&#xff0c;也可以正常调用。如FindWindow函数 [DllImport("user32.dll", EntryPoint"FindWindow", SetLastError true)] public static ext…

来啦| LVMH路威酩轩25届校招智鼎高潜人才思维能力测验高分攻略

路威酩轩香水化妆品(上海)有限公司是LVMH集团于2000年成立&#xff0c;负责集团旗下的部分香水化妆品品牌在中国的销售包括迪奥、娇兰、纪梵希、贝玲妃、玫珂菲、凯卓、帕尔马之水以及馥蕾诗等。作为目前全球最大的奢侈品集团LVMH 集团秉承悠久的历史&#xff0c;不断打破常规&…

群晖最新版(DSM 7.2) 下使用 Web Station 部署 flask 项目

0. 需求由来 为了在 DSM 7.2 版本下的群晖 NAS 里运行我基于 flask 3.0.2 编写的网页应用程序&#xff0c;我上网查了非常多资料&#xff0c;也踩了很多坑。最主要的就是 7.2 版本的界面与旧版略有不同&#xff0c;而网络上的资料大多基于旧版界面&#xff0c;且大部分仅仅说明…

记忆化搜索【下】

375. 猜数字大小II 题目分析 题目链接&#xff1a;375. 猜数字大小 II - 力扣&#xff08;LeetCode&#xff09; 题目比较长&#xff0c;大致意思就是给一个数&#xff0c;比如说10&#xff0c;定的数字是7&#xff0c;让我们在[1, 10]这个区间猜。 如果猜大或猜小都会说明…

2024AI绘画工具排行榜:探索最受欢迎的AI绘图软件特点与选择指南

AI绘画工具各有优势&#xff0c;从开放性到对特定语言和文化的支持&#xff0c;以及对图像细节和艺术性的不同关注点&#xff0c;根据具体需求选择合适的工具 MidJourney 图片品质卓越&#xff0c;充满独特创意&#xff0c;初期能够免费获取数十账高质量图片&#xff0c;整个生…

C++20中支持的非类型模板参数

C20中支持将类类型作为非类型模板参数&#xff1a;作为模板参数传入的对象具有const T类型&#xff0c;其中T是对象的类型&#xff0c;并且具有静态存储持续时间(static storage duration)。 在C20之前&#xff0c;非类型模板参数仅限于&#xff1a;左值引用类型、整数类型、指…

VMware Fusion Pro 13 Mac版虚拟机 安装Win11系统教程

Mac分享吧 文章目录 Win11安装完成&#xff0c;软件打开效果一、VMware安装Windows11虚拟机1️⃣&#xff1a;准备镜像2️⃣&#xff1a;创建虚拟机3️⃣&#xff1a;虚拟机设置4️⃣&#xff1a;安装虚拟机5️⃣&#xff1a;解决连不上网问题 安装完成&#xff01;&#xff0…

用Pytho解决分类问题_DBSCAN聚类算法模板

一&#xff1a;DBSCAN聚类算法的介绍 DBSCAN&#xff08;Density-Based Spatial Clustering of Applications with Noise&#xff09;是一种基于密度的聚类算法&#xff0c;DBSCAN算法的核心思想是将具有足够高密度的区域划分为簇&#xff0c;并能够在具有噪声的空间数据库中发…

关于SpringMVC的理解

1、SpringMVC 应用 1.1、简介 1.1.1、MVC 体系结构 三层架构&#xff1a; 我们的开发架构⼀般都是基于两种形式&#xff0c;⼀种是 C/S 架构&#xff0c;也就是客户端/服务器&#xff1b;另⼀种是 B/S 架构&#xff0c;也就是浏览器服务器。在 JavaEE 开发中&#xff0c;⼏乎…

陪护系统|陪护系统源码|护理陪护小程序

随着医疗水平的不断提高&#xff0c;人们对护理服务的需求也越来越高。为了更好地满足患者和家属的需求&#xff0c;陪护系统定制开发应运而生。 陪护系统定制开发是根据医疗机构的实际需求&#xff0c;设计并开发一套专门用于陪护服务的系统。该系统拥有一系列丰富的功能&…

基于人工智能的图片生成系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图片生成是计算机视觉领域的一个重要任务&#xff0c;基于生成对抗网络&#xff08;GAN&#xff09;的图片生成系统能够从噪声中生成逼…

大数据-119 - Flink Window总览 窗口机制-滚动时间窗口-基于时间驱动基于事件驱动

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

揭秘 AMD GPU 上 PyTorch Profiler 的性能洞察

Unveiling performance insights with PyTorch Profiler on an AMD GPU — ROCm Blogs 2024年5月29日&#xff0c;作者&#xff1a;Phillip Dang。 在机器学习领域&#xff0c;优化性能通常和改进模型架构一样重要。在本文中&#xff0c;我们将深入探讨 PyTorch Profiler&#…