算法系列15天速成 第十一天 树操作(上)

先前我们讲的都是“线性结构”,他的特征就是“一个节点最多有一个”前驱“和一个”后继“。那么我们今天讲的树会是怎样的呢?

我们可以对”线性结构“改造一下,变为”一个节点最多有一个"前驱“和”多个后继“。哈哈,这就是我们今天说的”树“。

一: 树

我们思维中的”树“就是一种枝繁叶茂的形象,那么数据结构中的”树“该是怎么样呢?对的,他是一种现实中倒立的树。

1:术语

其实树中有很多术语的,这个是我们学习树形结构必须掌握的。

<1>  父节点,子节点,兄弟节点

这个就比较简单了,B和C的父节点就是A,反过来说就是B和C是A的子节点。B和C就是兄弟节点。

<2>  结点的度

其实”度“就是”分支数“,比如A的分支数有两个“B和C",那么A的度为2。

<3> 树的度

看似比较莫名其妙吧,他和”结点的度“的区别就是,树的度讲究大局观,乃树中最大的结点度,其实也就是2。

<4> 叶结点,分支结点

叶结点就是既没有左孩子也没有右孩子结点,也就是结点度为0。分支节点也就是if的else的条件咯。

<5> 结点的层数

这个很简单,也就是树有几层。

<6> 有序树,无序树

有序树我们先前也用过,比如“堆”和“二叉排序树”,说明这种树是按照一定的规则进行排序的,else条件就是无序树。

<7>  森林

现实中,很多的树形成了森林,那在数据结构中,我们把上图的“A”节点砍掉,那么B,C子树合一起就是森林咯。

2: 树的表示

树这个结构的表示其实有很多种,常用的也就是“括号”表示法。
     比如上面的树就可以表示为:(A(B(D),(E)),(C(F),(G)))

二: 二叉树

在我们项目开发中,很多地方都会用到树,但是多叉树的处理还是比较纠结的,所以俺们本着“大事化小,小事化了“的原则

把”多叉树“转化为”二叉树“,那么问题就简化了很多。

1: ”二叉树“和”树“有什么差异呢?

第一点:  树的度没有限制,而“二叉树”最多只能有两个,不然也就不叫二叉树了,哈哈。
         第二点:树中的子树没有左右划分,很简单啊,找不到参照点,二叉树就有参照物咯。

2: 二叉树的类型

二叉树中有两种比较完美的类型,“完全二叉树”和“满二叉树”。

<1>  满二叉树

除叶子节点外,所有节点的度都为2,文章开头处的树就是这里的“满二叉树”。

<2>  完全二叉树

必须要满足两个条件就即可:  干掉最后一层,二叉树变为“满二叉树”。

最后一层的叶节点必须是“从左到右”依次排开。

我们干掉文章开头处的节点“F和”G",此时还是“完全二叉树”,但已经不是“满二叉树”了,你懂的。

3: 二叉树的性质

二叉树中有5点性质非常重要,也是俺们必须要记住的。

<1>  二叉树中,第i层的节点最多有2(i-1)个。

<2>  深度为k的二叉树最多有2k-1个节点。

<3>  二叉树中,叶子节点树为N1个,度为2的节点有N2个,那么N1=N2+1。

<4>  具有N个结点的二叉树深度为(Log2 N)+1层。

<5>  N个结点的完全二叉树如何用顺序存储,对于其中的一个结点i,存在以下关系,

2*i是结点i的父结点。

i/2是结点i的左孩子。

(i/2)+1是结点i的右孩子。

4: 二叉树的顺序存储

同样的存储方式也有两种,“顺序存储”和“链式存储”。

<1> 顺序存储

说实话,树的存储用顺序结构比较少,因为从性质定理中我们都可以看出只限定为“完全二叉树”,那么如果二叉树不是

“完全二叉树”,那我们就麻烦了,必须将其转化为“完全二叉树”,将空的节点可以用“#”代替,图中也可看出,为了维护

