【数据结构】树及优先队列

二叉树遍历

递归遍历

以中序遍历为例:

class Solution {
public:
    vector<int> inorderTraversal(TreeNode *root) {
        vector<int> res;
        inorder(root, res);
        return res;
    }
    void inorder(TreeNode *root, vector<int> &res) {
        if (!root) return;
        if (root->left) inorder(root->left, res);
        res.push_back(root->val);
        if (root->right) inorder(root->right, res);
    }
};

非递归

先序遍历

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        if (!root) return {};
        vector<int> res;
        stack<TreeNode*> s{{root}};
        while (!s.empty()) {
            TreeNode *t = s.top(); s.pop();
            res.push_back(t->val);
            if (t->right) s.push(t->right);
            if (t->left) s.push(t->left);
        }
        return res;
    }
};

下面这种写法使用了一个辅助结点p,这种写法其实可以看作是一个模版,对应的还有中序和后序的模版写法,形式很统一,方便于记忆。辅助结点p初始化为根结点,while 循环的条件是栈不为空或者辅助结点p不为空,在循环中首先判断如果辅助结点p存在,那么先将p加入栈中,然后将p的结点值加入结果 res 中,此时p指向其左子结点。否则如果p不存在的话,表明没有左子结点,取出栈顶结点,将p指向栈顶结点的右子结点,参见代码如下:

// Non-recursion
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) 
{
        vector<int> res;
        stack<TreeNode*> st;
        TreeNode *p = root;
        while (!st.empty() || p) 
      {
            if (p) {
                st.push(p);
                res.push_back(p->val);
                p = p->left;
            } else {
                p = st.top(); st.pop();
                p = p->right;
            }
        }
        return res;
    }
};

中序遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> s;
        TreeNode *p = root;
        while (!s.empty() || p) {
            if (p) {
                s.push(p);
                p = p->left;
            } else {
                p = s.top(); s.pop();
                res.push_back(p->val);
                p = p->right;
            }
        }
        return res;
    }
};

后序遍历

思路:根-右-左reverse成左-右-根

vector<int> postorder(T* node)
{
 	vector<int> res;
 	stack<T*> s; //注意此时为空栈
 	T* p=root; 
 	while(!s.empty()||P)
 	{
 	 	if(p)
 	 	{
 	 		s.push(p);
 	 		res.insert(res.begin(),p->val);//根
 	 		p = p->right; //右
 	 	}
 	 	else
 	 	{
 	 	 	T* t = s.top();s.pop();
 	 	 	p = t->left; //左
 	 	}
 	}
 	return res;
}

或者

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        if (!root) return {};
        vector<int> res;
        stack<TreeNode*> s{{root}};
        while (!s.empty()) {
            TreeNode *t = s.top(); s.pop();
            res.insert(res.begin(), t->val);
            if (t->left) s.push(t->left);
            if (t->right) s.push(t->right);
        }
        return res;
    }
};

最小生成树MST

1. Prim算法

此算法采用贪心算法的思想,运行时间为
基本步骤:

设R是有n个定点的对称连通关系。

1)选取R的一个定点,设中所有节点始终构成一颗树。
2)选取与某个邻接的V的最近邻元, 并且边不与E中元素形成回路。添加到V中,添加到E中。
重复2),知道|E|=n - 1,于是V包含R的所有n个定点,E包含R的最小生成树。

include <limits.h>
#include <iostream>
#define MaxInt numeric_limits<int>::max()
#define MaxEle 120
using namespace std;
int map[MaxEle][MaxEle];
int visited[MaxEle];
int distance_low[MaxEle]; //记录其他节点到当前树的最短距离
int prim(int n) {
  int pos, min, MST = 0;
  //  The array 'visited' represent which
  //  vertix has been visited already to avoid forming circle.
  visited[1] = 1;
  pos = 1;
  //  The array 'distance_distance_low' represent that
  //  arriving vertix i will cost distance_distance_low[i] steps now.
  for (int i = 2; i <= n; i++) {
    if (map[pos][i] == 0) {
      distance_low[i] = MaxInt;
    } else {
      distance_low[i] = map[pos][i];
    }
  }
  // manipulate n-1 times at all.
  for (int i = 1; i <= n - 1; i++) {
    min = MaxInt;
    // Find the minimal value of distance
    // and corresponding position.
    for (int j = 1; j <= n; j++) {
      if (visited[j] == 0 && min > distance_low[j]) {
        min = distance_low[j];
        pos = j;
      }
    }
    visited[pos] = 1;
    MST += min; //路径总和
    //  Because of add a new vertix, it is neccessary to
    //  reload the information of 'distance_low'.
    for (int j = 1; j <= n; j++) {
      if (visited[j] == 0 && distance_low[j] > map[pos][j]) {
        if (map[pos][j] != 0) {
          distance_low[j] = map[pos][j];
        }
      }
    }
  }
  return MST;
}

2. Kruskal算法

Kruskal算法也是采用贪心算法的思想,运行时间为O(nlogn)。

算法描述
设R是有n个顶点的对称连通关系。是R的加权边集合。

