《C语言内核深度解析》——笔记及拓展(5)

本篇讲解数据结构方面,我只是想粗略了解,本科没有学过,不感兴趣的读者请略过此篇

注:此篇部分内容引用自解学武的内容

此部分因为涉及到本科没学过的内容,需要算法的知识,所以只简单记下,待日后有编写到相关程序,再来补充。

文章是我前几天读了朱有鹏,张先凤老师的《嵌入式Linux与物联网软件开发:C语言内核深度解析》写的,拜读之后,虽没有醍醐灌顶,至少解开了我之前的一些疑惑。

以前学C语言,就是在IDE上编一下代码,编译器会有错误有警告提示,很少思考过变量、指针、结构体、函数之间,及所编代码和当前所运行系统的关系(系统内存有多大,运行速度怎样,怎样优化算法)。等我真正学了嵌入式,才开始思考上面的问题,才开始去了解软件与硬件的关系。

本文章是边复习边记笔记,跟书籍目录不一样,以后可能会补充及修改。

有纰漏请指出,转载请说明。

学习交流请发邮件 1280253714@qq.com

第九章 数据结构&链表&状态机&多线程

  • 数据结构

数据结构是什么

数组是最简单的数据结构,链表复杂一些,二叉树、图则是更加复杂的数据结构。

数据结构由简单到复杂,它们所要解决的问题也由简单到复杂。要学习复杂的数据结构就要先学习简单的数据结构。

学好数据结构的难点在于:存储数据的同时还能将数据之间的关系也存储起来。

如:导航软件(比如高德、腾讯地图等),要想实现精确导航,软件必须存储大量的数据,包括各个省、市、区、县的道路、建筑物、红绿灯等位置信息,以及每个地区的天气信息、高速路况信息等等。

数据结构的存储结构

集中存储:方便后续查找数据

分散存储:方便后续增加或删除数据

数据结构的逻辑结构

无关系(查找表)

一对一(线性存储结构)

一对多(树存储结构)

多对多(图存储结构)

数据结构知识图谱
数据结构和算法的关系

对数据集{2,4,5,1,3}做升序排序,解决这个问题的算法就有很多种,比如冒泡排序算法、快速排序算法、归并排序算法、希尔排序算法等。借助任何一种算法,都可以得到{1,2,3,4,5}升序序列。同一个问题,使用不同的算法,虽然都可以解决问题,但有的算法执行效率高,有的算法执行效率低。

通常情况下,挑选算法主要考虑两个方面,分别是:

  • 执行效率:根据算法编写出的程序,执行时间越短,效率就越高;

  • 占用的内存空间:不同算法编写出的程序,执行时占用的内存空间也不相同。如果实际场景中仅能使用少量的内存空间,就要优先选择占用空间最少的算法。

算法只有两三个时,可以编写出每个算法,在计算机执行,计算各自执行时间和占用空间大小。实际开发中,往往采用 “预先估值”的方法挑选算法。具体来讲,就是分析各个算法的实现过程(步骤),估算出它们各自的运行时间和占用的内存大小,就可以挑选出“最好”的算法。

我们习惯用「时间复杂度」预估算法的执行时间,用「空间复杂度」预估算法占用的内存大小。

顺序表

线性表有顺序表(连续存储)和链表(分散存储)

#include <stdio.h>
#include <stdlib.h>
#define Size 5 //对Size进行宏定义,表示顺序表的最大容量
typedef struct{
    int* head;
    int length;
    int size;
}Table;
void initTable(Table * t) {
    //构造一个空的顺序表,动态申请存储空间
    t->head = (int*)malloc(Size * sizeof(int));
    //如果申请失败,作出提示并直接退出程序
    if (!t->head)
    {
        printf("初始化失败");
        exit(0);
    }
    //空表的长度初始化为0
    t->length = 0;
    //空表的初始存储空间为Size
    t->size = Size;
}
//输出顺序表中元素的函数
void displayTable(Table t) {
    int i;
    for (i = 0; i < t.length; i++) {
        printf("%d ", t.head[i]);
    }
    printf("\n");
}
int main() {
    int i;
    Table t = { NULL,0,0 };
    initTable(&t);
    //向顺序表中添加{1,2,3,4,5}
    for (i = 1; i <= Size; i++) {
        t.head[i - 1] = i;
        t.length++;
    }
    printf("顺序表中存储的元素分别是:\n");
    displayTable(t);
    free(t.head);//释放申请的堆内存
    return 0;
}
  • 链表的引入

为什么需要链表

C++支持变长数组,就是在内存中申请一块更大的数组,将原来数组的内容复制到新申请数组的起始部分,删除原来的数组。顺序表同样在表中间增删时也要改动其后元素的位置。二者在进行元素的插入和删除时,操作效率低。

如果简单的数据结构可以解决问题,就没有必要使用复杂的数据结构。数组和顺序表天生的缺陷导致它解决不了某些问题,所以人们才发明了链表。

链表与数组

数组的优点是操作简单,易于理解,而且不需要开辟额外的空间,但是在数组中间进行插入和删除的操作,其效率比较低。

链表的优点是操作灵活,插入删除效率很高,但是缺点是需要额外分配存放节点地址的空间,而且操作有些繁琐。

  • 单链表的实现

结点
最简单的单链表

单链表的创建到使用的大致步骤如下。

创建空的单链表,比如可以定义一个 creat_node 函数来创建第一个节点

操作单链表(增添、删除、查找、更改、排序),比如插入操作,定义一个 insert_tail() 函数来向链表(或是节点的后面追加新节点。

销毁单链表,比如定义一个 destroy_list() 函数,用于销毁链表。

由上面的步骤可知,构建一个简单链表至少需要两个步骤:创建空链表,以及增添节点。

单链表上的每个元素都是结点,结点都由数据源和指针域构成

#include <stdio.h>
struct ListNode{
    int val;
    struct ListNode *next;
};
int main(){
    struct ListNode a,b,c,d,e;
    a.val=1;
    b.val=2;
    c.val=3;
    d.val=4;
    e.val=5;

    a.next=&b;
    b.next=&c;
    c.next=&d;
    d.next=&e;
    e.next=NULL;

    struct ListNode *head=&a;
    while(head){
        printf("val=%d\t addr=%p\t next=%p\n",head->val,head,head->next);
        head=head->next;
    }
    return 0;
}

https://www.bilibili.com/video/BV1Gi4y1w7yG?t=205.6

val=1 add=20000408 next=20000400

val=2 add=20000400 next=200003f8

val=3 add=200003f8 next=200003f0

val=4 add=200003f0 next=200003e8

val=5 add=200003e8 next=00000000

  • 双向链表

虽然单链表能 100% 存储逻辑关系为 "一对一" 的数据,但在解决某些实际问题时,单链表的执行效率并不高。例如,若实际问题中需要频繁地查找某个结点的前驱结点,使用单链表存储数据显然没有优势,因为单链表的强项是从前往后查找目标元素,不擅长从后往前查找元素。

解决此类问题,可以建立双向链表(简称双链表)。

  • 状态机

考虑状态机的关键点:当前状态、外部输入、下一状态

水的三态

鼠标点击图标的状态(可能是以下的情况,用到了状态机。或者是用了更为高级的程序,不知道,以后也许会知道)

  • 多线程

OS下的并行执行

单核CPU:宏观并行,微观串行

多核CPU:微观并行,提高宏观上的并行度

进程和线程

https://blog.csdn.net/weixin_48271092/article/details/123649795

通俗理解:进程是打开的PPT,线程是PPT下的服务程序

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值