图及二叉树的遍历及其相关应用

1.二叉树遍历

#include <iostream>
//STL标准库
#include<stack>
#include<queue>
using namespace std;
#define ElemType char
//树结点结构
struct TreeNode
{
    ElemType data;
    struct TreeNode* lchild=NULL;
    struct TreeNode* rchild=NULL;
}*head;
//用于后序遍历
typedef struct node1
 {
   BinTree *btnode;
   bool isFirst;
 }BTNode;

int index = 0;

//先序遍历构造二叉树
// data[]={ 'A', 'B', 'D', '#', '#', 'E'}
void treeConstruct(TreeNode *&root, ElemType data[]){ ElemType e = data[index++]; if (e == '#'){ root = NULL; }
    else{
        root = new TreeNode;
        root->data = e;
        treeConstruct(root->lchild, data);
        treeConstruct(root->rchild, data);
    }
}
二叉树深度优先遍历(非递归)

A:先序遍历(先压右子树,再压左子树,根据栈后进先出,最终出栈顺序根左右)

//二叉树的深度优先遍历(非递归),借助栈(示例为先序遍历)
//https://www.cnblogs.com/fengxmx/p/3764512.html(先中后递归及非递归遍历参考)
void depthFirstSearch(TreeNode* root){
    stack<TreeNode *> nodestack;
    nodestack.push(root);
    TreeNode *node = nullptr;
    while (!nodestack.empty()){
        node = nodestack.top();//指针访问栈顶元素
        cout << node->data<<' ';
        nodestack.pop();
    }
    if (node->rchild){
        nodestack.push(root->rchild);//右子树压入栈
    }
    if (node->lchild){
        nodestack.push(root->lchild);//左子树压入栈
    }

}

B:先序遍历(判断栈是否为空或子树为空,若不为空,就对左孩子入栈并访问(根节点),直至左孩子为空,若左孩子为空,就出栈,然后对右孩子循环前面的操作)

 /*二叉树的非递归先序遍历*/
void inorderRecur(TreeNode* root){
    if (root == NULL) return;
    stack<TreeNode*> nodestack;
    TreeNode* p = root;
    nodestack.push(root);
    while (!nodestack.empty()){
        if (p != NULL){
            nodestack.push(p);
            cout << p->data << ' ';
            p = p->lchild;
        }
        else{
            TreeNode*q = nodestack.top();
            nodestack.pop();
            p = q->rchild;
        }
    }
}

中序遍历(非递归):判断栈是否为空或子树为空,若不为空,就对左孩子入栈,直至左孩子为空,若左孩子为空,就出栈并访问(左孩子与根节点),然后对右孩子循环前面操作

//树的中序遍历
void inorderRecur(TreeNode* root){
    if (root == NULL) return;
    stack<TreeNode*> nodestack;
    TreeNode* p = root;
    nodestack.push(root);
    while (!nodestack.empty()){
        if (p != NULL){//直至左孩子为空
            nodestack.push(p);
            p = p->lchild;
        }
        else{
            TreeNode*q = nodestack.top();
            nodestack.pop();
            cout << q->data << ' ';
            p = q->rchild;
        }
    }
}

后序遍历(非递归):要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问
思路二:类似先序遍历和中序遍历的思想,沿左子树一直往下搜索,直至出现没有左子树的结点,记下前驱结点(根结点),并判断父节点(当前前驱)是否第一次被访问,若是第一次被访问,将父节点入栈,父节点的右子树入栈,访问右子树,访问父节点(第二次出现在栈顶)

 void postOrder2(BinTree *root)    //非递归后序遍历
 {
     stack<BTNode*> s;
     BinTree *p=root;
     BTNode *temp;
     while(p!=NULL||!s.empty())
     {
         while(p!=NULL)              //沿左子树一直往下搜索,直至出现没有左子树的结点 
          {
             BTNode *btn=(BTNode *)malloc(sizeof(BTNode));
             btn->btnode=p;
             btn->isFirst=true;
             s.push(btn);
             p=p->lchild;
         }
         if(!s.empty())
         {
             temp=s.top();
             s.pop();
             if(temp->isFirst==true)     //表示是第一次出现在栈顶 
             {
                 temp->isFirst=false;
                 s.push(temp);
                 p=temp->btnode->rchild;    
             }
             else                        //第二次出现在栈顶 
             {
                 cout<<temp->btnode->data<<" ";
                 p=NULL;
             }
         }
     }    
 } 
