数据结构复习之二叉树:遍历、搜索节点&路径、查找、与单链表互转、逐层打印

直接上代码(vs2010环境下调试通过)。

#include "stdafx.h"
#include<malloc.h>
#include<assert.h>

struct TreeNode;
typedef struct TreeNode* Tree; 
typedef Tree Position;  //树的节点
typedef int ValueType;

struct TreeNode
{
    ValueType value;
    Position leftChild;
    Position rightChild;
};

struct ListNode
{
    ValueType value;
    ListNode *next;
};

 void nodeInit(ListNode *node, ValueType value, ListNode *next)
 {
     node->value=value;
     node->next=next;
 }
void initTreeNode(Position pos, ValueType value, Position leftChild, Position rightChild)
{
    //pos = (Position) malloc(sizeof(struct TreeNode)); //?
    pos->value = value;
    pos->leftChild = leftChild;
    pos->rightChild = rightChild;
}

//计算树的节点数
int nodesCount(Tree tree)
{
    if(tree == NULL)
        return 0;
    int count = 1;  //不为空则至少有一个节点
    int leftCount = nodesCount(tree->leftChild);
    int rightCount = nodesCount(tree->rightChild);
    return count + leftCount + rightCount;
}

/*
前序遍历,数组中的数字顺序与实际遍历顺序不一致,why??因为最先没有返回值,
递归过程中index的值不对。index的值必须要返回,虽然层层递归中会使index的值
增加,但是下一层递归完成返回到上一层时index还是上一层中原来的index值,即
index值退变为一个较小的值,导致不能在数组中正确存储遍历结果
*/
int findWhole_rootFirst_iterate(Tree tree, ValueType result[], int index)   
{
    if(tree == NULL)
        return index;

    //先遍历根节点
    //printf("%4d",tree->value);
    result[index] = tree->value;
    index++;

    //再遍历左孩子
     index = findWhole_rootFirst_iterate(tree->leftChild, result, index);

    //后遍历右孩子
    index = findWhole_rootFirst_iterate(tree->rightChild, result, index);

    return index;
}

/*
前序遍历:遍历的结果序列的第一个是根节点,后面的序列以某一个位置为分界点,前
部分是左子树,后部分是右子树,不可能出现左子树与右子树交替分布的情况,其它遍
历方式遵循同样的规律
*/
ValueType* findWhole_rootFirst(Tree tree)
{
    if(tree == NULL) //b保险起见
        return NULL;

    int count = nodesCount(tree);
    ValueType *result = (ValueType*)malloc(sizeof(ValueType)*count);
    //int index = 0;
    findWhole_rootFirst_iterate(tree, result, 0);

    return result;
}

//中序遍历
void findWhole_rootMiddle(Tree tree)
{
    if(tree == NULL)
        return;

    findWhole_rootMiddle(tree->leftChild);

    printf("%4d",tree->value);

    findWhole_rootMiddle(tree->rightChild);
}

//后续遍历:略

//重建二叉树:根据前序及中序遍历结果构建出二叉树,并输出二叉树的头节点
//length表示遍历结果数组的长度即每次待重建的树的节点数
Tree reconstructTree(ValueType pre[], ValueType mid[], int length)
{
    if(pre==NULL || mid == NULL || length <= 0)
        return NULL;

    ValueType rootValue = pre[0];   //前序遍历的第一个元素就是根节点值

    Position root = (Position) malloc(sizeof(struct TreeNode));
    assert(root != NULL);   //表达式为假时终止程序
    initTreeNode(root,rootValue,NULL,NULL);

    //迭代的出口
    if(length ==1)      
        return root;

    int rootValuePosition=0;    //rootValue在中序遍历数组中位置,此位置之前即为左子树,之后即为右子树
    while(mid[rootValuePosition] != rootValue)
    {
        rootValuePosition++;
        assert(rootValuePosition < length);//若在mid[]数组中没找到rootValue,则异常
    }
    //if(rootValuePosition > 0)         //因为有了迭代出口,这里的条件不再必要,这也是迭代的通用规律
        root->leftChild = reconstructTree(pre+1,mid,rootValuePosition);
    //if(rootValuePosition < length-1)  //因为有了迭代出口,这里的条件不再必要,这也是迭代的通用规律
        root->rightChild = reconstructTree(pre+rootValuePosition+1,mid+rootValuePosition+1,length-rootValuePosition-1);

    return root;
}

/*
二叉树转成单链表,有两种方式:
1)先遍历出树的全部节点再创建链表
2)仅改变树的指针,将左子树全移到右子树上返回的是特殊的tree,每个节点只有右子树,相当
于单链表。
下面程序的思路是:对于每个节点,先把左、右子树变成链表,再把左子树接到右子树后面,整个
树的左子树就去掉了,只剩右子树,即成链表。
*/
void treeToLinkList(Tree tree)
{
    if(tree == NULL)
        return;

    treeToLinkList(tree->rightChild);
    treeToLinkList(tree->leftChild);

    if(tree->rightChild == NULL)
        tree->rightChild = tree->leftChild;
    else
    {
        Position rightChild = tree->rightChild;

        while(rightChild->rightChild != NULL)
            rightChild = rightChild->rightChild;

        rightChild->rightChild = tree->leftChild;
    }
    tree->leftChild = NULL;
}

