樹的常見算法&圖的DFS和BFS


樹及二叉樹:

樹:(數據結構中常見的樹)

樹的定義

樹的存儲:下面介紹三種不同的樹的表示法:雙親表示法,、孩子表示法,、孩子兄弟表示法。

  • 雙親表示法

         我們假設以一組連續空間存儲樹的結點,同時在每個結點中,附設一個指示器指向其雙親結點到鏈表中的位置。也就是說每個結點除了知道自己之外還需要知道它的雙親在哪里。

    它的結構特點是如圖所示:            

    以下是我們的雙親表示法的結構定義代碼:

  • /*樹的雙親表示法結點結構定義  */  
        #define MAXSIZE 100  
        typedef int ElemType;       //樹結點的數據類型,暫定為整形   
        typedef struct PTNode       //結點結構  
        {  
            ElemType data;          //結點數據  
            int parent;             //雙親位置  
        }PTNode;  
          
        typedef struct  
        {  
            PTNode nodes[MAXSIZE];  //結點數組  
            int r,n;                //根的位置和結點數  
        }PTree;  

     

  • 孩子表示法

  • 換一種不同的考慮方法。由於每個結點可能有多棵子樹,可以考慮使用多重鏈表,即每個結點有多個指針域,其中每個指針指向一棵子樹的根結點,我們把這種方法叫做多重鏈表表示法。不過樹的每個結點的度,也就是它的孩子個數是不同的。所以可以設計兩種方案來解決。

    方案一:

     

    一種是指針域的個數就等於樹的度(樹的度是樹的各個結點度的最大值)

     

    其結構如圖所示:

     

     

    不過這種結構由於每個結點的孩子數目不同,當差異較大時,很多結點的指針域就都為空,顯然是浪費空間的,不過若樹的各結點度相差很小時,那就意味着開辟的空間都被利用了,這時這種缺點反而變成了優點。

     


     

    方案二:

     

    第二種方案是每個結點指針域的個數等於該結點的度,我們專門取一個位置來存儲結點指針域的個數。

     

    其結構如圖所示:

     

                     

     

    這種方法克服了浪費空間的缺點,對空間的利用率是很高了,但是由於各個結點的鏈表是不相同的結構,加上要維護結點的度的數值,在運算上就會帶來時間上的損耗。

     

    能否有更好的方法呢,既可以減少空指針的浪費,又能是結點結構相同。

     

    說到這大家肯定就知道是有的麥,那就是孩子表示法。

     

    具體辦法是,把每個結點的孩子排列起來,以單鏈表做存儲結構,則n個結點有n個孩子鏈表,如果是葉子結點則此單鏈表為空。然后n個頭指針有組成一個線性表,采用順序存儲結構,存放進入一個一維數組中

     

    為此,設計兩種結點結構,

     

    一個是孩子鏈表的孩子結點,如下所示:

     

     

    其中child是數據域,用來存儲某個結點在表頭數組中的下標。next是指針域,用來存儲指向某結點的下一個孩子結點的指針。

     

    另一個是表頭結點,如下所示:

     

                                    

     

    其中data是數據域,存儲某結點的數據信息。firstchild是頭指針域,存儲該結點的孩子鏈表的頭指針。

     

    以下是孩子表示法的結構定義代碼:

    /*樹的孩子表示法結點結構定義  */  
        #define MAXSIZE 100  
        typedef int ElemType;       //樹結點的數據類型,暫定為整形   
        typedef struct CTNode       //孩子結點  
        {  
            int child;  
            struct CTNode *next;  
        }*ChildPtr;  
          
        typedef struct              //表頭結構  
        {  
            ElemType data;  
            ChildPtr firstchild;  
        }CTBox;  
        typedef struct              //樹結構  
        {  
            CTBox nodes[MAXSIZE];   //結點數組  
            int r,n;                //根結點的位置和結點數  
        }CTree;  

    3、孩子兄弟表示法

    我們發現,任意一顆樹,它的結點的第一個孩子如果存在就是的,它的右兄弟如果存在也是唯一的。因此,我們設置兩個指針,分別指向該結點的第一個孩子和此結點的右兄弟。

    其結點結構如圖所示:

    以下是孩子兄弟表示法的結構定義代碼:

/*樹的孩子兄弟表示法結構定義 */  
    typedef struct CSNode  
    {  
        ElemType  data;  
        struct CSNode  *firstchild, *rightsib;  
    }CSNode, *CSTree;  

二叉樹:

二叉樹的定義就不贅述了,這里提供二叉樹幾種常見的算法:

  • 建樹(根據數組建樹)
  • 先、中、后序遍歷二叉樹
  • 層序遍歷二叉樹
  • 求二叉樹的寬度、高度
  • 判斷一棵樹是否為另一克二叉樹的子樹
  • 根據前序、中序遍歷重建二叉樹

 

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int val) :val(val) {};
};
//建立二叉樹
void CreateTree(TreeNode* &T,int a[],int len,int index) {
    if (index > len)
        return;
    TreeNode* root = new TreeNode(a[index]);
    root->left = NULL;
    root->right = NULL;
    CreateTree(root->left,  a,  len,  index * 2 + 1);
    CreateTree(root->right, a, len, index * 2 + 2);
}

TreeNode* CreateTree_2(int a[], int len, int index) {
    if (index > len)
        return NULL;
    TreeNode* root = new TreeNode(a[index]);
    root->left = CreateTree_2(a, len, 2 * index + 1);
    root->right = CreateTree_2(a, len, 2 * index + 2);
    return root;
}
//先序遍歷
void PreOrder(TreeNode* root) {
    if (!root)
        return;
    else {
        cout << root->val;
        PreOrder(root->left);
        PreOrder(root->right);
    }
    return;
}
//中序遍歷
void InOrder(TreeNode* root) {
    if (!root)
        return;
    else {    
        InOrder(root->left);
        cout << root->val;
        InOrder(root->right);
    }
    return;
}
//后序遍歷
void PostOrder(TreeNode* root) {
    if (!root)
        return;
    else {
        PostOrder(root->left);
        PostOrder(root->right);
        cout << root->val;
    }
    return;
}
//層序遍歷並計算寬度
int LevelOrder(TreeNode* root) {
    if (root == NULL)
        return;
    queue<TreeNode*> q1;
    q1.push(root);
    int cursize = 1,maxsize=1;
    while (!q1.empty()) {
        TreeNode* tmp = q1.front();
        q1.pop();
        cursize--;
        cout << tmp->val;
        if (tmp->left != NULL)
            q1.push(tmp->left);
        if (tmp->right != NULL)
            q1.push(tmp->right);
        if (cursize == 0) {
            cursize = q1.size();
            maxsize = cursize > maxsize ? cursize : maxsize;
        }
    }
    return maxsize;
}

//計算樹的高度
int HeightTree(TreeNode* root) {
    if (root == NULL)
        return 0;
    int left = 0, right = 0;
    if (root->left != NULL)
        left = HeightTree(root->left) + 1;
    if (root->right != NULL)
        right = HeightTree(root->right) + 1;
    return left > right ? left : right;
}

//根據前序和中序重建二叉樹
TreeNode* RebuildTree(vector<int>& preorder, vector<int>::iterator& i, vector<int>& inorder, vector<int>::iterator start, vector<int>::iterator end) {
    if (preorder.size() == 0 || inorder.size() == 0 || start == end)
        return NULL;
    vector<int>::iterator j = find(start, end, *i);
    if (j == end)
        return NULL;
    TreeNode* root = new TreeNode(*i++);
    root->left = RebuildTree(preorder, i, inorder, start, j);
    root->right = RebuildTree(preorder, i, inorder, j + 1, end);
    return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    auto i = preorder.begin();
    return RebuildTree(preorder, i, inorder, inorder.begin(), inorder.end());
}
//

 


 

