C++从零开始 ------ 二叉树与 轮子的故事

1 篇文章 0 订阅
1 篇文章 0 订阅

C++从零开始 ------ 二叉树与 轮子的故事

C++从零开始 ------ 二叉树与 轮子的故事

轮子

引入

一个优秀的程序员拿到一个新语言的第一件事往往就是造轮子 ,没有轮子的程序就像没有轮子的汽车,怎么跑都跑不动 。大家都喜欢Python,为什么呢?因为Python全部是轮子,C++跟Python一比,就是婴儿车对战高达(就便利性来说),当然,C++优秀的性能也是不容小觑的,想象一下全副武装的C++,那就是无敌的感觉啦。

我们一直说的轮子就是,自从这个概念被发明以来,人类的bug就以指数级下降,毫不夸张的说,没有类就没有今天的3A大作。最简单的类就长下面这个亚子。

class myclass{
// private和publlic 是类中特有的两个关键字,新手可以全部用public
// private一般存储不会被外界使用的变量,只在类中使用
private:
    int data;
public:
// public中写函数
    // 以类名命名的函数被称为构造函数,它将会在类被创建的时候调用
    myclass(){data = 0};
    // 你可以写多个构造函数,类被创建的时候会根据传入的参数调用合适的构造函数
    myclass(int data){this->data = data}
}

然后我们可以在main函数中这样构造一个类

// 这个main请跟在上面的class底下写
int main()
{
    // 创建一个myclass的指针a
    myclass * a = new myclass();   
    myclass * b = new myclass(2);
}

下面是最关键的,也是类能消灭诸多bug的神奇之处,它是松耦合的,也就是说你可以把它单独拿出来给另一个程序使用,他有一个好兄弟叫做.h文件,如果你把class单独拎出来放到一个叫filename.h的地方,那么你的main函数可以不紧跟着下面写,而可以写到另一个cpp里面,像这样(.h和.cpp文件请放在同一个文件夹中)

// 加载头文件
#include "filename.h"
// 这个main函数写在main.cpp中
int main()
{
    // 创建一个myclass的指针a
    myclass * a = new myclass();   
    myclass * b = new myclass(2);
}

所以在类的概念被提出 来之后,编程行业基本上就是上github上搜一堆.h文件,include一下,调几个函数,一个项目就完成了,它不香吗?

另外,除了类可以放在.h文件中,什么#include<iostream>之类的也可以放在.h文件中,让main.cpp变得更加清爽,由此衍生出来的QT项目有时候mian.cpp就只有一句话(当然这也应该是我们的追求)

#include <QT>
qt.start();

二叉树

创建一个.h文件,写入以下东西,直接看代码,要说的都在代码里

#include <iostream>
#include <vector>
using namespace std;
// 这里节点我没有使用类,而是使用C风格的struct,与类是一样的效果 
struct node       
{
    char data;     // 数据
    node * lnext; // 左节点 这里为什么要用指针?想一想!
    node * rnext; // 右节点
    bool done;     // 用于dfs判断是否访问过
    node(char d)   // 构造函数(用来初始化一个节点)
    {
        data  = d;    
        lnext = NULL; //NULL 是C++保留字,表示空
        rnext = NULL;
        done = false;
    }
};


// 一棵二叉树
class Tree
{
//新手最好一律public
public:
    node * root = NULL;    // 树根
    // 构造函数,r代表树根
    Tree(){root = NULL;};
    Tree(node * root)
    {
        this->root = root; // this->root 特指类中的root,root指传入参数
    }
    // root 代表树上的某一节点(不是类中本有的变量root), n表示新节点,isriaght 表示向左插入还是向右插入
    void insert(node *& root, node * n, bool isright)
    {
        // 保证节点右节点是空
        if(isright & root->rnext == NULL) root ->rnext =  n;
        else if(root->lnext == NULL) root ->lnext =n;
        else printf("insert faill!\n");
    };
    // 输出前序遍历结果
    void outputPreoder(node * root)
    {
        if (root!=NULL)
        {
            printf("%c ",root->data);
            outputPreoder(root->lnext);
            outputPreoder(root->rnext);
        }
    }
    // 依据前序遍历建树,root代表根节点,i代表进行到的前序遍历数组的下标,input代表前序遍历数组
    void preOrder(node *& root,int &i,vector<char> input) // *&的意思是再次函数内所有对root的改变都将·生效·, 如果想让root的改变只在该函数内·生效·,就用*,&i是一样的意思(仔细想一想为什么要&i)
    {
        if(i>=input.size())return ;                 // 已经到头
        if(input[i]=='#')                           // 空节点,没必要继续
        {
            i++;
            root=NULL;
        }
        else									// 建树
        {
            root=new node(input[i]);			  // 每个节点都需要空间
            // 简单的说就是一个节点要从NULL变为一个有实际意义的节点,就必须要分配空间,即new
            i++;
            preOrder(root->lnext,i,input); //递归
            preOrder(root->rnext,i,input);
        }
    }
    // 这里dfs将输出每个节点的数据
    // 什么问题适用于dfs解决?父节点需要解决的事情依赖于子节点的解决
   void dfs(node *& root)
    {
        if(root  == NULL) return;
        root->done = true;
        if (root->lnext!=NULL) //NULL数据是不可访问的,所以必须先判断
            if (!root->lnext->done) 
            {
                dfs(root->lnext);
                // TODO:在这里你可以进行数据处理
                // 并且由于dfs的特性,左子节点的数据已经处理好
                // 子节点的数据可以这样获得root->lnext->data(左子节点)
            }
        if (root->rnext!=NULL)
            if(!root->rnext->done)
            {
                dfs(root->rnext);
                // TODO:同上
            }
        // 经过数据处理后的data,如果进行了数据处理的话
        printf("%c",root->data);
    }
};

要想学好树,学好递归很重要,我甚至可以大胆的说,递归是程序员质检师。我大二的算法老师一句话道破天机

递归就是耍流氓,我不会写怎么办?递归。我相信这个函数可以做到我让他做的,一直相信,那这个函数就真的做到了。

学递归没有什么太好的办法,只能多写程序,多练,多想,直到想表达一件复杂的事情的时候想到递归,那基本就可以了。

还有一个问题:指针,怎么学好指针?这个倒是有捷径

1. 写几个有指针的程序,然后开启单步调试,看看这些指针究竟指到哪里去了。
2. 牢记一点:在编程里等于号永远不是等于号!它是·赋值·!!!

上述代码可以自行在main.cpp中include并测试,这里给出一个测试方案:

#include "tree.h"
int main()
{
    vector <char> a = {'A','B','C','#','#','D','E','#','G','#','#','F','#','#','#'};
    Tree *mytree = new Tree();
    int i = 0;
    mytree->preOrder(mytree->root,i,a);
    mytree->outputPreoder(mytree->root);
    mytree->dfs(mytree->root);
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值