一波C语言二元查找树算法题目解答实例汇总

按层次遍历二元树
问题描述:输入一颗二元树,从上往下按层打印树的每个结点,同一层中按照从左往右的顺序打印。 
例如输入:

 8
 / /
 6 10
/ / / /
5 7 9 11

输出

8 6 10 5 7 9 11

定义二元树(其实是二元搜索树,但并不遍历算法)的结点为:

struct BSTreeNode
{
 int value;
 BSTreeNode *left;
 BSTreeNode *right;
};

思路:利用队列的先进先出,很容易实现。每次取出队列的首元素,然后将其左右子女放入队列中。直至队列为空即可。按这种方式进出队列,正好是按层遍历二元树。
      参考代码:

//函数功能 : 按层次遍历二元树
//函数参数 : pRoot指向根结点
//返回值 : 无
void LevelReverse(BSTreeNode *pRoot)
{
 if(pRoot == NULL)
  return; 

 queue<BSTreeNode *> nodeQueue;
 nodeQueue.push(pRoot);
 while(nodeQueue.size())
 {
  BSTreeNode * pNode = nodeQueue.front(); //取队首元素
  nodeQueue.pop(); //必须出队列
  if(pNode->left) //左子女
   nodeQueue.push(pNode->left);
  if(pNode->right) //右子女
   nodeQueue.push(pNode->right); 

  cout<<pNode->value<<' ';
 }
}

扩展一:上文给出的代码,所有结点都输出在同一行。如果希望仅仅同层结点输出在同一行,该如何修改代码呢?
       思路:如果我们能知道每层的最后一个结点,那么就方便多了,输出每层最后一个结点的同时,输出一个换行符。因此,关键在于如何标记每层的结束。可以考虑在每层的最后一个点之后,插入一个空结点。比如队列中先放入根结点,由于第0层只有一个结点,因此放入一个空结点。然后依次取出队列中的结点,将其子女放入队列中,如果遇到空结点,表明当前层的结点已遍历完了,而队列中放的恰恰是下一层的所有结点。如果当前队列为空,表明下一层无结点,也就说是所有结点已遍历好了。如果不为空,那么插入一个空结点,用于标记下一层的结束。
      参考代码:

void LevelReverse(BSTreeNode *pRoot)
{
 if(pRoot == NULL)
  return;
 queue<BSTreeNode *> nodeQueue;
 nodeQueue.push(pRoot);
 nodeQueue.push(NULL); //放入空结点,作为层的结束符
 while(nodeQueue.size())
 {
  BSTreeNode * pNode = nodeQueue.front(); //取队首元素
  nodeQueue.pop(); //必须出队列
  if(pNode)
  {
   if(pNode->left) //左子女
    nodeQueue.push(pNode->left);
   if(pNode->right) //右子女
    nodeQueue.push(pNode->right);
   cout<<pNode->value<<' ';
  }
  else if(nodeQueue.size()) //如果结点为空并且队列也为空,那么所有结点都已访问
  {
   nodeQueue.push(NULL);
   cout<<endl;
  }
 }
}

扩展二:之前讨论的都是从上往下、从左往右遍历二叉树,那么如果希望自下往上、从左右往右遍历二叉树,该如何修改代码呢?
       思路:比较简单的方法,首先遍历二叉树,将所有结点保存在一个数组中,遍历的同时记录每一层在数组中的起止位置。然后根据起止位置,就可以自下往上的打印二叉树的结点。