性质定理5的要求,我们牺牲了两个”资源“的空间。

<2> 链式存储

上面也说了,顺序存储会造成资源的浪费,所以嘛,我们开发中用的比较多的还是“链式存储”,同样“链式存储”

也非常的形象,非常的合理。

一个结点存放着一个“左指针”和一个“右指针”,这就是二叉链表。

如何方便的查找到该结点的父结点,可以采用三叉链表。

5: 常用操作

一般也就是“添加结点“,“查找节点”,“计算深度”,“遍历结点”,“清空结点”

<1> 这里我们就用二叉链表来定义链式存储模型

代码如下:

#region 二叉链表存储结构
    /// <summary>
/// 二叉链表存储结构
/// </summary>
/// <typeparam name="T"></typeparam>
    public class ChainTree<T>
    {
        public T data;

public ChainTree<T> left;

public ChainTree<T> right;
    }
    #endregion

<2> 添加结点

要添加结点,我们就要找到添加结点的父结点,并且根据指示插入到父结点中指定左结点或者右结点。

代码如下:

#region 将指定节点插入到二叉树中
        /// <summary>
/// 将指定节点插入到二叉树中
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
/// <param name="node"></param>
/// <param name="direction">插入做左是右</param>
/// <returns></returns>
        public ChainTree<T> BinTreeAddNode<T>(ChainTree<T> tree, ChainTree<T> node, T data, Direction direction)
        {
            if (tree == null)
                return null;

if (tree.data.Equals(data))
            {
                switch (direction)
                {
                    case Direction.Left:
                        if (tree.left != null)
                            throw new Exception("树的左节点不为空,不能插入");
                        else
                            tree.left = node;

break;
                    case Direction.Right:
                        if (tree.right != null)
                            throw new Exception("树的右节点不为空,不能插入");
                        else
                            tree.right = node;

break;
                }
            }

BinTreeAddNode(tree.left, node, data, direction);
            BinTreeAddNode(tree.right, node, data, direction);

return tree;
        }
        #endregion

<3>  查找节点

二叉树中到处都散发着递归思想,很能锻炼一下我们对递归的认识,同样查找也是用到了递归思想。

代码如下:

#region 在二叉树中查找指定的key
        /// <summary>
///在二叉树中查找指定的key
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
/// <param name="data"></param>
/// <returns></returns>
        public ChainTree<T> BinTreeFind<T>(ChainTree<T> tree, T data)
        {
            if (tree == null)
                return null;

if (tree.data.Equals(data))
                return tree;

return BinTreeFind(tree, data);
        }
        #endregion

<4> 计算深度

这个问题纠结了我二个多小时,原因在于没有深刻的体会到递归,其实主要思想就是递归左子树和右子树,然后得出较大的一个。

代码如下:

#region 获取二叉树的深度
        /// <summary>
/// 获取二叉树的深度
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
/// <returns></returns>
        public int BinTreeLen<T>(ChainTree<T> tree)
        {
            int leftLength;
            int rightLength;

if (tree == null)
                return 0;

//递归左子树的深度
            leftLength = BinTreeLen(tree.left);

//递归右子书的深度
            rightLength = BinTreeLen(tree.right);

if (leftLength > rightLength)
                return leftLength + 1;
            else
                return rightLength + 1;
        }
        #endregion

<5>  遍历结点

二叉树中遍历节点的方法还是比较多的,有“先序”,“中序”,“后序”,“按层”,其实这些东西只可意会,不可言传,真的很难在口头

上说清楚,需要反复的体会递归思想。

先序:先访问根,然后递归访问左子树,最后递归右子树。(DLR模式)

中序:先递归访问左子树,在访问根,最后递归右子树。(LDR模式)

后序:先递归访问左子树,然后递归访问右子树,最后访问根。(LRD模式)

按层:这个比较简单,从上到下,从左到右的遍历节点。

代码如下:

#region 二叉树的先序遍历
        /// <summary>
