Cracking the Coding Interview(Trees and Graphs)

树和图的训练平时相对很少,还是要加强训练一些树和图的基础算法。自己对树节点的设计应该不是很合理,多多少少会有一些问题,需要找一本数据结构的书恶补一下如何更加合理的设计节点。

class TreeNode
{
public:
	int treenum;

	TreeNode** children;
	int child_num;
	int child_len;
	int depth;
	int iterator;

	TreeNode* rightchild;
	TreeNode* leftchild;
	TreeNode* father;

	TreeNode():child_num(0),depth(0),iterator(0),child_len(10)
	{
		children = new TreeNode*[child_len];
		rightchild = NULL;
		leftchild = NULL;
	}
	~TreeNode()
	{
		delete [] children;
	}
	void addchild(TreeNode* tn)
	{
		children[child_num++] = tn;
	}
	int isleaf()
	{
		if (child_num == 0)
		{
			return TRUE;
		}
		else
		{
			return FALSE;
		}
	}
	int hasnextchild()
	{
		if (iterator < child_num)
		{
			return TRUE;
		}
		else
		{
			return FALSE;
		}
	}
	TreeNode* nextchild()
	{
		return children[iterator++];
	}
protected:
private:
};

  

1.Implement a function to check if a tree is balanced.For the purposes of this question,a balanced tree is defined to be a tree such that no two leaf nodes differ in distance from the root by more than one.

(1)分别记录最深的叶子节点的高度和最浅的叶子节点的高度即可。以下是一种实现,利用队列对树进行广度的遍历...遍历的过程中记录最高的高度和最低的高度。

int isbalanced(TreeNode* root)
{
	queue<TreeNode*>* q = new queue<TreeNode*>;
	int max = 0,min = BIGNUM;
	root->depth = 0;
	q->push(root);
	while (!q->empty())
	{
		TreeNode* tn = q->front();
		q->pop();
		if (tn->isleaf() == TRUE)
		{
			min = tn->depth<min?tn->depth:min;
			max = tn->depth<max?max:tn->depth;
		}
		else
		{
			while (tn->hasnextchild() == TRUE)
			{
				TreeNode* child = tn->nextchild();
				child->depth = tn->depth + 1;
				q->push(child);
			}
		}
	}
	if (max - min > 1)
	{
		return FALSE;
	}
	else
	{
		return TRUE;
	}
}

(2)其实求解树的高度不必要进行这样的广度遍历,只需要递归的实现求解树的高度即可。树的结构很适合递归程序,算法也优先考虑是否有递归的实现,这样能够保证代码的简洁。

 

2.Given a directed graph,design an algorithm to find out whether there is a route between two nodes.

(1)图在程序中的表示方法也有邻接矩阵,邻接链表的形式,也可以直接表示为图的节点的连接形式,这样就需要合理的设计图的节点单元。以下程序的实现考虑邻接矩阵形式表示的有向图。

图中如果存在环,因为邻接矩阵中无法标记节点是否被访问,所以程序实现过程中手动修改一些矩阵元素,保证程序不会再环中无限递归调用。

int isroute(int src,int dst,int** grp,int length)
{
	int* srcarray = grp[src];
	if(srcarray[dst] == 1)
		return TRUE;
	else
	{
		for (int i = 0;i < length;i++)
		{
			if (srcarray[i] == 1&&i != src)
			{
				//indicate has visit
				srcarray[i] = 0;
				return isroute(i,dst,grp,length);
			}
		}
	}
	return FALSE;
}

(2)当然可以用更加普通的一些函数抽象来设计程序,将图的节点单元抽象化。

 

3.Given a sorted(increased order) array,write an algorithm to create a binary tree with minimal height.

(1)因为最小高度的二叉树,和有序几乎没有什么关系,生成一个完全二叉树即可满足最小高度。

TreeNode* createNode(int* array,int length)
{
	TreeNode* root = new TreeNode();
	int index = 0;
	queue<TreeNode*>* q = new queue<TreeNode*>();
	root->treenum = array[index++];
	q->push(root);
	while (index < length)
	{
		TreeNode* tn = q->front();
		q->pop();
		TreeNode* l = new TreeNode();
		l->treenum = array[index++];
		tn->leftchild = l;
		if (index > length)
			break;
		TreeNode* r = new TreeNode();
		r->treenum = array[index++];
		tn->rightchild = r;
		q->push(tn->rightchild);
		q->push(tn->leftchild);
	}
	return root;
}

(2)当然我们也可以生成一个左边子树小于右边子树的二叉树。

 

