1. 程式人生 > >五、二叉樹與圖(小象)

五、二叉樹與圖(小象)

113.路徑之和II

思考:

1、查找出路徑,並且記錄下路徑的值。

2、和要求的sum值對比,如果正確就插入到result之中。

3、進行遞迴查詢路徑的時候,什麼時候跳出遞迴?答:如果遍歷至空指標(葉子結點的孩子),結束

什麼時候新增路徑呢?為葉子結點,並且路徑值和要求的和值相同。

4、題目大概就是在前序遍歷的時候,完成插入節點到路徑的操作,在後序遍歷的時候,把該節點值從path彈出。

優化:舉個例子,如果要求的和值是5,然後我們搜尋路徑已經1->2->4,此時二叉樹下面還有一層,分別為3,5,這樣的話其實搜尋到第三層,路徑還沒有完全寫出的時候,已經是超過了要求的和值,就沒有必要再搜尋這個4節點下的左右孩子3,5,用什麼方法呢?這點我只是想到還沒解決。

5、程式碼AC了,還有個地方還是要多注意,在遞迴的時候,如果你的值需要一直保持改變後的值,就比如path_val,path,result,這種,他在每次遞迴都在 修改,就要加引用,這點如果沒有新增就會程式發生錯誤,還有可能debug半天。。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        vector<int> path;
		int path_val=0;
		vector<vector<int>> result;
		preOrder(root, path_val, sum, path, result);
		return result;
    }
    void preOrder(TreeNode* node, 
                  int &path_val, int sum, 
                  vector<int> &path, 
                  vector<vector<int>>& result) {
		if (!node) {
			return;
		}
		path.push_back(node->val);
		path_val += node->val;
		if (!node->left && !node->right && path_val == sum) {
			result.push_back(path);
		}
		preOrder(node->left, path_val, sum, path, result);
		preOrder(node->right, path_val, sum, path, result);
		path.pop_back();
		path_val -= node->val;
	}
};

236.二叉樹的最近公共祖先

思考:找到兩個節點的最近公共祖先,因為二叉樹的結構性,他都是從一個節點伸展出來的各個路徑,所以要找到節點p,q的公共最近祖先,只需要到p,q的兩條路徑,然後像找相交連結串列的公共節點(leetcode 160)一樣,一步步回退回去查詢到第一個相同的節點,這裡和上一題求所有路徑值為sum的題目不同在於,這裡只是查詢到p這個節點的路徑,所以只要找到了就可以設定一個標記變數finish直接退出查詢就可以了。

1、跳出迴圈的條件,當前節點為空或者finish==1

2、得到兩個路徑,向後pop_back(),知道兩個路徑相等,然後查詢是否是相同的節點。

3、和上題不同在於上一題是值,這一題是節點指標;上一題是所有路徑,這一題是單一路徑,所有設定一個finish標誌。

查詢某個節點的路徑:

void preorder(TreeNode* node,
	TreeNode *search,
	std::vector<TreeNode*> &path,
	std::vector<TreeNode*> &result,
	int &finish) {
	if (!node||finish) {
		return;
	}
	path.push_back(node);
	if (node == search) {
		finish = 1;
		result = path;
	}
	preorder(node->left, search, path, result, finish);
	preorder(node->right, search, path, result, finish);
	path.pop_back();
}

整體程式碼:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        int finish = 0;
		vector<TreeNode*> path;
		vector<TreeNode*> result1;//想到這個地方了!是因為內建屬性才一定需要初始化,有的類string這種會幫你初始化
		vector<TreeNode*> result2;
		preorder(root, p, path, result1, finish);
		finish = 0;
		preorder(root, q, path, result2, finish);
		while (result1.size() > result2.size()) {
			result1.pop_back();
		}
		while (result2.size() > result1.size()) {
			result2.pop_back();
		}
		while (result1.back() != result2.back()) {
			result1.pop_back();
			result2.pop_back();
		}
		return result1.back();
	}
    
    
    void preorder(TreeNode* node,
	TreeNode *search,
	std::vector<TreeNode*> &path,
	std::vector<TreeNode*> &result,
	int &finish) {
	if (!node||finish) {
		return;
	}
	path.push_back(node);
	if (node == search) {
		finish = 1;
		result = path;
	}
	preorder(node->left, search, path, result, finish);
	preorder(node->right, search, path, result, finish);
	path.pop_back();
}
};

114.二叉樹展開為連結串列

思考:

1、二叉樹和連結串列的儲存結構相比,只多了一個指標。題中整理的連結串列為二叉樹的先序遍歷,只需要將二叉樹的左子樹整理成單鏈表,右子樹整理成單鏈表,然後左子樹最後的一個節點指標指向右子樹的第一個指標,所以儲存資訊的時候還應該包含一個last指標。

2、在前序遍歷的時候,相當於分治演算法的最小一個子問題,就是訪問到葉子節點,此時把last = node;

3、如果存在左子樹,中序遍歷的時候,把當前的node->left = NULL;node->right = left_last;(當前節點的下一個應該是左子樹的最後一個結點),並且記錄下last = left_last(因為可能右子樹為空,所以臨時儲存last指標)

4、如果存在右子樹,後序遍歷的時候,若存在左子樹,把左子樹最後一個節點拼接到右子樹的首部(left_last = right),之後再輸出last = right_last;

5、設定最後一個節點指標的原因,若1->2->3->4...只是很深的樹的一小部分,1不是根節點,就必須把這小部分的最後一個節點指標傳出去,完成遞迴。

6、什麼時候對節點判空?感覺應該在flatten(TreeNode* )就判斷比較好。若傳入的就是空節點,直接返回。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    void flatten(TreeNode* root) {
        if(!root){
            return;
        }
        TreeNode* last = NULL;
        preorde(root,last);
    }
    void preorde(TreeNode* node,TreeNode* &last){
        if(!node->left&&!node->right){
            last = node;
            return ;
        }
        TreeNode* left = node->left;
        TreeNode* right = node->right;
        TreeNode* left_last = NULL;
        TreeNode* right_last = NULL;
        if(left){
            preorde(left,left_last);
            node->left = NULL;
            node->right = left;
            last = left_last;
        }
        if(right){
            preorde(right,right_last);
            if(left){
                left_last->right = right;
            }
            last = right_last;
        }
        
    }
};

207.課程表(圖)

圖的基礎資料結構:

struct GraphNode{

      int label;//標識圖的哪個節點

      vector<GraphNode*> negihbors;//節點的領接表

};

圖的深度優先遍歷,廣度優先遍歷:

TIPS:需要一個識別符號visit來儲存每個節點是否被訪問過了,只有沒有被訪問的才需要搜尋。

深度優先遍歷,從某個節點出發,一直深度搜索,當所有領接節點都搜尋完畢之後,再搜尋下一個節點,直到圖中所有節點都搜尋完畢。

廣度優先遍歷:通過一個queue佇列,來儲存圖的某個節點,然後先搜尋它領接的所有節點,然後再將新搜尋到的節點給存入佇列,不斷的進行搜尋,直到所有節點搜尋完畢。

思考:

1、首先構建有向圖,根據pair關係。

2、一般的visit只有兩個狀態,訪問完成或者未訪問。但是這裡需要一個visit = 0,第三中狀態代表正在被訪問。因為只有正在被訪問的時候,經過的路徑途中的點會被標註成0,要是此時遇到一個還沒有訪問的領節點,但是已經正在被訪問,說明其中是有環的。

3、有環的情況有分為兩種,一種是1->2->3->1,正好找到的就是環上的節點;另一種1->2->3->2,從這個節點出發,經過了一個環。

根據深度優先結果:

struct GraphNode{
    int label;
    vector<GraphNode*> neighbors;
    GraphNode(int x):label(x){};
};
bool DFS_Graph(GraphNode* node, vector<int>& visit) {
	visit[node->label] = 0;
	for (auto i : node->neighbors) {
		if (visit[i->label] == 0) {
			return false;
		}
		else if (visit[i->label] == -1 && !DFS_Graph(i, visit)) {
			return false;
		}
	}
	visit[node->label] = 1;
	return true;
}
class Solution {
public:
    bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
       //1構圖
		//GraphNode* Graph[numCourses]; /*表示式必須含有常量值*/
		vector<GraphNode*> Graph;
		//vector<int> visit(5,-1); //節點的狀態訪問 -1:沒有訪問過 0:正在訪問 1 :已經訪問過了
        vector<int> visit;
		for (int i = 0; i < numCourses; i++) {
			Graph.push_back( new GraphNode(i));
            visit.push_back(-1);
		}
		for (auto i : prerequisites) {
			//auto begin = i.first;
			auto begin = Graph[i.second];
			auto end =  Graph[i.first];
			//Graph[begin]->neighbors.push_back(endNode);
			begin->neighbors.push_back(end);
		}
		for (int i = 0; i < numCourses; i++) {
			if (visit[i] == -1&&!DFS_Graph(Graph[i],visit)) {
				return false;
			}
		}
		return true;
	} 
};

根據廣度優先結果:

廣度優先搜尋解題根據入度,若一個節點在環上,那麼它的入度肯定不會為0,所以先選擇入度為0的點,把他們加入到佇列之中,然後將它的所有領接點的入度都減去1,再進行把下一個入度為0的點加入佇列,繼續搜尋...如果到最後圖中所有點的入度都為0,則不存在環,否則就存在環。

struct GraphNode{
  int label;
    vector<GraphNode*> neighbors;
    GraphNode(int x ):label(x){};
};
class Solution {
public:
    bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
        //1構圖
        vector<GraphNode*> Graph;
        vector<int > indegree;
        for(int i = 0; i <numCourses;i++){
            Graph.push_back(new GraphNode(i));
            indegree.push_back(0);
        }
        //確定節點直接領接關係
        for(auto i : prerequisites){
            auto end = i.first;
            auto begin = i.second;
            Graph[begin]->neighbors.push_back( Graph[end]);
            indegree[end]++;
        }
        //把入度為0的節點傳入佇列Q之中
        queue<GraphNode*> Q;
        for(int i = 0; i <indegree.size();i++){
            if(indegree[i]==0){
                Q.push(Graph[i]);
            }
        }
        while(!Q.empty()){
            auto temp = Q.front();
            Q.pop();
//寫成了temp,就顯示“基於此範圍的for語句需要begin函式”,因為不是迭代器,temp 是GraphNode*的指標
//應該是他的領接點temp->neighbors,整好也是vector,有begin和end迭代器
            for(auto i : temp->neighbors){
                indegree[i->label]--;
                if(indegree[i->label]==0){
                    Q.push(i);
                }
            }
        }
        for(auto i : indegree){
            if(i){
                return false;
            }
        }
        return true;
    }
};