/// 二叉树的先序遍历
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
        public void BinTree_DLR<T>(ChainTree<T> tree)
        {
            if (tree == null)
                return;

//先输出根元素
            Console.Write(tree.data + "\t");

//然后遍历左子树
            BinTree_DLR(tree.left);

//最后遍历右子树
            BinTree_DLR(tree.right);
        }
        #endregion

#region 二叉树的中序遍历
        /// <summary>
/// 二叉树的中序遍历
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
        public void BinTree_LDR<T>(ChainTree<T> tree)
        {
            if (tree == null)
                return;

//优先遍历左子树
            BinTree_LDR(tree.left);

//然后输出节点
            Console.Write(tree.data + "\t");

//最后遍历右子树
            BinTree_LDR(tree.right);
        }
        #endregion

#region 二叉树的后序遍历
        /// <summary>
/// 二叉树的后序遍历
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
        public void BinTree_LRD<T>(ChainTree<T> tree)
        {
            if (tree == null)
                return;

//优先遍历左子树
            BinTree_LRD(tree.left);

//然后遍历右子树
            BinTree_LRD(tree.right);

//最后输出节点元素
            Console.Write(tree.data + "\t");
        }
        #endregion

#region 二叉树的按层遍历
        /// <summary>
/// 二叉树的按层遍历
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
        public void BinTree_Level<T>(ChainTree<T> tree)
        {
            if (tree == null)
                return;

//申请保存空间
            ChainTree<T>[] treeList = new ChainTree<T>[Length];

int head = 0;
            int tail = 0;

//存放数组
            treeList[tail] = tree;

//循环链中计算tail位置
            tail = (tail + 1) % Length;

while (head != tail)
            {
                var tempNode = treeList[head];

head = (head + 1) % Length;

//输出节点
                Console.Write(tempNode.data + "\t");

//如果左子树不为空,则将左子树存于数组的tail位置
                if (tempNode.left != null)
                {
                    treeList[tail] = tempNode.left;

tail = (tail + 1) % Length;
                }

//如果右子树不为空,则将右子树存于数组的tail位置
                if (tempNode.right != null)
                {
                    treeList[tail] = tempNode.right;

tail = (tail + 1) % Length;
                }
            }
        }
        #endregion

<6> 清空二叉树

虽然C#里面有GC,但是我们能自己释放的就不麻烦GC了,同样清空二叉树节点,我们用到了递归,说实话,这次练习让我喜欢

上的递归,虽然XXX的情况下,递归的不是很好,但是递归还是很强大的。

代码如下:

#region 清空二叉树
        /// <summary>
/// 清空二叉树
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
        public void BinTreeClear<T>(ChainTree<T> tree)
        {
            //递的结束点,归的起始点
            if (tree == null)
                return;

BinTreeClear(tree.left);
            BinTreeClear(tree.right);

//在归的过程中,释放当前节点的数据空间
            tree = null;
        }
        #endregion

