一文打通分治算法及递归算法的任通二脉:由二叉树遍历到排序算法

引言

之前在二叉树的苦修内功篇中,笔者简单介绍了树的遍历,并提出了树的遍历其实是个非常有趣的过程,树的遍历是递归的最经典的模板,它演示了递归的执行过程,更加揭示了分治法的本质思想。但是没有详细的说明树的遍历,分治算法,递归和一些排序都有什么关系,这篇文章就一起来尝试点破这些算法及数据结构的纱窗,打通任通二脉,一步青云。
还没看过苦修内功篇的朋友可以花一两分钟瞅一眼:树–苦修内功篇

前篇

分治及递归

分治法思路

字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
分治算法可以由递归过程来表示,因为分治法就是一种找大规模问题与小规模问题关系的方法,是递归设计的一种具体策略。

分治法(递归)步骤

在这里插入图片描述

step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;

step2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题

step3 合并:将各个子问题的解合并为原问题的解。

以前学习分治法看到这个基本步骤时,脑子忽然间恍惚了一下,仿佛在哪一个瞬间经历过以上学习的过程,后来想了想,唉,这不就是之前学习递归的步骤嘛。你看,我这么迟钝的人都能理解这个过程,我想你绝对没有问题的!

分治法经典例题

下面给出几个分治法的例题,有兴趣的朋友可以感受感受分治思想的光辉时刻,与伟大的前人进行思想交流!
最大子序和
多数元素
快速幂算法
当然,分治算法还有许多内容需要详细学习,这里就简单了解一下即可。

开篇

苦修内功篇我们了解了树的遍历,前篇我们了解了分治算法和递归过程,本篇我们就从几种排序来探究一下这些算法及数据结构的联系!

快速排序

该算法的实现可分为以下几步:

  1. 在数组中选一个基准数(通常为数组第一个);

  2. 将数组中小于基准数的数据移到基准数左边,大于基准数的移到右边;

  3. 对于基准数左、右两边的数组,不断重复以上两个过程,直到每个子集只有一个元素,即为全部有序。

看一下算法实现:

在快排代码实现中我给出了划分时的注解,请注意划分的先后顺序。

void QuickSort(int arr[], int low, int high)
{
    if (low < high) //递归跳出条件
    {
        int pivotpos = Partition(arr, low, high); //划分
        QuickSort(arr, low, pivotpos - 1);        //递归划分左子表
        QuickSort(arr,pivotpos + 1, high);       //递归划分右子表
    }
}
int Partition(int arr[], int low, int high)
{
    int pivot = arr[low]; //将第一个元素作枢轴
    while (low < high)    //用low,high搜索枢轴的最终位置
    {
        while (low < high && arr[high] >= pivot)
            --high;
        arr[low] = arr[high]; //比枢轴小的元素左移
        while (low < high && arr[low] <= pivot)
            ++low;
        arr[high] = arr[low]; //比枢轴大的元素右移
    }
    arr[low] = pivot; //枢轴元素放在最终位置
    return low;       //返回最终位置
}

看到没有,我们先对一个数组进行了一个划分,也就是将一个整体划分为两部分,再对它的左右部分再进行划分,这不就是分治思想吗,代码实现不就是递归过程吗。这个时候就有小朋友跳出来问:“唉,那和树的遍历有什么关系呢?”没错,我们知道树的遍历可以使用递归进行实现,也就是分治思想的体现,快速排序也是这样子,那你就不想一想,他们相似度那么高,是不是血浓于水的异父异母的亲兄弟呢?

我们回顾一下树的前序遍历框架:

void PreOrderTraverse(BiTree root){
	if(root==NULL)	return;
	visit(root->data);//执行操作
	preOrderTraverse(root->lchild);
	preOrderTraverse(root->rchild);
}

再来看一下快速排序:

void QuickSort(int arr[], int low, int high)
{
    if (low < high) //递归跳出条件
    {
        int pivotpos = Partition(arr, low, high); //划分
        QuickSort(arr, low, pivotpos - 1);        //递归划分左子表
        QuickSort(arr,pivotpos + 1, high);       //递归划分右子表
    }
}

哇塞,你看到了吗,你肯定看到了,这不就是二叉树的前序遍历吗!在树的遍历,先对根节点进行遍历,与其说遍历不如说是对根节点进行划分,因为只有确定了根节点才能确定左右孩子嘛,那快排不就一样了。怎么样,你的任通二脉是不是打通了一半?

归并排序

在这借一张图来说明
在这里插入图片描述
归并排序的代码实现:

void MergcSort(int A[], int low, int high)
{
    if (low < high)
    {
        int mid = (low + high) / 2;
        MergcSort(A, low, high / 2); //对左部分归并排序
        MergcSort(A, mid + 1, high); //右部分归并排序
        Mergc(A, low, mid, high);    //归并
    }
}
//将划分排序后的数组归并
void Mergc(int A[], int low, int mid, int high)
{
    int i, j, k;
    for (k = low; k <= high; k++)
        B[K] = A[K]; //将A所有元素取出到B中
    for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++)
    {
        if (B[i] <= B[j])
            A[k] = B[i++];
        else
            A[k] = B[j++];
    }
    while (i <= mid)
        A[k++] = B[i++];
    while (j <= high)
        A[k++] = B[j++];
}

先对左右子数组排序,然后合并,类似我们在线性表华山论剑篇的合并有序链表的过程,你看这是不是二叉树的后序遍历框架?另外,这不就是传说中的分治算法嘛,我想到这你的任通二脉应该也就完全打通了!

总结

其实二叉树的算法思想的运用广泛,甚至可以说,只要涉及递归,都可以抽象成二叉树的问题。而二叉树的遍历问题,关键在于先搞清楚当前root节点该做什么,然后根据函数定义递归调用子节点,递归调用会让孩子节点做相同的事情。总之,理解了二叉树的思想,也就理解了递归和分治的本质,遇到排序,查找也就容易理解许多,本篇是一篇简单的入门篇,门后的故事还有很多,具体的例题我们树的华山论剑篇再详细探究!
预知门后的世界,关注我Code_Pianist,一起苦修内功,华山论剑!
(笔者最近空闲时间在搭建自己的博客,后续会更新网站地址,可以期待一波)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code_Pianist

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值