C# 图 | 邻接矩阵图 | 邻接表图 的原理和代码实现

题外话: 呀,又偷懒了半天,(๑>؂<๑)罪恶罪恶。。。
PS: 本文的重点代码代码代码!而不是数据结构的一些基础概念和分类;
完全没接触过的同学,可以去看B站王卓教授的视频。


1.什么是图?

  • 官话: 顶点、及连接顶点之间边的集合(顶点和边的集合)。
  • 我的理解:
    • 一对一是线性结构,一对多是树,多对多是图
    • 图的概念比较大,你可以把树看做是一种特殊的图

2.为什么要有图?或者说有什么应用?

  • 场景1:高德地图导航
    • 每个小区、商铺、饭店、景点等可作为出发地和目的地的,就是图的顶点;
    • 这些顶点,现实中由一条条马路连接而成,这些马路就是边;
    • 马路的公里数、通过时间就是边的权重;
    • 上述的元素构成了一个图,计算从家到公司到底该走哪条路公里数最短or时间最短,就是图的应用。
  • 场景2:飞猪买飞机票
    • 全国各大有机场的城市,可以视为图的顶点;
    • 两个城市之间的航线(直达),可以视为边;
    • 各个航线的机票价格、航行时间可以视为边的权重;
    • 那么!!飞猪给你推荐从北京到上海坐哪个航班最便宜、怎么转机最便宜、做哪个航班时间最短,就是图的应用。

3.图按边是否有方向分类

  • 有向图:
    • 顾名思义,边带有方向的图;
    • 有方向就意味着两个顶点之间的关系,是具有方向性的;
    • 比如说A喜欢B,但B不喜欢A,放到图里面,可以理解为A->B,但B无法通向A。
  • 无向图:
    • 没有方向的图;
    • 只要两个顶点连上了,就意味着两个顶点可以相互触达
      在这里插入图片描述

4.图按存储结构分类

  • 邻接矩阵实现图
    在这里插入图片描述
    • 邻接矩阵我的理解:
      • 首先,一共n个顶点都保存在了数组中,每个顶点都有一个索引对吧;
      • 然后,建立一个n x n的二维数组,行/列都对应了顶点的索引也没问题吧;
      • 最后,以行,列的顺序去找到对应二维数组的值,是1就代表两个索引的顶点连通,是0则不连通,多好理解。
    • 有向图和无向图的邻接矩阵
      在这里插入图片描述
  • 邻接表实现图(链表)
    在这里插入图片描述
    • 邻接表的有向和无向图
      在这里插入图片描述
      在这里插入图片描述

5.邻接矩阵图的代码实现

namespace YoyoCode
{
    
    
    /// <summary>
    /// 邻接矩阵实现的图(默认按照有向图,弧不带权重)
    /// </summary>
    internal class MatrixGraph<T> where T : class
    {
    
    
        //最大顶点数量
        public int MaxVertexNum;
        //存储顶点的数组
        public T[] Vertex;
        //邻接矩阵
        public short[,] AdjacecntMatrix;
        //顶点数量
        public int Count = 0;
        //遍历时该节点是否访问过
        private bool[] _visited;