二叉树广度优先遍历
//二叉树广度优先遍历
void breadFirstSearch(TreeNode *root){
    queue<TreeNode*> nodeQueue;
    nodeQueue.push(root);
    TreeNode * node=nullptr;
    while (!nodeQueue.empty()){
        node = nodeQueue.front();
        nodeQueue.pop();
        cout << node->data << ' ';
    }
    if (node->lchild){
        nodeQueue.push(node->lchild);
    }
    if (node->rchild){
        nodeQueue.push(node->rchild);
    }
}

int main() {

    //上图所示的二叉树先序遍历序列,其中用'#'表示结点无左子树或无右子树
    ElemType data[6] = { 'A', 'B', 'D', '#', '#', 'E', '#' };
    TreeNode *tree;
    treeConstruct(tree, data);
    printf("深度优先遍历二叉树结果: ");
    depthFirstSearch(tree);
    printf("\n\n广度优先遍历二叉树结果: ");
    breadFirstSearch(tree);
    return 0;

}

2.图的遍历

2.1 图的存储

参考链接:https://blog.csdn.net/dengpei187/article/details/51899550
1)邻接矩阵

typedef struct{
    VertexType vexes[MAX]; //顶点表
    EdgeType arc[MAX][MAX]; //邻接矩阵
    int numVertexes,numEdges; //顶点数,边数
} MGraph;

无向网图的创建实现:

/*构建**无向网图**的邻接矩阵表示*/
void CreateMGraph(MGraph *G){
   int i,j,k,w;
   printf("输入顶点数和边数:\n");
   scanf("%d,%d",&G->numVertexes,&G->numEdges); //输入顶点数和边数
   for(i=0;i<G->numVertexes;i++)  
      scanf(&G->vexs[i]);  //读入顶点信息,建立顶点表
   for(i=0;i<G->numVertexes;i++)
      for(j=0;j<G->numVertexes;j++)
         G->arc[i][j]=INFINITY;  //邻接矩阵初始化
   for(k=0;k<G->numEdges;k++){ //给边表赋值
      printf("输入边(vi,vj)上的下标i,下标j和权值w:\n");
      scanf("%d,%d,%d",&i,&j,&w);  //输入边(vi,vj)上的权值w
      G->arc[i][j]=w;
      G->arc[j][i]=G->arc[i][j];  //无向图,矩阵对称
   }
}

n个顶点和e条边的无向网图创建的时间复杂度为 O(n+n^2+e) ,其中,对邻接矩阵的初始化耗费了 O(n^2) 的时间。
2) 邻接表

typedef char VertexType;   //顶点类型
typedef int EdgeType;  //边上的权值类型

typedef struct EdgeNode{    //边表结点
    int adjvex;
    EdgeType weight;
    struct EdgeNode *next;
} EdgeNode; 

typedef struct VertexNode{    //顶点表结点
    VertexType data;
    EdgeNode *firstedge;
} VertexNode, AdjList[MAX];

typedef struct{
    AdjList adjList;
    int numVertexes,numEdges;
} GraphAdjList;

构建邻接表实现:

/*建立无向图图的邻接表结构,算法的空间复杂度为O(n+e)*/
void CreateGraphAL(GraphAdjList *G){
   int i, j, k;
   EdgeNode *e;
   printf("请输入顶点数和边数(输入格式为:顶点数,边数):\n");
   // 读入顶点数和边数 
   scanf("%d,%d", &(G->numVertexes), &(G->numEdges));
   for (i = 0; i < G->numVertexes; i++){// 读入顶点信息,建立顶点表 
      scanf("\n%c", &(G->adjList[i].data)); // 读入顶点信息 
      G->adjList[i].firstedge = NULL; // 边表头指针设为空 
   }
   for (k = 0; k < G->numEdges; k++){ // 建立边表
      printf("请输入边的信息(输入格式为:i,j):\n"); 
      scanf("%d,%d", &i, &j); // 读入边(Vi,Vj)的顶点对应序号
      /*相当于将j的结点头插法插入到顶点i的边表链中*/
      e=(EdgeNode *)malloc(sizeof(EdgeNode));  //生成新边表结点 
      e->adjvex = j; // 邻接点序号为j
      // 将新边表结点s插入到顶点Vi的边表头部
      e->next = G->adjList[i].firstedge; 
      G->adjList[i].firstedge = e;
      /*相当于将i的结点头插法插入到顶点j的边表链中*/(无向图分别为两个顶点插入边)
      e=(EdgeNode *)malloc(sizeof(EdgeNode));
      e->adjvex = i;
      e->next = G->adjList[j].firstedge;
      G->adjList[j].firstedge = e;
     }
 }

