数据结构之数组

数组

1.数组的概述

  • 数组也是用来存储“一对一”的逻辑关系数据的线性表。
  • 顺序表、链表、栈和队列存储的都是不可再分的数据元素(如:int,char类型),但数组既可以用来存储不可再分的数据元素,也可以用来存储像顺序表、链表这样的数据结构。
  • 按逻辑结构的不同,数组可细分:
    • 一维数组
    • 二维数组
    • n 维数组
  • 无论数组的维数是多少,数组中的数据类型都必须一致,即是同一种数据类型。

2.数组的顺序存储结构

  • 数组中数据的存储有两种先后存储方式:
    • 以列序为主(先列后行);
    • 以行序为主(先行后序)。
  • C 语言中,多维数组的存储采用的是以行序为主的顺序存储方式。
2.1数组中查找元素
  • 查找元素之前,需要知道的信息如下:
    • 多维数组的存储方式;————以行序为主?还是以列为主?
    • 多维数组在内存中存放的起始地址;
    • 该指定元素在原多维数组的坐标;
    • 数组中元素的具体数据类型,即一个元素占用的字节。
  • 实例:以行为序的2维的int数组a[n][m]中查找a[i]j。则关系有:
  &a[i][j] = &a[0][0]+(i*m+j)*sizof(int);

3.矩阵的压缩存储

  • 常见特殊矩阵,主要分为以下两类:
    • 含有大量相同数据元素的矩阵,比如对称矩阵
    • 含有大量 0 元素的矩阵,比如稀疏矩阵上(下)三角矩阵
  • 数据结构的压缩存储思想是:矩阵中的相同数据元素(包括元素0)只存储一个。
3.1 对称矩阵
  • 定义:矩阵中,数据元素沿主对角线对应相等。
    • 主对角线:对称线类似右斜杠\;
    • 副对角线:对称线类似左斜杠/。
  • 根据对称矩阵的特点,可以发现上三角和下三角的元素是一样的。我们可以用一个一维数组来存储上三角或者下三角。
  • 具体实现步骤:
    • 假设矩阵是a[i][j],压缩存储成b[k],则i,j,k符号如下关系:(其中矩阵中的行列下标均是从1开始,即i,j从1开始计数)
    • (1)存储下三角
    • k = i * (i - 1) / 2 + j - 1
    • (2)存储上三角
    • k = j * (j - 1) / 2 + i - 1
    • 两种存储方式,最终的结果是一致,这样才方便从压缩后的数组去找数组。
  • 代码实现:
#include<stdio.h>
void main()
{
    int a[3][3] =
    {
        {1,2,3},
        {2,4,5},
        {3,5,6}
    };
    int b[10];
    int c[10];
    int k,m;
    //存储下三角(先按行读取)
    for (int i = 1; i <= 3; i++)
    {
        for (int j = 1; j <= i; j++)
        {
            k = i * (i - 1) / 2 + j - 1;
            b[k] = a[i - 1][j - 1];
            printf("下三角元素依次是:%d\n", b[k]);
        }
    }
    printf("\n");
    //存储上三角(先按列读取)
    for (int j = 1; j <= 3; j++)
    {
        for (int i = 1; i <= j; i++)
        {
            m = j * (j - 1) / 2 + i - 1;
            c[m] = a[i - 1][j - 1];
            printf("上三角的元素依次是:%d\n", c[m]);
        }
    }
}
3.2 上(下)三角矩阵
  • 主对角线下的数据元素全部相同的矩阵为上三角矩阵,主对角线上元素全部相同的矩阵为下三角矩阵。
  • 压缩存储的方法和对称矩阵一样。
3.3 稀疏矩阵
  • 定义:矩阵中分布有大量的相同的元素,比如 0,即非0元素非常少,这类矩阵叫稀疏矩阵。
  • 压缩存储稀疏矩阵的思想是:
    • 只存储矩阵中的非 0 元素。即稀疏矩阵非0元素的存储需同时存储该元素所在矩阵中的行标和列标。
    • 矩阵总行数和列数。
  • 压缩存储方法:
    • 三元组顺序表;
    • 行逻辑链接的顺序表;
    • 十字链表;
3.3.1 三元组顺序表
  • 三元组:由 3 部分数据组成的集合,组中数据分别表示(行标,列标,元素值)。
