码迷,mamicode.com
首页 > 其他好文 > 详细

数据结构——图

时间:2019-02-08 21:27:43      阅读:184      评论:0      收藏:0      [点我收藏+]

标签:信息   void   总数   stream   位置   简单图   push   iter   mic   

1.1 基础知识:

1.2 图的表达方式:

参考书籍: 算法(第4版).[美]Robert Sedgewick

必须要满足以下两点:

  • 必须为可能在实际应用中碰到的各种类型的图留出足够的空间
  • Graph 的实例方法的实现一定要快,速度很重要!!!

  1.邻接矩阵:

    1)邻接矩阵简介

  逻辑结构分为两部分:

Vexs[ ](存储顶点)用一个一维数组存放图中所有顶点数据;

Edges[ ][ ](邻接矩阵)集合,用一个二维数组存放顶点间关系(可以是权重,也可以是0,1)的数据,这个二维数组称为邻接矩阵。

typedef struct AdjMatrix{
    int  *Vexs;
    int **Edges;
    int  vexnum, edgenum;  // 顶点数,边数
}*Graph;

    2)邻接矩阵的特点:

  •  对于无向图来说,邻接矩阵一定是对称的;
  •     如果是简单图,主对角线一定为零,副对角线不一定;
  •  顶点Vi的度等于第i行非零元个数,或第i列非零元个数;(有向图只能是行)
  •  无向图矩阵非零元总数等于边数的2倍,有向图相同;

      3)完整实现:

  规定输入:

  1. 输入顶点数 g.vexnum 和 边数 g.edgenum;
  2.  输入n个顶点;
  3.  输入边的偶对 (vi,vj) 或 <vi,vj> ;
#include <stdio.h>
#include <string.h>
#include <malloc.h>

typedef struct AdjMatrix{
    int  *Vexs;
    int **Edges;
    int  vexnum, edgenum;  // 顶点数,边数
}*Graph;


//vn:vexnum;en:edgenum
Graph init(int vn,int en) {
    Graph g = (Graph)malloc(sizeof(AdjMatrix));
    g->vexnum = vn;
    g->edgenum = en;
    g->Vexs = (int *)malloc(sizeof(int)*vn);
    for (int i = 0; i < vn; i++) {
        scanf("%d",&g->Vexs[i]);
    }
    g->Edges = (int **)malloc(sizeof(int*)*vn);

    for (int i = 0; i < vn; i++) {
        g->Edges[i] = (int *)malloc(sizeof(int)*vn);
        memset(g->Edges[i], 0, sizeof(int)*vn);
    }
        
    for (int i = 0; i < en; i++) {
        int column,row;
        scanf("%d%d", &row,&column);
        g->Edges[row - 1][column - 1] = 1;
        //有向图把下面一行注释掉
        g->Edges[column - 1][row - 1] = 1;
    }
    return g;
}
void traverse(Graph g) {
    for (int i = 0; i < g->vexnum; i++) {
        for (int j = 0; j < g->vexnum; j++) {
            printf("%4d ", g->Edges[i][j]);
        }
        printf("\n");
    }
        
}

int main() {
    int vn, en;//顶点个数、边个数
    scanf("%d%d",&vn,&en);
    Graph g = init(vn, en);
    traverse(g);
    return 0;
}

      4)邻接矩阵图例:

技术图片

写到这也能看出来了邻接矩阵可以快速判断两个顶点之间是否存在边添加边或者删除边(通过索引 O(1))。

但是缺点也很致命存储稀疏矩阵时,十分较浪费空间,这显然不满足第一个条件。也就引申出了邻接表。

   2. 邻接表:

   1)基本特点:

邻接表是一种顺序分配和链式分配相结合的存储结构。可以说是简化版的十字链表。

N个顶点,对应N个表头节点,如果某个表头结点,所对应的顶点存在相邻顶点,则把相邻顶点依次(按顶点顺序)存放于表头结点所指向的单向链表中。

   2)完整实现(无向图):

  规定输入:

  1. 边数 g.edgenum;(根据输入的边关系,可以得到顶点。)
  2.  输入边的偶对 (vi,vj) 或 <vi,vj> ;

完整代码(不带权重):