3)图各种存储结构小结

存储结构空间复杂度时间复杂度特点
链接矩阵o(n^2)o(n^2)顺序存储(两个数组),方便查找访问,不方便插入、删除顶点;
邻接表o(n+e)o(n)数组+链表存储,适合边较稀疏的图,可查找某节点出度或入度(逆邻接表);
十字链表o(n+e)o(n)数组+链表,适合有向图的存储,方便同时查找某节点出度和入度;
邻接多重表适合无向图的链式存出结构,方便求两个顶点是否存在边(一条边存于一个节点中);
2.2 图的深度优先遍历(DFS)

参考链接:https://blog.csdn.net/sunlilan/article/details/69829195
存储结构为邻接矩阵:

typedef int Boolean;
Boolean visited[MAX];
void DFS(MGraph G,int i){
    int j;
    visited[i]=TRUE;
    printf("%c ",G.vexs[i]); //打印顶点,也可以进行其他操作
    for(j=0;j<G.numVertexes;j++){
        if(G.arc[i][j]==1 && !visited[j]) DFS(G,j); //对未访问的邻接顶点递归调用
    }
}

void DFSTraverse(MGraph G){
    int i;
    for(i=0;i<G.numVertexes;i++){
        visited[i]=FALSE;   //初始化所有顶点状态为未访问
    }
    for(i=0;i<G.numVertexes;i++){
        if(!visited[i]){   //对未访问过的顶点调用DFS,如果是连通图,只会执行一次
            DFS(G,i);
        }
    }
}

存储结构为邻接表:

void DFS(GraphAdjList GL, int i){
    EdgeNode *p;
    visited[i]=TRUE;
    printf("%c ",GL->adjList[i].data);
    p=GL->adjList[i].firstedge;
    while(p!=NULL){
        if(!visited[p->adjvex]){
            DFS(GL,p->adjvex);
        }
        p=p->next;
    }
}
void DFSTraverse(GraphAdjList GL){
    int i;
    for(i=0;i<GL->numVertexes;i++){
        visited[i]=FALSE;   //初始化所有顶点状态为未访问
    }
    for(i=0;i<GL->numVertexes;i++){
        if(!visited[i]){   //对未访问过的顶点调用DFS,如果是连通图,只会执行一次
            DFS(G,i);
        }
    }
}

对于n个顶点e条边的图来说,邻接矩阵由于是二维数组,要查找每个顶点的邻接点需要访问矩阵中的所有元素,因此都需要O(n2)的时间。
而邻接表做存储结构时,找邻接点所需时间取决于顶点和边的数量,所以是O(n+e),显然对于点多边少的稀疏图,邻接表结构使得算法在时间效率上大大提高。

2.3 图的广度优先遍历(非递归)

(邻接矩阵存储)

bool adj[9][9]; // 
bool visit[9];  // 标志数组,记录顶点是否被访问

void BFS()
{
    // 建立一个queue
    queue<int> q;
    // 初始化所有的点为false
    for (int i=0; i<9; i++)
        visit[i] = false;
    for (int k=0; k<9; k++)
        if (!visit[k])
        {
            //将起点放入queue
            q.push(k);
            visit[k] = true;
            // 重复,直到queue里面没有东西为止
            while (!q.empty())
            {
                // 从queue中取出一点
                int i = q.front(); q.pop();
                // 乙、找出跟此点相邻的点,并且尚未被遍历的点
                // 依照编号顺序通通放入queue
                for (int j=0; j<9; j++)
                    if (adj[i][j] && !visit[j])
                    {
                        q.push(j);
                        visit[j] = true;
                    }
            }
        }
}

时间复杂度:使用的存储结构为邻接矩阵,o(v^2);使用邻接表 adjacency lists, O(V+E)。

点赞