二叉树题总结

本文介绍了二叉树的基本概念及其多种应用,包括LCA算法的两种实现方式:Tarjan算法和转化成RMQ问题的方法。此外还探讨了二叉搜索树、二叉树遍历、对称性判断等主题。

1. LCA (lowest common ancestor)

1) Tarjan算法,参考http://hi.baidu.com/luyade1987/blog/item/5b609b016fcecbd3277fb5ac.html

#include <cstdio>

using namespace std;

const int N = 10001;
int id[N], lcs[N][N], g[N][N];

int n, e;

int get(int i)
{
        if(id[i] == i)
        {
                return i;
        }
        return id[i] = get(id[i]);
}

void unin(int i, int j)
{
        id[get(i)] = get(j);
}

void dfs(int rt, int n)
{
        int i;
        id[rt] =  rt;
        for(i = 0; i < n; i++)
        {
                if(g[rt][i] && -1 == id[i])
                {
                        dfs(i, n);
                        unin(i, rt);
                }
        }

        // after visiting rt
        //printf("after %d\n", rt);
        for(i = 0; i < n; i++)
        {
                //printf("%d ", id[i]);
                if(-1 != id[i])
                {
                        lcs[rt][i] = lcs[i][rt] = get(i);
                }
        }
        //printf("\n");
}

void init()
{
        for(int i = 0; i < n; i++)
        {
                id[i] = -1;
        }
}

void print()
{
        for(int i = 0; i < n; i++)
        {
                for(int j = 0; j < n; j++)
                {
                        printf("%d ", lcs[i][j]);
                }
                printf("\n");
        }
//        for(int i = 0; i < n; i++)
//        {
//                printf("%d ", id[i]);
//        }
        printf("\n");
}
int main()
{
        scanf("%d%d", &n, &e);
        for(int i = 0; i < e; i++)
        {
                int s, t;
                scanf("%d%d", &s, &t);
                g[s][t] = g[t][s] = 1;
        }
        init();
        dfs(0, n);
        print();
        return 0;
}

运行结果:

/home/a/j/nomad2:cat input_lca 
8 7
0 1
1 2
1 3
3 4
3 5
0 6
6 7

0 0 0 0 0 0 0 0 
0 1 1 1 1 1 0 0 
0 1 2 1 1 1 0 0 
0 1 1 3 3 3 0 0 
0 1 1 3 4 3 0 0 
0 1 1 3 3 5 0 0 
0 0 0 0 0 0 6 6 
0 0 0 0 0 0 6 7 

2) 转化为RMQ问题,

这篇文章解释的很好,http://hi.baidu.com/fandywang_jlu/blog/item/774caecae6165883c8176853.html

      (1)
      / \
   (2)   (7)
    / \     \
(3) (4)   (8)
     /   \
  (5)   (6)

一个nlogn 预处理,O(1)查询的算法. 
Step 1: 
        按先序遍历整棵树,记下两个信息:结点访问顺序和结点深度.
        如上图:
        结点访问顺序是: 1 2 3 2 4 5 4 6 4 2 1 7 8 7 1 //共2n-1个值
        结点对应深度是: 0 1 2 1 2 3 2 3 2 1 0 1 2 1 0
Step 2: 
        如果查询结点3与结点6的公共祖先,则考虑在访问顺序中
        3第一次出现,到6第一次出现的子序列: 3 2 4 5 4 6.
        这显然是由结点3到结点6的一条路径.
        在这条路径中,深度最小的就是最近公共祖先(LCA). 即
        结点2是3和6的LCA.
Step 3: 
        于是问题转化为, 给定一个数组R,及两个数字i,j,如何找出
        数组R中从i位置到j位置的最小值.. 
        如上例,就是R[]={0,1,2,1,2,3,2,3,2,1,0,1,2,1,0}. 
        i=2;j=7;
        这个问题就是经典的RMQ问题. 
        这里介绍一个比较简单的方法O(nlogn)预处理,O(1)回答每个询问.
        RMQ问题的预处理:
        用一个数组d[i][j]表示数组R中,从i位置到i+2^j-1位置的最小值.
        这个d[i][j]很容易dp得到.
        d[i][j+1]=min{d[i][j],d[i+2^j][j]};
        空间: O(nlogn) 时间: O(nlogn).
Step 4:
        询问:
        如果询问:从a到b里面最小的值..主要思路找两个长度是2^k的区间:
        [a,a+2^k-1]及[b-1-2^k,b]把区间[a,b]覆盖掉. 这很容易做到的,
        只要: 2^k*2>= |b-a|.
        于是a到b里面的最小值 = min {d[a][k],d[b-2^k-1][k]} 

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

const int N = 10001;


int g[N][N];

// number of edge
int e;
// number of vertex
int n;

// vis from root
bool visited[N];

int id;

// dep: dfs' depth, E: dfs sequence
int dep[2 * N + 1], E[2 * N + 1], R[N];

void DFS(int u, int d);

int d[20], st[2 * N + 1][20];

