经典算法题每日演练——第二十一题 十字链表
上一篇我们看了矩阵的顺序存储,这篇我们再看看一种链式存储方法“十字链表”,当然目的都是一样,压缩空间。
一:概念
既然要用链表节点来模拟矩阵中的非零元素,肯定需要如下5个元素(row,col,val,down,right),其中:
row:矩阵中的行。
col:矩阵中的列。
val:矩阵中的值。
right:指向右侧的一个非零元素。
down:指向下侧的一个非零元素。
现在我们知道单个节点该如何表示了,那么矩阵中同行的非零元素的表示不就是一个单链表吗?比如如下:
那么进一步来说一个多行的非零元素的表示不就是多个单链表吗,是的,这里我把单链表做成循环链表,我们来看看如何用十字链表
来表示稀疏矩阵。
从上面的十字链表中要注意两个问题:
第一:这里有一个填充色的节点,是十字链表中的总结点,它是记录该矩阵中的(row,col,value)和一个指向下一个头节点的next指针。
第二:每个链表都有一个头指针,总结点用next指针将它们贯穿起来。
二:操作
1:数据结构
刚才也说了,十字链表的总结点有一个next指针,而其他非零节点没有,所以为了方便,我们用一个Unit类包装起来。
1 #region 单一节点 2 /// <summary> 3 /// 单一节点 4 /// </summary> 5 public class Node 6 { 7 //行号 8 public int rows; 9 10 //列号 11 public int cols; 12 13 //向下的指针域 14 public Node down; 15 16 //向右的指针域 17 public Node right; 18 19 //单元值(头指针的next和val) 20 public Unit unit; 21 } 22 #endregion 23 24 #region 统一“表头节点”和“非零节点” 25 /// <summary> 26 /// 统一“表头节点”和“非零节点” 27 /// </summary> 28 public class Unit 29 { 30 //表头节点的next域 31 public Node next; 32 33 //非零元素的值 34 public int value; 35 } 36 #endregion
2:初始化
这一步,我们初始化总结点,并且用next指针将每个单链表的头节点链接成单链表(也就是上图中十字链表的第一行)
1 #region 十字链表中的“行数,列数,非零元素个数” 2 /// <summary> 3 /// 十字链表中的“行数,列数,非零元素个数” 4 /// </summary> 5 /// <param name="rows"></param> 6 /// <param name="cols"></param> 7 /// <param name="count"></param> 8 public void Init(int rows, int cols, int count) 9 { 10 var len = Math.Max(rows, cols) + 1; 11 12 //从下标1开始算起 13 nodes = new Node[len]; 14 15 //十字链表的总头节点 16 nodes[0] = new Node(); 17 18 nodes[0].rows = rows; 19 nodes[0].cols = cols; 20 nodes[0].unit = new Unit() 21 { 22 value = count, 23 next = null, 24 }; 25 26 //down和right都指向自身 27 nodes[0].right = nodes[0]; 28 nodes[0].down = nodes[0]; 29 30 var temp = nodes[0]; 31 32 //初始化多条链表的头结点 33 for (int i = 1; i < len; i++) 34 { 35 nodes[i] = new Node(); 36 37 nodes[i].rows = 0; 38 nodes[i].cols = 0; 39 nodes[i].unit = new Unit() 40 { 41 value = 0, 42 next = temp.unit.next 43 }; 44 45 //给上一个节点的next域赋值 46 temp.unit.next = nodes[i]; 47 48 //将当前节点作为下一次循环的上一个节点 49 temp = nodes[i]; 50 51 nodes[i].right = nodes[i]; 52 nodes[i].down = nodes[i]; 53 } 54 } 55 #endregion
3:插入节点
根据插入节点的row和col将节点插入到十字链表中指定的位置即可。
1 #region 插入十字链表中 2 /// <summary> 3 /// 插入十字链表中 4 /// </summary> 5 /// <param name="nums">矩阵</param> 6 /// <param name="rows">矩阵的行数</param> 7 /// <param name="cols">矩阵的列数</param> 8 /// <param name="count">非0元素个数</param> 9 /// <returns></returns> 10 public Node[] Insert(int[,] nums, int rows, int cols, int count) 11 { 12 //初始化操作 13 Init(rows, cols, count); 14 15 //插入操作 16 for (int i = 0; i < rows; i++) 17 { 18 for (int j = 0; j < cols; j++) 19 { 20 //直插入"非0元素" 21 if (nums[i, j] != 0) 22 { 23 var node = new Node(); 24 25 node.rows = i + 1; 26 node.cols = j + 1; 27 node.unit = new Unit() 28 { 29 value = nums[i, j] 30 }; 31 node.right = node; 32 node.down = node; 33 34 InsertNode(node); 35 } 36 } 37 } 38 39 return nodes; 40 } 41 #endregion
4:打印链表
我们只要遍历每行链表的right指针即可。
1 #region 打印十字链表 2 /// <summary> 3 /// 打印十字链表 4 /// </summary> 5 /// <param name="nodes"></param> 6 public void Print(Node[] nodes) 7 { 8 var head = nodes[0]; 9 10 //遍历每一行的right 11 for (int i = 1; i < head.rows + 1; i++) 12 { 13 var p = nodes[i]; 14 15 while (p.right != nodes[i]) 16 { 17 Console.WriteLine("({0},{1})\tval => {2}", 18 p.right.rows, 19 p.right.cols, 20 p.right.unit.value); 21 22 //指向下一个节点 23 p = p.right; 24 } 25 } 26 } 27 #endregion