这是数据结构选课的课内笔记,和 $OI$ 的高难数据结构关系不是很大,主要是记一些怕忘的定义.
ADL语言
算法 SM(A,n,&min,&max)1
2
3
4max<-min<a[1]
FOR i=2 TO n DO
if A[i]>max THEN max<-a[i]
if A[i]<min THEN min<-a[i]
举例
1 | a^=b^=a^=b |
(实际数据结构笔试的时候可以写c++,完全不用看ADL语言)
算法的鲁棒性
自动识别错误数据(并纠正)的能力.
算法测试 :两种,
黑盒测试:测试点.
白盒测试:语句覆盖,分支覆盖.
时间复杂度
$O$ ,读音:big-oh;表示上界,小于等于.
$ο$ ,读音:small-oh;表示上界,小于.
$\Theta$ ,读音:theta、西塔;既是上界也是下界,称为确界,等于.
$\omega$ ,读音:small omega;表示下界,大于.
$\Omega$ ,读音:big omega、欧米伽;表示下界,大于等于.
Ο是渐进上界,Ω是渐进下界.Θ需同时满足大Ο和Ω,故称为确界.Ο极其有用,因为它表示了最差性能.
时空积分
多维数组寻址
C代表sizeof,大乘号其实就是前缀积
矩阵三元组表转置
Q:为啥PPT说的是 $O(nt)$ 的?
这个可以做到 $O(t\log t)$ .
二叉树
满二叉树:顾名思义,节点全满,非常均匀.
完全二叉树:节点全的,末层缺失(注意从右侧缺失)
线索二叉树
节点1
val,l,r, pred,succ
后面这俩存根,先根或者后根, 可以存前根,中根或者后根 ,故又称前序,中序或者后序线索二叉树.
哈夫曼树
扩充二叉树:二叉树所有原来有空位的加子节点.
路径长度:
其中w代表权值(就是存的那个数),然后d是路径长(深度-1),这个w最小的叫最优二叉树.
哈夫曼编码:左儿子是0,右儿子是1,直接走,走到哪编码.
压缩:开一个map统计出现频率,然后建立哈夫曼树,直接译码. $O(n\log n)$ .
解码:先读哈夫曼树,然后直接译码即可.
哈夫曼树构造
所有节点都是一个森林,然后每次选权值最小的根组成一个新树,产生一个新节点,加入其中,直到剩一个根.
表达式树
中缀表达式树.一看例子就明白了.1
2
3
4
5
6
7
8(a+b)*(c+d)-e
-
/ \
* e
/ \
+ -
/ \ / \
a b c d
图
连通子图:顾名思义.
连通分量:把所有联通块抽出来就是.
拓扑排序
每次选一个入度为0的,然后删掉边,循环.
拓扑排序是 $O(n+e)$ 的.
希尔排序(Shell)
直接插入排序的改进.作为最先冲破 $O(n^2)$ 的排序算法,值得借鉴.
每次把增量分成n>>1,然后每个区间暴力插入排序.直到最后
基数分布和值分布
基数排序,是模按照指定位数进行排序的算法,计数排序就是俗称的桶排序.
基数排序:先开十个链表,然后取最后一位看剩下的数.按照这个顺序先排起来.
然后取第二位接着排,直到元素有序.
板子
下面的是Flu的离线stl库.
如果有人做数据结构作业不让使用stl库,也许可以参考一下.
注意, JLU 实际上是可以使用stl库的,因为无论是程序作业还是考试都有一堆爆零的.
栈
一度让我觉得没有封装的必要…但是既然数据结构提了那就写一个吧…1
2
3
4
5
6
7
8
9
10
11template<typename T>
struct sstack{
T* dat;
int ttop;
sstack(int n=100010){dat=new T[n];ttop=0;}
void push(T val){dat[++ttop]=val;}
T top(){if(ttop>0)return dat[ttop];return -114514;}
int size(){return ttop;}
int pop(){if(ttop==0)return -114514;ttop--;return 0;}
char empty(){return ttop<=0;}
};//所有操作不合法会返回-114514
双端队列 队列
因为双端队列兼容队列,所以就不再写队列的模板了.1
2
3
4
5
6
7
8
9
10
11
12
13
14template<typename T>
struct ddeque{
T* dat;
int ttop,l;
ddeque(int n=200010){dat=new T[n];ttop=(n>>1);l=(n>>1)+1;}
int size(){return ttop-l+1;}
T front(){if(ttop<l)return -114514;return dat[ttop];}
T back(){if(ttop<l)return -114514;return dat[l];}
int pop_front(){if(ttop<l)return -114514;ttop--;return 0;}
int pop_back(){if(ttop<l)return -114514;l++;return 0;}
void push_front(T val){dat[++ttop]=val;}
void push_back(T val){dat[--l]=val;}
char empty(){return (ttop-l+1)<=0;}
};
动态数组
推测vector怎么用.1
2
3
4
5
6
7
8
9
10
11template<typename T>
struct vvector{
static constexpr int base[]={0,20,110,1010,10010,100010,200010,1000010,2000010,10000010};
T* dat;
int sz,top;//当前块指向,顶端元素(Flu比较喜欢闭区间)
vvector(int size=0,T val=0){sz=size;dat=new T[base[size]];for(int i=0;i<base[size];++i){dat[i]=val;}top=-1;}
int size(){return top+1;}
void push_back(T val){if((top+1)>=base[sz]){T* p=new T[base[sz+1]];for(int i=0;i<base[sz];++i){p[i]=dat[i];}p[++top]=val;sz++;delete[]dat;dat=p;}else{dat[++top]=val;}}
void clear(){top=-1;}
T& operator[](int io){return dat[io];}
};
哈希表
低配版umap,只支持 long long 的哈希,但是够用了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63template<typename T>
struct uunordered_map{
struct node{
long long first;
T second;
node *next,*nnext;
node(long long a,T b,node* p=nullptr,node* pp=nullptr):first(a),second(b),next(p),nnext(pp){}
};
node** dat;
node* pp00;
const long long mmod=1000003;
int sz;
uunordered_map(){
sz=0;
pp00=nullptr;
dat=new node*[mmod];
for(int i=0;i<mmod;++i){
dat[i]=nullptr;
}
}
long long qqp(long long b,int po){
long long res=1;
while(po>0){
if(po&1)
res=res*b%mmod;
b=b*b%mmod;
po>>=1;
}
return res;
}
int hash(long long val){
return qqp(val,5);
}
int size(){
return sz;
}
T& operator[](const long long& io){
int h=hash(io);
node* i=dat[h];
for(;i!=nullptr;i=i->next){
if(i->first==io)
return i->second;
}
sz++;
node* j=new node(io,0,dat[h],pp00);
pp00=j;
dat[h]=j;
return j->second;
}
void triverse(){
for(node* i=pp00;i!=nullptr;i=i->nnext){
//i->first键 i->second键值
//这是遍历所有元素的过程
//do something
//
//
//
}
}
char empty(){
return sz<=0;
}
};
priority_queue
自定义类需要写比较函数 < ,记得输入0或者1表明建立小根堆还是大根堆,默认是0大根堆.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100template<class T>
struct ppriority_queue{
T* dat;
int sz,ff;/*ff0大根堆1小根堆*/
ppriority_queue(int flag=0,int n=100010):sz(0),ff(flag){
dat=new T[n];
}
ppriority_queue(T* arr,int num,int flag=0,int n=100010):sz(num),ff(flag){
dat=new T[n];
for(int i=0;i<num;++i){
dat[i+1]=dat[i];
}
maintain(1);
}
void push(T val){
dat[++sz]=val;
maintaindown(sz);
}
void maintaindown(int num){
int fa=num>>1;
if((dat[fa]<dat[num])^ff){
sswap(dat[fa],dat[num]);
maintaindown(fa);
}
}
int pop(){
if(sz<=0)return -114514;
dat[1]=dat[sz--];
maintainup(1);
return 0;
}
void maintainup(int num){
int ls=num<<1,rs=(num<<1)+1,gg=0;
if(rs<=sz)gg+=2;
if(ls<=sz)gg++;
switch(gg){
case 0:{
return;
}case 1:{
if((dat[num]<dat[ls])^ff){
sswap(dat[num],dat[ls]);
}
return;
}case 3:{
if((dat[ls]<dat[rs])^ff){
if((dat[num]<dat[rs])^ff){
sswap(dat[num],dat[rs]);
maintainup(rs);
}
}else{
if((dat[num]<dat[ls])^ff){
sswap(dat[num],dat[ls]);
maintainup(ls);
}
}
break;
}
}
}
T top(){
return dat[1];
}
int size(){
return sz;
}
void sswap(T& a,T& b){
T _tmp=a;
a=b;
b=_tmp;
}
void maintain(int num){/*O(n)建堆*/
int ls=num<<1,rs=(num<<1)+1,gg=0;
if(ls<=sz)maintain(ls),gg++;
if(rs<=sz)maintain(rs),gg+=2;
switch(gg){
case 0:{
return;
}case 1:{
if((dat[num]<dat[ls])^ff){
sswap(dat[num],dat[ls]);
}
return;
}case 3:{
if((dat[ls]<dat[rs])^ff){
if((dat[num]<dat[rs])^ff){
sswap(dat[num],dat[rs]);
}
}else{
if((dat[num]<dat[ls])^ff){
sswap(dat[num],dat[ls]);
}
}
break;
}
}
}
char empty(){
return sz<=0;
}
};
ppair
单纯模仿用的…1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<typename T1,typename T2>
struct ppair{
T1 first;
T2 second;
ppair(){}
ppair(T1 a,T2 b):first(a),second(b){}
ppair& operator =(const ppair& io){
first=io.first;
second=io.second;
return *this;
}
char operator ==(const ppair& io)const{
return (first==io.first)&&(second==io.second);
}
void swap(ppair& io){
ppair tmp=io;
io=*this;
*this=tmp;
}
};
list
十分暴力的链表.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59template<typename T>
struct llist{
int cnt,top;/*top目前链表占用多大空间,cnt链表有多少个元素*/
T* dat;
int* nxt;
llist(int n=200010):cnt(0),top(1){
dat=new T[n];
nxt=new int[n];
for(int i=0;i<n;++i){
nxt[i]=0;
}
}
llist(T* ddat,int num,int n=200010):cnt(num),top(1+num){
dat=new T[n];
nxt=new int[n];
for(int i=0;i<num;++i){/*[0,num)*/
dat[i+2]=ddat[i];
nxt[i+1]=i+2;
}
}
void _add(int pre,T val){
for(int i=0,j=1;;++i,j=nxt[j]){
if(i==pre){
nxt[++top]=nxt[j];
nxt[j]=top;
dat[top]=val;
cnt++;
return;
}
}
}
int add(int pos,T val){
if(pos<0||cnt<pos)return -114514;
_add(pos,val);
return 0;
}
void _del(int pre){
for(int i=0,j=1;;++i,j=nxt[j]){
if(i==pre){
nxt[j]=nxt[nxt[j]];
cnt--;
return;
}
}
}
int del(int pos){
if(pos<=0||cnt<pos)return -114514;
_del(pos-1);
return 0;
}
void triverse(){
for(int i=1,j=nxt[1];j!=0;++i,j=nxt[j]){
/*i是序号,j是当前元素指针*/
//do something...
}
}
};
高精度
懒得写,高精度维护起来太麻烦了.
ALL
1 |
|
做题
背板
数据结构包含哪方面内容?数据存储方法有多少种?
数据的逻辑结构,物理结构,存储结构.
存储表示方法有四种:
- 顺序存储(逻辑相邻存储在物理相邻的单元中)
- 链接存储(不要求物理相邻,节点间的关系使用指针表示)
- 索引存储(除了存储节点信息还添加索引表示节点地址)
- 散列存储(根据关键字计算出存储地址)
数组
一定要看清行优先存储还是列优先存储!!!
排序
内外排序的区别是,外排序数据很大,一次读不完需要好几次IO,此时限制IO次数比较重要.(听说外排序可以用归并)
快排辅助空间是O(logn)的,选择排序是不稳定的排序算法,快排,堆都是不稳定的排序算法.
快排的 划分 :依照某个元素把整个数组拆成两半的过程.
然而有个坑:在划分的时候元素顺序可能有锅:考虑选择某个元素,然后这个元素就能动了,然后朝不能动的那端进行比较,确保不需要额外空间.比如1
2
3
4
546 79 56 38 40 84 快排选择头节点
__ 79 56 38 40 __ (84) 84这个时候和46比较决定放在哪里
__ 79 56 38 __ 84 (40) 40这个时候和46比较决定放在哪里
40 __ 56 38 __ 84 (79) 79这个时候和46比较决定放在哪里
...这样类推,最后的放原来比较元素
堆排序 :建堆的时候是先从子树开始维护堆序性(建堆),然后弹出元素的时候是从头开始到链维护堆序性.
归并排序 :一组记录的关键字是(25,50,15,35,80,85,20,40,36,70)其中含有5个长度为2的有序表,用归并排序算法进行一趟归并的结果是啥
给的有序表可以直接合并,所以就是[25,50]和[15,35]合并,[80,85]和[20,40]合并,[36,70]不动,答案出来了.
查找
判定树:节点的值是当前位置元素的值.
比如5,12,17,19,23,25,30,36的判定树是1
2
3
4
5
6
7 19
/ \
12 25
/ \ /\
6 19 23 30
\
36
这个时候的成功查找长度是每个节点的高度(根节点高度定义为1)平均值
不成功的ASL计算: \sum深度空指针乘深度个数/指针个数 ,在这里看图,深度为3的节点有6 19 23 30,他们贡献7个空指针,36深度为4贡献2个空指针,加一起:
队列
两点:1.一定要注意队列是否为空!!然后就是,队列是加在队列末尾,队列前端先出.
2.循环队列元素公式 $(end-front+M)\mod M$
树论
迷惑问题:若完全二叉树的某节点无左孩子节点,则他必是叶子节点(T)
争议项:只有一个根,这个根已经是根了,还能是叶子吗???
课本教的”树转二叉树”,”二叉树转森林”,”森林转二叉树”的算法统称 自然对应法 :
树转二叉树 :一个节点的所有子节点之间连线,然后只保留最靠左的一条线,其余删掉.比如1
2
3
4
5 1 1 1
/|\ /|\ /
2 3 4 -> 2-3-4 -> 2-3-4
/|\ /|\ /
5 6 7 5-6-7 5-6-7
二叉树转树 :逆过程,记住左儿子右兄弟即可恢复.根节点的右兄弟直接砍掉,此时会变成森林.
二叉树转森林 :第一棵树的根的右孩子连其他树的根,然后以此类推.(注意到自然对应出来的二叉树的根只有一个左子树,所以可以大胆搞)
森林转二叉树 :根节点开始,一直向右走的链全部断开即可.
图论
前驱节点:就是树或者图能到达该点的几个节点.
后继节点:该点能到达的所有节点.(在图中,这俩想有多少个能有多少个).
AOV网:就是DAG(有向无环图)
AOE网:带权的DAG.
广度优先序列:直接遍历即可.
深度优先序列:dfs.复杂度O(n+e)别写错
关键路径 :从源点到汇点的权值 最长 的路径.
邻接矩阵存储:
注意自己连自己是0,没边填infty.
平衡树
平衡的定义是 |左子树-右子树|<=1 ,否则旋转.
平衡树删除就是直接挪到底端然后删除.
散列
给一个数组,制作哈希表:
拉链法:每个点都是一个链表.
开点法:顺延该点.
下面讨论拉链法的平均查找长度:
假设函数是%11,数列33,21,8,22,43,17,12,13,11,构造表如下:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| 33 | 12 | 13 | 17 | 8 | 21 | |||||
| 22 | 43 | |||||||||
| 11 |
成功:(每个元素的深度)/数列元素个数,上面
失败:(哈希表每个深度+1)/哈希表值域多宽,上面
待解决的问题
背kmp板子,失配指针是干什么的
背堆板子.