[备战软考]数据结构与算法基础

来源:互联网 发布:三一重工 知乎 编辑:程序博客网 时间: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.顺序表与链表的比较

  • 空间性能
项目 顺序存储 链式存储 存储密度 =1,更优 <1 容量分配 事先确定 动态改变,更优


  • 时间性能
项目 顺序存储 链式存储 查找运算 O(n/2) O(n/2) 读运算 O(1),更优 O([n+1]/2),最好1,最坏n 插入运算 O(n/2),最好0,最坏n O(1),更优 删除运算 O([n-1]/2) O(1),更优


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)=keyH(key) = a*key+b

  • 除余法
    以关键码除以表元素总数后得到的余数为地址

  • 基数转换法
    将关键码看作是某个基数制上的整数,然后将其转换为另一基数制上的数

  • 平方取中法
    取关键码的平方值,根据表长度取中间的几位数作为散列函数值

  • 折叠法
    将关键码分成多段,左边的段向右折,右边的段向左折,然后叠加

  • 移位法
    将关键码分为多段,左边的段右移,右边的段左移,然后叠加

  • 随机数法
    选择一个随机函数,取关键码的随机函数值

2.冲突处理方法

开放地址法

  • 线性探查法:冲突后直接向下线性地址找一个新的空间存放
  • 双散列函数法:用两个散列函数来解决冲突

拉链法

将散列表的每个结点增加一个指针字段,用于链接同义词的字表


查找算法

1.顺序查找

从一端开始逐个对比当前结点和关键字是否相等

2.二分查找

要求待查序列为有序表
每次对比中点和关键字是否相等,若相等则找到。若关键字大,则在右边的区间继续这一操作,否则在左边的区间继续这一操作

3.分块查找

用索引表记录块的最大关键字和起始地址,然后查找的时候只要找到关键字所在的块,然后在对应的块中查找就可以了

原创粉丝点击