//单链表转为(完全)二叉树
Tree linkListToTree(ListNode *list, int num)//num>=0为每个节点的序号,左、右子树的序号分别为2*num+1、2*num+2
{
    if(list == NULL)
        return NULL;

    int len=0;  //链表长度
    ListNode *node = list;
    while(node != NULL)
    {
        len++;
        node = node->next;  //最先没写这一句!!
    }
    if(num > len-1)
        return NULL;

    node = list;
    for(int i = 0; i < num; i++)
        node = node->next;

    Tree tree = (Tree)malloc(sizeof(struct TreeNode));

    tree->value = node->value;
    tree->leftChild = linkListToTree(list,2*num+1);
    tree->rightChild = linkListToTree(list,2*num+2);

    return tree;
}

//判断smallTree是不是bigTree的子结构
//方法1:通过遍历结果来比较。但是效率不是最好的,因为当返回结果是true时bigTree可能不需要全部遍历
bool isSubConstruction1(Tree bigTree, Tree smallTree)
{
    int len1 = nodesCount(bigTree);
    int len2 = nodesCount(smallTree);
    if(len1 < len2)
        return false;
    ValueType *a1 = findWhole_rootFirst(bigTree);   
    ValueType *a2 = findWhole_rootFirst(smallTree);

    for(int i = 0; i < len1-len2+1; i++)    //搜索bigTree/a1
    {
        if(a1[i] = a2[0])   //找到了第一个相等的
        {
            for(int j = 1; j < len2; j++)       //搜索smallTree/a2
            {
                if(a2[j] != a1[i+j])
                    break;
                if(j == len2-1) //a2[]中直到最后一个数都与a1[]中的书连续相等,说明找到了子结构
                    return true;
            }
        }
    }

    return false;
}

//方法2:对于树结构,往往最有效的就是递归算法!所以这里使用递归比较
//下面的函数是判断的集体实施过程,即判断tree2中的所有节点是否与tree1中对应位置的节点相同
bool treesEqual(Tree tree1, Tree tree2)
{
    if(tree1 == NULL)
        return false;
    if(tree2 == NULL)
        return true;
    if(tree1->value != tree2->value)
        return false;

    bool b1 = treesEqual(tree1->leftChild, tree2->leftChild);
    bool b2 = treesEqual(tree1->rightChild, tree2->rightChild);

    return b1 && b2;
}

/*
1.因为事先不知道小树在大数中相同的那部分的存在性及起点,所以先要找到具体比较
的入口,然后用上面的方法来具体比较;2.因为上一个函数的递归出口条件也可以说作
为下面函数的出口条件,故下面的函数不需要再写出口条件,当然如果不确定就写上——
重复的代码总比bug好!
*/
bool isSubConstruction2(Tree big, Tree small)
{
    bool result = false;

    result = treesEqual(big,small);
    if(!result)
        result = isSubConstruction2(big->leftChild,small);
    if(!result)
        result = isSubConstruction2(big->rightChild,small);

    return result;
}

//求二叉树的镜像,即所有节点的左右孩子全左右反过来
Tree mirrorTree(Tree tree)
{
    if(tree == NULL)
        return NULL;
    Tree mirTree = (Tree)malloc(sizeof(TreeNode));
    mirTree->value = tree->value;
    //mirTree->leftChild = tree->rightChild;    //迭代会为左右子树分配新的内存并实现左右子树交换,在这里简单的赋值不行
    //mirTree->rightChild = tree->leftChild;
    mirTree->leftChild = mirrorTree(tree->rightChild);
    mirTree->rightChild = mirrorTree(tree->leftChild);

    return mirTree;
}

/*
逐层打印二叉树节点值(从上到下,从左到右)

思路:通过对一个具体的树进行分析,找到入手点:从根节点开始,每打印一个节点,
就把他的左、右孩子放到一个数组中,然后逐次打印出数组中的数即可。那么这个数组
中的元素同时有进有出,数组该定义为多大?数组长度要想增加,就要满足每次进来的
节点值的个数比出去的多,增加最快的情况便是满二叉树的情况--每次出去一个都进来
两个。这种情况下,数组中元素最多的时刻就是最下面一层节点值(数量最多)全部进
入到数组中的时刻( 这之前数组中元素个数在增加,这之后又在减少,故这时刻最多)

下面的函数中,
--printIndex为arr[]中将要输出元素的下标,
--inputIndex为将要输出元素的左孩子(如果存在)放入arr[]中的下标

实际证明,c语言的数组是也可存放结构体的!
*/
void printLayerByLayer_iterate(Tree tree, Tree arr[], int printIndex, int inputIndex)
{
    if(printIndex >= inputIndex)
        return;

    printf("%4d",tree->value);

    int nextIndex = inputIndex;
    if(tree->leftChild != NULL)
    {
        arr[nextIndex] = tree->leftChild;
        nextIndex++;
    }
    if(tree->rightChild != NULL)
    {
        arr[nextIndex] = tree->rightChild;
        nextIndex++;
    }
    printLayerByLayer_iterate((Tree)arr[printIndex+1], arr, printIndex+1, nextIndex);
}

