Unity C# 爆破计划(十):基本算法

15 篇文章 1 订阅
1 篇文章 0 订阅
本文深入探讨了两种基本算法:并查集和堆排序。并查集用于判断元素是否属于同一集合及高效合并集合,通过优化树结构实现高效查找。堆排序则利用二叉堆性质实现数据的快速排序,介绍了最大堆的插入和删除操作。通过实例代码展示了这两种算法的实现过程。
摘要由CSDN通过智能技术生成


十、基本算法

Covers?基本算法

额……这一节不知道要写点什么好,算法这个主题太大了,游戏中要使用的算法也太多了,这部笔记不是算法讲座,姑且写两个比较经典的。

并查集

并查集就是“并集”和“查集”两个任务,现在假设有一堆集合,我们关注两件事:

  1. 任取两个元素,如何判断它们是否在同一个集合中
  2. 如果不处于同一个集合,如何用最优的方式将两个集合合并

完成这个算法的关键是“如何表示多个不相交且随时可能合并的集”,这要是直接做一个集合容器,然后不断摧毁、挪动元素还了得?这里要用数组来将各个元素连接成一棵树。

我写了一个例子,你最好写自己的,然后对照看看我写得哪里好、哪里不好,欢迎留言讨论:

并查集算法还有一个重要的功能:检查图中是否有环路,这个功能在例子中也完成了。

using static System.Console;

namespace Algorithms
{
    class DisjointSets
    {
        int[] _parent;

        public DisjointSets(int size)
        {
            // Initialize the tree, each node is a tree root:
            _parent = new int[size];
            for (int i = 0; i < size; ++i)
            {
                _parent[i] = -1;
            }
        }

        int FindParentNode(int x, out int depth)
        {
            // Assume x is tree root:
            int p = x;
            depth = 0;

            // Find parent:
            while (_parent[p] != -1)
            {
                p = _parent[p];
                ++depth;
            }

            // Optimize the tree:
            if (p != x)
            {
                _parent[x] = p;
            }

            return p;
        }

        public int GetTotalSets()
        {
            // We just need to count all -1 values in the array:
            int ans = 0;
            for (int i = 0; i < _parent.Length; ++i)
            {
                if (_parent[i] == -1)
                {
                    ++ans;
                }
            }

            return ans;
        }

        public bool IsSameSet(int x, int y)
        {
            // Find tree roots of x and y, search depths are unimportant:
            int px = FindParentNode(x, out int _);
            int py = FindParentNode(y, out int _);

            // We can make sure of the answer:
            return px == py;
        }

        public bool JoinSet(int x, int y)
        {
            // Find tree roots of x and y, saving search depths:
            int px = FindParentNode(x, out int dpx);
            int py = FindParentNode(y, out int dpy);

            // If x and y are in the same set, return false:
            if (px == py)
            {
                return false;
            }

            // Join tree roots, avoiding enlarging search depth:
            if (dpx > dpy)
            {
                _parent[py] = px;
            }
            else
            {
                _parent[px] = py;
            }

            return true;
        }

        public bool JoinSet(int[,] pairs)
        {
            // Assume no two elements are already in the same set before being associated:
            bool ans = true;

            // Make all associations according to the given matrix:
            for (int i = 0; i < pairs.GetLength(0); ++i)
            {
                ans = JoinSet(pairs[i, 0], pairs[i, 1]) && ans;
            }

            // If this is false, there MUST be a circuit within the edges:
            return ans;
        }
    }

    class Program
    {
        static void Main()
        {
            // All edges in a graph that contains 3 disjoint sets:
            // (Visualized graph here: https://s3.ax1x.com/2021/02/20/yIWjS0.png)
            int[,] edges =
            {
                {1, 3}, {2, 3}, {0, 2}, {4, 3},
                {5, 6}, {7, 6}
            };

            // Initialize the DisjointSets object:
            var sets = new DisjointSets(9);

            // Tell the object all the edges:
            // (We can also make sure if there is any circuit in the graph)
            if (!sets.JoinSet(edges))
            {
                WriteLine("Circuit found!\n");
            }
            
            // Check every two elements, see if they are in the same set:
            for (int a = 0; a < 8; ++a)
            {
                for (int b = a + 1; b <= 8; ++b)
                {
                    WriteLine(
                        sets.IsSameSet(a, b)
                            ? "{0} and {1} are in the same set!"
                            : "{0} and {1} are NOT in the same set!", a, b);
                }
            }

            WriteLine("\nThere are {0} disjoint sets!", sets.GetTotalSets());
        }
    }
}

这里用到的测试集合在这里可以看到:

并查集测试数据示意图

二叉堆与堆排序

二叉堆是用线性数组实现的、每个节点带有可比较数值(如整型)的完全二叉树,用它可以实现很多有用的数据结构。完全二叉树是一种非常稳定的二叉树,它的特点是 除了叶子层,每层都是满二叉树(完全填满的二叉树),且 叶子层的所有元素都尽可能处于这一层的左侧

完全二叉树很容易用线性数组实现:

  • 如果按层从上到下、每层内从左到右给每个节点顺次编号 0(根)、1(第 1 层左侧节点)、2(第 1 层右侧节点)、3(第 2 层左起第一个节点)……则每个父节点(编号 n)的左孩子序号一定是 2 n + 1 2n + 1 2n+1,右孩子序号一定是 2 n + 2 2n + 2 2n+2
  • 反推上面的公式,任何一个节点,如果是其父节点的左孩子,则编号必定为奇数(这里假设根节点为 0 号),如果是其父节点的右孩子,则编号必定为偶数。由此我们可由任一节点,推出其父节点和左右孩子;
  • 具有 k 层( k ⩾ 1 k \geqslant 1 k1)的满二叉树节点数为 2 k − 1 2^{k} - 1 2k1,具有 n 个节点的完全二叉树的层数 k 满足关系 2 k − 1 ⩽ n ⩽ 2 k − 1 {2^{k - 1}} \leqslant n \leqslant {2^{k} - 1} 2k1n2k1,从而可以由数组下标推知某节点所在的层。