關於圖的搜索有兩種:廣度優先(bfs)深度優先 (dfs)。
以下圖為例:


深度優先的基本思想簡單說就是搜到底,重新搜。從v0為起點進行搜索,如果被訪問過,則做一個標記,直到與v0想連通的點都被訪問一遍,如果這時,仍然有點沒被訪問,可以從中選一個頂點,進行再一次的搜索,重復上述過程,所以深度優先的過程也是遞歸的過程。
深度優先訪問的結果是:0->1->3->7->4->2->5->6


廣度優先的基本思想是,從頂點v0出發,訪問與v0相鄰的點,訪問結束后,再從這些點出發,繼續訪問,直到訪問結束為止。標記被訪問過的點同深搜一下,不過,廣度優先一般需要用到隊列。
上圖廣度優先的結果:0->1->2->3->4->5->6->7

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#define maxn 100
using namespace std;

typedef struct
{
    int edges[maxn][maxn];  //鄰接矩陣
    int n;   //頂點數
    int e;    //邊數
}MGraph;

bool vis[maxn];  //標記頂點是否被訪問過
void createGraph(MGraph &G) //用引用做參數
{
    int i,j;
    int s,t;  //存儲頂點的編號
    int v;  //存儲邊的權值
    for(i=0;i<G.n;i++)  //初始化
    {
        for(j=0;j<G.n;j++)
        {
            G.edges[i][j] = 0;
        }
        vis[i] = false;
    }
    for(i = 0;i<G.e;i++) //對鄰接矩陣的邊賦值
    {
        scanf("%d%d%d",&s,&t,&v);  //輸入邊頂點的編號以及權值
        G.edges[s][t] = v;
    }
}
void dfs(MGraph G,int v)
{
    int i;
    printf("%d ",v);  //訪問結點v
    vis[v] = true;
    for(int i = 0;i<G.n;i++) //訪問與v相鄰且未被訪問過的結點
    {
        if(G.edges[v][i]!=0&&vis[i]==false)
        {
            dfs(G,i);
        }
    }
}
void dfs1(MGraph G,int v)   //非遞歸實現(用到了棧,其實在遞歸的實現過程,仍是用到了棧,所以。。。)
{
    stack<int> s;
    printf("%d",v);  //訪問初始的結點
    vis[v]=true;
    s.push(v);  //入棧
    while(!s.empty())
    {
        int i,j;
        i=s.top();  //取棧頂頂點
        for(j=0;j<G.n;j++) //訪問與頂點i相鄰的頂點
        {
            if(G.edges[i][j]!=0&&vis[j]==false)
            {
                printf("%d ",j); //訪問
                vis[j]=true;
                s.push(j);  //訪問完后入棧
                break; //找到一個相鄰未訪問的頂點,訪問之后跳出循環
            }
        }
        if(j==G.n)   //如果與i相鄰的頂點都被訪問過,則將頂點i出棧
            s.pop();
    }
}
void bfs(MGraph G,int v)
{
    queue<int> Q;
    printf("%d",v);
    vis[v] = true;
    Q.push(v);
    while(!Q.empty())
    {
        int i,j;
        i = Q.front();  //取隊首頂點
        Q.pop();
        for(j=0;j<G.n;j++) //廣度遍歷
        {
            if(G.edges[i][j]!=0&&vis[j]==false)
            {
                printf("%d",j);
                vis[j]=true;
                Q.push(j);
            }
        }
    }
}
int main()
{
int n,e; //建圖的頂點數和邊數
while(scanf("%d%d",&n,&e)==2&&n>0)
{
    MGraph G;
    G.n = n;
    G.e = e;
    createGraph(G);
    dfs(G,0);
    printf("\n");
 //   dfs1(G,0);
 //   printf("\n");
  //  bfs(G,0);
  //  printf("\n");

}

return 0;
}
/*測試數據:
8 9
0 1 1
1 3 1
1 4 1
3 7 1
4 7 1
0 2 1
2 5 1
5 6 1
2 6 1
*/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM