最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

数组基本概念与存储结构

XAMPP相关 admin 353浏览 0评论
1. 数组

数组可以看成线性表的推广(特别是对于多维数组)

1.1 数组的基本概念

1.1.1 d维数组的抽象数据类型:

drc38

可以看出,数组除了初始化和销毁以外,在数组中通常只有下面两种操作:

· 读操作:给定一组下标,读取相应的数组元素。

· 写操作:给定一组下标,存储或修改相应的数组元素。

 

1.1.2 在C/C++中,数组的数据类型有以下性质:

(1)数据元素的数目固定,一旦定义了一个数组,其数据元素数目不再有变化。

(2)数据元素具有相同的数据类型。

(3)每个数据元素对应唯一一个下标。

(4)数组是一种随机存储结构,可以随机存取数组中的任意数据元素。

1.1.3 下面来体验一下数组的应用

用数组解决约瑟夫问题:设有n个人站成一圈其编号为1~n。从编号为1的人开始按顺时针方向“1,2,3,4···”循环报数,数到m的人出列,然后从出列者的下一个人重新开始报数,数到m的人又要出列,如此重复进行,直到n个人都出列为止,求出列顺序。

#约瑟夫问题
void josephus(int n, int m)
{
int p[MaxSize];
int i, j, t;
for(i = 0; i < n; i++)  //构建初始序列 
    p[i] = i+1;
  t = 0;  //首次报数起始位置 
printf("出列顺序:");
for(i = n; i >= 1; i--)  //i为当前p数组的人数,出列一次,人数减1 
  {
    t = (t+m-1) % i;  //t为出列者的编号 
printf("%d,", p[t]);
for(j = t+1; j <= i-1; j++)  //后面的元素前移一个位置 
      p[j-1] = p[j];
  } 
printf("\n");
}

1.2 数组的存储结构

数组适合采用顺序存储结构来存储。

 

1.2.1 一维数组的存储结构

因为数组元素存储到一块地址连续的内存单元中,假设第一个元素a1的存储地址为LOC(a1)表示,每个元素用k个存储单元,则:

LOC(ai) = LOC(a1) + (i – 1) × k

 

1.2.2 二维数组的存储结构(简单理解)

有两种:按行优先存放、按列优先存放。

二维数组按行优先存放

drc038

drc038

二维数组按列优先存储

drc0038

1.3 特殊矩阵的压缩存储(重点)

特殊矩阵主要形式有对称矩阵、对角矩阵等,他们都是方阵。

 

1.3.1 对称矩阵的压缩存储

drc00038

图6.3对称矩阵的压缩存储

上述过程是对称矩阵采用行序优先存储对角线+下三角部分的元素,因为是对称矩阵,所以我们只需存储“对角线+下三角(或上三角)”就可以了,又因为数组可以看作一个线性表,因为不管是一维数组还是多维数组,他们一旦定义后,所占用的都是一块连续的存储地址。所以上图的数组B就是访问二维数组的一种相当于线性表的形式。

对于“对角线+下三角”的存储,其中元素所处的 i(行) >= 列 j(列),因为对于行下标为 i-1 的元素所处的行一共有 i 个元素(行下标从 0 开始)。所以这 i 行一共有 1+2+3+···+i = i(i+1)/2 个元素,又因为列下标为 j 的元素前面还有 j 个元素(列下标也从 0 开始),所以元素 a(i,j) 在对应B数组中的位置 k = i(i + 1)/2 +j (数组B中的元素下标也是从 0 开始)。

若存储的是“对角线+上三角”,有 i < j ,又因为 a(i,j) = a(j,i) ,所以它存放在数组B下标为 j(j+1)/2 +i 的位置。

综上有

drc000038

所以在访问或修改对称矩阵中的某个元素 a(i,j) 时,就可以根据上式计算出对应在线性表(数组)中位置,找到该元素。

总之就是在计算一个对称矩阵中元素 a(i,j) 在 B 中存储位置 k 时,首先要求出 a(i,j) 前面共存放多少个元素(设为m),再看 B 中存放元素的下标是从 0 开始还是从 1 开始(设 B 的初始下标为s),则 k = m+s。

 

1.3.2 上、下三角矩阵的压缩存储

这个不是我们所认为的下三角或上三角部分为 0 的上、下三角矩阵,而是为常数 c 的上下三角矩阵。

其压缩方式仍是采用以行序为主序存储其对角线或上三角(或下三角)部分的元素。另外用一个元素存储常数 c ,并将压缩结果存放在一维数组 B 中,B 中元素的个数为 n(n+1)/2 + 1,即用 B[0….n(n+1)/2] 存放 A 中的元素。

drc0000038

图6.4上三角矩阵的压缩存储

上面是对于存储“对角线+上三角”(i <= j)的一个图解,得出对于元素 a(i,j) 这 i 行一共有 n+(n-1)+(n-2)+······+(n-i+1) = i(2n-i+1)/2 个元素,在行下标为 i 的行中,元素 a(i,j) 的前面也存储了(j-i) 个元素。所以元素 a(i,j) 前面共存储了 i(2n-i+1)/2 +j-i 个元素,B数组的下标也是从零开始的,所以该元素对应数组 B 中的位置下标 k = i(2n-i+1)/2 +j-i 。

若 a(i,j) 是矩阵中的下三角部分也就是常数部分,有 i > j 。其值为常数c,用 B 的最后一个位置(即下标为 n(n+1)/2 的元素)存放常数c。

综上有:

drc37

对于“对角线+下三角”,类比对称矩阵有:

drc037

1.3.3 对角矩阵的压缩存储

这里的对角矩阵也不是简单的只是对角线为非 0 的对角阵,是指满足所有非零元素都集中在以对角线为中心的带状区域中。

半带宽:其主对角线上、下方各有 b 条非零元素构成的次对角线,则称 b 为矩阵的半带宽。

带宽:(2b+1) 为矩阵的带宽。

对角矩阵中 a(i,j) 对应到一维数组 B 中的下标位置 k = 2i + j 。(具体过程,百度吧,啊哈哈哈哈)

 

1.4 稀疏矩阵

稀疏矩阵:当一个矩阵中的非零元素个数 s 远小于矩阵元素总个数 t ,即 s << t 时,称该矩阵为稀疏矩阵。

 

1.4.1 稀疏矩阵的三元组表示

稀疏矩阵的压缩存储是只存储非零元素,但也必须同时存储非零元素对应的行下标、列下标和元素值。这个每一个非零元素由一个三元组(i, j, a(i,j))唯一确定。其所有非零元素构成 三元组线性表。

若把稀疏矩阵的三元组线性表按顺序存储结构存储,则称为三元组顺序表,简称 三元组表。

· 三元组顺序表的数据类型声明如下:

#三元组顺序表的数据类型
typedef int ElemType; 

#define M 6   //稀疏矩阵的行数
#define N 6   //稀疏矩阵的列数
#define MaxSize 10  //稀疏矩阵中非零元素最多的个数

typedef struct
{
int r;  //行号 
int c;  //列号 
  ElemType d;  //元素值 
}TupNode;  //三元组类型

typedef struct
{
int rows;  //行数
int cols;  //列数
int nums;  //非零元素的个数 
  TupNode data[MaxSize];  // 结构体数组!
}TSMatrix;  //三元组顺序表类型

    · 从一个二位稀疏矩阵创建其三元组表示 :

#从一个二位稀疏矩阵创建其三元组表示 
void CreateMat(TSMatrix &t, ElemType A[M][N])
{
  int i, j;
  t.rows = M;
  t.cols = N;
  t.nums = 0;
  for(i = 0; i < M; i++)
  {
for(j = 0; j < N; j++)
    {
if(A[i][j] != 0)
      {
t.data[t.nums].r = i;
t.data[t.nums].c = j;
t.data[t.nums].d = A[i][j];
t.nums++;
      }
    }
  }
}

    · 三元组元素的赋值:

#三元组元素的赋值
bool Value(TSMatrix &t, ElemType x, int i, int j)
{
  int k = 0, k1;
if(i >= t.rows || j >= t.cols)
return false;
while(k < t.nums && i > t.data[k].r)  //查找第i行的非零元素 
    k++;  
while(k < t.nums && i == t.data[k].r && j > t.data[k].c)
    k++;  //在第i行的非零元素中查找第j列 
if(t.data[k].r == i && t.data[k].c == j)  //如果存在这样的非零元素,修改非零元素的值 
    t.data[k].d = x;
else
  {
for(k1 = t.nums-1; k1 >= k; k1--)  //若干元素均后移一位 
    {
      t.data[k1 + 1].r = t.data[k1].r;
      t.data[k1 + 1].c = t.data[k1].c;
      t.data[k1 + 1].d = t.data[k1].d;
    }
    t.data[k].r = i;  //插入非零元素x 
    t.data[k].c = j;
    t.data[k].d = x;
    t.nums++; 
  } 
return true;  
}

    · 将指定位置的元素值赋值给变量:

#将指定位置的元素值赋值给变量
bool Assign(TSMatrix t, ElemType &x, int i, int j)
{
  int k;
if(i >= t.rows || j >= t.cols)
return false;
while(k < t.nums && i > t.data[k].r)  //查找第i行的非零元素 
    k++;  
while(k < t.nums && i == t.data[k].r && j > t.data[k].c)
    k++;  //在第i行的非零元素中查找第j列 
if(t.data[k].r == i && t.data[k].c == j)
    x = t.data[k].d;
else
    x = 0;
return true;

    · 输出三元组:

#输出三元组
void DispMat(TSMatrix t)
{
int k;
if(t.nums <= 0)
return;  //因为函数为void 所以一个return就可以理解为返回空 
printf("\t%d\t%d\t%d\n", t.rows, t.cols, t.nums);
//  printf{"\n"};
for(k = 0; k < t.nums; k++)
printf("\t%d\t%d\t%d\n", t.data[k].r, t.data[k].c, t.data[k].d);
}

    · 稀疏矩阵的转置:

#稀疏矩阵的转置
void TranTat(TSMatrix &t, TSMatrix &tb)
{
  int k, k1 = 0, v;
  tb.rows = t.rows;
  tb.cols = t.cols;
  tb.nums = t.nums;
if(t.nums != 0)
  {
for(v = 0; v < t.cols; v++)  //按列循环 
for(k = 0; k < t.nums; k++)
if(t.data[k].c == v)  //找到一个列号为v的元素 
        {
          tb.data[k1].d = t.data[k].d;
          tb.data[k1].r = t.data[k].c;
          tb.data[k1].c = t.data[k].r;
          k1++;
        }
  }
}

以上算法中含有两重for 循环,其时间复杂度为O(t.cols×t. nums)。最坏的情况是当稀疏矩阵中的非零元素个数t.nums和m×n同数量级时,时间复杂度为O(m×n^2),所以这不是一种高效的算法。

从以上可以看出,稀疏矩阵采用三元组顺序表存储后,当非零元素个数较少时会在一定程度上节省存储空间。如果用一个二维数组直接存储稀疏矩阵,此时具有随机存取特性,但采用三元组顺序表存储后会丧失随机存取特性。

 

1.4.2 稀疏矩阵的十字链表表示(类比图的十字链表)

十字链表(orthogonal list)是稀疏矩阵的一种链式存储结构(相应的,前面的三元组顺序表是稀疏矩阵的一种顺序存储结构)。有如下3行4列的稀疏矩阵:

drc0037

创建稀疏矩阵 B 的十字链表的步骤如下:

(1)对于稀疏矩阵中每个非零元素创建一个结点存放它,包含元素的行号,列号和元素值。这里有 4 个非零元素,创建 4 个数据结点。

(2)将同一行的所有结点构成一个带头结点的循环单链表,行号为i的单链表的头结点为hr[i门。这里有 3 行,对应有 3 个循环单链表,头结点分别为hr[ 0 ]~hr[ 2 ]。hr[ i ] (0 ≤ i ≤ 2) 头结点的行指针指向行号为 i 的单链表的首结点。

(3)将同一列的所有结点构成一个带头结点的循环单链表,列号为j的单链表的头结点为hd[j] 。这里有 4 列,对应有 4 个循环单链表,头结点分别为 hd[0]~hd[3]。hd[j] (0 ≤ j ≤ 3) 头结点的列指针指向列号为 j 的单链表的首结点。

由此创建了 3十4=7 个循环单链表,头结点的个数也为 7 个。实际上,可以将 hr[ i ] 和 hd[ i ] 合起来变为h[ i ] ,即 h[ i ] 同时包含有行指针和列指针。h[ i ] (0 ≤ i ≤ 2) 头结点的行指针指向行号为i的单链表的首结点, h[ i ] (0 ≤ i ≤ 3) 头结点的列指针指向列号为i的单链表的首结点,这样头结点的个数为 MAX{3, 4} = 4 个。

 

(4)再将所有头结点h[ i ] (0 ≤ i ≤ 3) 连起来构成一个带头结点的循环单链表,这样需要增加一个总头结点 hm ,总头结点中存放稀疏矩阵的行数和列数等信息。

采用上述过程创建的稀疏矩阵B的十字链表如下图所示。每个非零元素就好比在一个十字路口,由此称为十字链表。

drc00037

两种节点类型:

drc000037

(a)数据结点结构                                                 (b)头结点结构

设计稀疏矩阵的十字链表的节点类型 MatNode :

#define M <稀疏矩阵的行数>
#define N <稀疏矩阵的列数>
#define Max ((M) > (N) ? (M) : (N))  //矩阵行列最大者 

typedef struct mtxn
{
int row;
int col;
struct mtxn *right, *down;  //行列指针 
union
  {
    ElemType value;
struct mtxn *link;  //指向下一头节点 
  }tag;
}MatNode;  //十字链表的结点类型

头结点 h[ i ] 的 h[ i ] ->right 指针可以逐行搜索行下标为i的所有非零元素。

h[ i ]-> down 指针可以逐列搜索列下标为i的所有非零元素。

转载请注明:XAMPP中文组官网 » 数组基本概念与存储结构

您必须 登录 才能发表评论!