C#编程-第六季-编程内功修炼-算法

05-使用分治法分析最大子数组问题_哔哩哔哩_bilibili

1.分治算法

2.二叉树

3.堆排序

4.动态规划

5.贪心算法
                                                                 

                                      一、  分治算法 

   对于一个规模较小的问题,则直接解决。若其规模较大,则分解为k个规模较小的子问题,子问题互相独立且与主问题形式相同。
去递归的解决这些子问题,然后将各个子问题的解结合得到原问题的解。
f(0)=1
f(1)=9

f(n)=f(n-1)+f(n-1) 

例题:

关于第三种情况,i到中间和中间到j之间的值也一定是最大的,所以左右分别遍历一次找到两个最大的和。

using System;

class Stock
{
    public int MaxProfit(int[] prices)
    {
        return MaxProfit(prices, 0, prices.Length - 1);
    }

    private int MaxProfit(int[] prices, int start, int end)
    {
        if (start >= end)
        {
            return 0;
        }

        int mid = (start + end) / 2;

        // 计算左半部分的最大收益
        int leftMaxProfit = MaxProfit(prices, start, mid);

        // 计算右半部分的最大收益
        int rightMaxProfit = MaxProfit(prices, mid + 1, end);

        // 计算跨越左右两部分的最大收益
        int crossMaxProfit = MaxCrossProfit(prices, start, mid, end);

        // 返回三者中的最大值
        return Math.Max(Math.Max(leftMaxProfit, rightMaxProfit), crossMaxProfit);
    }

    private int MaxCrossProfit(int[] prices, int start, int mid, int end)
    {
        int leftMax = int.MinValue;
        int rightMin = int.MaxValue;

        for (int i = mid; i >= start; i--)
        {
            leftMax = Math.Max(leftMax, prices[i]);
        }

        for (int i = mid + 1; i <= end; i++)
        {
            rightMin = Math.Min(rightMin, prices[i]);
        }

        return rightMin - leftMax;
    }
}

class Program
{
    static void Main()
    {
        int[] prices = { 7, 1, 5, 3, 6, 4 };
        Stock stock = new Stock();
        int maxProfit = stock.MaxProfit(prices);
        Console.WriteLine("最大收益为:" + maxProfit);
    }
}


注释:该方法首先拆分成三个部分,只计算第三种情况,然后把前两种情况再拆分,直到他们的中间数也允许他们成为第三种情况为止。 然后比较这三个情况中最大的那个数即可。

计算第三种情况的方法:该递归终止的条件,也就是左右无法再继续分下去,只有一个值的时候。

                             二、树(编号比索引少1)

结点关系:孩子,兄弟        度:拥有子树的个数       树采用链式存储↓,但完全二叉树可以采用顺序存储。

1.   data parent        2.data child1 child2 child3             3.data  firstchild  rightsilb  孩子兄弟表示法,并且只保存第一个孩子与第一个兄弟

                      2.1 二叉树:  每个根节点至多只有左右两个结点

左斜树:只有左结点     
 右斜树:只有右结点
满二叉树:每个结点都有左右子结点,且数量相同
完全二叉树:比起满二叉树,可以略微宽松。从右向左可以逐渐没有

完全二叉树的性质:若一个二叉树有n个结点,从1开始依次编号, 那么对于任意结点i
则除1外,每个结点的父节点为 i/2, 左孩子是2i,右孩子是2i+1。  
 2i>n说明无左孩子,2i+1>n说明无右孩子。

13-使用代码实现二叉树的顺序存储_哔哩哔哩_bilibili

也就是说,即使该结点为空,也要按顺序来存储。到时只要判断数组是否为空,就可以知道该结点是否为空了。  

所以造成了空间浪费,一般顺序存储只用于完全二叉树。

这种方法耗存比较多,而且访问不到父节点,所以如果是完全二叉树,不需要用这种方法。

以下是四种遍历方式:14-二叉树的遍历(代码实现)_哔哩哔哩_bilibili

是为了防止不是完全二叉树的情况下,出现空结点的情况。如果空结点则把它的数组位置设为-1

(前序遍历)中左右

编号是从1开始。索引是从0开始,所以编号=索引+1;

然后输出当前节点,获得左节点和右节点的索引。输出他们其实相当于递归调用

注意。因为传进来的是index,所以-1再加1

【这里结束递归的条件写错了,应该是index > = count, 而不是数组的长度。因为空结点的问题,数组不一定能装满,所以应该以存放的数据个数为界限】

(中序遍历)左中右

代码和前序大同小异,只不过调换一下顺序。

(后序遍历)左右中     不再举例

(层序遍历)直接遍历整个数组即可,因为它本来就是顺序存储的。

但要注意遍历的是已有数据的长度,不需要遍历整个数组的长度。同时考虑空结点的问题

                                            2.2 二叉排序树

排序、查找、删除、插入都很方便。 比它小的放左边,比它大的放右边就好了。

