FreeStyle分享 第二期

FreeStyle第二期树与链表在淘宝笔试题中的解决方法与害死人的笔试题

首先,树是一种数据结构,它是由nn>=1)个有限结点组成一个具有层次关系的集合。

树具有2种表示方法,也就是常说的双亲表存储和孩子链表存储。

让我们先来看下这2种表示法的实现

#################################################################

/*树的双亲表存储表示*/

#defineMAX_TREE_SIZE100

typedefstruct

{

TElemTypedata;

intparent;/*双亲位置域*/

}PTNode;

typedefstruct

{

PTNodenodes[MAX_TREE_SIZE];

intn;/*结点数*/

}PTree;

#################################################################

/*树的孩子链表存储表示*/

typedefstructCTNode{//孩子结点

intchild;

structCTNode*next;

}*ChildPtr;

typedefstruct{

ElemTypedata;//结点的数据元素

ChildPtrfirstchild;//孩子链表头指针

}CTBox;

typedefstruct{

CTBoxnodes[MAX_TREE_SIZE];

intn,r;//结点数和根结点的位置

}CTree;

#################################################################

这里对一些基本概念就不多说了。下面通过一些场景来深入了解树对常见问题的处理方式

有一颗结构如下的树,对其做镜像反转后如下,请写出能实现该功能的代码。注意:请勿对该树做任何假设,它不一定是平衡树,也不一定有序。
11
/|\ /|\
234432
/|\/\||/\/|\
65789101098756

有了前面的介绍,很容易看出,这个题目可以用孩子链表存储结构来存储这棵树,然后对孩子链表内部进行链表反转,对节点的处理可以用递归处理。

下面是一份网上流传开的参考代码,虽然他存在缺陷,但是大体上还是实现了我们想要的结果。

让我们一起来分析一下。

typedefstructTreeNode

{

intdata;

structTreeNode*firstchild;

structTreeNode*nextsibling;

}TreeNode,*Tree;

voidMirrorTree(Treeroot)

{

if(!root)

return;

if(root->firstchild)

{

Treep=root->firstchild;

Treecur=p->nextsibling;

p->nextsibling=NULL;

while(cur)

{

Treecurnext=cur->nextsibling;

cur->nextsibling=p;

if(p->firstchild)

MirrorTree(p);

p=cur;

cur=curnext;

}

root->firstchild=p;

}

}

intmain(void)

{

TreeNode*root=(TreeNode*)malloc(sizeof(TreeNode));

Init();

MirrorTree(root);

OutPut();

}

我们只看关键部分也就是从if(root->firstchild) 开始的那段,下面我们带入一组数据来更加直观的来说明这个问题

构建一棵树如下:(A是根节点,BCDE为同层的叶子)

A----

|------B

|------C

|------D

|------E

那么这棵树反转以后就是

A----

|------E

|------D

|------C

|------B

然后,我们走一边代码,看看效果

从if(root->firstchild)开始,此时

步骤

节点名

->nextsibling

链表

第一行

root->firstchild

B

C-D-E

B-C-D-E

第二行

p

B

C-D-E

B-C-D-E

第三行

cur

C

D-E

C-D-E

第四行

p

B

NULL

B

第五行

cur

C

D-E

C-D-E

第六行

curnext

D

E

D-E

第七行

cur

C

B

C-B

第八行,第九行

B节点进行递归(这里的B节点是叶子,所以直接返回)

第十行

p

C

B

C-B

第十一行

cur

D

E

D-E

第五行

cur

D

E

D-E

第六行

curnext

E

NULL

E

第七行

cur

D

C-B

D-C-B

第八行,第九行

C节点进行递归(这里的C节点是叶子,所以直接返回)

第十行

p

D

C-B

D-C-B

第十一行

cur

E

NULL

E

第五行

cur

E

NULL

E

第六行

curnext

NULL

NULL

NULL

第七行

cur

E

D-C-B

E-D-C-B

第八行,第九行

D节点进行递归(这里的D节点是叶子,所以直接返回)

第十行

p

E

D-C-B

E-D-C-B

第十一行

cur

NULL

NULL

NULL

第十二行

root->firstchild

E

D-C-B

E-D-C-B

细心的人一经发现问题了,按照代码走下来,到最后一步,整个镜像已经结束,但是对于E节点(这里是叶子),并没有做镜像,那么应该怎么解决呢?

方法很简单,只需要在第十一行与十二行之间加入if(p->firstchild)MirrorTree(p)

于是就有了

新加入行

E节点进行递归(这里的E节点是叶子,所以直接返回)

这样就是一个完整的利用孩子链表存储对树的镜像的解决方案了。

在这里补充一个阿里云关于链表反转的笔试题

一个链表是这样的:1->2->3->4->5通过反转后成为5->4->3->2->1

structlinka{

intdata;

linka*next;

};

voidreverse(linka*&head)

{

if(head==NULL)

return;

linka*pre,*cur,*ne;

pre=head;

cur=head->next;

while(cur)

{

ne=cur->next;//填空1

cur->next=pre;//填空2

pre=cur;//填空3

cur=ne;//填空4

}

head->next=NULL;

head=pre;

}