//每层的起止位置
struct Pos
{
 int begin;
 int end;
 Pos(int b, int e): begin(b),end(e) {}
};
void LevelReverse(BSTreeNode *pRoot)
{
 if(pRoot == NULL)
  return; 

 vector<BSTreeNode*> vec; //用以存放所有结点
 vector<Pos> pos;   //用以记录每层的起止位置
 vec.push_back(pRoot); 

 int level = 0; //树的层数
 int cur = 0;
 int last = 1; 

 while(cur < vec.size())
 {
  last = vec.size();
  pos.push_back(Pos(cur, last)); //记录当前层的起止位置 

  while(cur < last) //遍历当前层的结点,将子女放入数组中
  {
   if(vec[cur]->left) //先是左然后是右。如果希望自由向左,交换一下顺序即可
    vec.push_back(vec[cur]->left);
   if(vec[cur]->right)
    vec.push_back(vec[cur]->right);
   cur++;
  }
  level++; //层数加1
 } 

 for(int i = level - 1; i >= 0; i--) //自下往上遍历
 {
  for(int j = pos[i].begin; j < pos[i].end; j++)
   cout<<vec[j]->value<<' ';
  cout<<endl;
 }
}

输入一颗二元查找树,将该树转换为它的镜像
  问题描述:输入一颗二元查找树,将该树转换为它的镜像,即在转换后的二元查找树中,左子树的结点都大于右子树的结点。用递归和循环两种方法完成树的镜像转换。 
        例如输入:

 8
 / /
 6 10
 // //
5 7 9 11

输出:

 8
 / /
 10 6
 // //
11 9 7 5

定义二元查找树的结点为:

struct BSTreeNode
{
 int value;
 BSTreeNode *left;
 BSTreeNode *right;
};

思路:题目要求用两种方法,递归和循环,其实质是一样的。
      解法一:用递归。假设当前结点为pNode,只需交换该结点的左右子女,然后分别递归求解左子树和右子树即可。代码极为简单。
      解法二:用循环,需要一个辅助栈完成,每次取栈顶元素交换左右子女,然后将左右子女分别压入辅助栈,当栈中元素为空时,结束循环。其实不论是递归也好,循环也好,都是利用栈的特性完成。
      参考代码:

//函数功能 : 输入一颗二元查找树,将该树转换为它的镜像
//函数参数 : pRoot为根结点
//返回值 : 根结点
BSTreeNode * Mirror_Solution1(BSTreeNode * pRoot)
{
 if(pRoot != NULL)
 {
  BSTreeNode * pRight = pRoot->right;
  BSTreeNode * pLeft = pRoot->left;
  pRoot->left = Mirror_Solution1(pRight); //转化右子树
  pRoot->right = Mirror_Solution1(pLeft); //转化左子树
 }
 return pRoot;
}
BSTreeNode * Mirror_Solution2(BSTreeNode * pRoot)
{
 if(pRoot != NULL)
 {
  stack<BSTreeNode *> stk; //辅助栈
  stk.push(pRoot);   //压入根结点
  while(stk.size())
  {
   BSTreeNode *pNode = stk.top();
   BSTreeNode *pLeft = pNode->left;
   BSTreeNode* pRight = pNode->right;
   stk.pop(); 

   if(pLeft != NULL)
    stk.push(pLeft);
   if(pRight != NULL)
    stk.push(pRight);
   pNode->left = pRight; //交换左右子女
   pNode->right = pLeft;
  }
 }
 return pRoot;
}

判断整数序列是不是二元查找树的后序遍历结果
问题描述:输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。如果是返回true,否则返回false。
例如输入5、7、6、9、11、10、8,由于这一整数序列是如下树的后序遍历结果:

   8
  / /
  6 10
 / / / /
 5 7 9 11

因此返回true。如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。
         思路:分析后序遍历的特点,序列的最后一个数应该是根结点,剩余的节点分为两个连续的子序列,前一子序列的值小于最后一个数,后一子序列的值大于最后一个数。然后递归求解这两个子序列。
         如果是判断是前序遍历也很简单,只不过根节点变为了第一个数,剩余的节点也是分为两个连续的子序列。如果判断是中序遍历,更方便,只需扫描一遍,检查序列是不是排好序的,如果没有排好序,就不是中序遍历的结果。

把二元查找树转变成排序的双向链表
    问题描述:输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求不能创建任何新的结点,只调整指针的指向。

 10
 / /
 6 14
 / / / /
4 8 12 16

转换成双向链表

