经典算法题每日演练——第二十一题 十字链表

     上一篇我们看了矩阵的顺序存储,这篇我们再看看一种链式存储方法“十字链表”,当然目的都是一样,压缩空间。

一:概念

    既然要用链表节点来模拟矩阵中的非零元素,肯定需要如下5个元素(row,col,val,down,right),其中:

row:矩阵中的行。

col:矩阵中的列。

val:矩阵中的值。

right:指向右侧的一个非零元素。

down:指向下侧的一个非零元素。

现在我们知道单个节点该如何表示了,那么矩阵中同行的非零元素的表示不就是一个单链表吗?比如如下:

那么进一步来说一个多行的非零元素的表示不就是多个单链表吗,是的,这里我把单链表做成循环链表,我们来看看如何用十字链表

来表示稀疏矩阵。

从上面的十字链表中要注意两个问题:

第一:这里有一个填充色的节点,是十字链表中的总结点,它是记录该矩阵中的(row,col,value)和一个指向下一个头节点的next指针。

第二:每个链表都有一个头指针,总结点用next指针将它们贯穿起来。

 

二:操作

1:数据结构

   刚才也说了,十字链表的总结点有一个next指针,而其他非零节点没有,所以为了方便,我们用一个Unit类包装起来。

#region 单一节点
        /// <summary>
        /// 单一节点
        /// </summary>
        public class Node
        {
            //行号
            public int rows;

            //列号
            public int cols;

            //向下的指针域
            public Node down;

            //向右的指针域
            public Node right;

            //单元值(头指针的next和val)
            public Unit unit;
        }
        #endregion

        #region 统一“表头节点”和“非零节点”
        /// <summary>
        /// 统一“表头节点”和“非零节点”
        /// </summary>
        public class Unit
        {
            //表头节点的next域
            public Node next;

            //非零元素的值
            public int value;
        }
        #endregion

2:初始化

   这一步,我们初始化总结点,并且用next指针将每个单链表的头节点链接成单链表(也就是上图中十字链表的第一行)

#region 十字链表中的“行数,列数,非零元素个数”
        /// <summary>
        /// 十字链表中的“行数,列数,非零元素个数”
        /// </summary>
        /// <param name="rows"></param>
        /// <param name="cols"></param>
        /// <param name="count"></param>
        public void Init(int rows, int cols, int count)
        {
            var len = Math.Max(rows, cols) + 1;

            //从下标1开始算起
            nodes = new Node[len];

            //十字链表的总头节点
            nodes[0] = new Node();

            nodes[0].rows = rows;
            nodes[0].cols = cols;
            nodes[0].unit = new Unit()
            {
                value = count,
                next = null,
            };

            //down和right都指向自身
            nodes[0].right = nodes[0];
            nodes[0].down = nodes[0];

            var temp = nodes[0];

            //初始化多条链表的头结点
            for (int i = 1; i < len; i++)
            {
                nodes[i] = new Node();

                nodes[i].rows = 0;
                nodes[i].cols = 0;
                nodes[i].unit = new Unit()
                {
                    value = 0,
                    next = temp.unit.next
                };

                //给上一个节点的next域赋值
                temp.unit.next = nodes[i];

                //将当前节点作为下一次循环的上一个节点
                temp = nodes[i];

                nodes[i].right = nodes[i];
                nodes[i].down = nodes[i];
            }
        }
        #endregion

3:插入节点

     根据插入节点的row和col将节点插入到十字链表中指定的位置即可。

#region 插入十字链表中
        /// <summary>
        /// 插入十字链表中
        /// </summary>
        /// <param name="nums">矩阵</param>
        /// <param name="rows">矩阵的行数</param>
        /// <param name="cols">矩阵的列数</param>
        /// <param name="count">非0元素个数</param>
        /// <returns></returns>
        public Node[] Insert(int[,] nums, int rows, int cols, int count)
        {
            //初始化操作
            Init(rows, cols, count);

            //插入操作
            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < cols; j++)
                {
                    //直插入"非0元素"
                    if (nums[i, j] != 0)
                    {
                        var node = new Node();

                        node.rows = i + 1;
                        node.cols = j + 1;
                        node.unit = new Unit()
                        {
                            value = nums[i, j]
                        };
                        node.right = node;
                        node.down = node;

                        InsertNode(node);
                    }
                }
            }

            return nodes;
        }
        #endregion