第三种情况,因为中间结点一定是在左(小)右(大)两者之间的值,所以要么是左边最大的,要么是右边选一个最小的。所以要找到左边最大的结点,就遍历左边所有的右节点即可。要找到右边最小的结点,则遍历右边所有的左节点即可。

因为二叉排序树的存储,跟自身值的大小有关系,并不是想之前学习的完全二叉树使用顺序结构可以存储的 所以我们使用链式结构存储二叉排序树。

节点类

添加方法,前半部分
添加操作

通过中序遍历,从根节点开始

因为左边的编号最小,然后是中间,然后是右边

查找方法

这就是所说的,排序二叉树查找方便的原因。

查找优化

当左右结点为空时,它们被设为头结点的一刻会被判断为node==null ,返回false结束回调

查找-终极简化版

接下来是删除方法,前半部分同样查询然后把这个节点传到删除方法里。

查找删除值

要删除的结点没有子结点,则让该结点的父节点的对应节点为空。

要删除的结点只有一个孩子的情况,让孩子继承该结点的位置,然后取消它的子结点。 不过我感觉直接让node.parent.left/right=node.left/right  更严谨好些

接下来是第三种情况,要删除的结点有两个子节点。 因为前面两种情况都有return,所以没有被返回的一定是第三种情况,就不用写判断条件了。 这里用了遍历右边结点的所有左结点的情况,这样遍历完之后留下来的一定是最后一个左结点,也就是值最小的。 那么这个值本身要被删除的话,也只可能是前两种情况,所有递归调用一下delete方法

                                                    2.3    堆排序

在构造大顶堆时,找到最底下三个当中最大的那个,和三个当中的根节点交换,然后往上循环这个过程,就构成了整体的大顶堆。
从最后一个叶子节点遍历到根节点,只需要将最后一个编号/2可得到它的父节点,依次往上推

第一行是为了得到所有的非子节点,然后遍历他们。每一个根字节都判断左右的大小情况,先得到他们的索引。若左结点存在且大于本身,则更新最大编号为左结点,右边同理。如果这个编号没有变化,就说明没有交换,本身就是最大的,反之,则交换两个结点的位置。

小细节,用来比较原来根节点的tempI被定义在了外面,是为了让判断条件动态变化(随着最大编号的变化而变化,而不是随着i的变化而变化)说白了就是判断还是不是它自己了,所以是自己跟自己的索引比

交换-再构造大顶堆

有一些地方进行了改变。


                                           三、动态规划

第一种方法是 带备忘的自顶向下法
    此方法依然是按照自然的递归形式编写过程,但过程中会保存每个子问题的解(通常保存在一个数组中)。
当需要计算一个子问题的解时,过程首先检查是否已经保存过此解。如果是,则直接返回保存的值,从而节省了计算时间;
如果没有保存过此解,按照正常方式计算这个子问题。我们称这个递归过程是带备忘的。

第二种方法是 自底向上法
    首先恰当的定义子问题的规模,使得任何问题的求解都只依赖于更小的子问题的解。
因而我们将子问题按照规模排序,按从小到大的顺序求解。当求解某个问题的时候,它所依赖的更小的子问题都已经求解完毕,结果已经保存。 

回到钢条问题,假设只切一下分成两段,那么第一段就是从1开始,后面那段就是n-1;然后从2开始,后面那段就是n-2;以此类推。

现在要切成三段的话,就是在第二段的基础上,再重复切割成两段的过程。(第一段的结果已经保存,只要计算二三段之间的情况,而二三段不就是递归调用一二段的算法吗?)

要么就不切割,计算这两种情况的最优解。

代码实现:①自顶而下递归

依次遍历前一段有1节长、有2节长、有n-1节长的情况。

那么总价值就等于 前一段的价值,加上递归后一段(前段价值,后段价值)

然后通过比较更新最大收益,照常加上终止条件。

代码实现:②带备忘的自顶而下递归(之前提过的第一种方法)

(n应该是10吧。。)

长度为多少的情况,结果保存在这个数组中。0的情况也算进去,所以长度+1

将这一段的价值计算结果返回,下次碰到这个数组里相同位置的,就直接返回这个位置的值。

不用再进行后面的代码运算。 (用result[n]!=0 说明已经有相同的赋过值了)

代码实现:③自底向上法(之前提过的第二种方法)

【看不懂了,以后再补注释】

                                     3.2 背包问题的动态规划

算法:物品重量超过容量,直接总数减1不再考虑。  不超过容量,不放入,则同样减1不考虑。

不超过且放入,则数量减1,容量增加物体重量,价值增加物体价值。

代码实现:

当数量为0或重量为0时应该结束递归;

超过背包容量则直接返回,下一个物品计算;

第一种最大价值是放入,第二种最大价值是不放入。返回更大的那一个值

带备忘的版本



四、贪心算法

【未完待续】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值