广义表算是线性表的一种提升吧,广义表中的元素可以使一个原子类型,也可以是一个表,广义表的存储类型有两种,一种是混合类型,将原子类型和表结点类型放在一起的,此时每一个结点有一个自己的头指针和一个尾指针,中间是该节点的数据域,指向一个原子类型,另一种存储方式是,对每一个原子类型的结点也具有一个尾指针,这样当其中的一个结点为表结点的时候,其子表结点是原子结点不断向下指向的.
//两种存储方式的表示形式(伪码表示)
typedef struct GlNode
{
int tag;
union {
AtomType atom;
struct {
struct GlNode *hp ,* tp;
}ptr;
}
};
typedef struct GlNode
{
int tag;
union {
AtomType atom;
struct GlNode *hp;
}
struct GlNode *tp;
};
通过比较两种存储方式不难发现,当广义表中的元素大多是原子类型的时候,采用第二中方式是比较节省存储空间的。广义表的应用有通过广义表表示多元多项式,每一个元作为一个表结点,下面连接一个线性表,表示出其系数和次数。
求广义表的深度(采用地递归的方式得到每一个子表结点的深度,然后将最大的字表深度加1就是当前广义表的深度)
//计算广义表的深度
int GListDepth (GList L){
if (!L) return 1;
if (L -> tag == ATOM) return 0;
for (max = 0, p = L; p; p = p->tp){
dep = GListDepth (p -> hp);
if (dep > max)
max = dep;
}
return max+1;
}
矩阵,在这里分为特殊矩阵和稀疏矩阵,特殊矩阵就是具有特殊的性质,比如对称或者数据全部相等等,稀疏矩阵就是矩阵中为0的比较多,而且其分布没有规律性,关于矩阵就是如何来存储,使得其占有的空间最小,也就是压缩存储
通常在高级语言编程中,都是用二维数组来存储矩阵的元,然而在阶数很高的矩阵中,有很多值相同的元或者是零元素。有时为了节省存储空间,可以对这类矩阵进行压缩存储。所谓压缩存储是指:为多个值相同的元只分配一个存储空间;对零元素不分配空间。
三元组书序表又称有序的双下标法,其特点:非零元在表中按行序有序存储,因此便于进行依行顺序处理的矩阵运算。但是,若需按行号存取某一行的非零元素,则需从头开始进行查找。(时间复杂度较高)
行逻辑链接的顺序表:便于随机存取任意一行的非零元。
十字链表:当矩阵的非零元个数和位置在操作过程中变化较大时,就不适宜采用顺序存储结构来表示三元组的线性表。
对于稀疏矩阵的存储结构应该根据对稀疏矩阵的操作来选择合适的存储方式,否则出现以时间换空间的情况,适得其反。以下只举例说明三元组顺序表的实现方式:
三元组顺序表:以顺序存储结构来表示三元组表。
- /**
- * 三元组结构
- */
- class Triple {
- int rowIndex;//非零元的行下标
- int colIndex;//非零元的列下标
- int value;//非零元的值
- Triple(){
- this.rowIndex = 0;
- this.colIndex = 0;
- this.value = 0;
- }
- Triple(int i, int j, int value){
- this.rowIndex = i;
- this.colIndex = j;
- this.value = value;
- }
- }
- public class SparseMatrix {
- public static void main(String[] args) {
- int[][] a = { { 0, 12, 9, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0 },
- { -3, 0, 0, 0, 0, 14, 0 }, { 0, 0, 24, 0, 0, 0, 0 },
- { 0, 18, 0, 0, 0, 0, 0 }, { 15, 0, 0, -7, 0, 0, 0 } };
- System.out.println("稀疏矩阵转换成三元组顺序表:");
- LinkedList<?> list = constructTripleList(a);
- printTripleList(list);
- System.out.println("输出对应第2行的元素");
- restoreArray(list, 2);
- System.out.println("三元组顺序表还原成稀疏矩阵:");
- reverseToSparseMatrix(list);
- }
- /**
- * 根据行号还原顺序表中的某一行
- *
- * @param list
- * @param rowIndex
- * @return
- */
- public static float[] restoreArray(LinkedList<?> list, int rowIndex) {
- float[] array = new float[7];
- Triple t = null;
- int index = 0;
- for (int i = 0; i < list.size(); i++) {
- t = (Triple) list.get(i);
- if (t.getRowIndex() == rowIndex) {
- index = t.getColIndex();
- array[index] = t.getValue();
- } else {
- // array[index]=0;
- }
- if (t.getRowIndex() > rowIndex) {
- break;
- }
- }// for_i
- for(int i = 0; i < array.length; i++){
- System.out.print(array[i] + " ");
- }
- System.out.println();
- return array;
- }
- /**
- * 构造三元组顺序表
- *
- * @param matrix
- * @return
- */
- public static LinkedList<Triple> constructTripleList(int[][] matrix) {
- Triple e = null;
- LinkedList<Triple> list = new LinkedList<Triple>();
- for (int i = 0; i < matrix.length; i++) {
- for (int j = 0; j < matrix[0].length; j++)
- if (matrix[i][j] != 0) {
- e = new Triple(i, j, matrix[i][j]);
- list.add(e);
- }
- }
- return list;
- }
- /**
- * 将三元组顺序表转换成矩阵并输出
- *
- * @param list
- */
- public static void reverseToSparseMatrix(List<?> list) {
- Triple t = null;
- float[][] a = new float[6][7];
- int index = 0;
- for (int i = 0; i < 6; i++) {
- for (int j = 0; j < 7; j++) {
- if (index < list.size()) {
- t = (Triple) list.get(index);
- if (t.getRowIndex() == i) {
- // 肯定是存在t.getColIndex() == j的情况
- if (j == t.getColIndex()) {
- a[i][j] = t.getValue();
- index++;
- j = t.getColIndex();
- } else if (j > t.getColIndex()) {
- j = 0;
- }
- }
- }
- // 以下注释部分会存在j>t.getColIndex,而t.getColIndex的确有值的情况,若按照以下操作取值,则会丢掉前面描述的情况的值
- // if((t.getRowIndex()==i) && (t.getColIndex() == j)){
- // a[i][j] = t.getValue();
- // index++;
- // }
- }// for_j
- }// for_i
- for (int i = 0; i < 6; i++) {
- for (int j = 0; j < 7; j++) {
- System.out.print(a[i][j] + " ");
- }
- System.out.println();
- }
- }
- /**
- * 输出稀疏矩阵
- *
- * @param matrix
- */
- public static void printSparseMatrix(int[][] matrix) {
- for (int i = 0; i < matrix.length; i++) {
- for (int j = 0; j < matrix[0].length; j++)
- System.out.print(matrix[i][j] + " ");
- System.out.println();
- }
- }
- /**
- * 输出三元组顺序表
- *
- * @param list
- */
- public static void printTripleList(List<?> list) {
- Triple e = null;
- Iterator<?> iter = list.iterator();
- while (iter.hasNext()) {
- e = (Triple) iter.next();
- System.out.println((e.rowIndex + 1) + " " + (e.colIndex + 1) + " "
- + e.value);
- }
- }
- }
我们知道稀疏矩阵的三元组存储方式的实现很简单,每个元素有三个域分别是i, j, e。代表了该非零元的行号、列号以及值。那么在十字链表的存储方式下,首先这三个域是肯定少不了的,不然在进行很多操作的时候都要自己使用计数器,很麻烦。而十字链表的形式大家可以理解成每一行是一个链表,而每一列又是一个链表。如图所示:
通过上面的图我们可以知道,每个结点不止要存放i, j, e。还要存放它横向的下一个结点的地址以及纵向的下一个结点的地址。形成一个类似十字形的链表的结构。那么每个结点的结构体定义也就呼之欲出了
- typedef struct OLNode {
- int i, j; //行号与列号
- ElemType e; //值
- struct OLNode *right, *down; //指针域
- }OLNode, *OList;
这样我们对结点的插入与删除就要修改两个指针域。为了方便我们对结点的操作,我们要创建头指针或者头结点。至于到底是选择头指针呢还是头结点,请继续看下去..
我们想下要怎么创建头指针或者头结点,我们可以创建OLNode结构的结点形成一段连续的地址空间来指向某一行或者某一列中的结点(这是我最初的想法)
或者我们创建指针数组,数组元素中存放的地址就是某一行或者某一列的第一个结点的地址。来分析下两种方法
第一种方法会浪费大量的空间,而因为指针变量的空间都是4个字节,所以相对来说第二种节省空间。
毫无疑问我们选择第二种,也就是创建头指针。那么第二种我们用什么来实现?是数组还是动态内存分配?如果用数组我们要预先定义行和列的最大值,显然这不是一个好主意,而动态内存分配的方法我们可以在用户输入了行数与列数之后分配相应的一段地址连续的空间。更为灵活,所以我们选择动态内存分配。
- typedef struct {
- OLink *Rhead, *Chead;
- int mu, nu, tu; // 稀疏矩阵的行数、列数和非零元个数
- }CrossList;
注意Rhead与Chead的类型,它们是指向指针的指针,也就是说,它们是指向我们定义的OLNode结构的结点的指针的指针。这话有点绕,不过相信C学的不错的朋友都应该清楚。如果不清楚的请看这个帖子 http://topic.youkuaiyun.com/u/20110829/01/506b33e3-ebc9-4905-bf8d-d0c877f85c08.html
现在结构体已经定义好了,我们来想想下面应该干什么。首先需要用户输入稀疏矩阵的行数与列数以及非零元的个数。那么就需要定义一个CrossList的结构体变量来存储这些值。
- int main(void)
- {
- CrossList M;
- CreateSMatrix(&M);
- }
CreatSMatrix函数是我们今天要创建的函数,它用来建立稀疏矩阵并使用十字链表的方式存储矩阵。该函数的原型为int CreateSMatrix(CrossList *M);
当我们创建好了M就需要用户输入了,那么就要对用户的输入进行检查,看是否符合要求,首先mu, nu, tu都不能小于0,并且mu, nu不能等于0(我们这里假设行号与列号都是从1开始的,所以不能等于0),tu的值必须在0与mu * nu之间。
- int CreateSMatrix(CrossList *M)
- {
- int i, j, m, n, t;
- int k, flag;
- ElemType e;
- OLNode *p, *q;
- if (M->Rhead)
- DestroySMatrix(M);
- do {
- flag = 1;
- printf("输入需要创建的矩阵的行数、列数以及非零元的个数");
- scanf("%d%d%d", &m, &n, &t);
- if (m<0 || n<0 || t<0 || t>m*n)
- flag = 0;
- }while (!flag);
- M->mu = m;
- M->nu = n;
- M->tu = t;
- ...................................
- return 1;
- }
当用户输入了正确的值以后,我们要创建头指针的数组
- //创建行链表头数组
- M->Rhead = (OLink *)malloc((m+1) * sizeof(OLink));
- if(!M->Rhead)
- exit(-1);
- //创建列链表头数组
- M->Chead = (OLink *)malloc((n+1) * sizeof(OLink));
- if(!(M->Chead))
- exit(-1);
这里m+1与n+1是为了后面操作的方便使得它们的下标从1开始。注意我们创建时候的强制转换的类型。OLink * , 首先它是指针类型。我们连续创建了m+1个,每一个都指向OLink类型的变量,所以它里面存放的就应该是一个指向OLNode类型的指针的地址。
创建完以后必须初始化,因为我们后面的插入就其中一个判断条件就是它们的值为NULL,也就是该行或者该列中没有结点
- for(k=1;k<=m;k++) // 初始化行头指针向量;各行链表为空链表
- M->Rhead[k]=NULL;
- for(k=1;k<=n;k++) // 初始化列头指针向量;各列链表为空链表
- M->Chead[k]=NULL;
现在我们就可以进行结点的输入了,显而易见的要输入的结点个数刚才已经存放到了t变量中,那么我们要创建t个结点。这就是一个大的循环
而每创建一个结点我们都要修改它的两个指针域以及链表头数组。那么我们可以分开两次来修改,第一次修改行的指针域,第二次修改列的指针域。
- do {
- flag = 1;
- printf("输入第%d个结点行号、列号以及值", k);
- scanf("%d%d%d", &i, &j, &e);
- if (i<=0 || j<=0)
- flag = 0;
- }while (!flag);
- p = (OLink) malloc (sizeof(OLNode));
- if (NULL == p)
- exit(-1);
- p->i = i;
- p->j = j;
- p->e = e;
当用户输入一系列正确的值,并且我们也创建了一个OLNode类型的结点之后我们要讲它插入到某一行中。首先要确定插入在哪一行?我们输入的时候已经输入了行号i,那么我们自然要插入到i行中,那么应该怎样去插入?分两种情况
1、当这一行中没有结点的时候,那么我们直接插入
2、当这一行中有结点的时候我们插入到正确的位置
逐个来分析:
怎么判定一行中有没有结点? 记得我们前面对Rhead的初始化吗? 所有的元素的值都为NULL,所以我们的判断条件就是 NULL==M->Rhead[i].
现在我们来解决第二个问题。
怎么去找到要插入的正确位置。当行中有结点的时候我们无非就是插入到某个结点之前或者之后。那么我们再回到前面,在我们定义Rhead的时候就说过,某一行的表头指针指向的就是该行中第一个结点的地址。我们假设该行中已经有了一个结点我们称它为A结点,如果要插在A结点之前那么A结点的列号必定是大于我们输入的结点(我们称它为P结点)的列号的。我们的插入操作就要修改头指针与p结点的right域。就像链表中的插入。那么当该行中没有结点的时候我们怎么去插入?同样是修改头指针让它指向我们的P结点,同样要修改P结点的right域。看,我们可以利用if语句来实现这两种条件的判断。那么就有了下面的代码!
- if(NULL==M->Rhead[i] || M->Rhead[i]->j>j)
- {
- // p插在该行的第一个结点处
- // M->Rhead[i]始终指向该行的第一个结点 p->right = M->Rhead[i];
- M->Rhead[i] = p;
- }
现在我们再想一下怎么去插入到某一个结点的后面? 我们新创建的P结点要插入到现有的A结点的后面,那么P的列号必定是大于A的列号,那么我们只要找到第一个大于比P的列号大的结点B,然后插入到B结点之前!如果现有的结点没有一个结点列号是大于P结点的列号的,那么我们就应该插入到最后一个结点之后!所以我们首先要寻找符合条件的位置进行插入
- for(q=M->Rhead[i]; q->right && q->right->j < j; q=q->right)
- ;
- p->right=q->right; // 完成行插入
- q->right=p;
这样就插入完成了,至于列的指针域的修改和这个类似!现在贴出所有代码
- int CreateSMatrix(CrossList *M)
- {
- int i, j, m, n, t;
- int k, flag;
- ElemType e;
- OLNode *p, *q;
- if (M->Rhead)
- DestroySMatrix(M); do {
- flag = 1;
- printf("输入需要创建的矩阵的行数、列数以及非零元的个数");
- scanf("%d%d%d", &m, &n, &t);
- if (m<0 || n<0 || t<0 || t>m*n)
- flag = 0;
- }while (!flag);
- M->mu = m;
- M->nu = n;
- M->tu = t;
- //创建行链表头数组
- M->Rhead = (OLink *)malloc((m+1) * sizeof(OLink));
- if(!M->Rhead)
- exit(-1);
- //创建列链表头数组
- M->Chead = (OLink *)malloc((n+1) * sizeof(OLink));
- if(!(M->Chead))
- exit(-1);
- for(k=1;k<=m;k++) // 初始化行头指针向量;各行链表为空链表
- M->Rhead[k]=NULL;
- for(k=1;k<=n;k++) // 初始化列头指针向量;各列链表为空链表
- M->Chead[k]=NULL;
- //输入各个结点
- for (k=1; k<=t; ++k)
- {
- do {
- flag = 1;
- printf("输入第%d个结点行号、列号以及值", k);
- scanf("%d%d%d", &i, &j, &e);
- if (i<=0 || j<=0)
- flag = 0;
- }while (!flag); p = (OLink) malloc (sizeof(OLNode));
- if (NULL == p)
- exit(-1);
- p->i = i;
- p->j = j;
- p->e = e;
- if(NULL==M->Rhead[i] || M->Rhead[i]->j>j)
- {
- // p插在该行的第一个结点处
- // M->Rhead[i]始终指向它的下一个结点
- p->right = M->Rhead[i];
- M->Rhead[i] = p;
- }
- else // 寻查在行表中的插入位置
- {
- //从该行的行链表头开始,直到找到
- for(q=M->Rhead[i]; q->right && q->right->j < j; q=q->right)
- ;
- p->right=q->right; // 完成行插入
- q->right=p;
- } if(NULL==M->Chead[j] || M->Chead[j]->i>i)
- {
- p->down = M->Chead[j];
- M->Chead[j] = p;
- }
- else // 寻查在列表中的插入位置
- {
- //从该列的列链表头开始,直到找到
- for(q=M->Chead[j]; q->down && q->down->i < i; q=q->down)
- ;
- p->down=q->down; // 完成行插入
- q->down=p;
- }
- } return 1;
- }