        /// <summary>
        /// 自定义大小的矩阵
        /// </summary>
        /// <param name="MaxSize"></param>
        public MatrixGraph(int MaxSize)
        {
    
    
            MaxVertexNum = MaxSize;
            Vertex = new T[MaxVertexNum];
            AdjacecntMatrix = new short[MaxVertexNum, MaxVertexNum];
        }
        /// <summary>
        /// 增加顶点
        /// </summary>
        public void AddVertex(T vertex)
        {
    
    
            if (Count >= MaxVertexNum)
            {
    
    
                throw (new OverflowException("已达到数组最大可容纳顶点数!"));
            }
            Vertex[Count++] = vertex;
        }
        /// <summary>
        /// 按照顶点的索引添加边
        /// </summary>
        /// <param name="m">出度顶点的索引</param>
        /// <param name="n">入度顶点的索引</param>
        /// <param name="isMutual">该边是否是双向的</param>
        public void AddEdge(int m, int n, bool isMutual = false)
        {
    
    
            //判断顶点是否存在
            if (Vertex[m] == null || Vertex[n] == null)
            {
    
    
                throw (new IndexOutOfRangeException("输入的边对应的顶点不存在!"));
            }
            AdjacecntMatrix[m, n] = 1;
            if (isMutual)
            {
    
    
                AdjacecntMatrix[n, m] = 1;
            }
        }
        /// <summary>
        /// 图是否为空
        /// </summary>
        /// <returns></returns>
        public bool IsEmpty()
        {
    
    
            return Count == 0;
        } 
        /// <summary>
        /// 清空图
        /// </summary>
        public void Clear()
        {
    
    
            for (int i = 0; i < Count; i++)
            {
    
    
                Vertex[i] = null;
            }

            for (int i = 0; i < Count; i++)
            {
    
    
                for (int j = 0; j < Count; i++)
                {
    
    
                    AdjacecntMatrix[i, j] = 0;
                }
            }
        }
        /// <summary>
        /// 深度优先遍历
        /// </summary>
        /// <param name="myAct">遍历时对每个元素的处理函数</param>
        /// <param name="idx">当前顶点的索引</param>
        /// <param name="firstIn">是否为第一次递归,影响到辅助数组的初始化</param>
        public void DFS(Action<T> myAct,int idx, bool firstIn = false)
        {
    
    
            //初始化辅助数组
            if (firstIn)
            {
    
    
                _visited = new bool[Count];
            }
            _visited[idx] = true;
            myAct(Vertex[idx]);
            for (int i = 0; i < Count; i++)
            {
    
    
                if (AdjacecntMatrix[idx, i]!= 0 && !_visited[i])
                {
    
    
                    DFS(myAct, i);
                }
            }
        }
        /// <summary>
        /// 广度优先遍历
        /// </summary>
        public void BFS(Action<T> myAct, int idx)
        {
    
    
            //类似层次遍历,所以用队列
            Queue<int> myQueue = new Queue<int>();
            myQueue.Enqueue(idx);
            //初始化辅助数组
            _visited = new bool[Count];
            _visited[idx] = true;

            while (myQueue.Count != 0)
            {
    
    
                int curIdx = myQueue.Dequeue();
                myAct(Vertex[curIdx]);
                //邻接点入队
                for (int i = 0; i < Count; i++)
                {
    
    
                    if (AdjacecntMatrix[curIdx, i]!= 0 && !_visited[i])
                    {
    
    
                        myQueue.Enqueue(i);
                        _visited[i] = true;
                    }
                }
            }
        }
    }

}
  • 测试代码及结果

    在这里插入图片描述

说明: DFS是深度优先遍历,BFS是广度优先遍历;这两种遍历算法是图最重要的遍历算法。


6.邻接表的图代码实现

namespace YoyoCode
{
    
    
    internal class ListGraph<T>
    {
    
    
        //最大顶点数量
        public int MaxVertexNum;
        //存储顶点的数组
        public ListGraphVertex<T>?[] Vertex;
        //顶点数量
        public int Count = 0;
        //遍历时该节点是否访问过
        private bool[] _visited;

