本文最后更新于:2019年11月14日 晚上
这是我的数据结构课程设计,搬运至博客做一份保存,也把弄清楚的一些知识记录下来。
题目
- 按照行优先顺序将输入的数据建成4维数组,按照列优先顺序输出结果;
- 给出任意处的元素值,并给出对应的一维数组中的序号;
- 用
squeeze
函数来消除数组中的”孤维”,即大小等于1的维,从而起到降维的作用;
- 用
sub2ind
函数将下标转换为单一索引数值;
- 用
ind2sub
函数将数组的单一索引数值转换为数组的下标。
相关概念
1.行优先存储与列优先存储
说到存储,计算机的存储方式只有两种,物理结构——顺序存储结构和链式存储结构。而一切数据结构中的类型,如栈、队列、二叉树、图等都是使用这两种存储结构进行实现与表示的,这些结构都是逻辑上的结构,同样的,数组,无论多少维度都是采用这两种物理结构实现的,其在内存之中都只是一堆连续或者不连续的数据。
对于多维数组来说,存储的顺序可以不同,因为其下标不止一个,存储数据的先后次序也就可以不一样。
举个栗子,二维数组。行优先存储,优先存储每一行,依次向后,对应的下标变换为a[0][0]
至a[0][1]
至a[0][2]
…。低下标先开始变化,直至结束。因此,以行序为主序存储方式也称为低下标优先。C语言采用的就是这种方式。
列优先存储,优先存储每一列,依次向后,对应的下标变换为a[0][0]
至a[1][0]
至a[2][0]
…。高下标先开始变化,直至结束。因此,以列序为主序存储方式也称为高下标优先。Matlab采用的就是这种方式。
参考链接
行优先存储和列优先存储
行优先和列优先的原则问题——Matlab
2.C语言多维数组的理解
C语言数组,是一个用于存储固定大小的相同类型元素的集合。
int a[4] = {0,1,2,3};
int b[2][4] = {
{0,1,2,3},
{4,5,6,7}
};
如上所示的一维数组,基本元素是int
类型的数据,这个数组可称为int类型数据的数组。而二维数组,基本元素是int
类型数据的数组,这个数组可称为一维数组的数组。同理,n维数组的基本元素就是n-1维数组。
声明数组 int c[3][4][5];
表示数组 c 包含三个元素: c[0],c[1],c[2]
,而这每一个元素又都是二维数组,而每一个二维数组又包含四个元素——一维数组。
参考链接
C语言多维数组,以及多维数组中的二维数组
3.几个名词:维度、维界与孤维(未找到定义,自己的理解)
维度:几维数组对应的维度就是几。如 int c[3][4][5];
是三维数组。
维界:对应每一个维度的范围,一般下界为0。如 int c[3][4][5];
,其第三维维界为0至2。
孤维,就是维界只能存储数量为1的维度,如 int c[3][1][5];
,共能存放15个数据,而int c[3][5];
也是存放15个数据,故可以消除孤维,来简化多维数组。
数据结构与核心算法
数据结构
采用顺序存储的方式来存储数组:
- 一旦建立数组,数组存储的数量和结构中的元素之间的关系不需要再发生变化。
- 不需要插入和删除操作,还需要实现随机存取(
时间复杂度为O(1)
)。
1 2 3 4 5 6 7 8
| typedef struct{ ElemType * base; int dim; int elemtotal; int * bounds; int * constants; }Array;
|
核心算法——映像函数
映像函数是为了便于计算多维下标对应的内存基址。由于四维数组只是逻辑上的四维数组,其在内存上的真实分配情况也只是顺序存储结构,为了实现四维数组随机存取的特点,就要使用映像函数求得其地址。故上述结构体中 int * constants
的存在就是为了使得计算方便。
例如:int c[3][4][5]
这个三维数组,按照行优先存储,下标(1,2,3)对应的基址如何求得呢?
LOC(1,2,3) = 首元素基址 + 1 * 4 * 5 + 2 * 5 + 5 * 1
。而这里的 int * constants
就是把每一个维度能存储的元素个数存储了下来。对于 int c[3][4][5]
它的第三维包含三个元素 c[0][4][5]、c[1][4][5]、c[2][4][5]
,每个元素都能存储 4 * 5 = 20
个元素,第三维下标为1,故1 * 4 *5 = 20
,其余同理。
故使用 int * constants
来作为映像函数常量基址,每个 A.constands[i]
代表比 dim - i
低一维的所有元素的个数。
1 2 3 4 5 6
| A.constants = (int *)malloc(A.dim * sizeof(int)); A.constants[A.dim - 1] = 1; for (int i = A.dim - 2; i >= 0; --i) { A.constants[i] = A.bounds[i + 1] * A.constants[i + 1]; }
|
映像函数如下图
计算方法,求四维下标(a,b,c,d)对应的基址:
1
| ind = base + A.constants[0] * a + A.constants[1] * b + A.constants[2] * c + A.constants[3] * d;
|
参考链接
C语言中什么是数组映像函数常量基址
实现代码
我的代码分文件编程,Array.h
是关于数据结构的定义与声明,Array.cpp
是关于Array.h
内函数的实现,main.cpp
是运用这些函数进行流程控制之后的演示。编译环境是VS2013
。
Array.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #pragma once
#define OK 1 #define ERROR 0 #define OVERFLO -1 #define MAX 8
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <conio.h>
using namespace std;
typedef int ElemType; typedef int Status;
typedef struct{ ElemType * base; int dim; int elemtotal; int * bounds; int * constants; }Array;
typedef struct{ int a; int b; int c; int d; }Sub;
Status InitArray(Array &A, int dim, int *boundary);
Status ValueArray(Array &A);
void PrintArrayByRow(Array &A);
void PrintArrayByCol(Array &A);
Status GetValue(Array A, Sub sub,ElemType &elem);
Status sub2ind(Array A,Sub sub,int &ind);
Status ind2sub(Array A, int ind, Sub &sub);
Status squeeze(Array A, Array &B);
void ShowDate(Array A);
Status DestroyArray(Array &A);
|
Array.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
| #include "Array.h"
Status InitArray(Array &A, int dim, int *boundary) { if (dim<0 || dim>MAX) return ERROR; A.dim = dim; A.bounds = (int *)malloc(dim*sizeof(int)); if (!A.bounds) exit(OVERFLO); A.elemtotal = 1; for (int i = 0; i < dim; ++i) { A.bounds[i] = boundary[i]; A.elemtotal *= A.bounds[i]; } A.base = (ElemType *)malloc(A.elemtotal * sizeof(ElemType)); if (!A.base) exit(OVERFLO); A.constants = (int *)malloc(A.dim * sizeof(int)); if (!A.constants) exit(OVERFLO); A.constants[A.dim - 1] = 1; for (int i = A.dim - 2; i >= 0; --i) { A.constants[i] = A.bounds[i + 1] * A.constants[i + 1]; } return OK; }
Status ValueArray(Array &A) { cout << "您一共要输入 " << A.elemtotal << " 个数据(int类型)" << endl; for (int i = 0; i < A.elemtotal; ++i) { cin >> A.base[i]; } return OK; }
void PrintArrayByRow(Array &A) { cout << "按照行优先顺序输出结果" << endl; for (int i = 0; i < A.elemtotal; ++i) { cout << A.base[i]<<" "; } cout << endl; }
void PrintArrayByCol(Array &A) { int ind; Sub sub; cout << "按照列优先顺序输出结果" << endl; for (sub.d = 0; sub.d < A.bounds[3]; ++sub.d) { for (sub.c = 0; sub.c < A.bounds[2]; ++sub.c) { for (sub.b = 0; sub.b < A.bounds[1]; ++sub.b) { for (sub.a = 0; sub.a < A.bounds[0]; ++sub.a) { sub2ind(A, sub, ind); cout << A.base[ind] << " "; } } } } cout << endl; }
Status GetValue(Array A, Sub sub, ElemType &elem) { if ((sub.a<0 || sub.a>A.bounds[0] - 1) || (sub.b<0 || sub.b>A.bounds[1] - 1) || (sub.c<0 || sub.c>A.bounds[2] - 1) || (sub.d<0 || sub.d>A.bounds[3] - 1)) return ERROR; int ind; sub2ind(A, sub, ind); elem = A.base[ind]; return OK; }
Status sub2ind(Array A, Sub sub, int &ind) { if ((sub.a<0 || sub.a>A.bounds[0] - 1) || (sub.b<0 || sub.b>A.bounds[1] - 1) || (sub.c<0 || sub.c>A.bounds[2] - 1) || (sub.d<0 || sub.d>A.bounds[3] - 1)) return ERROR; ind = 0; ind += A.constants[0] * sub.a; ind += A.constants[1] * sub.b; ind += A.constants[2] * sub.c; ind += A.constants[3] * sub.d; return OK; }
Status ind2sub(Array A, int ind, Sub &sub) { if (ind<0 || ind>A.elemtotal - 1) return ERROR; sub.a = ind / A.constants[0]; sub.b = (ind % A.constants[0]) / (A.constants[1]); sub.c = ((ind % A.constants[0]) % (A.constants[1])) / A.constants[2]; sub.d = (((ind % A.constants[0]) % (A.constants[1])) % A.constants[2]) % A.constants[2]; return OK; }
Status squeeze(Array A,Array &B) { bool is = false; int dim = A.dim; int boundary[MAX]; int j = 0; for (int i = 0; i < A.dim; ++i) { if (A.bounds[i] == 1) { is = true; --dim; } else{ boundary[j] = A.bounds[i]; ++j; } } if (!is) return ERROR; else{ if (dim == 0) { dim = 1; boundary[0] = 1; } InitArray(B, dim, boundary); for (int i = 0; i < A.elemtotal; ++i) { B.base[i] = A.base[i]; } return OK; } }
void ShowDate(Array A) { cout << "-------------------------------------------" << endl; cout << " 当前数组信息 " << endl; cout << "\t维数:" << A.dim << endl; cout << "\t形如A[a][b][c][d]这样的形式,当前数组对应的各维维度是" << endl; for (int i = 0; i < A.dim; ++i) { cout <<"\t"<< char('a' + i) << " :" << A.bounds[i] << endl; } cout << "\t数据总数是" << A.elemtotal << endl<<"\t"; PrintArrayByRow(A); cout << "----------------------END------------------" << endl; }
Status DestroyArray(Array &A) { if (!A.base) return ERROR; free(A.base); A.base = NULL; if (!A.bounds) return ERROR; free(A.bounds); A.bounds = NULL; if (!A.constants) return ERROR; free(A.constants); A.constants = NULL; return OK; }
|
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
| #include "Array.h"
int main() { system("color 3E"); int inso; int inso1; char ch; Array A; A.elemtotal = 0; Array B; Sub sub; int ind; bool creat = false; ElemType elem; cout << "\t欢迎使用四维数组程序" << endl; while (1) { cout << "+------------------------------------------+" << endl; cout << "|\t1.创建四维数组 |" << endl; cout << "|\t2.查看四维数组信息 |" << endl; cout << "|\t3.输入下标查看四维数组值 |" << endl; cout << "|\t4.按照行优先顺序打印 |" << endl; cout << "|\t5.按照列优先顺序打印 |" << endl; cout << "|\t6.将下标转换为单一索引数值; |" << endl; cout << "|\t7.将单一索引数值转换为数组的下标; |" << endl; cout << "|\t8.降维\t\t |" << endl; cout << "|\t9.退出\t\t |" << endl; cout << "+------------------------------------------+" << endl; cout << "请输入相关序号执行操作" << endl; cout << "Input:"; cin >> inso; while (1) { if (inso == 1) { system("cls"); cout << "将创建形如 A[a][b][c][d] 形式的四维数组,请依次输入个维度对应的维界值\n各维对应的值应当大于零!请严格输入" << endl; int boundary[MAX]; for (int i = 0; i < 4; ++i) { cout << "\t" << char('a' + i) << " :"; too: cin >> inso1; if (inso1 <= 0) { cout << "输入错误,不符合要求,请重新输入:"; goto too; } boundary[i] = inso1; } InitArray(A, 4, boundary); ValueArray(A); cout << "-----四维数组创建成功!-----" << endl; break; } else if (inso == 2) { system("cls"); if (A.elemtotal == 0) { cout << " 四维数组还未创建,请您前去创建四维数组!" << endl; break; } else ShowDate(A); break; } else if(inso == 3){ system("cls"); if (A.elemtotal == 0) { cout << " 四维数组还未创建,请您前去创建四维数组!" << endl; break; } else{ while (1) { cout << " 请输入你要查看的下标值,共四个数据" << endl; cout << " 形如 A[a][b][c][d] ,各个下标对应的取值范围" << endl; cout << " a: 0 - " << A.bounds[0]-1 << endl; cout << " b: 0 - " << A.bounds[1]-1 << endl; cout << " c: 0 - " << A.bounds[2]-1 << endl; cout << " d: 0 - " << A.bounds[3]-1 << endl; cin >> sub.a >> sub.b >> sub.c >> sub.d; if (GetValue(A, sub, elem)) { cout << " 下标 A[" << sub.a << "][" << sub.b << "][" << sub.c << "][" << sub.d << "]对应的值为: " << elem << endl; cout << "是否继续查看?(N键退出查看,其余键继续):"; cin >> ch; if (ch == 'n' || ch == 'N') break; } else{ cout << " 您输入的数据有误,请重新输入" << endl; } } break; } } else if (inso == 4) { system("cls"); if (A.elemtotal == 0) { cout << " 四维数组还未创建,请您前去创建四维数组!" << endl; break; } else{ PrintArrayByRow(A); break; } } else if (inso == 5) { system("cls"); if (A.elemtotal == 0) { cout << " 四维数组还未创建,请您前去创建四维数组!" << endl; break; } else{ PrintArrayByCol(A); break; } } else if (inso == 6){ system("cls"); if (A.elemtotal == 0) { cout << " 四维数组还未创建,请您前去创建四维数组!" << endl; break; } else{ while (1) { cout << " 请输入你要转换的下标值,共四个数据" << endl; cout << " 形如 A[a][b][c][d] ,各个下标对应的取值范围" << endl; cout << " a: 0 - " << A.bounds[0] - 1 << endl; cout << " b: 0 - " << A.bounds[1] - 1 << endl; cout << " c: 0 - " << A.bounds[2] - 1 << endl; cout << " d: 0 - " << A.bounds[3] - 1 << endl; cin >> sub.a >> sub.b >> sub.c >> sub.d; if (sub2ind(A, sub, ind) == OK) { cout << " 下标 A[" << sub.a << "][" << sub.b << "][" << sub.c << "][" << sub.d << "]对应的索引值为: " << ind << endl; cout << "是否继续转换?(N键退出转换,其余键继续):"; cin >> ch; if (ch == 'n' || ch == 'N') break; } else{ cout << " 您输入的下标值不符合要求,请重新输入" << endl; } } break; } } else if (inso == 7){ system("cls"); if (A.elemtotal == 0) { cout << " 四维数组还未创建,请您前去创建四维数组!" << endl; break; } else{ while (1) { cout << " 请输入你要转换的索引值,范围: 0 - " <<A.elemtotal-1<< endl; cin >> ind; if (ind2sub(A, ind, sub) == OK) { cout << " 索引值: " << ind << " 对应的下标未 A[" << sub.a << "][" << sub.b << "][" << sub.c << "][" << sub.d << "] " << endl; cout << "是否继续转换?(N键退出转换,其余键继续):"; cin >> ch; if (ch == 'n' || ch == 'N') break; } else{ cout << "您输入的索引值不符合范围,请重新输入" << endl; } } break; } } else if (inso == 8) { system("cls"); if (A.elemtotal == 0) { cout << " 四维数组还未创建,请您前去创建四维数组!" << endl; break; } else{ cout << "执行降维操作" << endl; if (squeeze(A, B) == ERROR) cout << "本数组无维数为1的维度,无法降维" << endl; else { creat = true; ShowDate(B); } break; } } else if (inso == 9) { if (A.elemtotal != 0) { DestroyArray(A); }
if (creat == true) { DestroyArray(B); } system("cls"); cout << " 谢谢使用,再见!\n按任意键继续...."; _getch(); exit(0); } else{ cout << "您输入的字符有误,请重新输入" << endl; break; } } } }
|
另:一些注意事项
- 数组的维界是严格大于0的,所以维界值要格外注意。
- 本程序采用的是行优先存储方式创建的,对于所谓的列优先输出,因为列优先也就是高下标优先,所以按照高下标的遍历方式,依次将四维下标转换成一维基址(
sub2ind函数
),然后输出。
- 销毁空间时,如果那段空间本来就没有被
malloc
分配内存,就会报错,所以 main.cpp
中的流程9有两个判断语句。
- Array.h里定义的大部分函数返回值都是
Status
,这是为了便于 main.cpp
里的流程控制,因为判断数据是否正确写在 main.cpp
里回过于冗杂,也为了不让计算产生莫名奇妙的值,所以这些函数刚开始都是判断数据是否合理,不合理就不会继续进行计算。
- 本程序的大部分函数都不可扩展,参数直接写死了,只能对四维坐标进行操作,尤其是数组被降维之后,对应 低于四维数组 的操作函数寥寥无几,设计函数是应当注意!
- 消除孤维的时候要注意,如果都是1维,就要最后保留一个维度,为1维。
- 关于
ind2sub函数
(单一索引值转换成四维数组下标)的计算问题。刚开始稀里糊涂的写了上去,发现没啥问题,也没细想,写博客的时候又觉得有点不对劲,现在终于弄明白了。参看函数sub2ind
的计算,简化式ind = A * (constants[0]) + B * (constants[1]) + C*(constants[2]) + D *(constants[3]);
ABCD为四维数组下标。由此进行逆推,A = ind / constants[0];
,那是因为其余部分的值B * (constants[1]) + C*(constants[2]) + D *(constants[3])
除以 constants[0]
都要小于1。
功能展示
比较辣鸡,展示部分功能。
起始界面
创建四维数组
查看当前数组信息
下标转换为索引值
降维操作
完结感言
本次课设的难度一般,想清楚该怎么做后,几个小时就可以做完。但与之相比,课设的报告书,还有这篇博客都是非常的费时间。自己也挺菜的,函数写成了那个样子,没法复用,也得感谢严蔚敏老师的《数据结构》提供了非常好的数据结构与方法,但是为了一些操作的方便就没有使用原书中的用到的stdarg.h
和变参方法。太菜了,继续努力吧!
END! 2019/1/11 22:24:36