虽然2段代码的实现有点类似,但是仔细看了还是有区别的,那么我在这里再过一遍流程,

上一题没看懂的可以通过这个了解一下。这里的关键代码还是从pre=head开始吧

为了简单起见,我们把链表修改为1-2-3

步骤

节点名

->next

链表

第一行

pre

1

2-3-4

1-2-3-4

第二行

cur

2

3-4

2-3-4

第三行

cur

2

3-4

2-3-4

第四行

ne

3

4

3-4

第五行

cur

2

1-2-3

2-1-2-3

第六行

pre

2

1-2-3

2-1-2-3

第七行

cur

3

4

3-4

第三行

cur

3

4

3-4

第四行

ne

4

NULL

4

第五行

cur

3

2-1-2-3-4

3-2-1-2-3-4

第六行

pre

3

2-1-2-3-4

3-2-1-2-3-4

第七行

cur

4

NULL

4

第三行

cur

4

NULL

4

第四行

ne

NULL

NULL

NULL

第五行

cur

4

3-2-1-2-3-4

4-3-2-1-2-3-4

第六行

pre

4

3-2-1-2-3-4

4-3-2-1-2-3-4

第七行

cur

NULL

NULL

NULL

第八行

Head

1

NULL

1

第九行

Head

4

3-2-1-2-3

4-3-2-1-2-3-4

写到这里,是不是又发现问题了呢?简单的,所作的修改仅仅是在while循环之前加入一句pre->next=null就可以解决这个问题。然后我们可以得到一张新的流程图(弱弱说一句,常见的一个错误,苦了多少无辜的娃)

步骤

节点名

->next

链表

第一行

pre

1

2-3-4

1-2-3-4

第二行

cur

2

3-4

2-3-4

新加入的行

pre

1

NULL

1

第三行

cur

2

3-4

2-3-4

第四行

ne

3

4

3-4

第五行

cur

2

1

2-1

第六行

pre

2

1

2-1

第七行

cur

3

4

3-4

第三行

cur

3

4

3-4

第四行

ne

4

NULL

4

第五行

cur

3

2-1

3-2-1

第六行

pre

3

2-1

3-2-1

第七行

cur

4

NULL

4

第三行

cur

4

NULL

4

第四行

ne

NULL

NULL

NULL

第五行

cur

4

3-2-1

4-3-2-1

第六行

pre

4

3-2-1

4-3-2-1

第七行

cur

NULL

NULL

NULL

第八行

head

1

NULL

1

第九行

head

4

3-2-1

4-3-2-1

接下来是一个海量存储的问题。这个问题引入了B+树这个数据结构

B+树是应文件系统所需而出的一种B-树的变型树。一棵m阶的B+树和m阶的B-树的差异在于:  

1.有n棵子树的结点中含有n个关键字。 

2.所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。 

3.所有的非终端结点可以看成是索引部分,结点中仅含其子树(根结点)中的最大(或最小)关键字。  

4.通常在B+树上有两个头指针,一个指向根结点,一个指向关键字最小的叶子结点。

那么我们来看场景:

假设某个网站每天有超过10亿次的页面访问量,出于安全考虑,网站会记录访问客户端访问的ip地址和对应的时间,如果现在已经记录了1000亿条数据,想统计一个指定时间段内的区域ip地址访问量,那么这些数据应该按照何种方式来组织,才能尽快满足上面的统计需求呢,设计完方案后,并指出该方案的优缺点,比如在什么情况下,可能会非常慢?
解法:

B+树来组织,非叶子节点存储(某个时间点,页面访问量),叶子节点是访问的IP地址。这个方案的优点是查询某个时间段内的IP访问量很快,但是要统计某个IP的访问次数或是上次访问时间就不得不遍历整个树的叶子节点。或者可以建立二级索引,分别是时间和地点来建立索引。

答案是网上提供的,从数据结构来看,B+树是这个问题比较好的解决方案。

最后是咱们的牛P柬之大神提供的一个面试题。柬之可以职业面试官哦~

柬之口头描述问题如下:给你1棵非常大的树,任意取2个节点,请你找出他们最小父节点(所有的节点的最大父节点都是根)

柬之的分析:题目并不难,但是要在短短的几分钟面试时间做出来,还是有点点难度的。

下面是柬之给出的答案:

2个节点的所有父节点分别存储在2个数组中,然后从根节点开始做数组比较,直到2者不相等的temp=n的位置,那么他们的最小父节点就是在temp=n-1这个位置。

然后勇乔也提供了一种解法:

将深度比较大的节点的父节点存储在一个MAP命名为Tmap中,以节点的地址作为key,所有的value都为1。然后将另一个节点的所有父节点从底到根存入刚才的MAP中,如果Tmap[地址]==1,那么就命中,返回的该节点,否则就继续往根部寻找。

最后留下一个问题,对于这2种解法,时间/空间复杂度的分析还不是很清楚,希望大家帮助解答。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值