/*
*稀疏矩阵采用三元组来存储
* 稀疏矩阵为a[3][3] = {
* {1,0,0},
* {0,3,0},
* {0,0,5}
* };
* 用三元组类型的数组b[3]表示。则有b[3] = {
* (1,1,1),(3,2,2),(5,3,3)};
*/
#include<stdio.h>

//1.三元组结构体
typedef struct
{
    int i, j;
    int data;
}triple;
//2.矩阵,除了三原组还得加上总行数和总列数
typedef struct
{
    triple data[20]; //非0数的三元组形式
    int n, m, num; //矩阵为n行*m列,总共有num个非零数
}T_Matrix;

//3.打印矩阵
void DisPlay(T_Matrix M)
{
    int i, j, k;
    int Temp;
    int flag;
    for (i = 1; i <= M.n; i++)
    {
        for (j = 1; j <= M.m; j++)
        {
            flag = 0;
            for (k = 0; k < M.num; k++)
            {
                if ((i == M.data[k].i) && (j == M.data[k].j))
                {
                    Temp = M.data[k].data;
                    flag = 1;
                    break;
                }
            }
            if (0 == flag)
            {
                Temp = 0;
            }
            printf("%d\t", Temp);
        }
        printf("\n");
    }
}

void main()
{
    T_Matrix M; //定义矩阵M
    M.n = 3; //表示3行
    M.m = 3; //表示3列
    M.num = 3; //表示非0的数有3个。

    M.data[0].i = 1;
    M.data[0].j = 1;
    M.data[0].data = 1;

    M.data[1].i = 2;
    M.data[1].j = 2;
    M.data[1].data = 3;

    M.data[2].i = 3;
    M.data[2].j = 3;
    M.data[2].data = 5;

    DisPlay(M);
}
  • 该方法缺点:在访问矩阵中指定的某元素需要遍历整个压缩数组,因此效率比较低。为了提高效率,在压缩存储时,多用一个数组记录每行非0元素在新的压缩一未数组的索引位置,该方法即是行逻辑链接的顺序表。
3.3.2 行逻辑链接的顺序表
  • 实现思想:
    • 将矩阵M中非0元素的三元组(行标、列标和元素值)存储在一个维数组b[k]中;
    • 再用另一个数组c[n](n <= k)记录矩阵中每行第一个非 0 元素在一维数组b[k]中的位置。
  • 实例:
/*
*稀疏矩阵采用按行逻辑存储 = 三元组来存储 + 额外一个一维数组存储每行首个非0元素在新一维数组中的位置。
* 稀疏矩阵为a[3][4] = {
* {1,0,0,6},
* {0,3,0,0},
* {0,0,5,8}
* };
* 用三元组类型的数组arr[6]表示。则有arr[6] = {
* (0,0,0),(1,1,1),(1,4,6),(2,2,3),(3,3,5),(3,4,8)};
*用一个数组存储矩阵每行的非0的数在arr[]中的位置。loc[4]= {0,1,3,4};
*注意:为了方便逻辑,存数索引从1开始累计,相当于索引0上的值无效。
*/
#include<stdio.h>
#define SIZE 6
typedef struct
{
	int data;
	int i, j;
}triple;
typedef struct
{
	triple arr[SIZE]; //SIZE大于或等于非0元素的个数
	int loc[4]; // 4表示行数+1,用来存储每行非0的数值在arr中的位置(索引从1开始计)。
	int m, n, num;//分别表示矩阵的行数、列数、非0元素的个数
}T_Matrix;

