数据结构基础一
数据结构基础一
1.单链表(数组模拟)
常用的是邻接表(其实就是单链表,邻接表就是用多个单链表存储了多条线?!),一般用来存储图和树,使用结构体和指针来表示数组的时候,是使用的动态开辟空间,速度太慢,所以一般使用数组来静态模拟单链表。
数组模拟单链表的思路:首先开辟两个足够大的数组,一个是存储链表中的数,一个表示链表中结点的下一个结点的指针,还需要有两个数,一个数来存储头指针,一个是存储当前已经用到了哪个数组位置。
注意到单链表有个特质,就是只能向后看,你只能找到一个结点的下一个结点,但是不能找到上一个结点。
1
|
|
2.双链表(数组模拟)
双链表也就是同时储存上一个点的指针和下一个点的指针
1
|
|
3.栈和队列(数组模拟)
栈:先进后出 (想象成单口的水井,每次进行一次操作,不是拿出就是放入,先被放入的会更晚拿出来)
队列:先进先出(想象成双口的罐子,每次进行一次操作,从上面的口放入,从下面的口拿出,所以先放入队列的元素会先拿出来)
栈的数组模拟
1
|
|
队列的数组模拟
1
|
|
单调栈和单调队列
单调栈一般应用:给定一个序列,找到这个序列中每一个数左边(右边)离他最近并且比它小或者大的数的位置,存在就返回这个数,不存在就返回这个-1。
单调栈
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1 。
输入格式
第一行包含整数 N ,表示数列长度。
第二行包含 N 个整数,表示整数数列。
输出格式
共一行,包含 N 个整数,其中第 ii 个数表示第 ii 个数的左边第一个比它小的数,如果不存在则输出 −1 。
数据范围
$1≤N≤10^5$
$1≤数列中元素≤10^9$输入样例:
1
25
3 4 2 7 5输出样例:
1-1 3 -1 2 2
暴力做法类似于双指针算法,就是遍历一个数左边所有的数,来查找到离这个数最近且比这个数小的数。
使用单调栈优化,就是使用栈来存放所有的数据,在输入数据的时候就看一下栈顶元素是不是比当前的数大,如果栈顶元素大于你当前读入的元素,说明后面读入的所有数都不可能以栈顶元素为答案,因为你新读入的数大于栈顶元素并且距离左边更近,所以当读入的数据小于栈顶元素的时候就弹出栈顶元素。
代码
1
|
|
时间复杂度分许:每个元素最多进栈出栈一次,所以是O(n)的时间复杂度。
单调队列一般应用:求一个滑动窗口里的最大值或者最小值
滑动窗口(单调队列)
给定一个大小为$n≤10^5$的数组
有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边。
你只能在窗口中看到 k 个数字。
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为
[1 3 -1 -3 5 3 6 7], k 为 33。
窗口位置 最小值 最大值 [1 3 -1] -3 5 3 6 7 -1 3 1 [3 -1 -3] 5 3 6 7 -3 3 1 3 [-1 -3 5] 3 6 7 -3 5 1 3 -1 [-3 5 3] 6 7 -3 5 1 3 -1 -3 [5 3 6] 7 3 6 1 3 -1 -3 5 [3 6 7] 3 7 你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
输入格式
输入包含两行。
第一行包含两个整数 n 和 k,分别代表数组长度和滑动窗口的长度。
第二行有 n 个整数,代表数组的具体数值。
同行数据之间用空格隔开。
输出格式
输出包含两个。
第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值。
输入样例:
1
28 3
1 3 -1 -3 5 3 6 7输出样例:
1
2-1 -3 -3 -3 3 3
3 3 5 5 6 7
暴力做法,双重遍历,时间复杂度O(n^2^)。
使用单调队列来优化,就拿寻找最小值举例,如果向右移动后的数据是最小的数,那么窗口中的前两个数在寻找最小值的过程中是不是就没有用了,因为最新进入窗口的数更小。寻找最大值优化也是类似的。
代码
1
|
|
从代码的实现过程中不难看出,单调栈和单调队列都是利用在插入的时候进行判断弹出了栈顶元素或者是队尾元素,通过这样的方式来维护栈和队列,使得其始终是单调递增或者单调递减的,这样就能满足某些寻找最大值或者最小值的条件。
4.KMP
KMP算法就是匹配字符串的优化算法,给定模板字符串和目标字符串,需要寻找目标字符串是不是模板字符串的子串,如果是的话就返回字串的初始下标
暴力做法:暴力做法就是遍历每一个模板字符串中的每一个字符,将模板字符串中的每一个字符当作目标字符串的初始字符,来匹配后面的字符串是不是和目标字符串相匹配。
1
|
|
KMP算法思路:每次匹配完一个字符后,如果相同就匹配两个字符串的下一个字符,如果不相同,按照暴力思路的话,就需要从下一个模板字符串的字符开始重新依次循环匹配,而KMP算法就是通过优化,减少不必要的移动后再次匹配。例如一个模板字符串abababababcabab(p[]),一个目标字符串ababcabab(s[]),按照暴力思路的话,第一次匹配的时候第五个字符不同,就会向后移动一位,但是很显然,移动一位后模板字符串的首字符变成了b,很明显这就是无效的移动。那我们再想一想,目标字符串ababcabab以s[3]为终点的子串,以及以s[0]为起点的子串,最大长度是2(就是ab),注意观察,我们能发现,暴力模拟每次匹配失败的字符前的都是匹配成功的,那么以s[0]为起点的子串,它与以s[3]为终点的子串相等,那么也就和模板字符串匹配失败的字符的前一个字符相同长度的子串是相同的,所以可以直接将目标字符串向后移动多位,这个位数就是匹配失败的字符的前一个字符的下标和这个字符的最大子串长度的差。
KMP演示代码
1
|
|
5. Trie
作用:高效的储存和查找字符串集合的数据结构
原理:其实就是使用一种类似于链表的方法,储存字符串集合的时候,从根结点出发,判断根节点是否有链接到该字符串的第一个字符,没有的话就创建一个结点,让它和根结点链接,如果存在的话,就直接移动到该结点,然后查找该结点是否有和第二个字符有链接,依次类推,当每一个字符串的最后一个字符存储进去后,在这个结点使用一个标记,代表有一个以这个结点结束的字符,避免子串被母串所覆盖,或者有相同的字符串相覆盖的情况存在,查找字符串的时候类似,从根结点开始查找第一个字符,查找到了后就移动到第二个字符的结点然后查找这个结点是否和第三个结点相连接,一次类推。
代码实现
1
|
|
6.并查集
作用:将两个集合合并,询问两个元素是否在一个集合当中
原理:每个集合用一棵树来表示,树根的编号就是整个集合的编号,每个结点存储它的父节点,p[x]表示x的父节点,根结点的父节点就让他等于自己的编号,所以查找x的集合编号的时候就依次向上遍历父节点是否等于自身的编号就是了,需要将两个集合合并的时候,只需要将其中任意一个的根节点的父节点指向另一个集合的父节点就行了。
路径压缩优化:朴素版本的并查集的时间复杂度依旧较高,主要原因是查找的必须向上遍历多次,并且每次都需要遍历,所以路径优化的原理就是,对于查找过根结点的结点,直接将它们的父节点指向根结点,这样在需要重复查找的时候就能更加快速,使用路径压缩优化后的时间复杂度接近O(1)
代码实现
1
|
|
其余变形的并查集无非就是多维护一些别的数据,接下来将给出两个例题。
连通块中点的数量
给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。
现在要进行 m 个操作,操作共有三种:
C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;Q2 a,询问点 a 所在连通块中点的数量;输入格式
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为
C a b,Q1 a b或Q2 a中的一种。输出格式
对于每个询问指令
Q1 a b,如果 a 和 b 在同一个连通块中,则输出Yes,否则输出No。对于每个询问指令
Q2 a,输出一个整数表示点 a 所在连通块中点的数量每个结果占一行。
数据范围
$1≤n,m≤10^5$
输入样例:
1
2
3
4
5
65 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5输出样例:
1
2
3Yes
2
3
思路:此题是给定了n个点的无向图,这些点之间连接线后,可以形成连通块,同一个连通块可以只用一个集合来维护,所以就很容易的想到使用并查集,但是,操作当中需要寻找到连通块中点的数量,所以就需要有一个数组来维护连通块中点的数量是多少,注意,只需要维护每个根节点的这个变量就行了,只需要每个根节点的这个数字是正确的就能完成对应的操作。
1
|
|
7.堆
手写堆最基本的操作:
- 插入一个数
- 求集合当中的最小值
- 删除最小值
- 删除任意一个元素
- 修改任意一个元素
前三个要求STL是可以实现的,最后两个是STL不能直接实现的。
堆是一棵完全二叉树,除了最后一层结点之外上层的所有结点都是满的,最后一层的结点是从左到右排列的,同时,(小根堆)堆满足所有的结点都小于左右子结点,所以很明显根节点就是最小值。
堆的存储是一种全新的存储方式,使用的是一维数组,x 结点的左儿子是 2x,右儿子是 2x+1,所以数组的下标从1开始比较方便。
主要函数是down()函数和up()函数,对传入的数据进行向上的维护或者向下的维护。
实现方式:heap[]数组模拟堆,size记录最后一个元素
- 插入一个数 heap[++size] = x; up(size);
- 求集合当中的最小值 heap[1];
- 删除最小值 heap[1] = heap[size];size–;down(1);
- 删除任意一个元素 heap[k] = heap[size];size–;down(k);up(k);
- 修改任意一个元素 heap[k] = x;down(k);up(k);
代码实现:
1
|
|

浙公网安备 33010602011771号