最大二叉堆是满足最大堆特性的二叉堆,也叫 大根堆最大堆,即 所有节点的值都大于其左右孩子(如果有),亦即:除根节点外,每个节点的父节点都比当前节点大。

我们关心的是如何用 C# 构建一个最大堆,并提供插入节点、提出(删除)最大值两种功能。

我们不关心删除最大堆中任意元素的算法,因为这并不是最大堆的设计意图,二叉排序树更适合进行该功能。

最大堆的建立就是一系列的插入操作,插入算法如下:

  1. 如果堆中没有任何节点,则新元素就是根节点;否则,转步骤 2;
  2. 直接将新元素插入堆的末尾(数组末端);
  3. 访问新元素的父节点,若自身大于父节点的值,将自身与父节点对调,转步骤 4;否则,插入完毕;
  4. 转步骤 3。

从最大堆中提取最大值(并删除)的算法:

  1. 直接取得最大堆根节点的值,这就是堆中最大值;
  2. 将根节点的值替换为堆末尾(数组末端)的值,堆的元素数(占用的数组长度)减 1,现在称根节点为“当前节点”;
  3. 访问当前节点的左右孩子(如果有),如果两个孩子中数值较大的一个大于当前节点,则将当前节点与该孩子对调,现在称该孩子原本所在的位置为“当前节点”,转步骤 4;否则,操作完毕;
  4. 转步骤 3。

根据上面的算法和分析,我写了一个例子,你最好写自己的,然后对照看看我写得哪里好、哪里不好,欢迎留言讨论:

using static System.Console;

namespace Algorithms
{
    class Heap
    {
        int[] _array;
        int _rear;

        public Heap(int capacity = 0xffff)
        {
            // Pre-allocate memory for the heap:
            _array = new int[capacity];

            // Now it's an empty heap:
            _rear = -1;
        }

        public bool IsFull => _rear + 1 == _array.Length;

        public bool IsEmpty => _rear < 0;

        public int Length => _rear + 1;

        public int Capacity => _array.Length;

        static int GetParentOf(int id)
        {
            // A little bit of technique:
            return id == 0 ? -1 : id % 2 == 0 ? (id - 2) / 2 : (id - 1) / 2;
        }

        int GetLeftChildOf(int id)
        {
            // Formula:
            int ans = 2 * id + 1;

            // Make sure that child exists:
            return ans <= _rear ? ans : -1;
        }

        int GetRightChildOf(int id)
        {
            // Formula:
            int ans = 2 * id + 2;

            // Make sure that child exists:
            return ans <= _rear ? ans : -1;
        }

        int GetGreaterChildOf(int id)
        {
            // If it has no child:
            int l = GetLeftChildOf(id);
            if (l == -1)
            {
                return -1;
            }

            // If it has only left child:
            int r = GetRightChildOf(id);
            if (r == -1)
            {
                return l;
            }

            // Otherwise, compare, left child is preferred if equal:
            return _array[r] > _array[l] ? r : l;
        }

        bool Swap(int a, int b)
        {
            // If a or b exceeds the heap:
            if (a > _rear || b > _rear)
            {
                return false;
            }

            // Swap the value of two nodes:
            int tmp = _array[a];
            _array[a] = _array[b];
            _array[b] = tmp;
            return true;
        }

        public bool Push(int data)
        {
            // Refuse to insert if heap is full:
            if (IsFull)
            {
                return false;
            }

            // Just make the root if heap is empty:
            if (IsEmpty)
            {
                _rear = 0;
                _array[0] = data;
                return true;
            }

            // Algorithm:
            int c = ++_rear, p = GetParentOf(c);
            _array[c] = data;
            while (p != -1 && _array[p] < data)
            {
                Swap(p, c);
                c = p;
                p = GetParentOf(c);
            }

            return true;
        }

        public bool Push(int[] data)
        {
            // Refuse to push anything if remaining capacity is not enough:
            if (Capacity - Length < data.Length)
            {
                return false;
            }

            // Push each element in data:
            foreach (int i in data)
            {
                if (!Push(i))
                {
                    return false;
                }
            }

            return true;
        }

        public bool Pop(out int data)
        {
            // If heap is empty, return false to notify, data is meaningless:
            if (IsEmpty)
            {
                data = 0;
                return false;
            }

            // Save root value as output:
            data = _array[0];

            // Just pop the root if heap is empty:
            if (_rear == 0)
            {
                _rear = -1;
                return true;
            }

            // Algorithm:
            _array[0] = _array[_rear--];
            int p = 0, c = GetGreaterChildOf(p);
            while (c != -1 && _array[p] < _array[c])
            {
                Swap(p, c);
                p = c;
                c = GetGreaterChildOf(p);
            }

            return true;
        }
    }

    class Program
    {
        static void Main()
        {
            int[] arr = {4, 14, 9, 13, -12, -21, 0, 0, 0, 127};
            var heap = new Heap();
            heap.Push(arr);

            while (!heap.IsEmpty)
            {
                heap.Pop(out int ans);
                Write(ans + " ");
            }
        }
    }
}

在测试代码中,将一个数组插入到最大堆中,再从堆中取出所有元素(每次都是取最大元素),此时输出结果被自然地从大到小排列,这就是 堆排序 算法的原理。


T.B.C.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值