void printLayerByLayer(Tree tree)
{
    int count = nodesCount(tree);
    if(count == 0)
        return;

    int len = count - 1;//(count+1)/2;  //为方便起见(避免数组中元素的整体移动),将数组长度增大
    Tree *a = (Tree*)malloc(sizeof(Tree)*len);
    printLayerByLayer_iterate(tree, a, -1, 0);  //注意:这里的其实打印索引为-1,因为根节点可以直接输出,不需要存入数组,
                                                //下一个要打印的索引才从0开始
    free(a);
}

//证明一个元素互异的数组是不是一个二叉查找树的后序遍历结果
//方法一:利用了数学规律给定实数a、b、c,若在数轴上a、c分布在b的两侧,则(a-c)(b-c)<0。
bool isSearchedTree1(int a[], int startIndex, int endIndex)
{
    if(endIndex - startIndex < 2)   //在假设条件下,只有两个元素时一定是查找树
        return true;

    int root =a[endIndex];
    int rightStartIndex = startIndex+1; //右子树起始点
    for(int i = startIndex; i < endIndex-1; i++)
    {
        int mul = (root - a[i]) * (root - a[i+1]);
        if(mul < 0)
        {
            //nextStartIndex = i+1;
            break;
        }
        else
            rightStartIndex++;
    }
    for(int i = rightStartIndex; i < endIndex - 1; i++)
    {
        int mul = (root - a[i]) * (root - a[i+1]);
        if(mul < 0)
            return false;
    }
    bool b1 = isSearchedTree(a, startIndex,rightStartIndex-1);
    bool b2 = isSearchedTree(a, rightStartIndex, endIndex-1);
    return b1 & b2;
}

//方法二:查找树树已定义为左子树节点值<根节点值<右子树节点值,所以判断左右子树直接与根节点比较即可
bool isSearchedTree2(int a[], int len)
{
    if(a == NULL || len < 1)
        return false;
    ValueType root = a[length-1];
    int rightStart = 0;    //右子树起点下标
    for(;rightStart < length-1; rightStart++)
        if(a[rightStart] > root);
            break;
    for(int  i = rightStart; i < length-1; i++)
        if(a[rightStart] < root)
            return false;
    //检验左右子树
    bool b1 = isSearchedTree2(a, rightStart);
    bool b2 = isSearchedTree2(a+rightStart, len-rightStart);    //a[i]的地址为a+i
    return b1 && b2;
}

/*
给定某个值,在二叉树中试找出一条路径,使各节点的value之和等于该值,然后找出
所有符合的路径这里的路径规定为从根节点到叶节点的路径(否则比较麻烦)
*/
void findPath_traceback(Tree tree, int sum, Tree *result, int index, int currentSum)
{
    /*
    //这样不能判断是否没找到,应该有一个数来记录搜索的次数,如果全部节点都被搜索还没找到,才能断定没找到
    if(index <= 0)
    {
        printf("没找到和为%d的路径\n\n",sum);
    }
    */

    if(tree == NULL)
        return;

    result[index] = tree;
    index++;

    currentSum += tree->value;
    bool isLeaf = tree->leftChild == NULL && tree->rightChild == NULL;
    if(currentSum == sum && isLeaf)
    {
        printf("找到了和为%d的路径:",sum);
        for(int i = 0; i < index; i++)
        {
            Tree path = (Tree)result[i];
            printf("%4d",path->value);
        }
        printf("\n\n");
        //return;
    }

    /*
    找到一条路径之后应该继续找下一条,不应该返回。在找到一条路径的情况下:
    1.会回溯到tree的上一个节点,继续寻找其它路径;
    2.下面的两个递归都不会被执行(因为叶节点的leftChild、rightChild都是NULL,所以只会在没找到路径的情况下执行递归)
    */
    int nextIndex = index;
    findPath_traceback(tree->leftChild, sum, result, nextIndex, currentSum);

    //应在左子树遍历完之后再遍历右子树
    findPath_traceback(tree->rightChild, sum, result, nextIndex, currentSum);

    //回溯
    index--;
    currentSum -= tree->value;
}

void findPath(Tree tree, int sum)
{
    int count = nodesCount(tree);
    if(count == 0)
        return;
    Tree *result = (Tree*)malloc(sizeof(Tree)*count);
    findPath_traceback(tree, sum, result, 0, 0);
    free(result);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值