我们通过一元多项式的例子认识了链表和顺序表,但是遇到了二元多项式的时候又该如何表示呢?
比如,给定二元多项式:
遇到这种问题,有两种解决办法:
①重新定义一个符合需求的链表(本文以链表来说明广义表),但是每次都遇到不同需求都要定义不同链表,这实在是太麻烦了。所以不推荐
②可以将上述二元多项式看成关于X的一元多项式
然后前面系数a,b,c又是一个一元多项式,因此我们可以在原来的代码中Value1存储关于y的一元多项式的指针,下图如示:
由此我们可以引出广义表的定义
广义表:
广义表是线性表的推广;
对于线性表而言,n个元素都是基本的单元素;
在广义表中,这些元素不仅可以是单元素也可以是另一个广义表。
其中,多重链表就是广义表的具体实现,首先让我们来认识一下多重链表的定义
多重链表:
多重链表:链表中的结点可能同时隶属于多个链表
特点:
①多重链表中结点的存储的指针会有多个,如前面的例子包含了下一个多项式结点和系数结点
②但是!!包含两个指针的链表不一定是多重链表,比如双向链表,他至始至终都是在描述同一个链表,无非是多了个从末端到前端的指针而已,这点需要注意!!
实际上,多重链表有广泛的用途:基本上如后面将学习到的树、图这样相对复杂的数据结构都可以采用多重链表方式实现存储。
下面我们来看个例子:矩阵是如何表示
所谓的稀疏矩阵,就是存在多个0的矩阵,其他的非零项看上去稀稀疏疏,松松垮垮的所以称为稀疏矩阵。
在改进方法之前还是让我们先分析非零项的关键数据:
①行坐标Row
②列坐标Col
③数值Value
经过分析,我们得知行坐标和列坐标和数值归为数据类,因此通过两个指针,把同行、同列串起来串成一个类似于网格的形式。
行指针(或称为向右指针)Right
列指针(或称为向下指针)Down
因此有以下类似的结点结构
所以矩阵A的多重链表如下图:
其中第一个Term是这个多重链表的入口,代表着这个矩阵,4是4行,5是5列,有7个非零项,所以行的头结点和列的头结点都连着这个入口
可以理解为每一行对应一个链表,每一列对应一个链表
下面我们用代码实现一下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 数据结构
{
class MultilinkedList<T> : LinkList<T> where T : IComparable<T>
{
private int row;//行坐标
private int col;//列坐标
private int values;//非零项 和 数据
private int rowCount;
private int colCount;
private int[] RowNumCount;
private int[] ColNumCount;
public Node<T> Term;//入口结点
public Node<T>[] HeadRow;//行坐标头结点0
public Node<T>[] HeadCol;//列坐标头结点0
public MultilinkedList(int Row, int Col, int Values)
{
HeadInitialize(Row, Col);
}
public void HeadInitialize(int Row, int Col)//初始化
{
HeadRow = new Node<T>[Row];
HeadCol = new Node<T>[Col];
Term = new Node<T>();
Term.Next = HeadRow[0];
Term.Right = HeadCol[0];
Term.Value1 = (T)(object)Row;
Term.Value2 = (T)(object)Col;
RowNumCount = new int[Row];
ColNumCount = new int[Col];
for (int i = 0; i < Row; i++)//各行头链表相连
{
HeadRow[i] = new Node<T>();
if (i>=1)
{
HeadRow[i-1].Next = HeadRow[i];
}
if (i+1 == Row)
{
HeadRow[i].Next = HeadRow[0];
}
}
for (int i = 0; i < Col; i++)//各列头链表相连
{
HeadCol[i] = new Node<T>();
if (i >= 1)
{
HeadCol[i - 1].Next = HeadCol[i];
}
if (i + 1 == Col)
{
HeadCol[i].Next = HeadCol[0];
}
}
}
public void AddData(T Row,T Col,T Values)//增加数据
{
Node<T> dataNode = new Node<T>(Row,Col,Values);
Node<T> NewHeadRow = new Node<T>();
Node<T> NewHeadCol = new Node<T>();
Node<T> newRowNode = new Node<T>();//用来指向行方向下一个结点
Node<T> newColNode = new Node<T>();//用来指向列方向下一个结点
Term.Value3 = (T)(object)++values;//非零项个数加1
int Rows = Convert.ToInt32(Row);
int Cols = Convert.ToInt32(Col);
newRowNode = HeadRow[Convert.ToInt32(Row)];//newRowNode和要增加数据的行链表关联
RowNumCount[(int)(object)Row]++;//该行存储数据个数加一
for (int i = 0; i < RowNumCount[(int)(object)Row]-1; i++)
{
newRowNode = newRowNode.Right;
}
newRowNode.Right = dataNode; //和该行末端结点相连
dataNode.Right = HeadRow[Convert.ToInt32(Row)];
newColNode = HeadCol[Convert.ToInt32(Col)];//newColNode和要增加数据的列链表关联
ColNumCount[(int)(object)Col]++;//该列存储数据个数加一
for (int i = 0; i < ColNumCount[(int)(object)Col]-1; i++)
{
newColNode = newColNode.Down;
}
newColNode.Down = dataNode;//和该列末端结点相连
dataNode.Down = HeadCol[Convert.ToInt32(Col)];
}
public void InsertData(T Row, T Col, T Values)//插入数据
{
Node<T> dataNode = new Node<T>(Row, Col, Values);
Node<T> NewHeadRow = new Node<T>();
Node<T> NewHeadCol = new Node<T>();
Node<T> newRowNode = new Node<T>();//用来指向行方向下一个结点
Node<T> newColNode = new Node<T>();//用来指向列方向下一个结点
Term.Value3 = (T)(object)++values;//非零项个数加1
int Rows = Convert.ToInt32(Row);
int Cols = Convert.ToInt32(Col);
newRowNode = HeadRow[Convert.ToInt32(Row)];//newRowNode和要增加数据的行链表关联
RowNumCount[(int)(object)Row]++;//该行存储数据个数加一
for (int i = 0; i < RowNumCount[(int)(object)Row] - 1; i++)
{
newRowNode = newRowNode.Right;//下一个
Node<T> Last = newRowNode.Right;//下下一个
if (Convert.ToInt32(newRowNode.Value2)<Convert.ToInt32(dataNode.Value2))
{
if (Convert.ToInt32(Last.Value2)>Convert.ToInt32(dataNode.Value2))//前小后大插中间
{
Node<T> NextNode = newRowNode.Right;
newRowNode.Right = dataNode;
dataNode.Right = NextNode;
break;
}
}
}
newColNode = HeadCol[Convert.ToInt32(Col)];//newRowNode和要增加数据的列链表关联
ColNumCount[(int)(object)Col]++;//该列存储数据个数加一
for (int i = 0; i < ColNumCount[(int)(object)Col] -1; i++)
{
newColNode = newColNode.Down;//下一个
Node<T> Last = newColNode.Down;//下下一个
if (Convert.ToInt32(newColNode.Value1)<Convert.ToInt32(dataNode.Value1))
{
if (Convert.ToInt32(Last.Value1)>Convert.ToInt32(dataNode.Value1))//前小后大插中间
{
Node<T> NextNode = newColNode.Down;
newColNode.Down = dataNode;
dataNode.Down = NextNode;
break;
}
}
}
Console.WriteLine("插入成功");
}
public T GetValue(T Row, T Col)
{
T data = default(T);
Node<T> newRowNode = new Node<T>();//用来指向行方向下一个结点
int Rows = Convert.ToInt32(Row);
newRowNode = HeadRow[Rows];
for (int i = 0; i < RowNumCount[(int)(object)Row]; i++)
{
newRowNode = newRowNode.Right;
if (newRowNode.Value2.ToString() == Col.ToString())
{
data = newRowNode.Value3;
return data;
}
}
return data;
}
}
}
//多重链表
MultilinkedList<int> a = new MultilinkedList<int>(4,5,7);
a.AddData(0, 0, 18);
//a.AddData(0, 3, 2);
a.AddData(1, 1, 27);
a.AddData(2, 3, -4);
a.AddData(3, 0, 23);
//a.AddData(3, 1, -1);
a.AddData(3, 4, 12);
a.AddData(0, 3, 2);
a.InsertData(3, 1, -1);
Console.WriteLine("第{0}行{1}列的值是:{2}", 0, 0, a.GetValue(0, 0));
Console.WriteLine("第{0}行{1}列的值是:{2}", 0, 3, a.GetValue(0, 3));
Console.WriteLine("第{0}行{1}列的值是:{2}", 1, 1, a.GetValue(1, 1));
Console.WriteLine("第{0}行{1}列的值是:{2}", 2, 3, a.GetValue(2, 3));
Console.WriteLine("第{0}行{1}列的值是:{2}", 3, 0, a.GetValue(3, 0));
Console.WriteLine("第{0}行{1}列的值是:{2}", 3, 1, a.GetValue(3, 1));
Console.WriteLine("第{0}行{1}列的值是:{2}", 3, 4, a.GetValue(3, 4));
Console.WriteLine("该矩阵为{0}行{1}列矩阵", a.Term.Value1, a.Term.Value2);
Console.WriteLine("该矩阵的非零项个数为:{0}", a.Term.Value3);
Console.ReadKey();
让我们看一下结果
成功实现要求!!!
但是这里需要注意一下:
①本文这里的InsertData函数是用在某行里前后都有结点的情况,如果要插入为某行末端结点,须使用AddData函数,实验中以增加新的第0行3列的数据为范例来证明。
②本文的AddData函数是以先行后列来增加,而且是头尾循环
③本文的Term和上面的图片中的结构不同,这里的Term是以Next连接头行结点,以Right连接头列结点,但是作用不变,仍是多重链表的入口结点
总结:
作者现在边看视频边学习数据结构,看视频的时候觉得什么都会了,一想象代码就出来了,但是真正轮到自己动手去写的时候仍会发现很多问题,比如搭建矩阵框架的时候,作者是以某一行没有非零项为前提,需要加非零项的时候才新建新的头行链表再将之前的头行链表与之连接,这导致了异常复杂,琢磨了半天仍写不出,索性干脆直接创建Row行头链表,Col列头链表再相连,之后增加数据的时候直接往里面填就好,其他零项的一概为0处理,但也造成了空间浪费的问题,并不是最佳解决方案。
这里只写了增插查的函数,还有删除函数没有写,如果对本文有兴趣的小伙伴可以试着自己写写,思路是和插入函数差不多的,难点在于目标结点的前结点和后结点相连,这里提示一下:直接将前结点和后结点相连,不用进行清零操作,别忘了Term非零项个数-1和行列头结点存储个数-1!
快过年了,也祝大家新年快乐,在新的一年里,祝大家心想事成,万事如意,敲代码敲的顺利哈哈哈哈