        /// <summary>
        /// 自定义大小的数组,用于存放顶点
        /// </summary>
        /// <param name="MaxSize"></param>
        public ListGraph(int MaxSize)
        {
    
    
            MaxVertexNum = MaxSize;
            Vertex = new ListGraphVertex<T>[MaxVertexNum];
        }
        /// <summary>
        /// 增加顶点
        /// </summary>
        public void AddVertex(T value)
        {
    
    
            if (Count >= MaxVertexNum)
            {
    
    
                throw (new OverflowException("已达到数组最大可容纳顶点数!"));
            }
            Vertex[Count++] = new ListGraphVertex<T>(value);
        }
        /// <summary>
        /// 对外的添加边的函数
        /// </summary>
        /// <param name="fromIdx"></param>
        /// <param name="toIdx"></param>
        /// <param name="isMutual"></param>
        public void AddEdge(int fromIdx, int toIdx, bool isMutual = false)
        {
    
    
            //判断顶点是否存在
            if (Vertex[fromIdx] == null || Vertex[toIdx] == null)
            {
    
    
                throw (new IndexOutOfRangeException("输入的边对应的顶点不存在!"));
            }
            InternalAddEdge(fromIdx, toIdx);
            //一次输入,添加双向的两条边
            if (isMutual)
            {
    
    
                InternalAddEdge(toIdx, fromIdx);
            }
        }
        /// <summary>
        /// 对内的添加边的函数,为了防止代码重复而写的
        /// </summary>
        /// <param name="fromIdx"></param>
        /// <param name="toIdx"></param>
        private void InternalAddEdge(int fromIdx, int toIdx)
        {
    
    
            //顶点是否已经有一条边
            if (Vertex[fromIdx].Edge == null)
            {
    
    
                Vertex[fromIdx].Edge = new ListGraphEdge(toIdx);
                return;
            }
            //其他情况
            var curEdg = Vertex[fromIdx].Edge;
            while (curEdg.NextEdge != null)
            {
    
    
                curEdg = curEdg.NextEdge;
            }
            curEdg.NextEdge = new ListGraphEdge(toIdx);
        }
        /// <summary>
        /// 图是否为空
        /// </summary>
        /// <returns></returns>
        public bool IsEmpty()
        {
    
    
            return Count == 0;
        }
        /// <summary>
        /// 清空图
        /// </summary>
        public void Clear()
        {
    
    
            for (int i = 0; i < Count; i++)
            {
    
    
                Vertex[i] = null;
            }
            Count = 0;
        }
        /// <summary>
        /// 深度优先遍历
        /// </summary>
        /// <param name="myAct">遍历时对每个元素的处理函数</param>
        /// <param name="idx">当前顶点的索引</param>
        /// <param name="firstIn">是否为第一次递归,影响到辅助数组的初始化</param>
        public void DFS(Action<T> myAct, int idx, bool firstIn = false)
        {
    
    
            //初始化辅助数组
            if (firstIn)
            {
    
    
                _visited = new bool[Count];
            }
            _visited[idx] = true;
            myAct(Vertex[idx].Value);

            var curEdg = Vertex[idx].Edge;
            while (curEdg != null)
            {
    
    
                if (!_visited[curEdg.VertexIdx])
                {
    
    
                    DFS(myAct, curEdg.VertexIdx);
                }
                curEdg = curEdg.NextEdge;
            }
        }
        /// <summary>
        /// 广度优先遍历
        /// </summary>
        public void BFS(Action<T> myAct, int idx)
        {
    
    
            //类似层次遍历,所以用队列
            Queue<int> myQueue = new Queue<int>();
            myQueue.Enqueue(idx);
            //初始化辅助数组
            _visited = new bool[Count];
            _visited[idx] = true;
            //邻接点入队
            while (myQueue.Count != 0)
            {
    
    
                int curIdx = myQueue.Dequeue();
                myAct(Vertex[curIdx].Value);
                var curEdg = Vertex[curIdx].Edge;
                while (curEdg != null)
                {
    
    
                    if (!_visited[curEdg.VertexIdx])
                    {
    
    
                        myQueue.Enqueue(curEdg.VertexIdx);
                        _visited[curEdg.VertexIdx] = true;
                    }
                    curEdg = curEdg.NextEdge;
                }
            }
        }
    }
    /// <summary>
    /// 顶点
    /// </summary>
    internal class ListGraphVertex<T>
    {
    
    
        public T Value {
    
     get; set; }

        public ListGraphEdge? Edge {
    
     get; set; }

        public ListGraphVertex(T data)
        {
    
    
            Value = data;
        }
    }
    /// <summary>
    /// 边
    /// </summary>
    internal class ListGraphEdge
    {
    
    
        public int VertexIdx;

        public ListGraphEdge? NextEdge {
    
     get; set; }

        public ListGraphEdge(int idx)
        {
    
    
            VertexIdx = idx;
        }
    }
}


结语: 写完了,至此常用的一些基本数据结构(顺序表、链表、顺序栈、链栈、顺序循环队列、链队、顺序结构二叉树、二叉链表二叉树、邻接矩阵图、邻接表图),我都以代码的形式展现出来啦~
后面会开始肝排序算法,噶油~~

猜你喜欢

转载自blog.csdn.net/Liyager/article/details/129095544
今日推荐