4.Given a binary search tree,design an algorithm which creates a linked list of all the nodes at each depth(ie,if you have a tree with depth D,you'll have D linked lists).

(1)仅仅需要实现一个广度优先搜索的变种,因为广度优先搜索的时候可以把节点的高度信息记录下来,这样把不同高度的节点加入不同的链表即可

TreeNode*** backnodelist(TreeNode* root)
{
	TreeNode*** tn = new TreeNode**[20];
	int index[20] = {0};
	for (int i = 0;i < 20;i++)
	{
		tn[i] = new TreeNode*[20];
	}
	for (int i = 0;i < 3;i++)
	{
		for (int j = 0;j < 10;j++)
		{
			tn[i][j] = NULL;
		}
	}
	queue<TreeNode*>* q = new queue<TreeNode*>();
	root->depth = 0;
	q->push(root);
	while(!q->empty())
	{
		TreeNode* outtn = q->front();
		q->pop();
		tn[outtn->depth][index[outtn->depth]++] = outtn;
		if (outtn->rightchild != NULL)
		{
			outtn->rightchild->depth = outtn->depth+1;
			q->push(outtn->rightchild);
		}
		if (outtn->leftchild != NULL)
		{
			outtn->leftchild->depth = outtn->depth+1;
			q->push(outtn->leftchild);
		}
		
		
	}
	return tn;
}

  

(2)

 5.Write an algorithm to find the 'next' node(ie,in-order successor) of a given node in a binary search tree where each node has a link to its parent.

(1)in-order序是访问左边节点,访问当前节点,访问右边节点。有了指向父节点的指针的话算法实现比较容易,只需要一些简单逻辑的判断。所以这个题目的重点在于树的几种遍历顺序。

TreeNode* nextnode(TreeNode* node)
{
	if (node->rightchild->treenum != 0)
	{
		return node->rightchild;
	}
	else
	{
		if (node == node->father->rightchild)
		{
			return node->father->father;
		}
		else
		{
			return node->father;
		}
	}
}

 

6.Design an algorithm and write code to find the first common ancestor of two nodes in a binary tree.Avoid storing additional nodes in a data structure.Note:This is not necessarily a binary search tree.

 这个题目记得师兄跟我提起过,说面试的时候问到他这个题目,当时我想其实这个问题挺简单的...一定很容易。但是在我实现的时候就遇到了困难...搞不定这个题目。

(1)如果这个题目跟上个题目类似,存在子节点到父节点的指针就比较简单了,从两个节点开始向上层搜索,第一次同时遇到的父节点必定是第一个共同的祖先。但是这里仅仅是描述,实现的时候应该还需要考虑更多的问题,题目要求不能使用额外的存储节点的数据结构,所以这里回溯的时候应该是一个一步的回溯,另外一个每次回溯至根节点,检测是否遇到,如果遇到则为第一个共同的祖先,所以这里的时间复杂度应该是O(n*m),n为一个节点到共同祖先的距离,m是另外一个节点的深度。

(2)另外一个直观的想法是深搜,然后将已经遍历的节点存储在一个堆中,如果找到其中一个节点,不断的退出堆,在进行深搜,如果找到另外一个节点,该节点为共同祖先。但是深搜的算法不知道如何实现,还要利用堆,这个违背了题目中的限定条件,所以仅仅是一个思路。 

(3)

递归的思想。如果节点p和q再root节点的同一侧,则继续递归至那一侧寻找first common ancestor,如果节点p和q再root节点的两侧,则该root节点及为寻找的first common ancestor。

//copy from Cracking the coding interview
public Tree commonAncestor(Tree root,Tree p,Tree q) {
    if(covers(root.left,p)&&covers(root.left,q))
        return commonAncestor(root.left,p,q)
    if(covers(root.right,p)&&covers(root.right,q))
        return commonAncestor(root.right,p,q))
    return root;
}
private boolean covers(Tree root,Tree p) {
    if(root == null) return false;
    if(root == p) return true;
    return covers(root.left,p) || covers(root.right,p);
}

 

(4) 

//copy from cracking the coding interview
static int TWO_NODES_FOUND=2;
static int ONE_NODES_FOUND=1;
static int NO_NODES_FOUND=0;

int covers(TreeNode root,TreeNode p ,TreeNode q) {
    int ret = NO_NODES_FOUND;
    if(root == null) return ret;
    if(root == p || root == q) ret += 1;
    ret += covers(root.left,p,q);
    if(ret == TWO_NODES_FOUND)
        return ret;
    return ret + covers(root.right,p,q);
}