4:打印链表

   我们只要遍历每行链表的right指针即可。

#region 打印十字链表
        /// <summary>
        /// 打印十字链表
        /// </summary>
        /// <param name="nodes"></param>
        public void Print(Node[] nodes)
        {
            var head = nodes[0];

            //遍历每一行的right
            for (int i = 1; i < head.rows + 1; i++)
            {
                var p = nodes[i];

                while (p.right != nodes[i])
                {
                    Console.WriteLine("({0},{1})\tval => {2}",
                        p.right.rows,
                        p.right.cols,
                        p.right.unit.value);

                    //指向下一个节点
                    p = p.right;
                }
            }
        }
        #endregion

总的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.IO;

namespace ConsoleApplication2
{
    public class Program
    {
        public static void Main()
        {
            Crosslist crosslist = new Crosslist();

            int[,] nums = {
            {2,0,4 },
            {0,3,0 },
            {0,0,9 }
           };

            var nodes = crosslist.Insert(nums, 3, 3, 4);

            crosslist.Print(nodes);

            Console.Read();
        }
    }

    /// <summary>
    /// 十字链表
    /// </summary>
    public class Crosslist
    {
        #region 单一节点
        /// <summary>
        /// 单一节点
        /// </summary>
        public class Node
        {
            //行号
            public int rows;

            //列号
            public int cols;

            //向下的指针域
            public Node down;

            //向右的指针域
            public Node right;

            //单元值(头指针的next和val)
            public Unit unit;
        }
        #endregion

        #region 统一“表头节点”和“非零节点”
        /// <summary>
        /// 统一“表头节点”和“非零节点”
        /// </summary>
        public class Unit
        {
            //表头节点的next域
            public Node next;

            //非零元素的值
            public int value;
        }
        #endregion

        Node[] nodes;

        #region 十字链表中的“行数,列数,非零元素个数”
        /// <summary>
        /// 十字链表中的“行数,列数,非零元素个数”
        /// </summary>
        /// <param name="rows"></param>
        /// <param name="cols"></param>
        /// <param name="count"></param>
        public void Init(int rows, int cols, int count)
        {
            var len = Math.Max(rows, cols) + 1;

            //从下标1开始算起
            nodes = new Node[len];

            //十字链表的总头节点
            nodes[0] = new Node();

            nodes[0].rows = rows;
            nodes[0].cols = cols;
            nodes[0].unit = new Unit()
            {
                value = count,
                next = null,
            };

            //down和right都指向自身
            nodes[0].right = nodes[0];
            nodes[0].down = nodes[0];

            var temp = nodes[0];

            //初始化多条链表的头结点
            for (int i = 1; i < len; i++)
            {
                nodes[i] = new Node();

                nodes[i].rows = 0;
                nodes[i].cols = 0;
                nodes[i].unit = new Unit()
                {
                    value = 0,
                    next = temp.unit.next
                };

                //给上一个节点的next域赋值
                temp.unit.next = nodes[i];

                //将当前节点作为下一次循环的上一个节点
                temp = nodes[i];

                nodes[i].right = nodes[i];
                nodes[i].down = nodes[i];
            }
        }
        #endregion

