1,数组
1.1,数组的定义
数组是数据结构的基本结构形式,它是一种顺序式的结构,数组是存储同一类型数据的数据结构。
数组是顺序存储的随机存取结构,数组是其他数据结构实现顺序存储的基础。
使用数组时需要定义数组的大小和存储数据的数据类型。
数组分为一维数组和多维数组。数组的维数是由数组下标的个数确定的:
- 一个下标称为一维数组。
- 一个下标以上的数组称为多维数组。
- 从这个意义上讲,确定了对于数组的一个下标总有一个相应的数值与之对应的关系;或者说数组是有限个同类型数据元素组成的序列。
数组是二元组<下标,值>的一个集合。
1.2,一维数组
一维数组是指下标的个数只有一个的数组,有时称为向量,是最基本的数据类型。一维数组具有线性表的结构,但操作简单,一般不进行插入和删除操作,只定义给定下标读取元素和修改元素的操作。
PS:在java 中需要事先声明,程序才能够在编译过程中预留内存空间。声明的格式一般是:<数据类型> <变量名称> [ ]= new <数据类型> [<数组大小>];
一维数组的数据存储按照顺序存储,逻辑地址和物理地址都是连续的。如果已知第一个数据元素的地址loc(a0),则第i个元素的地址loc(ai)为:Loc(ai)= Loc(a0)+i ×c
1.3,二维数组
多维数组:是指下标的个数有两个以上,我们比较常用的是二维数组。多维数组是线性表的扩展。下面以二维数组为例说明多维数组。
二维数组是一维数组的扩充,二维数组是“元素为一维数组”的一维数组。一个m行n列(m>0,n>0)的二维数组,既可以看成由m个一维数组所组成的线性表,也可以看成n个一维数组所组成的线性表。
可以将二维数组A看成是由m个行向量[X0,X1, …,Xm-1]T组成,其中,Xi=( ai0, ai1, ….,ain-1), 0≤i≤m-1;也可以将二维数组A看成是由n个列向量[y0, y1, ……,yn-1]组成,其中 yi=(a0i, a1i, …..,am-1i), 0≤i≤n-1。
二维数组中,每个数据元素对应一对数组下标,在行方向上和列方向上都存在一个线性关系,即存在两个前驱(前件)和两个后继(后件)。也可看作是以线性表为数据元素的线性表。
二维数组的遍历
- 行主序:a0,0,a0,1,…,a0,n-1,a1,0,a1,1,…,a1,n-1,…,am-1,0,am-1,1,…,am-1,n-1。
- 列主序:a0,0,a1,0,…,am-1,0,a0,1,a1,1,…,am-1,1,…,a0,n-1,a1,n-1,…,am-1,n-1。
二维数组的存储:由于计算机的内存结构是一维的,因此用一维内存来表示多维数组,就必须按某种次序将数组元素排成一列序列,然后将这个线性序列存放在存储器中。又由于对数组一般不做插入和删除操作,也就是说,数组一旦建立,结构中的元素个数和元素间的关系就不再发生变化。因此,一般都是采用顺序存储的方法来表示数组。
- 行主序:
- 列主序:
声明二维数组
int element[][]; //声明 element = new int[3][4]; //动态申请 int[][] element={ {1,2,3}, {4,5,6} };//赋初值 element.length //返回二维数组的长度,行数 element[0].length //返回一维数组的长度,列数
1.4,多维数组
对于数组与线性表的关系的两种不同理解,可图示如下,以三维数组array[5][3][4]为例:
按二维数组的数据元素计算公式可以推广到多维数组的数据元素的地址计算(假设按照行优先顺序存储): m行n 列纵标为k的三维数组,假设第一个元素的地址是loc(a000),如果按行优先顺序存储,i行j列纵标为p的数据元素的地址为(可以将它分解为二维数组):loc(aijp)=loc(a000)+[(i-0)*n*k+(j-0)*k +(p-0)]*c;
2,矩阵
2.1,矩阵类
矩阵是工程设计中常用的数学对象。设Am*n=[ai,j]是由m*n个元素ai,j组成的矩阵:
public class Matrix{ //矩阵类 protected int rows, columns; //矩阵行数、列数 protected int[][] element; //二维数组,存储矩阵元素 public Matrix(int m, int n){ //构造m×n零矩阵。若m或n为负数,Java抛出负数组长度异常 this.element = new int[m][n]; //数组元素初值为0 this.rows = m; this.columns = n; } public Matrix(int n){ //构造n×n零方阵 this(n,n); } public Matrix(int m, int n, int[][] value){ //构造m×n矩阵,由value[][]提供元素 this(m, n); for (int i=0; i<value.length && i<m; i++) //value元素不足时补0,忽略多余元素 for (int j=0; j<value[i].length && j<n; j++) this.element[i][j] = value[i][j]; } public int getRows(){ //返回矩阵行数 return this.rows; } public int getColumns(){ //返回矩阵列数 return this.columns; } public int get(int i, int j){ //返回矩阵第i行第j列元素。若i、j越界,抛出序号越界异常 if (i>=0 && i<this.rows && j>=0 && j<this.columns) return this.element[i][j]; throw new IndexOutOfBoundsException("i="+i+",j="+j); } public void set(int i, int j, int x){ //设置矩阵第i行第j列元素为x。若i、j越界,抛出序号越界异常 if (i>=0 && i<this.rows && j>=0 && j<this.columns) this.element[i][j]=x; else throw new IndexOutOfBoundsException("i="+i+",j="+j); } public String toString(){ //返回矩阵所有元素的描述字符串,行主序遍历 String str=" 矩阵"+this.getClass().getName()+"("+this.rows+"×"+this.columns+"):\n"; for (int i=0; i<this.rows; i++){ for (int j=0; j<this.columns; j++) str+=String.format("%6d", this.element[i][j]); //"%6d"格式表示十进制整数占6列 str += "\n"; } return str; } //设置矩阵为m行n列。若参数指定行列数较大,则将矩阵扩容,并复制原矩阵元素。 public void setRowsColumns(int m, int n){ if (m>0 && n>0){ if (m>this.element.length || n>this.element[0].length){ //参数指定的行数或列数较大时,扩充二维数组容量 int[][] source = this.element; this.element = new int[m*2][n*2]; //重新申请二维数组空间,元素初值为0 for (int i=0; i<this.rows; i++) //复制原二维数组元素 for(int j=0; j<this.columns; j++) this.element[i][j] = source[i][j]; } this.rows = m; this.columns = n; } else throw new IllegalArgumentException("矩阵行列数不能≤0,m="+m+",n="+n); } }
2.2,特殊矩阵的压缩存储
但是在矩阵中非零元素呈某种规律分布或者矩阵中出现大量的零元素的情况下,看起来存储密度仍为1,但实际上占用了许多单元去存储重复的非零元素或零元素,这对高阶矩阵会造成极大的浪费,为了节省存储空间, 我们可以对这类矩阵进行压缩存储:即为多个相同的非零元素只分配一个存储空间;对零元素不分配空间。
所谓矩阵的压缩存储,也就是在存储数组时,尽量减少存储空间,但是数组中的每个元素必须存储,所以在矩阵存储中,如果有规律可寻,只要存储其中一部分,而另一部分的存储地址可以通过相应的算法将它计算出来,从而占有比较少的存储空间达到存储整个矩阵的目的,称为矩阵的压缩存储。
矩阵的压缩存储仅是针对特殊矩阵的;而对于没有规律可循的二维数组则不能够使用压缩存储。二维数组(矩阵)的压缩存储一般有三种,它们分别是对称矩阵、稀疏矩阵和三角矩阵。三种矩阵中以稀疏矩阵比较常见。
1、三角矩阵:以主对角线划分,三角矩阵有上三角和下三角两种。上三角矩阵如图所示,它的下三角(不包括主对角线)中的元素均为常数。下三角矩阵正好相反,它的主对角线上方均为常数,如图所示。在大多数情况下,三角矩阵常数为零。
三角矩阵中的重复元素c可共享一个存储空间,其余的元素正好有n(n+1)/2个,因此,三角矩阵可压缩存储到数组element[0..n(n+1)/2]中,其中c存放在数组的最后一个元素中。
(1)下三角矩阵:下三角矩阵的压缩存放与对称矩阵用下三角形式存放类似,但必须多一个存储单元存放上三角部分元素,使用的存储单元数目为n(n+1)/2+1。故可以将n´n的下三角矩阵压缩存放到只有n(n+1)/2+1个存储单元的数组中,假设仍按行优先存放,这时s[k]与a[i][j]的对应关系为:
(2)上三角矩阵:和下三角矩阵的存储类似,共需 n(n+1)/2+1个存贮单元,假设仍按行优先顺序存放,这时s[k]与a[i][j]的对应关系为:
2、对称矩阵:若n 阶矩阵A中的元素满足以下条件:aij=aji i≥1,j≥1 则称为n阶对称矩阵。
对于对称矩阵,如果不采用压缩存储,占有的存储单元有n2个,因为是对称矩阵,所以只要存储对角的数据元素和一半的数据元素即可,占有的存储单元有n(n+1)/2个存储单元中。
(1)如果我们以行序为主序存储其下三角(包括对角线)的元素,其上三角的元素可以推算出来(反之亦然)。
(2)除了用下三角形式存放外,还可以用上三角形式存放。这时a[0][0]存入 s[0],a[0][1]存入s[1],a[0][2]存入 s[2],…。这时s[k]与a[i][j]的对应关系可以按下面方法推出:
3,稀疏矩阵:它只是一个凭人们的直觉来理解的概念。一般认为,一个较大的矩阵中,零元素的个数相对于整个矩阵元素的总个数所占比例较大时,该矩阵就是一个稀疏矩阵。
例如,有一个5×6阶的矩阵A,其30个元素中只有7个非零元素,那么,可以称矩阵A为稀疏矩阵。
稀疏矩阵一般是指矩阵中的大部分元素为零,仅有少量元素非零的矩阵称为稀疏矩阵;或者说矩阵A(m × n)中有S个非零元素,如果S远远小于矩阵的元素总数,称A为稀疏矩阵。
稀疏矩阵的存储一般只要保存非零元素即可,对于零元素可以不与保存,这样可以实现稀疏矩阵的压缩存储。
稀疏矩阵的压缩存储采用三元组的方法实现。其存储规则如下:每一个非零元素占有一行,每行中包含非零元素所在的行号、列号、非零元素的数值。
((0,2,11),(0,4,17),(1,1,20),(3,0,19),(3,2,36),(3,5,28),(4,2,50))
如果每个非零元素按照此种方法存储,虽然能够完整地描述非零元素,但如果矩阵中有整行(或整列)中没有非零元素,此时可能不能够还原成原来的矩阵,所以为了完整地描述稀疏矩阵,在以上描述的情况下,如果增加一行的内容,该行包括矩阵的总的行数、矩阵的总的列数,矩阵中非零元素的个数,就可以还原为原来的矩阵描述了。
public class Triple implements Comparable<Triple>, Addible<Triple>{ int row, column, value; //行号、列号、元素值,默认访问权限 //构造方法,参数指定行号、列号、元素值。若行号、列号为负,则抛出无效参数异常 public Triple(int row, int column, int value){ if (row>=0 && column>=0){ this.row = row; this.column = column; this.value = value; } else throw new IllegalArgumentException("行、列号不能为负数:row="+row+",column="+column); } public Triple(Triple tri){ //拷贝构造方法,复制一个三元组 this(tri.row, tri.column, tri.value); } public String toString(){ //返回三元组描述字符串 return "("+row+","+column+","+value+")"; } public boolean equals(Object obj){ //比较两个三元组是否相等,比较位置和元素值 if (this==obj) return true; if (!(obj instanceof Triple)) return false; Triple tri = (Triple)obj; return this.row==tri.row && this.column==tri.column && this.value==tri.value; } //根据行、列位置比较三元组对象大小,与元素值无关,约定三元组排序次序 public int compareTo(Triple tri){ if (this.row==tri.row && this.column==tri.column) return 0; //相等,与equals()方法含义不同 return (this.row<tri.row || this.row==tri.row && this.column<tri.column)?-1:1; } public void add(Triple term){ //加法(+=运算),实现Addible<T>接口 if (this.compareTo(term)==0) this.value += term.value; else throw new IllegalArgumentException("两项的指数不同,不能相加。"); } public boolean removable(){ //约定删除元素条件,实现Addible<T>接口 return this.value==0; //不存储值为0的元素 } public Triple toSymmetry(){ //返回矩阵对称位置元素的三元组。 return new Triple(this.column, this.row, this.value); } }
(1)稀疏矩阵的三元顺序表
public class SeqMatrix{ int rows, columns; //行数、列数 SortedSeqList<Triple> list; //排序顺序表 }
(2)稀疏矩阵的三元单链表
public class LinkedMatrix { //三元组行的单链表存储的矩阵类 int rows, columns; //矩阵行数、列数 SeqList<SortedSinglyList<Triple>> rowlist; //行指针顺序表,元素是排序单链表,默认权限 public LinkedMatrix(int m, int n){ //构造m×n零矩阵。若m或n≤0,抛出无效参数异常 if (m>0 && n>0){ this.rows = m; this.columns = n; this.rowlist = new SeqList<SortedSinglyList<Triple>>(); //构造顺序表,默认容量,初值为null for (int i=0; i<m; i++) //顺序表增加m个空单链表 this.rowlist.insert(new SortedSinglyList<Triple>()); //顺序表尾插入,自动扩容 }else throw new IllegalArgumentException("矩阵行列数不能≤0,m="+m+",n="+n); } public LinkedMatrix(int m){ //构造m×m零矩阵 this(m, m); } public LinkedMatrix(int m, int n, Triple[] tris){ //构造m×n矩阵,由三元组数组tris提供矩阵初值 this(m, n); for (int i=0; i<tris.length; i++) this.set(tris[i]); //按行主序插入一个元素的三元组 } public int getRows(){ //返回矩阵行数。方法体省略 return this.rows; } public int getColumns(){ //返回矩阵列数。方法体省略 return this.columns; } public int get(int i, int j) { if (i>=0 && i<this.rows && j>=0 && j<this.columns) { //在第i行排序单链表中顺序查找三元组(i,j,0),比较三元组大小 Node<Triple> find=this.rowlist.get(i).search(new Triple(i,j,0)); return (find!=null) ? find.data.value : 0; //若查找成功,返回元素值,否则返回0 } throw new IndexOutOfBoundsException("i="+i+",j="+j); } //设置矩阵第i行第j列元素为x。若i、j越界,则抛出序号越界异常。 //查找、插入、删除算法均比较三元组大小 public void set(int i, int j, int x){ if (i>=0 && i<this.rows && j>=0 && j<this.columns) { SortedSinglyList<Triple> link = this.rowlist.get(i);//获得第i行排序单链表 if (x==0) link.remove(new Triple(i,j,0)); //若查找成功,删除(i,j,?)结点 else{ //以下查找再插入或替换元素操作,遍历link排序单链表二次 Triple tri = new Triple(i,j,x); Node<Triple> find=link.search(tri); //顺序查找tri,若有元素>tri,则查找不成功 if (find!=null) find.data.value = x; //查找成功,修改修改find引用对象的成员变量值 else link.insert(tri); //查找不成功,排序单链表link按(i,j)次序插入三元组tri } }else throw new IndexOutOfBoundsException("i="+i+",j="+j);//抛出序号越界异常 } public void set(Triple tri){ //以三元组tri设置矩阵元素 this.set(tri.row, tri.column, tri.value); } }
- 采用行的单链表存储的稀疏矩阵及相加运算
public void addAll(LinkedMatrix mat){ if (this.rows==mat.rows && this.columns==mat.columns) for (int i=0; i<this.rows; i++) this.rowlist.get(i).addAll(mat.rowlist.get(i)); //调用多项式单链表相加算法 else throw new IllegalArgumentException("两个矩阵阶数不同,不能相加"); }
比较两个矩阵是否相等
public boolean equals(Object obj){ //比较两个矩阵是否相等,算法同SeqSparseMatrix类 if (this==obj) return true; if (!(obj instanceof LinkedMatrix)) return false; LinkedMatrix mat=(LinkedMatrix)obj; return this.rows==mat.rows && this.columns==mat.columns &&this.rowlist.equals(mat.rowlist); }
- 设置矩阵行列数
public void setRowsColumns(int m, int n){ if (m>0 && n>0){ if (m > this.rows) //若m参数指定行数较大 for (int i=this.rows; i<m; i++) this.rowlist.insert(new PolySinglyList<Triple>());//顺序表尾插入,自动扩容 this.rows = m; this.columns = n; }else throw new IllegalArgumentException("矩阵行列数不能≤0,m="+m+",n="+n); }
- 输出矩阵
public void printMatrix(){ //输出矩阵 System.out.println("矩阵"+this.getClass().getName()+"("+rows+"×"+columns+"):"); for (int i=0; i<this.rows; i++){ Node<Triple> p = this.rowlist.get(i).head.next;//遍历第i行排序单链表 for (int j=0; j<this.columns; j++) if (p!=null && j==p.data.column) //有i==p.data.row { System.out.print(String.format("%4d", p.data.value)); p = p.next; } else System.out.print(String.format("%4d", 0)); System.out.println(); } }
3,广义表
3.1,广义表抽象数据类型
广义表是线性表的扩展,具体定义为n(n≥0)个元素的有限集合,能够表示树结构和图结构。其中元素有以下两种类型:
- 一个原子元素(指不可再分的元素)。
- 一个可以再分的元素(或称为一个子表)。
如果所有元素都是原子元素,则称为线性表。如果含有子表则是广义表。n的值是广义表的长度,如果n=0,称为空表。
广义表的元素个数n称为广义表的长度,当n=0时,为空表。广义表的深度是指表中所包含的括号的层数,原子深度为0,空表深度为1。如果广义表作为自身的一个元素,则称该广义表为递归表。递归表的深度是无穷值,长度是有限值。
广义表的特性
- 线性结构:广义表是一种线性结构,数据元素之间是线性关系。广义表是线性表的扩展,线性表的特例,仅当广义表的元素全部是原子时,该广义表是线性表。
- 多层次结构,有深度:当广义表包含子表时,它是多层次结构。广义表也是树的扩展。当限制表中成分不能共享时,该广义表就是树,树中的叶子结点对应广义表中原子,非叶子结点对应子表。
- 可共享:一个广义表可作为其他广义表的子表,多个广义表可共享一些广义表。
- 可递归:广义表是一个递归表,当广义表中有共享或递归成分的子表时构成图结构,与有根、有序的有向图对应。
广义表的图形表示
广义表的抽象数据类型
ADT GenList<T>{ boolean isEmpty() //判空 int size() //长度 int depth() //深度 GenListNode<T> insert(int i, T x)//插入 GenListNode<T> insert(int i, GenList<T> glist); //插入子表 void remove(int i); //删除第i个元素 }
3.2,广义表的存储结构
广义表的存储方法有很多种,一般采用链表存储。采用链表存储时的结点存储的逻辑结构(如图所示)一般是:
其中flag表示标志位,当flag为0时,该结点表示原子元素,当flag为1时,该结点表示子表;当flag为0时,info表示原子元素的值,当flag为1时,info表示指针,指向该子表的第一个结点;link表示指针,指向广义表的下一个元素。
(1)广义表的单链表示
(2)广义表的双链表示