[备战软考]数据结构与算法基础
来源:互联网 发布:三一重工 知乎 编辑:程序博客网 时间:2024/05/21 22:09
:
数据结构与算法基础
- 数据结构与算法基础
- 线性表
- 顺序表
- 链表
- 链表的操作
- 顺序表与链表的比较
- 栈
- 队列
- 循环队列
- 树和二叉树
- 基本概念
- 树的遍历
- 二叉树
- 二叉树的遍历
- 查找二叉树二叉排序树
- 基本操作
- 最优二叉树哈夫曼树
- 基本概念
- 构造哈夫曼树
- 线索二叉树
- 将二叉树转化为线索二叉树
- 平衡二叉树
- 平衡树调整
- 图
- 基本概念
- 图的存储
- 邻接矩阵
- 邻接表
- 图的遍历
- 深度优先DFS
- 广度优先BFS
- 最小生成树
- Prim算法
- Kruskal算法
- 拓扑排序
- 关键路径
- 排序算法
- 插入排序
- 选择排序
- 交换排序
- 归并排序
- 基数排序
- 哈希表
- Hash函数的构造
- 冲突处理方法
- 开放地址法
- 拉链法
- 查找算法
- 顺序查找
- 二分查找
- 分块查找
- 线性表
线性表
1.顺序表
顺序的存储结构,元素在内存中以顺序存储。内存中占用连续的一个区域。
顺序表的删除
把要删除的元素后面每个元素向前移动一位顺序表的插入
把要插入的位置后面的(包括自己)所有元素向后移动一位,再把要插入的元素放入该位置。
2.链表
离散的存储结构,各个点的存储空间是离散的,通过指针联系起来,从而成为一个整体的链表。
单链表
从第一个元素开始指向下一个元素,最后一个元素指向NULL循环链表
最后一个元素指向头双链表
两个指针域,从两个方向连接
链表的操作
- 单链表的结点删除
前驱指向后继
Node* a1=(Node*)malloc(sizeof(Node)); Node* a2=(Node*)malloc(sizeof(Node)); Node* a3=(Node*)malloc(sizeof(Node)); a1->next=a2; a2->next=a3; a3->next=NULL; cout<<a1->next<<endl;//未删除时 //删除a2这个结点 //关键步骤:将前一个元素的结点的next指向下一个节点 a1->next=a2->next; //将删除的结点的内存释放 free(a2); cout<<a1->next<<endl;
- 单链表的结点插入
①将新的结点指向后一个元素
②前一个结点指向要插入的结点
(顺序不能颠倒,否则下一个结点的地址会找不到!)
Node* a1=(Node*)malloc(sizeof(Node)); Node* a2=(Node*)malloc(sizeof(Node)); a1->next=a2; a2->next=NULL; //cout<<a1->next<<' '<<a2<<endl; //插入x这个结点至a1与a2中间 Node* x=(Node*)malloc(sizeof(Node)); x->next=a1->next;//第一步 a1->next=x; //cout<<a1->next<<' '<<x<<endl;
- 双链表的结点插入和删除
也是参照单链表的方法,做两次操作而已。但是都要先进行完第一步,再进行第二步。
3.顺序表与链表的比较
- 空间性能
- 时间性能
4.栈
先进后出,只能对栈顶进行操作。可以用顺序表和链表实现。
5.队列
先进先出,只能从队尾插入,对头读取。
循环队列
头指针:head 尾指针:tail
如果没有任何元素,head=tail,如果有元素入队,tail向后移一位
如果在最后一个位置也插入了元素,那么tail又会回到head的位置。
为了避免队列空和队列满是一个状态,将最后一个元素的位置舍弃不用。
树和二叉树
1.基本概念
结点的度:与下一层有几个结点相关联,它的度就是多少
树的度:整个树中度数最大的结点的度是多少,树的度就是多少
叶子结点:度为0的结点
分支结点:除了叶子结点的所有结点都是分支结点(下一层有分支)
内部结点:分支结点中除了根结点的所有结点都是内部结点(中间层的结点)
父结点
子节点
兄弟结点
层次
公式:所有结点的度之和+1=结点总个数
2.树的遍历
前序遍历:先访问根节点,再依次访问子结点(访问完了一个子结点在访问后一个子结点)
1 2 5 6 7 3 4 8 9 10
后序遍历:先访问子结点,再访问根结点
5 6 7 2 3 9 10 8 4 1
层次遍历:一层一层地访问
1 2 3 4 5 6 7 8 9 10
3.二叉树
每个结点最多只能有两个子结点,分为左子结点和右子结点。
满二叉树:二叉树的每层都是满的(完整金字塔形状)
完全二叉树:对于n层的二叉树,其n-1层是满二叉树,第n层的结点从左到右连续排列
4.二叉树的遍历
与树的遍历是一样的,就是多了一种中序遍历
- 中序遍历:先访问左子结点,再访问根节点,再访问右子结点
5.查找二叉树(二叉排序树)
空树或满足以下递归条件:
查找树的左右子树各是一颗查找树
若左子树非空,则左子树上的各个结点的值均小于根节点的值
- 若右子树非空,则左子树上的各个结点的值均大于于根节点的值
基本操作
查找:比较当前结点的值与键值,若键值小,则进入左子结点,若键值大,则进入右子结点
插入结点:
- 如果相同键值的结点已经在查找二叉树中,则不再插入
- 如果查找二叉树为空树,则以新结点为查找二叉树
- 比较插入结点的键值与插入后的父节点的键值,就能确定新结点是父节点的左子结点还是右子结点,并插入
- 删除操作
- 若删除的结点p是叶子结点,则直接删除
- 若p只有一个子结点,则将这个子结点与待删除的结点的父节点直接连接,然后删除节点p
- 若p有两个子结点,在左子树上,用中序遍历找到关键值最大的结点s,用s的值代替p的值,然后删除结点s,结点s必须满足上面两种情况之一
6.最优二叉树(哈夫曼树)
基本概念
- 树的路径长度:到达每个叶子结点所需要的长度之和
- 权:人为定义的每个结点的值
- 带权路径长度:路径长度*该结点的权值
- 树的带权路径长度(树的代价):每个叶子结点的带权路径长度之和
构造哈夫曼树
①把每个权值作为根节点,构造成树
②选择两颗根节点最小的树作为子树合成一颗新的树,根节点的值为两个根节点值的和
③重复②,直到只剩一棵树为止
7.线索二叉树
- 表示
[Lbit][Lchild][Data][Rchild][Rbit]
标志域规定:
Lbit=0,Lchild是通常的指针
Lbit=1,Lchild是线索(指向前驱)
Rbit=0,Rchild是通常的指针
Rbit=1,Rchild是线索(指向后继)
将二叉树转化为线索二叉树
对于每个空余的左右指针,都用线索替代,左指针指向前驱,右指针指向后继
前序:A B D E H C F G I
中序:D B H E A F C G I
后序:D H E B F I G C A
8.平衡二叉树
对某个数列构造排序二叉树,可以构造出多颗形式不同的排序二叉树
- 定义:树中任一结点的左、右子树的深度相差不超过1
平衡树调整
浅谈算法和数据结构: 九 平衡查找树之红黑树
- LL型平衡旋转(单向右旋平衡处理)
- RR型平衡旋转(单向左旋平衡处理)
- LR型平衡旋转(双向旋转,先左后右)
- RL型平衡旋转(双向旋转,先右后左)
图
1.基本概念
图的构成:
图由两个集合:V和E所构成,V是非空点集,E是边集,图 G=(V,E)无向图和有向图:
边是单向的是有向图,双向的就是无向图顶点的度
无向图:有几条边相连度就为几
有向图:分为入度和出度子图
完全图
无向图中每对顶点都有一条边相连,有向图中每对顶点都有两条有向边相互连接路径和回路
连通图
有向图中,任意两点都有路径到达
无向图中,没有孤立点的图强连通
有向图中,任意两点作为起点和终点都有路径到达则为强连通。如果只能确保单向连通,则是弱连通。连通分量
图的一个子图是连通图,那么这个子图就是一个连通分量网络
每一条边都有一个权值
2.图的存储
(此部分图片来自刘伟老师)
邻接矩阵
邻接表
又叫邻接链表
3.图的遍历
深度优先(DFS)
- 首先访问一个未访问的节点V
- 依次从V出发搜索V的每个邻接点W
- 若W未访问过,则从该点出发继续深度优先遍历
#include <iostream>#include <cstring>#define mem(a,b) memset(a,b,sizeof(a))using namespace std;const int maxn=100;struct EDG{ int u; int v; int w; //初始化列表 EDG(int uu=0,int vv=0,int ww=0):u(uu),v(vv),w(ww){}}e[maxn];int first[maxn],nxt[maxn*2];int vis[maxn];int len=0;void mk_edg(int u,int v,int w)//加入u到v权值为w的边{ e[++len]=EDG(u,v,w); nxt[len]=first[u]; first[u]=len; e[++len]=EDG(v,u,w); nxt[len]=first[v]; first[v]=len;}//图的深度优先遍历(递归写法)void DFS(int v){ vis[v]=1; cout<<v<<' '; for(int i=first[v];i!=-1;i=nxt[i]) { if(!vis[e[i].v]) { DFS(e[i].v); } }}int main(){ mem(first,-1),mem(nxt,-1),mem(vis,0); int n,m; cin>>n>>m; for(int i=0;i<m;i++) { int u,v,w; cin>>u>>v>>w; mk_edg(u,v,w); mk_edg(v,u,w); } for(int i=0;i<n;i++) { if(!vis[i]) DFS(i); } return 0;}
广度优先(BFS)
- 首先访问一个未访问的顶点V
- 然后访问与顶点V邻接的全部未访问顶点W、X、Y……
- 然后再依次访问W、X、Y邻接的未访问顶点
4.最小生成树
(此部分代码来自刘伟老师)
Prim算法
思想:
(1) 任意选定一点s,设集合S={s}
(2) 从不在集合S的点中选出一个点j使得其与S内的某点i的距离最短,则(i,j)就是生成树上的一条边,同时将j点加入S
(3) 转到(2)继续进行,直至所有点都己加入S集合
#include<iostream> using namespace std; #define MAXN 2001#define INF 1000000int n, m;int G[MAXN][MAXN]; //存储图void init(){ for(int i = 0 ; i < n ; i++){ for(int j = 0 ; j < n ; j++) G[i][j] = INF; //初始化图中两点间距离为无穷大 }}void prim(){ int closeset[n], //记录不在S中的顶点在S中的最近邻接点 lowcost[n], //记录不在S中的顶点到S的最短距离,即到最近邻接点的权值 used[n]; //标记顶点是否被访问,访问过的顶点标记为1 for (int i = 0; i < n; i++) { //初始化,S中只有第1个点(0) lowcost[i] = G[0][i]; //获取其他顶点到第1个点(0)的距离,不直接相邻的顶点距离为无穷大 closeset[i] = 0; //初始情况下所有点的最近邻接点都为第1个点(0) used[i] = 0; //初始情况下所有点都没有被访问过 } used[0] = 1; //访问第1个点(0),将第1个点加到S中 //每一次循环找出一个到S距离最近的顶点 for (int i = 1; i < n; i++) { int j = 0; //每一次循环计算所有没有使用的顶点到当前S的距离,得到在没有使用的顶点中到S的最短距离以及顶点号 for (int k = 0; k < n; k++) if ((!used[k]) && (lowcost[k] < lowcost[j])) j = k; //如果顶点k没有被使用,且到S的距离小于j到S的距离,将k赋给j printf("%d %d %d\n",closeset[j] + 1, j + 1, lowcost[j]); //输出S中与j最近邻点,j,以及它们之间的距离 used[j] = 1; //将j增加到S中 //每一次循环用于在j加入S后,重新计算不在S中的顶点到S的距离 //主要是修改与j相邻的边到S的距离,修改lowcost和closeset for (int k = 0; k < n; k++) { if ((!used[k]) && (G[j][k] < lowcost[k])) //松弛操作,如果k没有被使用,且k到j的距离比原来k到S的距离小 { lowcost[k] = G[j][k]; //将k到j的距离作为新的k到S之间的距离 closeset[k] = j; //将j作为k在S中的最近邻点 } } }} int main(){ int a , b , w; scanf("%d%d" , &n , &m); init(); for(int i = 0 ; i < m ; i++){ scanf("%d%d%d" , &a , &b , &w); if(G[a-1][b-1] > w) G[a-1][b-1] = G[b-1][a-1] = w; //无向图赋权值 } prim(); system("pause"); return 0; }
Kruskal算法
思想:
(1) 将边按权值从小到大排序后逐个判断,如果当前的边加入以后不会产生环,那么就把当前边作为生成树的一条边
(2) 最终得到的结果就是最小生成树
#include <iostream>#include <algorithm>using namespace std;/* 定义边(x,y),权为w */struct edge{ int x, y; int w;};const int MAX = 26;edge e[MAX * MAX];int rank[MAX];/* rank[x]表示x的秩 */int father[MAX];/* father[x]表示x的父节点 */int sum; /*存储最小生成树的总权重 */ /* 比较函数,按权值非降序排序 */bool cmp(const edge a, const edge b){ return a.w < b.w;}/* 初始化集合 */void make_set(int x){ father[x] = x; rank[x] = 0;}/* 查找x元素所在的集合,回溯时压缩路径 */int find_set(int x){ if (x != father[x]) { father[x] = find_set(father[x]); } return father[x];}/* 合并x,y所在的集合 */int union_set(int x, int y, int w){ if (x == y) return 0; if (rank[x] > rank[y]) { father[y] = x; } else { if (rank[x] == rank[y]) { rank[y]++; } father[x] = y; } sum += w; //记录权重 return 1;}int main(){ int i, j, k, m, n, t; char ch; while(cin >> m && m != 0) { k = 0; for (i = 0; i < m; i++) make_set(i); //初始化集合,m为顶点个数 //对后m-1进行逐行处理 for (i = 0; i < m - 1; i++) { cin >> ch >> n; //获取字符(顶点) for (j = 0; j < n; j++) { cin >> ch >> e[k].w; //获取权重 e[k].x = i; e[k].y = ch - 'A'; k++; } } sort(e, e + k, cmp); //STL中的函数,直接对数组进行排序 sum = 0; for (i = 0; i < k; i++) { int result = union_set(find_set(e[i].x), find_set(e[i].y), e[i].w); if(result) cout<< e[i].x + 1<< "," << e[i].y + 1 <<endl; } cout << sum << endl; } system("pause"); return 0;}
5.拓扑排序
AOV网
拓扑排序
(1) 将所有入度为0的点加入队列
(2) 每次取出队首顶点
(3) 删除其连出的边,检查是否有新的入度为0的顶点,有则加入队列
(4) 重复(2)直到队列为空
6.关键路径
- AOE网
在AOV网中把边加上权值就变成了AOE网
概念:
- 顶点 j 事件的最早发生时间,即从源点到顶点 j 的最长路径长度,记作Ve( j );
- 活动ai的最早发生时间:Ve( j )是以顶点为 j 为起点的出边所表示的活动ai的最早开始时间,记作e( i )
- 顶点 j 事件的最迟发生时间:即在不推迟整个工程完成的前提下,事件 j 允许最迟的发生时间,记作Vl( j );
- 活动ai的最迟发生时间:Vl( j ) - (ai所需的时间),就是活动ai的最迟开始时间,其中j是活动ai的终点,记作l(i);
排序算法
1.插入排序
直接插入排序:
- 每一步把当前的数插入到已经有序的序列中
Shell排序:也称缩小增量排序
- 根据步长d,把相距间隔为d的元素分到一组,在内部进行直接插入排序,然后步长减半,重复这一步操作,直到步长d=1为止
2.选择排序
简单选择排序:
- 每一步查找剩余序列中最小的元素,然后将该元素放到已序序列的末尾
堆排序:
- 还没搞明白
3.交换排序
冒泡排序:
- 从后往前每一步对比相邻的两个元素,如果后面的元素小,则交换
快速排序:
- 运用分治思想。每次选择第一个元素作为基准,将比它小的元素放前面,比它大的放后面,接着在这两个子区间继续进行这一操作。
4.归并排序
- 首先把元素两个一组分组,使每个组内都有序,接下来在把每两组合并,重复这一操作直到只剩一组。
5.基数排序
- 根据元素的每一位来排序,高位的优先级比低位的高
哈希表
Hash表示一种十分实用的查找技术,具有极高的查找效率
1.Hash函数的构造
没有特定的要求,所以方法很多,只要能尽量避免冲突,就叫好的Hash函数,要根据实际情况来构造合理的Hash函数
直接定址法
H(key)=key 或 H(key) = a*key+b除余法
以关键码除以表元素总数后得到的余数为地址基数转换法
将关键码看作是某个基数制上的整数,然后将其转换为另一基数制上的数平方取中法
取关键码的平方值,根据表长度取中间的几位数作为散列函数值折叠法
将关键码分成多段,左边的段向右折,右边的段向左折,然后叠加移位法
将关键码分为多段,左边的段右移,右边的段左移,然后叠加随机数法
选择一个随机函数,取关键码的随机函数值
2.冲突处理方法
开放地址法
- 线性探查法:冲突后直接向下线性地址找一个新的空间存放
- 双散列函数法:用两个散列函数来解决冲突
拉链法
将散列表的每个结点增加一个指针字段,用于链接同义词的字表
查找算法
1.顺序查找
从一端开始逐个对比当前结点和关键字是否相等
2.二分查找
(要求待查序列为有序表)
每次对比中点和关键字是否相等,若相等则找到。若关键字大,则在右边的区间继续这一操作,否则在左边的区间继续这一操作
3.分块查找
用索引表记录块的最大关键字和起始地址,然后查找的时候只要找到关键字所在的块,然后在对应的块中查找就可以了
- [备战软考]数据结构与算法基础
- 备战软考(3) 数据结构算法基础
- 备战软考(一)——数据结构与算法基础
- 软考(1)--数据结构与算法基础
- 【软考视频】数据结构与算法基础
- 软考(1)--数据结构与算法基础
- 【软考】——数据结构与算法基础
- 软考-数据结构与算法
- 软考备战中
- [备战软考]操作系统
- 软考备战(2)语言处理程序基础
- 考试备战系列--软考--01基础架构概念
- 备战“软考”之软件工程
- 备战“软考”之心得
- 备战软考,重在行动
- 软考——数据结构与算法小结
- 软考(二)——数据结构与算法
- 【软考之旅】第一章 数据结构与算法
- Servlet如何编写以及写Servlet注意事项
- c++静态数据成员和静态成员函数
- Eclipse安装maven插件
- pyton阶乘
- 【机器学习】朴素贝叶斯
- [备战软考]数据结构与算法基础
- PAT 1099. Build A Binary Search Tree (30) 叒掉语言陷阱!!
- "libcudnn.so.5 cannot open shared object file: No such file or directory"
- css day02
- 微服务从设计到部署(四)服务发现
- Linux搭建maven私服nexus3.5
- 软件项目开发模式——三层模式
- 通用冒泡排序算法
- OCS——史上最疯狂的iOS动态化方案