        #region 在指定的“行,列”上插入节点
        /// <summary>
        /// 在指定的“行,列”上插入节点
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public void InsertNode(Node node)
        {
            //先定位行
            Node pnode = nodes[node.rows];

            //在指定的“行”中找,一直找到该行最后一个节点(right指针指向自己的为止)
            while (pnode.right != nodes[node.rows] && pnode.right.cols < node.cols)
                pnode = pnode.right;

            //将最后一个节点的right指向插入节点的right,以此达到是循环链表
            node.right = pnode.right;

            //将插入节点给最后一个节点的right指针上
            pnode.right = node;

            //再定位列
            pnode = nodes[node.cols];

            //同理
            while (pnode.down != nodes[node.cols] && pnode.down.rows < node.rows)
            {
                pnode = pnode.down;
            }

            node.down = pnode.down;
            pnode.down = node;
        }
        #endregion

        #region 插入十字链表中
        /// <summary>
        /// 插入十字链表中
        /// </summary>
        /// <param name="nums">矩阵</param>
        /// <param name="rows">矩阵的行数</param>
        /// <param name="cols">矩阵的列数</param>
        /// <param name="count">非0元素个数</param>
        /// <returns></returns>
        public Node[] Insert(int[,] nums, int rows, int cols, int count)
        {
            //初始化操作
            Init(rows, cols, count);

            //插入操作
            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < cols; j++)
                {
                    //直插入"非0元素"
                    if (nums[i, j] != 0)
                    {
                        var node = new Node();

                        node.rows = i + 1;
                        node.cols = j + 1;
                        node.unit = new Unit()
                        {
                            value = nums[i, j]
                        };
                        node.right = node;
                        node.down = node;

                        InsertNode(node);
                    }
                }
            }

            return nodes;
        }
        #endregion

        #region 打印十字链表
        /// <summary>
        /// 打印十字链表
        /// </summary>
        /// <param name="nodes"></param>
        public void Print(Node[] nodes)
        {
            var head = nodes[0];

            //遍历每一行的right
            for (int i = 1; i < head.rows + 1; i++)
            {
                var p = nodes[i];

                while (p.right != nodes[i])
                {
                    Console.WriteLine("({0},{1})\tval => {2}",
                        p.right.rows,
                        p.right.cols,
                        p.right.unit.value);

                    //指向下一个节点
                    p = p.right;
                }
            }
        }
        #endregion
    }
}

 

时间: 2024-08-03 06:21:14

经典算法题每日演练——第二十一题 十字链表的相关文章

经典算法题每日演练——第二十三题 鸡尾酒排序

原文:经典算法题每日演练--第二十三题 鸡尾酒排序     这篇我们继续扯淡一下鸡尾酒排序,为了知道为啥取名为鸡尾酒,特意看了下百科,见框框的话,也只能勉强这么说了.   要是文艺点的话,可以说是搅拌排序,通俗易懂点的话,就叫"双向冒泡排序",我想作为码农的话,不可能不知道冒泡排序, 冒泡是一个单向的从小到大或者从大到小的交换排序,而鸡尾酒排序是双向的,从一端进行从小到大排序,从另一端进行从大 到小排序. 从图中可以看到,第一次正向比较,我们找到了最大值9.             

经典算法题每日演练——第十一题 Bitmap算法

     在所有具有性能优化的数据结构中,我想大家使用最多的就是hash表,是的,在具有定位查找上具有O(1)的常量时间,多么的简洁优美, 但是在特定的场合下: ①:对10亿个不重复的整数进行排序. ②:找出10亿个数字中重复的数字. 当然我只有普通的服务器,就算2G的内存吧,在这种场景下,我们该如何更好的挑选数据结构和算法呢?   一:问题分析      这年头,大牛们写的排序算法也就那么几个,首先我们算下放在内存中要多少G: (10亿 * 32)/(1024*1024*1024*8)=3.6

经典算法题每日演练——第二十题 三元组

         我们知道矩阵是一个非常强大的数据结构,在动态规划以及各种图论算法上都有广泛的应用,当然矩阵有着不足的地方就是空间和时间 复杂度都维持在N2上,比如1w个数字建立一个矩阵,在内存中会占用1w*1w=1亿的类型空间,这时就会遇到outofmemory...那么面 临的一个问题就是如何来压缩矩阵,当然压缩的方式有很多种,这里就介绍一个顺序表的压缩方式:三元组. 一:三元组     有时候我们的矩阵中只有零星的一些非零元素,其余的都是零元素,那么我们称之为稀疏矩阵,当然没有绝对的说有多

