一. 目录:
涉及:
抽象数据类型ADT(abstract data type),列表 list,数组 array,链表 linked list,栈 stack,队列queue,树 tree,图表 graph
研究内容:
逻辑视图 Logical view,操作 operation,操作成本 cost of operations(主要是时间方面),如何实现 imprementation
二. ADT
list 是相同类型对象的集合,可以将 list 定义为一个 ADT。
1. 静态列表
可以实现以下功能:
(1)储存给定数据类型的元素的给定数字(列表中元素数量不会改变)
(2)书写/更改任何位置的元素
(3)读取。。。
如何实现呢?使用数组
对应如图所示
2. 动态列表:
实现功能:
(1)如果列表没用元素,则列表为空,其大小为0,sige 0
(2)在列表中任何位置插入元素
(3)。。。删除。。。
(4)计算列表的元素数量
(5)读/更改。。
(6)指定数据类型(int 等)
如何实现:
使用Int 来定义一个数组,如上图所示。
定义对应的max size,
如果列表为空,则可以初始化变量 / 使用int end =-1,将变量设置为-1,因为最低的索引是0,所以不存在。
如果数组需要变大,变大多大合适呢?
变大一倍。
针对于 access等操作,时间复杂度如图,但是如何更高效?则使用链表 Linked lists。
三. linked list:
1. 为何从list 转到 linked list
首先了解下数组List的储存原理。
对于内存 memory 如图,存在一个内存管理器 memory manager 来分配内存,对于程序员需要跟 manager 申请4个内存空间来存贮整型 int x,如图所示。
之后,如果申请一个整型数组 list,如 int A[4],则需要分配如下16个内存空间。
但是需要注意的是,因为之前已经申请过int x存储了,所以对应List 的 A[4] 的后面被 x 堵死了,所以如果想扩大数组 A,则需要重新申请内存,并且把所有的元素进行复制。
但是由于扩大多少内存空间,程序员也不知道,所以依旧会存在内存不够用或者内存过大导致浪费的情况,所以如何更加高效?引出 linked list 链表。
链表的内存是分散储存的,但是如果不是连续的,如何依次读取呢?所以就引出链表的储存特点:每个元素储存时候存在两个方面:一部分储存元素本身(整型变量),另一部分存储下一个元素的地址(指针变量)。
因为最后一块没有下一个元素,所以它的第二个部分存储为0,0为无效地址,因此也可以用这个标记是列表的末尾。
同时整型和指针变量都占据四个字节。
总之,linked list 的数据结构就是将节点在不同的地址进行储存,并且每个节点都指向下一个节点 的位置。
其中第一个节点,称为 head node,我们始终保持的关于列表的唯一信息就是头节点的地址,通过其可以访问完整的列表,这也是读取列表的唯一方法,当然遍历到最后一个节点的时间复杂度为O(n)。
最后一个节点的地址是 null 或者0,意思是不指向任何节点。
如果在末节点想添加一个3,则需要先创建一个节点,分配它的内存地址,然后改之前的末节点的指向地址,就可以添加了,相比于 List 大幅度提高效率。
当然在中间插入的时候,时间复杂度也是O(n)。
2. array VS linked list
对比数组和链表,以下分为两列进行对比:
(1)首先是访问元素的时间成本:
由于array 是连续地储存在内存当中,所以对应的时间成本是O(1).
而 linked list 是每个模块(node)分别储存在不同地方,所以对应的时间成本为O(n)
因此,如果有需要始终访问列表中元素的需求时候,选用数组 array 比较好。
(2)内存需求/内存使用:
array 所占内存要求是连续的,且是固定大小的,一旦数组别填满需要扩大,则需要重新分配内存并进行复制
而 linked list 的内存储存是分开的,但是每个node 所占字节是比 array 大,比如说整型Int ,linked list 每个node 所占内存为8,而 array 的每个node 则是4,这也是一个区别。
(3)在其中插入元素的时间成本:
这个需要分情况讨论:
a. 在头节点插入元素:针对于array,需要每一个节点都向后移动,所以是O(n),而对于 Linked list ,则是O(1)。
b. 如果在末尾插入元素:针对于array,如果数组没有被填满,则是O(1),如果数组被填满了,则是O(n),对于linked list,因为只能从头开始找,一直找到end node,所以时间成本为O(n)。
c. 如果是在中间插入元素:针对于array,假如在中间位置进行插入,则需要移动后面一半的元素位置,对应时间成本为O(2/n),可以视为O(n)。针对于linked list,同理需要找到这个位置的所有前面的元素地址,所以可以视为O(n)。
同理于删除元素。
(4)谁更容易用:
3. imprementation in CPP:
(1)CPP基础:
C++基础——C++ 变量类型_c++定义变量时用sword-优快云博客
在C++中,extern
关键字用于指明变量或函数的声明是外部链接的,这意味着它们的定义在当前的编译单元之外。使用extern
声明的变量或函数可以在多个不同的源文件中访问,但它们只能被定义一次。
具体来说:
-
变量声明:当你在多个文件中使用同一个全局变量时,你可以在一个文件中定义这个变量,并在其他文件中使用
extern
来声明这个变量。这样,所有文件中使用这个变量的值都是相同的。 -
函数声明:如果你在一个文件中定义了一个函数,并希望在其他文件中调用它,你可以在其他文件中使用
extern
来声明这个函数。这告诉编译器该函数的定义在其他地方。
// file1.cpp
int a = 10; // 定义变量a
int b = 20; // 定义变量b
// file2.cpp
extern int a; // 声明变量a,它在file1.cpp中定义
extern int b; // 声明变量b,它在file1.cpp中定义
void printAB() {
std::cout << "a = " << a << ", b = " << b << std::endl;
}
在这个例子中:
file1.cpp
中定义了两个全局变量a
和b
。file2.cpp
中使用extern
声明了这两个变量,表明它们在其他地方定义。然后,printAB
函数可以访问这两个变量并打印它们的值。
注意:
- 变量或函数只能在一个文件中定义,但可以在多个文件中声明。
- 如果没有使用
extern
声明,编译器会默认在当前文件中寻找变量或函数的定义,如果没有找到,会导致链接错误。
extern
关键字在大型项目中非常有用,它允许在不同的文件之间共享全局变量和函数
C++ 中的内联函数(Inline Function)是一种请求编译器尽可能在每个调用点上“内联展开”该函数的特殊函数。内联展开意味着编译器会在每个调用该函数的地方直接插入(或替换)该函数的代码体,而不是像通常那样进行函数调用(即生成调用指令、保存调用状态、跳转到函数体执行、恢复调用状态并返回)。这样做的好处是可以减少函数调用的开销,特别是对于那些体积小、调用频繁的函数,可以显著提高程序的执行效率。