int query(int x, int y) {
	int k;
	k = int(log(double(y - x + 1)) / log(2));
	return dep[st[x][k]] > dep[st[y - d[k] + 1][k]] ? st[y - d[k] + 1][k] : st[x][k];
}

void answer() {
	int i, Q;
	scanf("%d", &Q);
	for (i = 0; i < Q; i++) {
		int x, y;
		scanf("%d%d", &x, &y);
		x = R[x];
		y = R[y];
		if (x > y) {
			swap(x, y);
		}
		printf("%d\n", E[query(x, y)]);
	}
}

void DFS(int u, int d) {
	visited[u] = true;
	R[u] = id;
	E[id] = u;
	dep[id++] = d;
	for (int i = 0; i < n; i++) {
		if (!visited[i] && g[u][i]) {
			DFS(i, d + 1);
			E[id] = u;
			dep[id++] = d;
		}
	}
}

void print()
{
	cout << endl;
	cout << "R: ";
	for(int i = 0; i < n; i++)
	{
		cout << R[i] << " ";
	}
	cout << endl;

	cout << "E: ";
	for(int i = 0; i < 2 * e + 1; i++)
	{
		cout << E[i] << " ";
	}
	cout << endl;

	cout << "D: ";
	for(int i = 0; i < 2 * e + 1; i++)
	{
		cout << dep[i] << " ";
	}
	cout << endl;
}

void initRMQ(int id) {
	int i, j;
	for (d[0] = 1, i = 1; i < 20; i++)
		d[i] = d[i - 1] * 2;
	for (i = 0; i < id; i++)
		st[i][0] = i;
	int k = int(log(double(id)) / log(2)) + 1;
	for (j = 1; j < k; j++) {
		for (int i = 0; i < id; i++) {
			if (i + d[j - 1] - 1 < id) {
				st[i][j]
						= dep[st[i][j - 1]] > dep[st[i + d[j - 1]][j - 1]] ? st[i + d[j - 1]][j - 1]
								: st[i][j - 1];
			} else {
				break;
			}
		}
	}
}

int main() {
	scanf("%d%d", &n, &e);
	for (int i = 0; i < e; i++) {
		int s, t;
		scanf("%d%d", &s, &t);
		g[s][t] = g[t][s] = 1;
	}
	DFS(0, 0);
	print();
	initRMQ(id);
	answer();
	return 0;
}

运行结果:

8 7
0 1
1 2
1 3
3 4
3 5
0 6
6 7
1
2 5
R: 0 1 2 4 5 7 11 12 
E: 0 1 2 1 3 4 3 5 3 1 0 6 7 6 0 
D: 0 1 2 1 2 3 2 3 2 1 0 1 2 1 0 

1

2. If you have a binary tree and want to send it to a mobile device, how would you do it? Write the code to create the flattened tree and to recreate the tree once flattened.

3. 给定一个中序遍历序列,可以构造多少个二叉树?

    含有n个节点的不相似的二叉树有1/(n+1) * C(2n, n)

4. 二叉搜索树变成双向链表,链表应排序

5. 二叉树遍历(前序、中序、后序、BFS/DFS)

6. 判断一个二叉树是不是二叉搜索树

7. 判断一个二叉树是否对称/互为镜像?(中序遍历为回文串)

bool IsMirror(Node * rootA, Node * rootB)
{
	if ( rootA == 0 && rootB == 0 )
		return true;
	
	if ( rootA == 0 || rootB == 0)
		return false;

	return rootA->Value == rootB->Value
		&& IsMirror(rootA->Left, rootB->Right)
		&& IsMirror(rootA->Right, rootB->Left);
}

bool IsMirror(Node * root)
{
	return IsMirror(root->Left, root->Right);
}

8. 在二叉树中找出和为某一值的所有路径

refer to: http://www.coder4.com/archives/3245

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

class Node
{
public:
        Node *left;
        Node *right;
        int data;
        Node(int d = 0):data(d),left(NULL),right(NULL){}
};

void print(vector<int> v, int level, int i2);
void sum(Node *head, int s, vector<int> v, int level)
{
        if(head == NULL)
                return;

        int tmp = s;
        cout << "adding " << head->data << endl;
        v.push_back(head->data);
        for(int i = level; i >= 0; i--)
        {
                tmp -= v[i];
                if(tmp == 0)
                {
                        print(v, i, level);
                }
        }

        vector<int> v1(v);
        vector<int> v2(v);
        sum(head->left, s, v1, level+1);
        sum(head->right, s, v2, level+1);
}

void print(vector<int> v, int level, int i2)
{
        for(int i = level; i<= i2; i++)
        {
                cout << v[i] << " ";
        }
        cout << endl;
}

Node* buildTree()
{
        Node* root = new Node(3);
        Node* n1 = new Node(1);
        Node* n2 = new Node(0);
        Node* n3 = new Node(1);
        Node* n4 = new Node(-1);
        root->left = n2;
        root->right = n1;
        n2->left = n3;
        n2->right = n4;
        return root;
}

int main()
{
        Node* r = buildTree();
        vector<int> v;
        sum(r, 3, v, 0);
}

9. 二叉搜索树,求第k个元素,求第二大元素

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值