TreeNode commonAncestor(TreeNode root,TreeNode p,TreeNode q) {
    if(q == p&&(root.left == q|| root.right == q)) return root;
    int nodesFromLeft = covers(root.left,p,q);
    if(nodesFromLeft == TWO_NODES_FOUND) {
        if(root.left == p || root.left == q) return root.left;
        else return commonAncestor(root.left,p,q);
    }else if(nodesFromLeft == ONE_NODES_FOUND) {
        if(root == p) return p;
        else if(root == q) return q;
    }

    int nodesFromRight = covers(root.right,p,q);
        if(nodesFromLeft == TWO_NODES_FOUND) {
            if(root.right== p || root.right== q) return root.right;
            else return commonAncestor(root.right,p,q);
        }else if(nodesFromRight == ONE_NODES_FOUND) {
            if(root == p) return p;
            else if(root == q) return q;
        }


    if(nodesFromLeft == ONE_NODES_FOUND&&nodesFromRight == ONE_NODES_FOUND)
        return root;
    else 
        return null;

 

 

7.You have two very large binary trees:T1,with millions of nodes,and T2,with hundreds of nodes.Create an algorithm to decide if T2 is a subtree of T1.

 这个题目我看到之后没有任何思路,T1的节点数量太多,直接吓到了。下面的思路均来自答案。

(1)其中一种思路是利用字符串表示这里的树,及利用其中一种遍历树的顺序将树转换为字符串。无论什么顺序,只要两个树的遍历顺序相同即可。如果得到的字符串T2是T1的子串,则可以说明T2就是T1的子树。(子串的检测可以利用suffix tree,suffix tree, 或 后缀树,是一种相当神奇的数据结构,它包含了字符串中的大量信息,能够于解决很多复杂的字符串问题 —— 事实上,基本上目前为止俺遇到过的所有与字符串有关的问题都可以通过它解决<可以了解一下这种数据结构>),但是因为T1有数百万的节点,所以建立suffix tree的时候可能会需要很大的memory space,所以这一种解决方案的缺陷在于空间复杂度比较高。

 

(2) 另外一种思路就是直接遍历T1,如果遇到T2的root节点则接下来同时遍历T1和T2,如果相同的遍历完成T2,则可以判定T2是T1的子树。时间复杂度最坏为O(n*m),这里n是T1的节点数量,m是T2的节点数量。这里是没有T2的root节点在T1中位置信息,如果有位置信息,能够提供更加准确的时间复杂度。

//copy from cracking the coding interview
boolean containsTree(TreeNode t1,TreeNode t2) {
    if(t2 == null) return true;
    else return subTree(t1,t2);
}
boolean subTree(TreeNode r1,TreeNode r2) {
    if(r1 == null)
        return false;
    if(r1.data == r2.data)
        if(matchTree(r1,r2)) return true;
    return (subTree(r1.left,r2)||subTree(r1.right,r2));
}
boolean matchTree(TreeNode r1,TreeNode r2) {
    if(r2 == null && r1 == null)
        return true;
    if(r1 == null || r2 == null)
        return false;
    if(r1.data != r2.data)
        return false;
    return (matchTree(r1.left,r2.left)&&matchTree(r1.right,r2.right));
}

 

8.You are given a binary tree in which each node contains a value.Design an algorithm to print all paths which sum up to that value.Note that it can be any path in the tree it does not have to start at the root.

深搜的变种,因为路径可以开始于树种的任意节点。所以算法递归的计算树种的所有节点即可。因为树的节点信息可能是负数,所以这里的路径即使找到了sum总和也不能停止搜索这条路径,因为这条路径的延伸还有可能等于sum。

但是有一个小技巧可能会减少编程复杂度,我们倒过来看,不是输出所有从某个节点出发的路径,而是输出所有截止于该点的等于sum的路径。 

//copy from cracking the coding interview
void findSum(TreeNode head,int sum,ArrayList<Integer> buffer,int level) {
    if(head == null) return;
    int tmp = sum;
    buffer.add(head.data);
    for(int i = level;i > -1;i--) {
       tmp -= buffer.get(i);
       if(tmp == 0) print(buffer,i,level);
    }
    ArrayList<Integer> c1 = (ArrayList<Integer>) buffer.clone();
    ArrayList<Integer> c2 = (ArrayList<Integer>) buffer.clone();
    findSum(head.left,sum,c1,level+1);
    findSum(head.right,sum,c2,level+1);
}

void print(ArrayList<Integer> buffer,int level,int i2) {
    for(int i = level;i <= i2;i++) {
        System.out.print(buffer.get(i)+"");
    }
    System.out.println();
}

 

posted @ 2013-09-15 14:36  weixliu  阅读(991)  评论(0编辑  收藏  举报