#include <iostream>
#include <map>
#include <list>
#include <queue>
#include <set>
using namespace std;

class  node {
public:
    int data;
    node* next;
    node(int d) {
        data = d;
        next = NULL;
    }
};

class AdjList {
public:
    map<int, node*> listhead;
    int  vexnum, edgenum;  // 顶点数,边数
    AdjList(int vn, int en) {
        vexnum = vn;
        edgenum = en;
        for (int i = 0; i < en; i++) {
            int from, to;
            cin >> from >> to;
            if (!listhead[from]) {
                listhead[from] = new node(from);
            }
            if (!listhead[to]) {
                listhead[to] = new node(to);
            }
            node *s = new node(to);
            s->next = listhead[from]->next;
            listhead[from]->next = s;
                        //有向图就是把下面三行删掉
                s = new node(from);
            s->next = listhead[to]->next;
            listhead[to]->next = s;
        }
    }


};
typedef AdjList* Graph;

void print(Graph g) {
    map<int, node *>::iterator it = g->listhead.begin();
    while (it != g->listhead.end()) {
        node *p = it->second->next;
        cout << it->second->data << "";
        while (p) {
            cout << p->data << " ";
            p = p->next;
        }
        cout << endl;
        it++;
    }
}        

 邻接表和邻接矩阵这两种方式是最常见的,但是邻接表还是会有部分空间浪费,因为每条链表里用的node是独立的。

  3. 自己实现的一种:

看了严蔚敏教授的数据结构教材书上的数据结构,我认为真的太繁琐了,

#define N 200

typedef struct ArcNode
{
    int adjvex;//该弧指向的顶点的位置
    ArcNode *nextarc;//指向下一条弧的指针
    int *info;   //该弧的相关信息
};
typedef struct
{
    VertexType data;//顶点信息
    ArcNode *firstarc; //指向第一条依附该顶点的弧的指针
} VNode,AdjList[N];

typedef struct ALGraph
{
    AdjList vertices;
    int vexnum,arcnum;//弧的当前顶点数和弧数
    int kind;//图的种类
};

自己写了一个,借助map,直接可以用下标来找表头了。

class node {
public:
    int data;//该节点的数据
    vector<node *>next;
    vector<int>weight;
};
class AdjList {
public:
    map<int, node *>Nodes;
}

我觉得算是邻接表的升级版吧,后面有完整实现,简单分析了一下优劣(E:边数,V:顶点数):

  • 内存:相同data不会重复建立节点:O(V),这点上一定是优于邻接表的:O(V+E)。
  • 存取操作:首先要找到起点所在的表头,邻接表也可以用map实现表头,这点就算无差异吧。
  • 检测两条边是否相邻、遍历V的所有相邻节点:都是 V 顶点的度数,无差异。
  • 关键是更能想象出画面。

1.3 图的遍历方式:

  1. 广度优先遍历(BFS):

广度优先搜索算法(Breadth-First-Search,缩写为 BFS),是一种利用队列实现的搜索算法。其搜索过程和 “水滴落下产生波纹” 类似。

因为每条链表里相同data的节点时独立存在的,BFS、DFS要判断当前节点是否输出过,就要放到set里,如果存节点的话,那肯定得用set<node *>;因为set<node>的话,set里存的就不是指针了,占空间更多。但是存指针问题就来了,相同data的节点是独立的,那么set里就只能添加node->data

 对于遍历而言,没有有向图、无向图之分。这里不考虑非连通图。(非连通,再加一个大循环,很好实现)

void BFS() {
        queue<node *>queue;
        set<node*>set;
        node *n = Nodes.begin()->second;
        queue.push(n); set.insert(n);
        cout << n->data << " ";
        while (!queue.empty()) {
            node *p = queue.front(); queue.pop();
            for (int i = 0; i < p->next.size(); i++) {
                if (set.find(p->next[i]) == set.end()) {
                    cout << p->next[i]->data <<  ;
                    set.insert(p->next[i]);
                    queue.push(p->next[i]);
                }
            }
        }
    }

  2. 深度优先遍历(DFS):

深度优先搜索算法(Depth-First-Search,缩写为 DFS),是一种利用递归(栈)实现的搜索算法。其过程像 “不撞南墙不回头” 类似。