最后上一下总的代码

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ChainTree
{
    public class Program
    {
        static void Main(string[] args)
        {
            ChainTreeManager manager = new ChainTreeManager();

//插入节点操作
            ChainTree<string> tree = CreateRoot();

//插入节点数据
            AddNode(tree);

//先序遍历
            Console.WriteLine("\n先序结果为: \n");
            manager.BinTree_DLR(tree);

//中序遍历
            Console.WriteLine("\n中序结果为: \n");
            manager.BinTree_LDR(tree);

//后序遍历
            Console.WriteLine("\n后序结果为: \n");
            manager.BinTree_LRD(tree);

//层次遍历
            Console.WriteLine("\n层次结果为: \n");
            manager.Length = 100;
            manager.BinTree_Level(tree);

Console.WriteLine("\n树的深度为:" + manager.BinTreeLen(tree) + "\n");

Console.ReadLine();

}

#region 生成根节点
        /// <summary>
/// 生成根节点
/// </summary>
/// <returns></returns>
        static ChainTree<string> CreateRoot()
        {
            ChainTree<string> tree = new ChainTree<string>();

Console.WriteLine("请输入根节点,方便我们生成树\n");

tree.data = Console.ReadLine();

Console.WriteLine("根节点生成已经生成\n");

return tree;
        }
        #endregion

#region 插入节点操作
        /// <summary>
/// 插入节点操作
/// </summary>
/// <param name="tree"></param>
        static ChainTree<string> AddNode(ChainTree<string> tree)
        {
            ChainTreeManager mananger = new ChainTreeManager();

while (true)
            {
                ChainTree<string> node = new ChainTree<string>();

Console.WriteLine("请输入要插入节点的数据:\n");

node.data = Console.ReadLine();

Console.WriteLine("请输入要查找的父节点数据:\n");

var parentData = Console.ReadLine();

if (tree == null)
                {
                    Console.WriteLine("未找到您输入的父节点,请重新输入。");
                    continue;
                }

Console.WriteLine("请确定要插入到父节点的:1 左侧,2 右侧");

Direction direction = (Direction)Enum.Parse(typeof(Direction), Console.ReadLine());

tree = mananger.BinTreeAddNode(tree, node, parentData, direction);

Console.WriteLine("插入成功,是否继续?  1 继续, 2 退出");

if (int.Parse(Console.ReadLine()) == 1)
                    continue;
                else
                    break;
            }

return tree;
        }
        #endregion
    }

#region 插入左节点或者右节点
    /// <summary>
/// 插入左节点或者右节点
/// </summary>
    public enum Direction { Left = 1, Right = 2 }
    #endregion

#region 二叉链表存储结构
    /// <summary>
/// 二叉链表存储结构
/// </summary>
/// <typeparam name="T"></typeparam>
    public class ChainTree<T>
    {
        public T data;

public ChainTree<T> left;

public ChainTree<T> right;
    }
    #endregion

/// <summary>
/// 二叉树的操作帮助类
/// </summary>
    public class ChainTreeManager
    {
        #region 按层遍历的Length空间存储
        /// <summary>
/// 按层遍历的Length空间存储
/// </summary>
        public int Length { get; set; }
        #endregion

#region 将指定节点插入到二叉树中
        /// <summary>
/// 将指定节点插入到二叉树中
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
/// <param name="node"></param>
/// <param name="direction">插入做左是右</param>
/// <returns></returns>
        public ChainTree<T> BinTreeAddNode<T>(ChainTree<T> tree, ChainTree<T> node, T data, Direction direction)
        {
            if (tree == null)
                return null;

if (tree.data.Equals(data))
            {
                switch (direction)
                {
                    case Direction.Left:
                        if (tree.left != null)
                            throw new Exception("树的左节点不为空,不能插入");
                        else
                            tree.left = node;

break;
                    case Direction.Right:
                        if (tree.right != null)
                            throw new Exception("树的右节点不为空,不能插入");
                        else
                            tree.right = node;

break;
                }
            }

BinTreeAddNode(tree.left, node, data, direction);
            BinTreeAddNode(tree.right, node, data, direction);

return tree;
        }
        #endregion

#region 获取二叉树指定孩子的状态
        /// <summary>
/// 获取二叉树指定孩子的状态
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
/// <param name="direction"></param>
/// <returns></returns>
        public ChainTree<T> BinTreeChild<T>(ChainTree<T> tree, Direction direction)
        {
            ChainTree<T> childNode = null;

if (tree == null)
                throw new Exception("二叉树为空");

switch (direction)
            {
                case Direction.Left:
                    childNode = tree.left;
                    break;
                case Direction.Right:
                    childNode = tree.right;
                    break;
            }

return childNode;
        }

#endregion

#region 获取二叉树的深度
        /// <summary>
/// 获取二叉树的深度
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
/// <returns></returns>
        public int BinTreeLen<T>(ChainTree<T> tree)
        {
            int leftLength;
            int rightLength;

if (tree == null)
                return 0;

//递归左子树的深度
            leftLength = BinTreeLen(tree.left);

//递归右子书的深度
            rightLength = BinTreeLen(tree.right);

if (leftLength > rightLength)
                return leftLength + 1;
            else
                return rightLength + 1;
        }
        #endregion

#region 判断二叉树是否为空
        /// <summary>
/// 判断二叉树是否为空
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
/// <returns></returns>
        public bool BinTreeisEmpty<T>(ChainTree<T> tree)
        {
            return tree == null ? true : false;
        }
        #endregion

#region 在二叉树中查找指定的key
        /// <summary>
///在二叉树中查找指定的key
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
/// <param name="data"></param>
/// <returns></returns>
        public ChainTree<T> BinTreeFind<T>(ChainTree<T> tree, T data)
        {
            if (tree == null)
                return null;

if (tree.data.Equals(data))
                return tree;

return BinTreeFind(tree, data);
        }
        #endregion

#region 清空二叉树
        /// <summary>
/// 清空二叉树
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
        public void BinTreeClear<T>(ChainTree<T> tree)
        {
            //递的结束点,归的起始点
            if (tree == null)
                return;

BinTreeClear(tree.left);
            BinTreeClear(tree.right);

//在归的过程中,释放当前节点的数据空间
            tree = null;
        }
        #endregion

#region 二叉树的先序遍历
        /// <summary>
/// 二叉树的先序遍历
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
        public void BinTree_DLR<T>(ChainTree<T> tree)
        {
            if (tree == null)
                return;

//先输出根元素
            Console.Write(tree.data + "\t");

//然后遍历左子树
            BinTree_DLR(tree.left);

//最后遍历右子树
            BinTree_DLR(tree.right);
        }
        #endregion

#region 二叉树的中序遍历
        /// <summary>
/// 二叉树的中序遍历
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
        public void BinTree_LDR<T>(ChainTree<T> tree)
        {
            if (tree == null)
                return;

//优先遍历左子树
            BinTree_LDR(tree.left);

//然后输出节点
            Console.Write(tree.data + "\t");

//最后遍历右子树
            BinTree_LDR(tree.right);
        }
        #endregion

#region 二叉树的后序遍历
        /// <summary>
/// 二叉树的后序遍历
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
        public void BinTree_LRD<T>(ChainTree<T> tree)
        {
            if (tree == null)
                return;

//优先遍历左子树
            BinTree_LRD(tree.left);

//然后遍历右子树
            BinTree_LRD(tree.right);

//最后输出节点元素
            Console.Write(tree.data + "\t");
        }
        #endregion

#region 二叉树的按层遍历
        /// <summary>
/// 二叉树的按层遍历
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
        public void BinTree_Level<T>(ChainTree<T> tree)
        {
            if (tree == null)
                return;

//申请保存空间
            ChainTree<T>[] treeList = new ChainTree<T>[Length];

int head = 0;
            int tail = 0;

//存放数组
            treeList[tail] = tree;

//循环链中计算tail位置
            tail = (tail + 1) % Length;

while (head != tail)
            {
                var tempNode = treeList[head];

head = (head + 1) % Length;

//输出节点
                Console.Write(tempNode.data + "\t");

//如果左子树不为空,则将左子树存于数组的tail位置
                if (tempNode.left != null)
                {
                    treeList[tail] = tempNode.left;

tail = (tail + 1) % Length;
                }

//如果右子树不为空,则将右子树存于数组的tail位置
                if (tempNode.right != null)
                {
                    treeList[tail] = tempNode.right;

tail = (tail + 1) % Length;
                }
            }
        }
        #endregion

}
}

