2.栈和队列
2.1 堆栈
2.1.1 顺序栈
1.栈的概念
栈的操作特性:后进先出(Last In First Out, LIFO )
确定栈底:设top为栈顶元素索引(下标)
进栈:top先 + 1,然后在top指向的位置压入数据。
出栈:先top所指向的元素弹出,然后top - 1。
栈空:top = -1;
栈满:top = MAX_SIZE - 1; PS:MAX_SIZE———栈的最大容量
上溢:栈顶指针或栈顶元素下标指出栈的外面。——-Push 时要先判断是否上溢
下溢:表示栈为空栈,却仍要执行弹栈操作。———–pop时要先判断是否下溢
示例:
字符类:
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 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| #include <iostream>
using namespace std;
const int MAX_SIZE = 100;
class Stack { private: char* data; int size; int top;
public:
Stack();
Stack(int s);
~Stack();
void push(char ch); char Pop(); char getTop(); bool isEmpty(); bool isFull(); void setNull(); };
Stack::Stack() { size = MAX_SIZE; top = -1; data = new char[MAX_SIZE]; }
Stack::Stack(int s) { size = s; top = -1; data = new char[size]; }
Stack::~Stack() { delete[] data; }
void Stack::setNull() { top = -1; }
void Stack::push(char ch) { if (!isFull()) { top++; data[top] = ch; } }
char Stack::Pop() { if (!isEmpty()) { return data[top--]; } }
char Stack::getTop() { if (!isEmpty()) return data[top]; }
bool Stack::isEmpty() { if (top == -1) { return true; } else { return false; } }
bool Stack::isFull() { if (top + 1 == size) { return true; } else { return false; } }
int main(void) { Stack s1(2);
s1.push('a'); s1.push('b');
cout << s1.isFull() << endl; cout << s1.getTop ()<< endl; cout << s1.Pop() << endl; cout << s1.Pop()<< endl; cout << s1.isEmpty() << endl;
return 0; }
|
2.1.2 异常捕获优化顺序栈
C++异常处理
try…catch语句语法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| try { 语句1; 语句2; 语句3; ... }
catch(异常类型) { 异常处理代码 } ... catch(异常类型) { 异常处理代码 }
|
语句1,2,3均正常,则执行最后一个catch块后面的语句,所有catch块中的语句都不被执行。
如果语句2发生异常,那么抛出异常后立即跳转到第一个异常类型和抛出的异常类型匹配的catch块中执行。
语句3和try块中位于语句2下面的其他语句将会被跳过,不再执行。
假设语句2发生异常,那么抛出异常,跳出try块,假设没有catch块可以捕获此错误,跳转到最后一个catch块后面的语句,造成程序终止。
什么时候会出现异常?
程序有错误会产生异常,eg:
- 除法中分母是0;
- 用new运算动态分配内存空间时,空间不够导致无法分配;
- 访问数组元素,下标越界;
- 打开文件读取时,文件不存在。
程序主动抛出异常,eg:
自定义异常类的使用
优化顺序栈后:
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 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
| #include <iostream>
using namespace std;
const int MAX_SIZE = 100;
class Stack { private: char* data; int size; int top;
public:
Stack();
Stack(int s);
~Stack();
void push(char ch); char Pop(); char getTop(); bool isEmpty(); bool isFull(); void setNull(); class Full{}; class Empty{}; };
Stack::Stack() { size = MAX_SIZE; top = -1; data = new char[MAX_SIZE]; }
Stack::Stack(int s) { size = s; top = -1; data = new char[size]; }
Stack::~Stack() { delete[] data; }
void Stack::setNull() { top = -1; }
void Stack::push(char ch) { if (isFull()) { throw Full(); } else { data[++top] = ch; } }
char Stack::Pop() { if (isEmpty()) { throw Empty(); } else { return data[top--]; } }
char Stack::getTop() { if (!isEmpty()) return data[top]; }
bool Stack::isEmpty() { if (top == -1) { return true; } else { return false; } }
bool Stack::isFull() { if (top + 1 == size) { return true; } else { return false; } }
int main(void) { Stack s1(2); char ch;
try { s1.push('a'); s1.push('b'); s1.push('c'); } catch (Stack::Full) { cout << "Stack Full!!!" << endl; }
try { ch = s1.Pop(); cout << ch << endl;
ch = s1.Pop(); cout << ch << endl;
ch = s1.Pop(); cout << ch << endl; } catch (Stack::Empty) { cout << "Stack Empty!!!" << endl; }
return 0; }
|
输出结果:
Stack Full!!!
b
a
Stack Empty!!!
分析:数组只定义了2个长度,却压入三个数据,当压入第三个数据时,执行创建Full类并抛出,被catch捕获,因此输出”Stack Full!!!”,结束执行pop函数,当弹出第2个元素以后,栈已经空了,但main函数中仍要弹出第三个数据,因此执行创建Empty类并抛出,被catch捕获输出“Stack Empty!!!”。
2.1.3 用类模板实现顺序栈
编写通用的栈类
将上面的代码声明部分改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| template<class DataType> class Stack { private: DataType* data; int size; int top;
public:
Stack();
Stack(int s);
~Stack();
void push(DataType e); DataType Pop(); DataType getTop(); ... };
|
成员函数每一项前加上 **template<class DataType>
**,并把字符类型改为DataType,eg:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| //析构函数 template<class DataType> Stack<DataType>::~Stack() { delete[] data; }
//入栈 template<class DataType> void Stack<DataType>::push(DataType ch) { if (isFull()) //当栈满时,创建异常对象,并抛出 { throw Full(); } else { data[++top] = ch; } }
|
树
树的定义
树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。
树具有的特点有:
(1)每个结点有零个或多个子结点
(2)没有父节点的结点称为根节点
(3)每一个非根结点有且只有一个父节点
(4)除了根结点外,每个子结点可以分为多个互不相交的子树。
树的基本术语
结点的度:结点所拥有的子树的个数。
树的度:树中个结点度的最大值。
叶子结点:度为0的点,也叫终端结点。
分支结点:度不为0的结点,也称为非终端结点。
孩子、双亲:略。
兄弟:略。
树的遍历操作
从根结点出发,按照某种次序访问树中所有结点,使每个结点被访问一次且仅被访问一次
遍历的类型:
前序遍历:ABDEHIFCG
前序:ABEFCDGHIJK
后序:EFBCIJKHGDA
层序:ABCDEFGHIJK
树的存储结构
双亲孩子表示法
孩子兄弟表示法
二叉树:
基本性质:
叶子结点数等于度为2的结点数+1(n0=n2+1)
二叉树第i层上最多有2^(i-1)个结点(i>=1)
一棵深度为K的二叉树中,最多有2^k-1个结点(已知深度,可以知道要为二叉树分配多少内存空间),最少有k个结点(斜树)
假设具有n个结点的完全二叉树的深度为k,k=[log2n]+1(不大于log2n的整数加1)
对n个结点的完全二叉树从1开始按层序编号,则
- 结点i的双亲结点为i/2
- 结点i的为2i
- 结点i的右孩子为2i+1
二叉树的存储结构:
二叉树的顺序存储结构一般仅存储完全二叉树,否则会造成很大的浪费
结点结构:
具有n个结点的二叉链表中,有2n-n-1=n+1个空指针。
但是二叉链表有个缺点就是没有办法找到双亲。因此我们引入三叉链表、
可以用指针表示,也可以用数组表示。
三叉链表的静态链表可以用数组存储,有点是存储方便,搜索快捷,但是它适用 于频发删除和插入的场合。最好是一个变化很少的树。
二叉树的遍历 方式:
- 前序遍历
- 中序遍历(树的遍历没有中序)
- 后序遍历
- 层序遍历
树状数组
前置知识:
lowbit
:
lowbit(n)=n&(~n+1)
因为~n+1=-n,
所以lowbit(n)=n&(~n+1)=n&-n
观察可知,
- t[x]结点覆盖的长度等于lowbit(x),
- t[x]的父结点等于t[x+它自己的长度],即t[x+lowbit(x)],
- 整棵树的深度为logn+1.
单点修改操作:
(以加k为例)
找到要修改的点对应的下标,加k,并一层一层地往上找到它的父结点,让它的父结点加上k。代码如下:
1 2 3 4
| void add(int x,int k){ for(;x<=n;x+=x&-x)t[x]+=k; }
|
查询操作:ask(7)从t[7]这个结点往左上走找到上一层结点,这个结点值等于x-lowbit(x),即x-=x&-x
1 2 3 4 5 6
| int ask(int x){ int ans=0; for(;x;x-=x&-x)ans+=t[x]; return ans; }
|
其他一些操作:
1 2 3 4 5 6 7 8 9
| add(x,k); ask(x)-ask(x-1); add(x,k); ask(r)-ask(l-1); add(l,d);add(r+1,-d); a[x]+ask(x);
|
下面来看一下模板题
题目链接:https://vjudge.net/problem/HDU-1166
参考代码:
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
| #include<bits/stdc++.h> using namespace std; typedef long long ll; using namespace std; int a[50005],t[50005]; int n;
void add(int x,int k){ for(;x<=n;x+=x&-x)t[x]+=k; }
int ask(int x){ int ans=0; for(;x;x-=x&-x)ans+=t[x]; return ans; } int main(){ int test; cin>>test; for(int i=1;i<=test;i++){ memset(a,0,sizeof(a)); memset(t,0,sizeof(t)); cout<<"Case "<<i<<":"<<endl; cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; add(i,a[i]); } string s; int x,y; while(cin>>s&&s[0]!='E'){ cin>>x>>y; if(s[0]=='Q'){ int sum=ask(y)-ask(x-1); cout<<sum<<endl; } else if(s[0]=='A'){ add(x,y); } else if(s[0]=='S'){ add(x,-y); } } } return 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
| #include <iostream> #include <cstdio> #include<string> using namespace std;
struct node{ int l,r; int v; }tr[200020]; int a[200020];
void pushup(int u){ tr[u].v=tr[u<<1].v+tr[u<<1|1].v; }
void build(int u,int l,int r){ if(l==r){ tr[u]={l,r,a[r]}; } else{ tr[u].l=l; tr[u].r=r; int mid = (r+l) >>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); pushup(u); } }
void modify_add(int u,int x,int v){ if(tr[u].l==x&&tr[u].r==x)tr[u].v+=v; else{ int mid=(tr[u].l+tr[u].r)>>1; if(x<=mid)modify_add(u<<1,x,v); else modify_add(u<<1|1,x,v); pushup(u); } } void modify_sub(int u,int x,int v) { if(tr[u].l==x&&tr[u].r==x)tr[u].v-=v; else{ int mid=(tr[u].l+tr[u].r)>>1; if(x<=mid)modify_sub(u<<1,x,v); else modify_sub(u<<1|1,x,v); pushup(u); } }
int query(int u,int l,int r){ if(l<=tr[u].l&&r>=tr[u].r)return tr[u].v; else{ int mid=(tr[u].l+tr[u].r)>>1; if(r<=mid)return query(u<<1,l,r); else if(l>mid)return query(u<<1|1,l,r); else{ int v=0; v+=query(u<<1,l,r); v+=query(u<<1|1,l,r); return v; } } } int main() { string op; int l,r; int p=1; int n,len; cin>>n; while(n--) { cin>>len; for(int i=1;i<=len;i++) { cin>>a[i]; } build(1,1,len); printf("Case %d:\n",p++); while(cin>>op&&op[0]!='E') { cin>>l>>r; if(op[0]=='A')modify_add(1,l,r); else if(op[0]=='S')modify_sub(1,l,r); else printf("%d\n",query(1,l,r)); } } }
|
图
前置理论知识
无向完全图的边:n×(n-1)/2
有向完全图的边:n×(n-1)
TD:度
无向图度和边数之和的关系:
∑ ^n^ TD(vi)=2e
i=1
有向各顶点入度出度之和与边的关系:
入度和=出度和=边数
简单路径:序列中顶点不重复出现的路径
简单回路:除了第一个顶点和最后一个顶点外,,其余顶点不重复出现的回路。
连通图:无向图中,如果任意两个顶点都是连通的,则称该图为连通图
连通分量:无向图中,非连通图的极大连通子图
强连通图:有向图中,……
强连通分量:有向图中,……
生成树:n个顶点的连通图G的生成树是包含G中全部顶点的一个极小连通子图
极小连通子图:含有n-1条边(多:构成回路;少:不连通)
生成森林:在非连通图中,由每个连通分量都可以得到一颗生成树,这些连通分量的生成树就组成了非连通图的生成森林。
图的存储方式
邻接矩阵
基本思想:一维数组存储途中顶点的信息
二维数组(邻接数组)存储各顶点的邻接关系
arc[i] [j]=1有边
=0无边
无向图邻接矩阵
对称
速建版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const int maxn=200; int Nv,Ne; int G[maxn][maxn]; void buildGraph(){ int v1,v2,w; cin>>Nv; for(int i=0;i<Nv;i++){ for(int j=0;j<Nv;j++) G[i][j]=0x3f3f3f; } cin>>Ne; for(int i=0;i<Ne;i++){ cin>>v1>>v2>>w; G[v1][v2]=w; G[v2][v1]=w; } }
|
有向图邻接矩阵
某顶点的出度:此行元素和
某顶点的入度:此列元素和
邻接表
基本思想:对于图的每个顶点vi,将所有邻接与vi的顶点连成一个单链表,称为顶点vo的边表(有向图称为:出边表),所有边表的头指针和存储顶点信息的一维数组构成了顶点表。
无向图的邻接表
待补……
有向图的邻接表
顶点i的出度:第i行的边表结点个数
顶点i的入度:找到终点为i的边表,则度+1
求顶点i的所有邻接点:第i行的边表结点个数
速建版:
1 2 3 4 5 6 7 8 9
| int a[maxn]; int head[maxn],ne[maxn],ver[maxn],edge[maxn],tot=0; void add(int x,int y) { ver[++tot]=y; ne[tot]=head[x]; head[x]=tot; }
|
图的存储结构的比较
|
空间性能 |
时间性能 |
适用范围 |
唯一性 |
邻接矩阵 |
O(n^2^) |
O(n^2^) |
稠密图 |
唯一 |
邻接表 |
O(n+e) |
O(n+e) |
稀疏图 |
不唯一 |
图的遍历
关键问题:
图中可能存在回路,如何避免不重复访问同一个结点?
解决方案:附设访问标志数组visited[n]
dfs-深度优先遍历
基本思想:
- 访问顶点v;
- 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历
- 重复上述两步,直至所有和v有路径相通的顶点都被访问到。
伪代码:
1 2 3 4 5 6 7 8
| void DFS (int v){ visited[v]=true; for(v的每个邻接点w){ if(!visited[w]){ DFS(w); } } }
|
是树的先序遍历的推广
bfs-广度优先搜索
借助队列来实现,伪代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| void BFS(int v){ visited[v]=true; Enqueue(v,Q); while(!Isempty(Q)){ v=Dequeue(Q); for(v的每个邻接点w){ if(!visited[w]){ visited[w]=true; Enqueue(w,Q); } } } }
|