树和图的学习


一、树和图的存储

树是特殊的图,因此我们只要学会了图的存储方式,自然也就知道了树的存储。图分为有向图和无向图两种。采用的方式也有邻接矩阵和邻接表两种方式。
1、邻接矩阵

邻接矩阵,顾名思义,就是采用矩阵来存储信息。无向图的矩阵是对称的,所以可以只存储上三角矩阵或者是下三角矩阵;而有向图则需要挨个边进行存储。

2、邻接表

邻接表就是每一个结点的位置开一个单链表,假设有n个结点,可以用一个数组来记录这n个元素。每一个结点创建一个链表,用next指向该结点所连接的各个结点。插入方式采用头插法。


一、最短路径地杰斯特拉算法及其优化

Dijkstra算法
首先,定义一个dist数组,记录起点到其他个点的距离最小值。然后将dist数组除了起点以外的所有元素都赋成无限大。然后开始扫描与起点相连的点,找出一个直接距离最短的点,加入已生成的图中,并将连接它们的这条边加入最小生成树中。

然后继续,从已有的最小生成树中的所有点出发,找到一个距离最近的,继续加入生成树中。
优化:
可以使用由优先队列,每次弹出的都是最小的元素,而且时间开销很少(由于采用了最小堆)

if(dis[j]>dis[k]+map[k][j])
 {
      dis[j]=dis[k]+map[k][j];
      path[j]=k;
 }

二、最小生成树算法

1.Kruskal算法

此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。

  1. 把图中的所有边按代价从小到大排序;
  2. 把图中的n个顶点看成独立的n棵树组成的森林;
  3. 按权值从小到大选择边,所选的边连接的两个顶点ui,viui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
  4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。
///克鲁斯卡尔
struct edge
{
    int m;
    int n;
    int d;
}a[5010];
int cmp(const void *a,const void *b) ///按升序排列
{
    return ((struct edge *)a)->d>((struct edge *)b)->d;
}
int main(void)
{
    int n,t,num,minn,x[100];
    cin>>n;///顶点数
    t=n*(n-1)/2;
    for(int i=1;i<=n;i++)
        x[i]=i;
    printf("请输入每条边的起始端点、权值:/n");
    for(int i=0;i<t;i++)///输入每条边的权值
        cin>>a[i].m>>a[i].n>>a[i].d;
    qsort(a,t,sizeof(a[0]),cmp);
    minn=num=0;
    for(int i=0;i<t && num<n-1;i++)
    {
        for(int k=a[i].m;x[k]!=k;k=x[k])  ///判断线段的起始点所在的集合
            x[k]=x[x[k]];
        for(int g=a[i].n;x[g]!=g;g=x[g])  ///判断线段的终点所在的集合
            x[g]=x[x[g]];
        if(k!=g)  ///如果线段的两个端点所在的集合不一样
        {
            x[g]=k;
            minn+=a[i].d;
            num++;
        }
    }
    cout<<minn<<endl;
    return 0;
}

2.Prim算法

此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

图的所有顶点集合为VV;初始令集合u={s},v=V−uu={s},v=V−u;
在两个集合u,vu,v能够组成的边中,选择一条代价最小的边(u0,v0)(u0,v0),加入到最小生成树中,并把v0v0并入到集合u中。
重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。
由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组closedge,用来维护集合v中每个顶点与集合u中最小代价边信息。

三、并查集原理以及应用

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

///并查集
int findx(int x) /// 查找根节点
{
    if(x == fa[x])
        return x;
    return findx(fa[x]);
}
int findx(int x)
{
    if(fa[x] != x)
    fa[x] = findx(fa[x]);  /// 路径压缩优化
    return fa[x];
}
void judge(int a,int b){///判断是否在同一个集合里
    if(findx(a)!=findx(b)){
        printf("No\n");
    }else{
        printf("Yes\n");
    }
    return;
}
void unionn(int a,int b){
    fa[b]=a;///将两棵树连接起来
    return;
}

四、深度优先搜索DFS

深度优先搜索的实质就是穷举,按照一定的顺序和规则不断地去尝试,直到找到问题的解。

对于一个问题的第一个状态叫做初始状态,最后要求的状态叫做目的状态。

在搜索的过程中,对当前状态进行检测,如果当前状态满足目的状态,那么这个当前状态就是结果之一。

可求所有解
深度优先搜索(Depth First Search,DFS)——求出可行解(所有解)

void DFS(int start)
  {
      visited[start] = 1;
      for (int i = 1; i <= N; i++)
     {
          if (!visited[i] && maze[start - 1][i - 1] == 1)
              DFS(i);
     }
      cout << start << " ";
  }

五、广度优先搜索BFS

广度优先搜索——求出最优一个解

void BFS(int start)
 {
     queue<int> Q;
      Q.push(start);
      visited[start] = 1;
     while (!Q.empty())
      {
          int front = Q.front();
          cout << front << " ";
         Q.pop();
            for (int i = 1; i <= N;i++)           {
  if (!visited[i] && maze[front - 1][i - 1]== 1)
             {
                  visited[i] = 1;
                  Q.push(i);
             }
          }
      }
  }

六、可行性剪枝

剪枝算法按照其判断思路可大致分成两类:可行性剪枝及最优性剪枝.

1、可行性剪枝
该方法判断继续搜索能否得出答案,如果不能直接回溯。

2、最优性剪枝
最优性剪枝,又称为上下界剪枝,是一种重要的搜索剪枝策略。它记录当前得到的最优值,如果当前状态已经无法产生比当前最优解更优的解时,可以提前回溯。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值