我们把文章开头的“二叉树”的节点输入到我们的结构中,看看遍历效果咋样。

(0)

相关推荐

  • 算法系列15天速成 第四天 五大经典查找【上】

    在我们的算法中,有一种叫做线性查找. 分为:顺序查找.        折半查找. 查找有两种形态: 分为:破坏性查找,   比如有一群mm,我猜她们的年龄,第一位猜到了是23+,此时这位mm已经从我脑海里面的mmlist中remove掉了. 哥不找23+的,所以此种查找破坏了原来的结构. 非破坏性查找, 这种就反之了,不破坏结构. 顺序查找: 这种非常简单,就是过一下数组,一个一个的比,找到为止. 复制代码 代码如下: using System;using System.Collections.

  • 算法系列15天速成 第七天 线性表【上】

    哈哈,我们的数据也一样,存在这三种基本关系,用术语来说就是: <1>  线性关系.<2>  树形关系.<3>  网状关系. 一: 线性表 1 概念:                 线性表也就是关系户中最简单的一种关系,一对一.                  如:学生学号的集合就是一个线性表. 2 特征:                 ① 有且只有一个"首元素".                 ② 有且只有一个"末元素".

  • 算法系列15天速成 第八天 线性表【下】

    一:线性表的简单回顾 上一篇跟大家聊过"线性表"顺序存储,通过实验,大家也知道,如果我每次向顺序表的头部插入元素,都会引起痉挛,效率比较低下,第二点我们用顺序存储时,容易受到长度的限制,反之就会造成空间资源的浪费. 二:链表 对于顺序表存在的若干问题,链表都给出了相应的解决方案. 1. 概念:其实链表的"每个节点"都包含一个"数据域"和"指针域". "数据域"中包含当前的数据. "指针域"

  • 算法系列15天速成 第三天 七大经典排序【下】

    直接插入排序: 这种排序其实蛮好理解的,很现实的例子就是俺们斗地主,当我们抓到一手乱牌时,我们就要按照大小梳理扑克,30秒后, 扑克梳理完毕,4条3,5条s,哇塞......  回忆一下,俺们当时是怎么梳理的. 最左一张牌是3,第二张牌是5,第三张牌又是3,赶紧插到第一张牌后面去,第四张牌又是3,大喜,赶紧插到第二张后面去, 第五张牌又是3,狂喜,哈哈,一门炮就这样产生了. 怎么样,生活中处处都是算法,早已经融入我们的生活和血液. 下面就上图说明:              看这张图不知道大家可

  • 算法系列15天速成 第九天 队列

    一:概念 队列是一个"先进先出"的线性表,牛X的名字就是"First in First Out(FIFO)",生活中有很多这样的场景,比如读书的时候去食堂打饭时的"排队".当然我们拒绝插队. 二:存储结构 前几天也说过,线性表有两种"存储结构",① 顺序存储,②链式存储.当然"队列"也脱离不了这两种服务,这里我就分享一下"顺序存储". 顺序存储时,我们会维护一个叫做"head头

  • 算法系列15天速成 第十四天 图【上】

    今天来分享一下图,这是一种比较复杂的非线性数据结构,之所以复杂是因为他们的数据元素之间的关系是任意的,而不像树那样 被几个性质定理框住了,元素之间的关系还是比较明显的,图的使用范围很广的,比如网络爬虫,求最短路径等等,不过大家也不要胆怯, 越是复杂的东西越能体现我们码农的核心竞争力. 既然要学习图,得要遵守一下图的游戏规则. 一: 概念 图是由"顶点"的集合和"边"的集合组成.记作:G=(V,E): <1> 无向图 就是"图"中的边没

  • 算法系列15天速成 第一天 七大经典排序【上】

    针对现实中的排序问题,算法有七把利剑可以助你马道成功. 首先排序分为四种:       交换排序: 包括冒泡排序,快速排序.      选择排序: 包括直接选择排序,堆排序.      插入排序: 包括直接插入排序,希尔排序.      合并排序: 合并排序. 那么今天我们讲的就是交换排序,我们都知道,C#类库提供的排序是快排,为了让今天玩的有意思点,我们设计算法来跟类库提供的快排较量较量.争取KO对手. 冒泡排序: 首先我们自己来设计一下"冒泡排序",这种排序很现实的例子就是:我抓一

  • 算法系列15天速成 第五天 五大经典查找【中】

    哈希查找: 对的,他就是哈希查找,说到哈希,大家肯定要提到哈希函数,呵呵,这东西已经在我们脑子里面形成固有思维了.大家一定要知道"哈希"中的对应关系.     比如说: "5"是一个要保存的数,然后我丢给哈希函数,哈希函数给我返回一个"2",那么此时的"5"和"2"就建立一种对应关系,这种关系就是所谓的"哈希关系",在实际应用中也就形成了"2"是key,"5

  • 算法系列15天速成 第十二天 树操作【中】

    先前说了树的基本操作,我们采用的是二叉链表来保存树形结构,当然二叉有二叉的困扰之处,比如我想找到当前结点的"前驱"和"后继",那么我们就必须要遍历一下树,然后才能定位到该"节点"的"前驱"和"后继",每次定位都是O(n),这不是我们想看到的,那么有什么办法来解决呢? (1) 在节点域中增加二个指针域,分别保存"前驱"和"后继",那么就是四叉链表了,哈哈,还是有点浪费空

  • 算法系列15天速成 第十天 栈

    一: 概念 栈,同样是一种特殊的线性表,是一种Last In First Out(LIFO)的形式,现实中有很多这样的例子, 比如:食堂中的一叠盘子,我们只能从顶端一个一个的取. 二:存储结构 "栈"不像"队列",需要两个指针来维护,栈只需要一个指针就够了,这得益于栈是一种一端受限的线性表. 这里同样用"顺序结构"来存储这个"栈",top指针指向栈顶,所有的操作只能在top处. 代码段: 复制代码 代码如下: #region

  • 算法系列15天速成——第十三天 树操作【下】

    听说赫夫曼胜过了他的导师,被认为"青出于蓝而胜于蓝",这句话也是我比较欣赏的,嘻嘻. 一  概念 了解"赫夫曼树"之前,几个必须要知道的专业名词可要熟练记住啊. 1: 结点的权 "权"就相当于"重要度",我们形象的用一个具体的数字来表示,然后通过数字的大小来决定谁重要,谁不重要. 2: 路径 树中从"一个结点"到"另一个结点"之间的分支. 3: 路径长度 一个路径上的分支数量. 4: 树

  • 算法系列15天速成——第十五天 图【下】(大结局)

    今天是大结局,说下"图"的最后一点东西,"最小生成树"和"最短路径". 一: 最小生成树 1. 概念 首先看如下图,不知道大家能总结点什么. 对于一个连通图G,如果其全部顶点和一部分边构成一个子图G1,当G1满足: ① 刚好将图中所有顶点连通.②顶点不存在回路.则称G1就是G的"生成树". 其实一句话总结就是:生成树是将原图的全部顶点以最小的边连通的子图,这不,如下的连通图可以得到下面的两个生成树. ② 对于一个带权的连通图,

  • 算法系列15天速成 第六天 五大经典查找【下】

    大家是否感觉到,树在数据结构中大行其道,什么领域都要沾一沾,碰一碰.就拿我们前几天学过的排序就用到了堆和今天讲的"二叉排序树",所以偏激的说,掌握的树你就是牛人了. 今天就聊聊这个"五大经典查找"中的最后一个"二叉排序树". 1. 概念:     <1> 其实很简单,若根节点有左子树,则左子树的所有节点都比根节点小.                             若根节点有右子树,则右子树的所有节点都比根节点大.     &

  • 算法系列15天速成 第二天 七大经典排序【中】

    首先感谢朋友们对第一篇文章的鼎力支持,感动中.......  今天说的是选择排序,包括"直接选择排序"和"堆排序". 话说上次"冒泡排序"被快排虐了,而且"快排"赢得了内库的重用,众兄弟自然眼红,非要找快排一比高下. 这不今天就来了两兄弟找快排算账. 1.直接选择排序: 先上图: 说实话,直接选择排序最类似于人的本能思想,比如把大小不一的玩具让三岁小毛孩对大小排个序, 那小孩首先会在这么多玩具中找到最小的放在第一位,然后找到次

随机推荐