经典算法题每日演练——第二十二题 奇偶排序

原文:经典算法题每日演练--第二十二题 奇偶排序   这个专题因为各种原因好久没有继续下去了,MM吧...你懂的,嘿嘿,不过还得继续写下去,好长时间不写,有些东西有点生疏了, 这篇就从简单一点的一个"奇偶排序"说起吧,不过这个排序还是蛮有意思的,严格来说复杂度是O(N2),不过在多核的情况下,可以做到 N2 /(m/2)的效率,这里的m就是待排序的个数,当m=100,复杂度为N2 /50,还行把,比冒泡要好点,因为重点是解决问题的奇思妙想.     下面我们看看这个算法是怎么描述的,既

经典算法题每日演练——第二十五题 块状链表

原文:经典算法题每日演练--第二十五题 块状链表 在数据结构的世界里,我们会认识各种各样的数据结构,每一种数据结构都能解决相应领域的问题,每一种数据结构都像 是降龙十八掌中的某一掌,掌掌毙命... 当然每个数据结构,有他的优点,必然就有它的缺点,那么如何创造一种数据结构 来将某两种数据结构进行扬长避短,那就非常完美了.这样的数据结构也有很多,比如:双端队列,还有就是今天讲的 块状链表,    我们都知道 数组 具有 O(1)的查询时间,O(N)的删除,O(N)的插入...            

经典算法题每日演练——第三题 猴子吃桃

原文:经典算法题每日演练--第三题 猴子吃桃           猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾就多吃了一个.第二天早上又将剩下的桃子吃了一半,还是不过瘾又多 吃了一个.以后每天都吃前一天剩下的一半再加一个.到第10天刚好剩一个.问猴子第一天摘了多少个桃子?   分析: 这是一套非常经典的算法题,这个题目体现了算法思想中的递推思想,递归有两种形式,顺推和逆推,针对递推,只要         我们找到递推公式,问题就迎刃而解了.                令S10=1,容易看

经典算法题每日演练——第六题 协同推荐SlopeOne 算法

原文:经典算法题每日演练--第六题 协同推荐SlopeOne 算法               相信大家对如下的Category都很熟悉,很多网站都有类似如下的功能,"商品推荐","猜你喜欢",在实体店中我们有导购来为我们服务,在网络上 我们需要同样的一种替代物,如果简简单单的在数据库里面去捞,去比较,几乎是完成不了的,这时我们就需要一种协同推荐算法,来高效的推荐浏览者喜 欢的商品. 一:概念      SlopeOne的思想很简单,就是用均值化的思想来掩盖个体的打

经典算法题每日演练——第七题 KMP算法

原文:经典算法题每日演练--第七题 KMP算法       在大学的时候,应该在数据结构里面都看过kmp算法吧,不知道有多少老师对该算法是一笔带过的,至少我们以前是的, 确实kmp算法还是有点饶人的,如果说红黑树是变态级的,那么kmp算法比红黑树还要变态,很抱歉,每次打kmp的时候,输 入法总是提示"看毛片"三个字,嘿嘿,就叫"看毛片算法"吧. 一:BF算法      如果让你写字符串的模式匹配,你可能会很快的写出朴素的bf算法,至少问题是解决了,我想大家很清楚的知

经典算法题每日演练——第十七题 Dijkstra算法

原文:经典算法题每日演练--第十七题 Dijkstra算法         或许在生活中,经常会碰到针对某一个问题,在众多的限制条件下,如何去寻找一个最优解?可能大家想到了很多诸如"线性规划","动态规划" 这些经典策略,当然有的问题我们可以用贪心来寻求整体最优解,在图论中一个典型的贪心法求最优解的例子就莫过于"最短路径"的问题.   一:概序    从下图中我要寻找V0到V3的最短路径,你会发现通往他们的两点路径有很多:V0->V4-&g