4=6=8=10=12=14=16

思路:利用递归的思想求解,分别调整某结点的左右子树,调整完后,将该结点的左指针指向左子树的最大节点,右指针指向右子树的最小节点。
   代码如下:

BSTreeNode * Convert(BSTreeNode *node)
{
 if(node == NULL)
  return NULL;
 BSTreeNode *leftMax,*rightMin;
 leftMax = node->left;
 rightMin = node->right;
 //找到左子树的最大结点
 while(leftMax != NULL && leftMax->right != NULL)
  leftMax = leftMax->right;
 //找到右子树的最小结点
 while(rightMin != NULL && rightMin->left != NULL)
  rightMin = rightMin->left;
 //递归求解
 Convert(node->right);
 Convert(node->left);
 //将左右子树同根结点连起来,只不过是以兄弟的关系
 if(leftMax != NULL)
  leftMax->right = node;
 if(rightMin != NULL)
  rightMin->left = node;
 node->left = leftMax;
 node->right = rightMin;
 return node;
}

测试当中,需要建立二叉搜索树,下面给出建立及遍历二叉树的代码。

struct BSTreeNode
{
 int value;
 BSTreeNode *left;
 BSTreeNode *right;
};
BSTreeNode * Insert(BSTreeNode *p, int x)
{
 if(p == NULL)
 {
  p = new BSTreeNode;
  p->value = x;
  p->left = NULL;
  p->right = NULL;
 }
 else
 {
  if(p->value > x)
   p->left = Insert(p->left, x);
  if(p->value < x)
   p->right = Insert(p->right, x);
 }
 return p;
}
void Traverse(BSTreeNode *p) //中序遍历
{
 if(p == NULL)
  return;
 Traverse(p->left);
 cout<<p->value<<' ';
 Traverse(p->right);
}

在二元树中找出和为某一值的所有路径(树)
   问题描述:输入一个整数和一棵二元树。从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。打印出和与输入整数相等的所有路径。
例如输入整数22和如下二元树

 10
 / /
 5 12
 / /
4  7

则打印出两条路径:10, 12和10, 5, 7。
二元树节点的数据结构定义为:

struct BinaryTreeNode
{
int data;
BinaryTreeNode *pLeft;
BinaryTreeNode *pRight;
};

思路:递归的思想。很多树的题目都是用递归解决的,例如把二元查找树转变成排序的双向链表(树)。递归的终止条件为当前为空结点或当前结点的值大于剩余和。如果当前结点的值等于剩余和,并且是叶结点,那么打印路径。否则,将剩余和减去当前结点的值,递归求解。至于路径的记录,可以利用栈的思想来实现。
       代码:

void FindPath(BinaryTreeNode *pNode,int sum,vector<int> &path)
{
 //结点为空或值大于当前和
 if(pNode == NULL || pNode->data > sum)
  return;
 path.push_back(pNode->data);
 //判断是不是叶结点
 bool isLeaf = (pNode->pLeft == NULL && pNode->pRight == NULL)? true: false;
 //找到一条路径,打印
 if(pNode->data == sum && isLeaf)
 {
  vector<int>::iterator iter = path.begin();
  for(; iter != path.end(); iter++)
   cout<<*iter<<' ';
  cout<<endl;
 }
 else
 {
  //求剩余和
  sum = sum - pNode->data;
  //递归求解
  FindPath(pNode->pLeft, sum, path);
  FindPath(pNode->pRight, sum, path);
 }
 path.pop_back();
}

判断二叉树是不是平衡的
问题描述:输入一棵二叉树的根结点,判断该树是不是平衡二叉树。如果某二叉树中任意结点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。例如下图中的二叉树就是一棵平衡二叉树:

