文章目录
本文部分涉及展示流程的gif太大无法上传(超过5Mb),想要学习的可以发邮箱找我要MarkDown文件。
Day 1 - 数据结构先导课
在刷提前需要简单了解以下的内容
![image-20240824185228541](https://img-blog.csdnimg.cn/img_convert/79192aef537a9be9f5f824f20f733734.png)
1.数组
索引为何从0开始?
- 1、2能通过范围的左值减右值得到元素个数,较好。这里一般选用1的左闭右开规范。
![Snipaste_2024-08-25_21-42-38](https://img-blog.csdnimg.cn/img_convert/c8c836843616d7a43ad7b9d73a8b3d88.png)
- 如果是左开右闭,当数组从0开始时(如上图),左边界的数就不是数组中的元素了。
- 例如索引3的元素前有3个元素。
- Dijkstra本人对选取从零开始索引的这种表示方法的阐述。
数组的初始化
数组的遍历
Java:
/* 遍历数组 */
void traverse(int[] nums){
int count = 0,
// 通过索引遍历数组
for (int i= 0;i<nums.length; i++){
count++;
}
// 直接遍历数组
for (int num : nums){
count++;
}
}
C++:
/* 遍历数组 */
void traverse(int* nums, int size){
int count = 0;
//通过索引遍历数组
for(int i=0; i<size; i++){
count++;
}
}
Python:
def traverse(nums):
"""遍历数组"""
count = 0
# 通过索引遍历数组
for i in range(len(nums)):
count += 1
# 直接遍历数组
for num in nums:
count +=1
2.链表
![Snipaste_2024-08-25_21-57-57](https://img-blog.csdnimg.cn/img_convert/757ee1eb8422d74ad8de0f4863cdd686.png)
链表的插入
![链表插入](https://img-blog.csdnimg.cn/img_convert/7420c8782d45a1cfd65a4b0a4a5cfa28.gif)
链表的删除
![链表删除](https://img-blog.csdnimg.cn/img_convert/c8552a4fdb28b5556e1e62afd528f934.gif)
查找链表特定值节点
/* 在链表中查找值为target的首个节点并返回其索引值 */
int find(ListNode head, int target)
int index = 0;
while(head != null){
if (head.val == target)
return index;
head = head.next;
index++;
}
return -1;
}
链表的其他形式
![Snipaste_2024-08-25_22-33-04](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fcdn.jsdelivr.net%2Fgh%2Fweaki233%2Fimage-gallery%2FLeetcodeLearning%2F202408261304700.png&pos_id=img-m1GTNWgo-1724683857925%29)
3.列表
列表的初始化
Java
/*初始化列表 */
// 无初始值
List<Integer> list1 = new ArrayList>()
// 有初始值(注意数组的元素类型需为 int[] 的包装类 Integerl])
Integerl[] numbers = new Integer[]{ 1, 3, 2, 5, 4 };
List<Integer>list = new ArrayList<>(Arrays.asList(numbers));
C++
/* 初始化列表 */
//需要注意,C++中的vector即为本文描述的list
//无初始值
vector<int> list1;
//有初始值
vector<int> list = {1,3,2,5,4};
Python
"""初始化列表"""
# 无初始值
list1 = []
# 有初始值
list = [1,3,2,5,4]
访问元素
/* 访问元素 */
int num = list.get(1); //访问索引1处的元素
更新元素
list.set(1,0); //将索引1处的元素更新为0
4.栈
![Snipaste_2024-08-26_11-17-41](https://img-blog.csdnimg.cn/img_convert/553c2fe94dacc01f26c65a45ab9b1463.png)
需要掌握的相关API
API | 作用 |
---|---|
S.top() | 获取栈顶元素 |
S.empty() | 判断栈是否为空 |
S.push(x) | 将元素x添加至栈 |
S.pop() | 将栈顶元素弹出 |
S.size() | 求栈中元素的个数 |
5.单调栈
- 单调栈即满足单调性的栈结构。
6.队列
先进先出(First in, first out. FIFO)
队列相关API
API | 作用 |
---|---|
Q.front() | 获取队头元素,如1 |
Q.empty() | 判断队列是否为空,为空返回True |
Q.push(x) | 将元素x添加至队尾 |
Q.pop() | 将队头元素弹出 |
Q.size() | 求队列中元素的个数 |
7.双向队列
对于普通队列,我们只能在头部删除或在尾部添加元素,而「双向队列 Deque」更加灵活,在其头部和尾部都能执行元素添加或删除操作。
双向队列相关API
方法名 | 描述 | 时间复杂度 |
---|---|---|
pushFirst() | 将元素添加至队首 | O(1) |
pushLast() | 将元素添加至队尾 | O(1) |
popFirst() | 删除队首元素 | O(1) |
popLast() | 删除队尾元素 | O(1) |
peekFirst() | 访问队首元素 | O(1) |
peekLast() | 访问队尾元素 | O(1) |
8.优先队列
需要后面学习了堆与二叉树的知识才能理解优先队列的具体实现,因此这里先当黑盒。
能够通过O(1)复杂度找出其中的最值。
9.哈希表
Python中的字典就是基于哈希表实现的。
哈希表相关API
get
:获取哈希表中的value
值
put
:更新哈希表的value
值
相关实现结构
帮助快速存储元素值并进行匹配操作。
映射 map 是一组键值对的结构,具有极快的查找速度。
数组也可实现
集合 set 是一组 key 的集合,但不存储value。由于key不能重复,所以,在 set 中,没有重复的key。
10.插入排序
1、多次遍历待排序的数组
2、首先,将数组的第1个元素看做是包含1个元素的已排序数组
3、接着,按顺序把待排序数组中的元素插入到已排序数组的正确位置
- 每次排序会将一个待排序的元素插入到已排序数组中的合适位置,因此除去第一个当作已排好的元素不需要排序,剩下的n-1个元素需要插入到数组中,因此外层循环n-1次。
- 内层循环需要将未排序的某一元素与已排序数组中的元素进行逐一对比,直到找到比该未排序的元素小的为止,并将该元素插入到该较小已排序元素的后面。
以下为 以外层循环i = n-1
为例的一次插入流程(将未排序的2插入):
//外层循环从a[1]开始遍历,即i从1循环至n-1
for(int i=1; i<n; i++){
//将a[i]保存至key中
int key = a[i]
//a[j]是a[i]的前一个元素
int j = i-1;
//只要j大于等于0并且a[j]大于key
while(j>=0 && a[j]>key){
//将处于j处的元素右移到j+1的位置上
a[j+1] = a[j];
j--;
}
//最初的a[i]找到了一个插入位置
a[j+1] = key;
}
11.计数排序(较少涉及)
1、找出原数组中元素值最大的,记为 max
2、创建一个新数组,记为 count
,其长度是 max+1
其元素默认值都为 0
3、遍历原数组中的元素,以原数组中的元素作为count
数组的索引,以原数组中的元素出现次数作为 count
数组的元素值
4、创建结果数组 result
,起始索引 index
5、遍历 count
数组,找出其中元素值大于0 的元素将其对应的索引作为元素值填充到 result
数组中去,每处理一次,count
中的该元素值减1,直到该元素值不大于0,依次处理 count
中剩下的元素
6、返回结果数组 result
12.冒泡排序
1、多次遍历待排序的数组
2、每一轮遍历,依次比较两个相邻的元素,如果顺序错误,将这两个元素交换,使得较小的元素放在较大的元素前面,这样,一轮遍历之后,最大的元素被交换到了序列尾部
- 外层循环需要遍历n-1次
- 当第0次遍历时,需要比较n-1次大小,当第1次遍历时需要比较n-2次大小,以此类推,第i次遍历时需要进行n-i-1次比较,第n-2次遍历时需要比较1次。
下面以第i次遍历(从第0次算起)为例,演示n-i-1的含义:
-
当第i次遍历时,需要比较n-i-1次,因此待排序的数组右边界的元素索引为n-i-1,最后的一次比较不能越过此右边界。(此时i==0代表第一次遍历)
-
如果外层循环条件改为for(int i=1; i < n; i++),则内层循环相应改为for(int j=0; j < n - i; j++)即可(此时i==1代表第一次遍历)
// 外层循环需要遍历的次数,n个元素的排序,需要n-1次遍历
// 即从0到n-2,总共为n-1次遍历
for(int i=0; i < n-1; i++){
//内层循环n-i-1次,代表第i次循环中比较的次数
for(int j=0; j < n-i-1; j++){
//如果比较的两个元素a[j]大于a[j+1],说明较大的在较小的前面
if(a[j] > a[j+1]){
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
13.选择排序
1、多次遍历待排序的数组
2、每一轮遍历,从待排序序列中选出最小的元素,存放到已排序序列的未尾,即和待排序序列中的开始元素进行交换
下面以第i次循环为例,理解内层循环:
- 外层循环n-1次以依次找到未排序数组中最小的值
- 内层循环为第i次循环时,为了在n-i个未排序数组中找出最小值需要比较的次数,即n-i-1次
// 外层循环寻找最小元素的次数,n个元素的排序,需要进行n-1次选择
for(int i=0; i < n-1; i++){
// 保存待排序部分中最小元素的下标,初始化为i
int index = i;
// 内层循环n-i-1次,代表第i次循环中比较的次数
for( int j=i+1; j < n; j++){
// 将待排序部分中的最小元素下标找到并更新index
if(a[j] < a[index]){
index = j;
}
}
// 将a[index]和待排序部分的第一个元素a[i]交换
int temp = a[i];
a[i] = a[index];
a[index] = temp;
}
14.思考题
-
数组/列表的索引为什么是从
0
开始的?-
历史原因: 许多早期编程语言(如C语言)和硬件架构设计的背景决定了这一习惯。在这些系统中,数组的地址计算是通过基址加上偏移量来实现的。如果索引从0开始,计算第
i
个元素的地址时只需加上i
个元素大小的偏移量。这种设计非常简洁且高效。 -
地址计算的简化: 在内存中,数组通常表示为连续的内存块。假设数组的基地址为
base
,元素大小为size
,那么访问第i
个元素的地址可以通过以下公式计算:
Address of element i = base + i × size \text{Address of element } i = \text{base} + i \times \text{size} Address of element i=base+i×size
如果索引从0开始,计算第一个元素的地址时,i
的值为0,结果就是base
,无需额外的计算。如果索引从1开始,计算地址时需要进行额外的减1操作,这会增加计算复杂性。 -
简化循环操作: 当处理数组时,循环遍历数组元素是常见操作。如果索引从0开始,循环结构如
for (int i = 0; i < n; i++)
非常直观。而且,这种循环结构与数组长度和索引之间的关系也更为紧密。
-
-
数组和链表之间有什么异同点?
-
共同点
- 都可以用来存储一组数据且可以是相同或者是不同类型的数据
- 可以遍历其中所有元素
-
不同点:
-
内存分配方式:
- 数组:数组在内存中分配的是一块连续的内存空间。这意味着数组的大小在创建时必须确定,并且在使用过程中无法动态改变。
- 链表:链表中的元素(称为节点)在内存中并不一定是连续的。每个节点包含数据和一个指向下一个节点的指针。链表的长度可以动态变化,根据需要插入或删除节点。
-
访问方式:
- 数组:数组支持通过索引(如
arr[i]
)进行随机访问,可以直接访问任意位置的元素,访问速度为O(1)。 - 链表:链表不支持随机访问,访问元素时必须从头节点开始依次遍历,访问速度为O(n)。
- 数组:数组支持通过索引(如
-
插入和删除:
- 数组:在数组中插入或删除元素时,需要移动后续元素的位置,特别是当插入或删除发生在数组的中间位置时,效率较低,时间复杂度为O(n)
- 链表:链表中插入和删除元素非常灵活,只需要修改相关节点的指针即可,不需要移动其他元素,效率较高。只要有节点的指针,插入和删除的时间复杂度为O(1)。
-
使用场景:
- 数组:适用于需要快速访问元素且数组大小固定的场景,如频繁读操作的场景。
- 链表:适用于频繁插入和删除操作,以及无法预知元素数量的场景。
-
-
-
数组和列表之间有什么异同点?
-
共同点
- 存储数据:数组和列表都可以用来存储一组数据,可以是相同类型的元素,也可以是不同类型的元素(取决于语言的实现)。
- 遍历:两者都支持遍历操作,可以逐一访问每个元素。
-
不同点
-
数据类型约束与灵活性
- 数组:在大多数编程语言中,数组通常是定长的,并且所有元素必须是相同的数据类型。例如,在C或Java中,数组的元素类型是固定的。
- 列表:列表在许多语言中是动态类型的,可以包含不同类型的元素(例如,Python的列表)。此外,列表的长度是动态的,可以根据需要自动增长或缩减。
-
使用场景
- 数组:适用于需要确定大小、固定类型、且追求高效随机访问的场景,例如处理定长的传感器数据、矩阵运算等。
- 列表:适用于需要动态添加、删除元素或者元素类型多样化的场景,例如处理不确定数量的数据、动态生成的数据序列等。
-
-
-
在你所使用的编程语言中,数组和列表之间是否存在严格的区别?
Python中的数组和列表
-
列表(List):
- 定义:Python中的
list
是一个内置的数据结构,支持存储任意类型的元素,且元素类型可以是混合的。列表是动态的,其长度可以随时增加或减少。 - 特性:列表具有丰富的操作方法,如添加、删除、排序等。由于
list
在底层是实现为动态数组,所以它支持O(1)的随机访问,并且能够动态调整大小。 - 使用:通常使用方括号
[]
来创建列表。例如,my_list = [1, "hello", 3.14]
。
- 定义:Python中的
-
数组(Array):
- 定义:Python中的数组通常是指
array
模块提供的array.array
对象。与列表不同,array.array
要求所有元素必须是相同的数据类型(例如,全部是整数或全部是浮点数)。 - 特性:数组在内存中的表现更为紧凑,因为它直接存储元素的二进制表示。这使得数组在处理大量同类型数据时比列表更高效,尤其是在存储空间和处理速度方面。
- 使用:使用
array
模块中的array()
函数创建数组。例如,import array
后,创建一个整数数组可以使用my_array = array.array('i', [1, 2, 3])
,其中'i'
表示整数类型。
- 定义:Python中的数组通常是指
-
-
栈的特点是什么?队列的特点是什么?
-
栈 是一种“后进先出”(LIFO)的数据结构,主要用于需要逆序处理数据的场景,如函数调用栈、表达式求值等。
-
队列 是一种“先进先出”(FIFO)的数据结构,主要用于需要按顺序处理数据的场景,如任务调度、广度优先搜索等。
-
-
你能否用一个双向队列来实现一个栈?
可以使用双向队列(Deque)来实现一个栈。双向队列允许我们从两端(队首和队尾)添加和移除元素,这使得它非常适合用来模拟栈的后进先出(LIFO)行为。在这个实现中,我们只需要在双向队列的一端进行元素的添加和移除操作,具体来说,可以选择队尾作为栈顶:
- 压栈(Push):将元素添加到双向队列的尾端(模拟栈的压栈操作)。
- 弹栈(Pop):从双向队列的尾端移除并返回元素(模拟栈的弹栈操作)。
- 查看栈顶(Peek/Top):返回双向队列尾端的元素但不移除它。
-
优先队列和队列之间有什么异同点?
共同点
- 存储元素:两者都可以用于存储一组元素,元素的数量通常可以动态增加或减少。
- 基本操作:两者都支持添加(入队)和移除(出队)元素的操作。
- 数据类型:两者都可以存储相同类型或不同类型的数据,具体取决于编程语言的实现。
不同点
- 处理顺序:
- 普通队列(Queue):
- 先进先出(FIFO):普通队列遵循“先进先出”的原则,即第一个进入队列的元素将是第一个被移除的元素。元素的添加发生在队尾,而移除则发生在队首。
- 优先队列(Priority Queue):
- 基于优先级的出队顺序:优先队列中的每个元素都附带一个优先级。移除元素时,优先移除优先级最高的元素,而不是按照添加的顺序。因此,即使一个元素先入队,如果它的优先级低,它可能会在其他具有更高优先级的元素之后才被移除。
- 应用场景:
- 普通队列:
- 适用于需要按顺序处理任务或数据的场景,例如任务调度、排队系统等。
- 优先队列:
- 适用于需要根据优先级处理任务的场景,例如操作系统中的进程调度、事件驱动系统、Dijkstra算法中的最短路径计算等。
- 实现方式:
- 普通队列:
- 普通队列可以使用数组、链表或循环数组(环形缓冲区)来实现。
- 优先队列:
- 优先队列通常使用堆(Heap)数据结构来实现,最常见的是最小堆或最大堆。也可以使用其他数据结构如平衡二叉树或无序数组实现,但堆通常提供更好的性能。
- 时间复杂度:
-
普通队列:
- 在普通队列中,入队和出队操作的时间复杂度通常为O(1)。
-
优先队列:
- 在优先队列中,插入元素的时间复杂度通常为O(log n)(基于堆实现),移除最高优先级元素的时间复杂度也为O(log n)。
-
哈希表和数组之间有什么异同点?
共同点
- 存储数据:哈希表和数组都可以用于存储和管理一组数据。
- 直接访问:在某些编程语言中,哈希表和数组都可以通过索引或键直接访问存储的元素。
不同点
- 存储结构:
- 数组:
- 连续内存:数组在内存中占用的是一块连续的空间。每个元素都按顺序存储,且可以通过整数索引直接访问。
- 哈希表:
- 散列存储:哈希表使用键值对的形式存储数据。键通过哈希函数映射到一个哈希值,该哈希值决定了元素在内存中的位置。哈希表的存储结构通常是分散的,并且元素不按顺序存储。
- 数据类型和使用方式:
- 数组:
- 同类型元素:数组通常存储相同类型的元素,例如一组整数或字符串。在某些语言中,数组的长度是固定的。
- 使用场景:数组适用于需要按顺序存储数据或频繁进行随机访问的场景,如矩阵运算、缓冲区等。
- 哈希表:
- 键值对:哈希表存储的是键值对,每个键通常是唯一的,可以是多种数据类型(整数、字符串等),而值可以是任何类型的数据。
- 使用场景:哈希表适用于需要快速查找、插入和删除数据的场景,如符号表、缓存、字典等。
- 操作效率:
- 数组:
- 索引访问:数组的元素通过索引访问,索引通常是整数,且从0开始。访问任意元素的时间复杂度为O(1)。
- 插入和删除:在数组的中间插入或删除元素需要移动其他元素,因此时间复杂度为O(n)。
- 哈希表:
- 键访问:哈希表的元素通过键来访问,而不是通过整数索引。访问元素的时间复杂度平均为O(1),但在最坏情况下可能是O(n)(例如当所有键都映射到同一个位置时,发生哈希碰撞)。
- 查找、插入、删除效率:哈希表的查找、插入和删除操作平均时间复杂度为O(1),但在最坏情况下为O(n)。
- 内存管理:
-
数组:
- 固定大小:在许多编程语言中,数组的大小在创建时确定,不能动态扩展(除非使用动态数组或列表类的数据结构)。
-
哈希表:
- 动态扩展:哈希表可以根据需要动态调整大小,当元素数量接近表的容量时,哈希表会进行再哈希(rehashing),以保持操作的高效性。
-
哈希表(map)和哈希集合(set)之间有什么异同点?
共同点
- 基于哈希函数:两者都使用哈希函数将元素或键值对映射到哈希表中的特定位置,以便实现快速的查找、插入和删除操作。
- 操作效率:查找、插入、删除操作在平均情况下的时间复杂度都是O(1),但在最坏情况下可能为O(n)(当发生大量哈希碰撞时)。
- 无序存储:两者通常不维护元素的插入顺序(除非使用特定的实现,如LinkedHashMap或LinkedHashSet)。
不同点
-
存储内容:
- 哈希表(Map):存储键值对(key-value pairs),每个键都唯一,并且通过键来查找对应的值。例如,在Python中是
dict
,在Java中是HashMap
。 - 哈希集合(Set):仅存储唯一的元素,没有键值对的概念。例如,在Python中是
set
,在Java中是HashSet
。
- 哈希表(Map):存储键值对(key-value pairs),每个键都唯一,并且通过键来查找对应的值。例如,在Python中是
-
访问方式:
- 哈希表(Map):通过键来访问对应的值。例如,
value = map[key]
。 - 哈希集合(Set):直接通过元素本身进行操作,没有键的概念。例如,检查元素是否存在:
if element in set:
。
- 哈希表(Map):通过键来访问对应的值。例如,
-
主要操作:
- 哈希表(Map):
- 插入/更新:将键和值关联起来,如果键已存在,则更新其值。
- 查找:通过键查找对应的值。
- 删除:通过键删除键值对。
- 哈希集合(Set):
- 插入:添加元素到集合中,如果元素已存在,则不会重复添加。
- 查找:检查元素是否存在于集合中。
- 删除:从集合中删除元素。
- 哈希表(Map):
-
应用场景:
- 哈希表(Map):适用于需要通过唯一键来存储和查找数据的场景,如实现字典、缓存、映射关系等。
- 哈希集合(Set):适用于需要确保元素唯一性、去重以及集合运算(如交集、并集、差集)的场景。
10.给定一个数组[5, 3, 8, 1, 9, 6, 7, 8, 2]
,请描述用这个数组构建一个单调递减栈(从栈顶到栈底单调递减)的过程。
5入栈,3<5,5弹出,3入栈,8>3, 8入栈,1<3<8,3和8弹出,1入栈,9>1,9入栈,6<9,9弹出6入栈,7>6,7入栈,8>7,8入栈,2<7<8, 7和8弹出,2入栈,最终从栈顶到栈底元素为:[2, 1]