1)在S中选择最小权的边,设取代S。
2)在S中选最小权的边,并且不与E中的元素形成回路。用代替E,并用取代S。
3)重复步骤知道|E|=n-1.

代码设计

  • 1、利用优先级队列将权值小的边放到队列最前,优先出对,保证了每次选择的都是权值最小的边。
  • 2、利用并查集的查找及结合把同处同一连通分量中的顶点连到同一父节点下。这样,每次判断是否构成回路,只要判断父节点是否相同的即可。

并查集

我们可以把每个连通分量看成一个集合,该集合包含了连通分量的所有点。而具体的连通方式无关紧要,好比集合中的元素没有先后顺序之分,只有“属于”与“不属于”的区别。图的所有连通分量可以用若干个不相交集合来表示。

而并查集的精妙之处在于用数来表示集合。如果把x的父结点保存在p[x]中(如果没有父亲,p[x]=x),则不难写出结点x所在树的递归程序:

find(int x) {return p[x]==x?x:(p[x]=find(p[x]));}

意思是,如果p[x]=x,说明x本身就是树根,因此返回x;否则返回x的父亲p[x]所在树的根结点。

既然每棵树表示的只是一个集合,因此树的形态是无关紧要的,并不需要在“查找”操作之后保持树的形态不变,只要顺便把遍历过的结点都改成树根的儿子,下次查找就会快很多了。

#include <iostream>
#include <queue>
using namespace std;
#define max 200

int MST; //  最小生成树
int Nodenum; //节点总数
int father[max]; //构建并查集
struct edge {
  int from;
  int to;
  int cost;
  friend bool operator < (const edge& a, const edge& b) { //小顶堆
    return a.cost > b.cost;
  }
};
// to find the minimal distance of edge.
priority_queue<edge> SQ;
//并查集查找
int find(int x) {
  return x == father[x] ? x : find(father[x]);
}

int Kruskal() {
  MST = 0;
  for (int i = 1; i <= Nodenum; i++) {
    father[i] = i;
  } //首先各个节点自成一个集合
  int num = 0;
  int edgeNum; //  边总数
  cin >> Nodenum;
  cin >> edgeNum;
  while (edgeNum--) {
    int from; int to; int cost;
    cin >> from >> to >> cost;
    edge a;
    a.from = from;
    a.to = to;
    a.cost = cost;
    SQ.push(a);
  }
  while (!SQ.empty() && num != Nodenum - 1) {
    edge temp = SQ.top();
    SQ.pop();
    int father_x = find(temp.from);
    int father_y = find(temp.to);
    if (father_x != father_y) {//保证没有环
      father[father_y] = find(father[father_x]);//并查集合并
      MST += temp.cost;
      num++;
    }
  }
  /**
   *  Let every index has their final father.
   */
  for (int i = 1; i <= Nodenum; i++) {
    father[i] = find(father[i]);
  }
  return MST;
}
bool judge() { //判断是否生成了MST,最终所有树节点必须在一个并查集里面
  int flag = father[1];
  for (int i = 2; i != Nodenum + 1; i++) {
    if (flag != find(father[i])) {
      return false;
    }
  }
  return true;
}

优先队列在Dijkstra算法中的使用

sort(RandomAccessIterator first, RandomAccessIterator last, Compare comp) 排序中,greater表示从大到小排序;

priority_queue<Type, Container, Functional>优先队列里, greater却是小顶堆。默认大顶堆。

自定义 cmp函数:

struct cmp  //函数对象
{
 	bool opderator()(T a, T b) // 例如小顶堆,相当于greater
 	{
 	 	if(a.x == b.x) return a.y>b.y;
 	 	else return a.x>b.x;
 	}
}

Dijkstra's in priority_queue of STL

typedef pair<int,int> ipair;//(weight,vertex) for Function in priority_queue
class Graph
{
	int V;//# of vertices
	list<pair<int,int>> * adj;//(vertex, weight)
	
	public:
		Graph(int v);
		void shortestpath(int s);
}

void Graph:shortestpath(int src)
{
	priority_queue<ipair,vector<ipair>,greater<ipair>> qp; //(weight,vertex) 
	vector<int> dist(V,INF);//INF==INT_MAX
	pq.push(make_pair(0,src));
	dist[src] = 0;
	
	while(!pq.empty())
	{
	 //取队首
		int u = pq.top().second;//vertex
		pq.pop();
		
		list<pair<int,int>>:: iterator i;
		//遍历当前节点邻居
		for(int i = adj[u].begin(); i!=adj[u].end();++i)
		{
			int v = i->first;
			int wight = i->second;
			if(dist[v]>dist[u] + weight)//更新优先队列
			{
				dist[v] = dist[u] + weight;
				pq.push(make_pair(dist[v],v)); //直接放入,有重复不怕
			}
		}
	}
	
	//取结果
	for(int i = 0;i<V;++i)
		cout<<i<<" "<<dist[i]<<endl;
}
posted @ 2020-10-14 00:12  satire  阅读(179)  评论(0编辑  收藏  举报