思路:对于树的题目,第一反应就是用递归。对于以某个结点为根的树,只需计算出它的左右子树的深度,如果深度相差小于等于1,则递归判断它的左右子树是不是平衡树;否则肯定不是平衡二叉树。这个问题的关键是要计算树的深度,如果是自顶向下,会有很多重复的计算。计算以1为根的树的深度,会牵涉到以2为根、以3为根的子树。计算以2为根的树的深度,会牵涉到以4为根、以5为根的子树。由于要遍历每个结点,判断以该结点为根的树是不是平衡二叉树。所以计算以1为根的树的深度,与计算以2为根的树的深度,会重复计算以4为根、以5为根的子树的深度。

消除重复办法,当时是能记录下之前计算过的子树的深度,下次使用就不用重新计算。这就需要自底向上的计算深度。庆幸的是递归解决树的问题,就是自底向上的过程。因为我们在递归求解中,先要得出子树的解,子树的解最终会转换为叶结点的解。可以利用后序遍历的方法,遍历每个结点时,先判断它的左右子树是不是平衡二叉树,同时记录下左右子树的深度,然后判断该结点为根的树是不是平衡二叉树,至于该树的深度计算很方便,取左右子树中较大的深度+1就可以了。这里左右子树的深度在递归求解中已经计算出来,不需要重复计算了。

参考代码:

struct BinaryTreeNode
{
  int data;
  BinaryTreeNode *pLeft;
  BinaryTreeNode *pRight;
};
//函数功能 : 判断二叉树是不是平衡的
//函数参数 : pRoot为根结点,pDepth为根结点的深度。
//返回值 :  是否平衡的
bool IsBalanced(BinaryTreeNode *pRoot, int *pDepth)
{
  if(pRoot == NULL)
  {
    *pDepth = 0;
    return true;
  }
  int leftDepth, rightDepth; //左右子树的深度
  if(IsBalanced(pRoot->pLeft, &leftDepth)&&
    IsBalanced(pRoot->pRight, &rightDepth))
  {
    int diff = leftDepth - rightDepth;
    if(diff == 0 || diff == 1 || diff == -1) //相差为0或1或-1
    {
      *pDepth = 1 + (leftDepth > rightDepth ? leftDepth: rightDepth);
      return true;
    }
    else
      return false;
  }
  return false;
} 
(0)