//打印矩阵
void DisPlay(T_Matrix M)
{
	for (int i = 1; i <= M.m; i++) 
	{
		for (int j = 1; j <= M.n; j++) 
		{
			int value = 0;
			if (i + 1 <= M.m) //保证本次循环不是最后一行
			{
				for (int k = M.loc[i]; k < M.loc[i+1]; k++) //从本行非0元素开始寻找,直到下一行的首个非0元素
				{
					if (i == M.arr[k].i && j == M.arr[k].j) 
					{
						printf("%d ", M.arr[k].data);
						value = 1;
						break;
					}
				}
				if (value == 0) 
				{
					printf("0 ");
				}
			}
			else //最后一行开始寻找
			{
				for (int k = M.loc[i]; k <= M.num; k++)  //最后一行开始循环,不用找到最后一个元素,只需找到所有非0元素的个数即可停止循环
				{
					if (i == M.arr[k].i && j == M.arr[k].j) 
					{
						printf("%d ", M.arr[k].data);
						value = 1;
						break;
					}
				}
				if (value == 0) 
				{
					printf("0 ");
				}
			}
		}
		printf("\n");
	}
}
void main()
{
	T_Matrix M;
	M.m = 3;
	M.n = 4;
	M.num = 5;

	//每行非0元素在新压缩数组arr中的位置。
	M.loc[1] = 1;
	M.loc[2] = 3;
	M.loc[3] = 4;

	M.arr[1].data = 1;
	M.arr[1].i = 1;
	M.arr[1].j = 1;

	M.arr[2].data = 6;
	M.arr[2].i = 1;
	M.arr[2].j = 4;

	M.arr[3].data = 3;
	M.arr[3].i = 2;
	M.arr[3].j = 2;

	M.arr[4].data = 5;
	M.arr[4].i = 3;
	M.arr[4].j = 3;

	M.arr[5].data = 8;
	M.arr[5].i = 3;
	M.arr[5].j = 4;

	DisPlay(M);
}
  • 前两种方法,本质上都是通过数组来存储稀疏矩阵,数组的缺点就是不方便插入或者删除元素。因此可以考虑采用链表的方式,即十字链表法。
3.3.3 十字链表法
  • 实现思想:链表+数组
    *元素用三元组表示,将每行每列的非0元素各存到一个链表中,然后将行链表的表头存在一个数组中,将列链表的表头存在另一个数组中。
    十字链表法示意图
3.4 矩阵的转置
  • 定义:将矩阵中所有元素的行标和列标进行互换,得到新的矩阵。示意图如下:
    矩阵转置图
  • 实现步骤:
    1. 将矩阵的行数和列数互换;
    1. 将原矩阵元素的行列交换;
    1. 对原矩阵以列为序,重新排列三元组表中存储各三元组的先后顺序。
#include<stdio.h>
/*
* 原矩阵为arr[3][2] = {
* {0,1},
* {0,3},
* {6,5}
* };
* 转置后为arr2[2][3] = {
* {0,0,6},
* {1,3,5}
* };
* 注意:在实现过程中,均是以索引1为开始计数。
*/
typedef struct
{
   int data;
   int i, j;
}triple;
typedef struct
{
   triple arr[5];
   int m, n, num;
}T_Mtriax;

T_Mtriax RePlaceMtriax(T_Mtriax M, T_Mtriax T) {
   T.m = M.n;
   T.n = M.m;
   T.num = M.num;
   if (T.num) //说明矩阵中存在非0元素
   {
   	int q = 1;//新压缩数组的索引
   	for (int col = 1; col <= M.m; col++) //对列开始遍历
   	{
   		for (int p = 1; p <= M.num; p++) //对原先的压缩数组进行遍历,依次与每列进行匹配
   		{
   			if (M.arr[p].j == col) //当遍历到列相同时,对应的行数也就通过M.arr[p].i知道了。
   			{
   				//行列互换
   				T.arr[q].i = M.arr[p].j;
   				T.arr[q].j = M.arr[p].i;
   				T.arr[q].data = M.arr[p].data;
   				q++;//新压缩数组的索引
   			}
   		}
   	}
   }
   return T;
}

void main()
{
   T_Mtriax M;
   T_Mtriax NewM;
   M.n = 3;
   M.m = 2;
   M.num = 4;

   M.arr[1].data = 1;
   M.arr[1].i = 1;
   M.arr[1].j = 2;

   M.arr[2].data = 3;
   M.arr[2].i = 2;
   M.arr[2].j = 2;

   M.arr[3].data = 6;
   M.arr[3].i = 3;
   M.arr[3].j = 1;

   M.arr[4].data = 5;
   M.arr[4].i = 3;
   M.arr[4].j = 2;

   //对新的矩阵的压缩存储数组初始化
   for (int i = 1; i <= M.num; i++)
   {
   	NewM.arr[i].data = 0;
   	NewM.arr[i].i = 0;
   	NewM.arr[i].j = 0;
   }
   NewM = RePlaceMtriax(M, NewM);

   for (int i = 1; i <= NewM.num; i++) 
   {
   	printf("(%d,%d,%d)\n", NewM.arr[i].i, NewM.arr[i].j, NewM.arr[i].data);
   }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值