void DFS() {
        stack<node *>stack;
        set<node *>set;
        node *n = Nodes.begin()->second;
        stack.push(n); set.insert(n);
        while (!stack.empty()) {
            node *p = stack.top(); stack.pop();
            cout << p->data <<  ;
            for (int i = 0; i < p->next.size(); i++) {
                if (set.find(p->next[i]) == set.end()) {
                    set.insert(p->next[i]);
                    stack.push(p->next[i]);
                }
            }
        }
    }

 留档一下:邻接表的BFS、DFS

技术图片
void BFS(Graph g) {
    queue<node *>q;
    set<int>s;
    map<int, node*>::iterator it = g->listhead.begin();
    node *n = it->second;
    q.push(n);
    s.insert(n->data);
    cout << n->data << " ";
    while (!q.empty()) {
        node *p = q.front(); q.pop();
        while (p) {
            if (s.find(p->data) == s.end()) {
                cout << p->data << " ";
                s.insert(p->data);
                q.push(g->listhead[p->data]);
            }
            p = p->next;
        }
    }
}

void DFS(Graph g) {
    stack<node *>stack;
    set<int>set;
    map<int, node*>::iterator it = g->listhead.begin();
    node *p = it->second;
    stack.push(p);
    set.insert(p->data);
    while (!stack.empty()) {
        p = stack.top(); stack.pop();
        cout << p->data << " ";
        while (p->next) {
            p = p->next;
            if (set.find(p->data) == set.end()) {
                stack.push(g->listhead[p->data]);
                set.insert(p->data);
            }
        }
    }
}
View Code

 第三种实现的完整代码:

技术图片
class node {
public:
    int data;//该节点的数据
    vector<node *>next;
    vector<int>weight;
    node(int data) {
        this->data = data;
    }
};
class AdjList {
public:
    map<int, node *>Nodes;
    
    AdjList(int en) {
        while (en--) {
            int from, to, weight;
            cin >> from >> to >> weight;
            if (!Nodes[from]) {
                Nodes[from] = new node(from);
            }
            if (!Nodes[to]) {
                Nodes[to] = new node(to);
            }
            node *p = Nodes[from], *q = Nodes[to];
            p->next.push_back(q);
            p->weight.push_back(weight);
            q->next.push_back(p);
            q->weight.push_back(weight);
        }
    }
    /*void BFS() {
        map<int, node* >::iterator it = Nodes.begin();
        node *n = it->second;
        queue<node *>q;
        set<node *>s;
        q.push(n); s.insert(n);

        while (!q.empty()) {
            node *n = q.front(); q.pop();
            cout << n->data << " ";
            for (int i = 0; i < n->next.size(); i++) {
                node *p = n->next[i];
                if (s.find(p) == s.end()) {
                    s.insert(p);
                    q.push(p);
                }
            }
        }
    }*/
    void BFS() {
        queue<node *>queue;
        set<node*>set;
        node *n = Nodes.begin()->second;
        queue.push(n); set.insert(n);
        cout << n->data << " ";
        while (!queue.empty()) {
            node *p = queue.front(); queue.pop();
            for (int i = 0; i < p->next.size(); i++) {
                if (set.find(p->next[i]) == set.end()) {
                    cout << p->next[i]->data <<  ;
                    set.insert(p->next[i]);
                    queue.push(p->next[i]);
                }
            }
        }
    }

    void DFS() {
        stack<node *>stack;
        set<node *>set;
        node *n = Nodes.begin()->second;
        stack.push(n); set.insert(n);
        while (!stack.empty()) {
            node *p = stack.top(); stack.pop();
            cout << p->data <<  ;
            for (int i = 0; i < p->next.size(); i++) {
                if (set.find(p->next[i]) == set.end()) {
                    set.insert(p->next[i]);
                    stack.push(p->next[i]);
                }
            }
        }
    }
};
typedef AdjList *Graph;
View Code

 写了几题BFS、DFS的练习题,才发现,DFS BFS是思想,基本上是碰不到能完全套模板的题目。

---------------------------------------------------------------线---------

 

数据结构——图

标签:信息   void   总数   stream   位置   简单图   push   iter   mic   

原文地址:https://www.cnblogs.com/czc1999/p/10353235.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!