相关推荐

  • C语言输出旋转后数组中的最小数元素的算法原理与实例

    问题描述:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个排好序的数组的一个旋转,输出旋转数组的最小元素.例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1.      思路:这道题最直观的解法并不难.从头到尾遍历数组一次,就能找出最小的元素,时间复杂度显然是O(n).但这个思路没有利用输入数组的特性.既然有时间复杂度更小的算法,我们容易想到二分查找,因为它的时间复杂度为O(logn).这个问题是否可以运用二分查找呢?答

  • 一些C语言中字符串的算法问题解决实例小结

    字符串问题是面试中经常出现的问题,这类问题有很多,难以不一.下面是几道字符串的题目,网上都能找到解答,自己实现了一下,供网友参考.感觉算法重要的是要有正确的思路,实现起来不是问题.自己一定要多思考,这样收获可能会更多一点.         问题1:找两个字符串的最长公共子串.         具体描述,如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串.注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中.请编写一个函数,输入两个字符串,求

  • 对C语言中递归算法的深入解析

    许多教科书都把计算机阶乘和菲波那契数列用来说明递归,非常不幸我们可爱的著名的老潭老师的<C语言程序设计>一书中就是从阶乘的计算开始的函数递归.导致读过这本经书的同学们,看到阶乘计算第一个想法就是递归.但是在阶乘的计算里,递归并没有提供任何优越之处.在菲波那契数列中,它的效率更是低的非常恐怖. 这里有一个简单的程序,可用于说明递归.程序的目的是把一个整数从二进制形式转换为可打印的字符形式.例如:给出一个值4267,我们需要依次产生字符'4','2','6',和'7'.就如在printf函数中使用

  • C语言实现的排列组合问题的通用算法、解决方法

    尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手.由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论.以在n个数中选取m(0<m<=n)个数为例,问题可分解为: 1. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止. 2. 从n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m. 很明显,上述方法是一个递归的过程,也

  • c语言快速排序算法示例代码分享

    步骤为:1.从数列中挑出一个元素,称为 "基准"(pivot);2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边).在这个分区退出之后,该基准就处于数列的中间位置.这个称为分区(partition)操作.3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序.递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了.虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iterat

  • 常用排序算法的C语言版实现示例整理

    所谓排序,就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来.其确切定义如下: 输入:n个记录R1,R2,-,Rn,其相应的关键字分别为K1,K2,-,Kn. 输出:Ril,Ri2,-,Rin,使得Ki1≤Ki2≤-≤Kin.(或Ki1≥Ki2≥-≥Kin).     排序的时间开销可用算法执行中的数据比较次数与数据移动次数来衡量.基本的排序算法有如下几种:交换排序(冒泡排序.快速排序).选择排序(直接选择排序.堆排序).插入排序(直接插入排序.希尔排序).归并排序.分配排序(基数排

  • C语言实现魔方阵算法(幻方阵 奇魔方 单偶魔方实现)

    例如三阶魔方阵为: 魔方阵有什么的规律呢? 魔方阵分为奇幻方和偶幻方.而偶幻方又分为是4的倍数(如4,8,12--)和不是4的倍数(如6,10,14--)两种.下面分别进行介绍. 2 奇魔方的算法 2.1 奇魔方的规律与算法 奇魔方(阶数n = 2 * m + 1,m =1,2,3--)规律如下: 数字1位于方阵中的第一行中间一列:数字a(1 < a  ≤ n2)所在行数比a-1行数少1,若a-1的行数为1,则a的行数为n:数字a(1 < a  ≤ n2)所在列数比a-1列数大1,若a-1的列

  • C语言中压缩字符串的简单算法小结

    应用中,经常需要将字符串压缩成一个整数,即字符串散列.比如下面这些问题: (1)搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节.请找出最热门的10个检索串. (2)有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M.返回频数最高的100个词. (3)有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复.要求你按照query的频度排序. (4)给定a.b两个文件

  • 算法学习入门之使用C语言实现各大基本的排序算法

    首先来看一下排序算法的一些相关概念: 1.稳定排序和非稳定排序 简单地说就是所有相等的数经过某种排序方法后,仍能保持它们在排序之前的相对次序,我们就说这种排序方法是稳定的.反之,就是非稳定的. 比如:一组数排序前是a1,a2,a3,a4,a5,其中a2=a4,经过某种排序后为a1,a2,a4,a3,a5,则我们说这种排序是稳定的,因为a2排序前在a4的前面,排序后它还是在a4的前面.假如变成a1,a4,a2,a3,a5就不是稳定的了. 2.内排序和外排序 在排序过程中,所有需要排序的数都在内存,

  • c语言实现冒泡排序、希尔排序等多种算法示例

    实现以下排序 插入排序O(n^2) 冒泡排序 O(n^2) 选择排序 O(n^2) 快速排序 O(n log n) 堆排序 O(n log n) 归并排序 O(n log n) 希尔排序 O(n^1.25) 1.插入排序 O(n^2) 一般来说,插入排序都采用in-place在数组上实现.具体算法描述如下:⒈ 从第一个元素开始,该元素可以认为已经被排序⒉ 取出下一个元素,在已经排序的元素序列中从后向前扫描⒊ 如果该元素(已排序)大于新元素,将该元素移到下一位置⒋ 重复步骤3,直到找到已排序的元素

  • 用C语言举例讲解数据结构中的算法复杂度结与顺序表

    数据结构算法复杂度 1.影响算法效率的主要因素 (1)算法采用的策略和方法: (2)问题的输入规模: (3)编译器所产生的代码: (4)计算机执行速度. 2.时间复杂度 // 时间复杂度:2n + 5 long sum1(int n) { long ret = 0; \\1 int* array = (int*)malloc(n * sizeof(int)); \\1 int i = 0; \\1 for(i=0; i<n; i++) \\n { array[i] = i + 1; } for(

随机推荐