数据结构之线性表再思考
数据结构学起来妙不可言,贼有意思。
很久没写博客了,今天来一篇长的。前面写的关于线性表的代码和思路,经过我多次反复思考,又有了新的收获,与大家一起分享。
1、线性表的定义
首先要明白什么是线性表,一种常用且最简单的数据结构。
数据结构通俗来说就是:装水的杯子,有的是圆的、有的是方的。
官方定义:线性表是n个数据元素的有限序列。
把这个样的一个数据模型映射到计算机,等同找一块存储空间给它,而线性表一般是申请动态内存,因此就是在堆上给它分配一块内存。
看下图
通过图我们可以了解到,线性表数据结构如何映射在计算机中。有了这个基础就可以进入代码逻辑部分。/* 图像中的堆是为了更好讲解 */
2、线性表的操作思路
在严奶奶的书有如下操作集合
1、初始化
2、销毁
3、置空表
4、空表检测
5、返回元素个数
6、返回指定位序元素的值
7、返回表中第一个与指定值满足compare()的数据元素的位序,若不存在,返回0
8、返回指定元素的前驱
9、返回指定元素的后驱
10、插入元素
11、删除元素
12、遍历
13、表A和表B,A = A U B 减去 A n B(A和B的补集)
14、已知非递减序列表A和表B为合并非递减序列表的表C , C = A U B
在所有的操作之前,需要定义一个表的结构体:
结构体:
表首地址
表使用长度
表最大长度
代码实现:
typedef struct ChangeList{
ElemType *elem;
int Length;
int ListSize;
}CL;
初始化
1、表首地址指向申请内存的首地址及定义申请的内存多少
2、进行动态内存分配成功的检测
3、表使用长度归0
4、表最大长度被赋值申请内存长度
/* 动态内存的申请通过malloc函数来实现,需配套使用free函数释放内存 */
销毁
1、释放动态内存
2、表首地址指向NULL(消除表头指针)
3、表使用长度归0 (消除表使用长度)
4、表最大长度归0 (消除表最大长度)
/*
2的作用是假设1失败,作为补救手段;
销毁就是表的所有结构全部销毁,包括首指针、使用长度、最大长度
结构体在内存中存储是按照最大变量类型所占字节依次排序的,如果表的三个变量没有全部销毁,则会造成内存浪费。
*/
置空表
1、使用长度归0
空表判断
1、判断使用长度是否为0
返回元素个数
1、返回使用长度
返回指定位序元素的值
1、判断位序是否合法
2、返回指定位序的值
7、返回表中第一个与指定值满足compare()的数据元素的位序,若不存在,返回0
在此之前,需要写一个compare函数
Compare( ElemType ElemOne , ElemType ElemTwo )
函数作用:两数相等,返回TURE;两数不等,返回FALSE
1、在使用长度的范围内判断第一个满足要求的元素,并返回该元素的位序
返回前驱
1、位序2至使用长度之间判定
2、若有与指针值相等的元素,返回其元素前驱
返回后驱
1、位序1至 (使用长度 – 1)之间判定
2、若有与指针值相等的元素,返回其元素前驱
插入元素
1、判断插入位序是否合法
2、判断存储空间是否满了,如果满,追加动态内存
3、追加动态分配成功检测,失败结束程序
4、新基址
5、最大长度加一
删除元素
1、判断删除序号是否合法
2、返回删除元素
3、删除位置之后的元素左移
4、表长减一
遍历
1、visit函数(自己编写,打印单个指定位置的值)
2、把visit函数作为函数参数,遍历
表A和表B,A = A U B 减去 A n B(A和B的补集)
1、获取B中的元素
2、进行LocateEem判断
3、插入不符合LocateEem判断的B中的元素到A中
4、A实际长度增加
已知非递减序列表A和表B为合并非递减序列表的表C , C = A U B
1、为C申请动态内存,分配成功检测
2、合并算法
2-1、比较大小
2-2、插入C,C表增加
2-3、A、B是否有一方先插完,再继续插入另一表
代码:
1 /***************************************
2 程序日期:2018/10/04/21:47 to
3 程序功能:实现动态线性表的各类操作,详细参见各函数注解
4 程序结构:作为晚辈,我自己的代码很多没有严奶奶的精炼。
5 程序作者:Jamu Foo
6 ***************************************/
7 #include<stdio.h>
8 #include<stdlib.h>
9 /**************************************/
10 #define Status int /* 方便对线性表的数据类型更改,本实验代码为整数型 */
11 #define ElemType int
12 /**************************************/
13 #define MaxSize 100 /* MaxSize为固态线性表最大长度 100 */
14 #define Increment 10 /* Increment为固态线性表单次增长量 10 */
15 /**************************************/
16
17 #define OVERFLOW -1 /* 溢出标志 */
18 #define INFEASIBLE -2 /* 不可实行标志 */
19 #define TURE 0 /* 操作正确标志1 */
20 #define FALSE 1 /* 操作错误标志1 */
21 #define OK 0 /* 操作正确标志2 */
22 #define ERROR 1 /* 操作错误标志2 */
23 /**************************************/
24 typedef struct ChangeList{ /* 动态线性表定义 */
25 ElemType *elem; /* 存储空间的首地址 */
26 int Length; /* 当前长度 */
27 int ListSize; /* 当前分配的存储容量 */
28 }CL;
29 /**************************************/
30 /*函数声明集合*/
31
32 Status compare( ElemType ElemOne, ElemType ElemTwo ); /* 两数相等返回0,否则返回1 */
33 Status InitList( CL *L ); /* 初始化 */
34 Status DestoryList( CL *L ); /* 销毁表 */
35 Status ClearList( CL *L ); /* 置空表 */
36 Status ListEmpty( CL *L ); /* 空表检测 */
37 Status LisTLength( CL *L ); /* 返回表长 */
38 Status GetElem( CL *L, int i, ElemType *e); /* 用e返回第i个位置的元素的值 */
39 Status LocateElem( CL *L, ElemType e, Status ( *compare)( ElemType, ElemType ) ); /* 返回表中数据元素与e满足conpare关系的位序 */
40 Status PriorElem( CL *L,ElemType cur_e, ElemType *pre_e ); /* cur_e为L中的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败 */
41 Status NextElem( CL *L,ElemType cur_e, ElemType *next_e ); /* cur_e为L中的数据元素,且不是最后个,则用next_e返回它的后驱,否则操作失败 */
42 Status ListInsert ( CL *L, int i, ElemType ie ); /* 在序号i之前插入ie */
43 Status ListDelete ( CL *L, int i, ElemType *de );/* 删除序号i的元素 */
44 void visit( ElemType *p ); /* 打印单个指定位置的值 */
45 Status ListTraverse( CL *L, void ( *visit )( ElemType* ) ); /* 遍历 */
46 void Union(CL *La, CL *Lb);
47 void MergeList( CL *La, CL *Lb, CL *Lc );/* 已知线性表La和Lb的元素按值非递减排列(俗话:按从小到大排列) */
48 /* 将La和Lb合并为Lc,Lc的元素按值非递减排列 */
49
50 /*************************************/
51 int main(void){ /*主函数*/
52 CL Ldemo;
53 ElemType e = 11;
54 int i = 1;
55 int ie = 100;
56 int flag = 1;
57 int gete = 0;
58 ElemType deOne = 0;
59 InitList( &Ldemo );
60
61 flag = LisTLength( &Ldemo );
62 printf( "表长%d\n", flag );
63
64 for( i; i <= 10; i++ ) { /* 插入实验 */
65 ListInsert( &Ldemo, i, ie );
66 flag = LisTLength( &Ldemo );
67 printf( " 表长 %d\n", flag );
68 ie++;
69 }/* 第一个for循环后i的值变11,因此要重新给i赋值1 */
70 for( i = 1 ; i <= 10; i++ ){
71 GetElem( &Ldemo, i, &gete );
72 printf( "获取到的元素的值为%d\n", gete );
73 }
74 return 0;
75 // ListDelete( &Ldemo, 1, &deOne );
76 /* 疑点,当ListDelete函数的第二个参数为声明且初始化为0的指针变量,会出现莫名错误 */
77 /* 解答,因为指针初始化=0,表示指向0X00(不可读不可写的内存),进入delete函数后,拷贝了一个不影响的副本变量并赋值给*de,这时候的*de指向内存0X00,*de = *p这个操作,便出错了 */
78 /* 每个指针只能指向一个地址,假设 指针a 初始化指向0X00,指针b指向i=1的地方,那么经过指针a = 指针b的操作,就变成了指针b指向指针a,指针a指向i=1的地方*/
79 }
80 /**************************************/
81 Status compare( ElemType ElemOne, ElemType ElemTwo ){
82 if( ElemOne != ElemTwo ){ /* 两数相同返回TURE,否则返回FALSE */
83 return FALSE;
84 }
85 else{
86 return TURE;
87 }
88 }
89 /**************************************/
90 Status InitList( CL *L ){
91 /* malloc 申请内存成功 则返回 void* 类型的内存首地址,否则返回NULL,void* 可强制转换为任何类型 */
92 L->elem = ( ElemType * )malloc( MaxSize * sizeof(ElemType));
93 if( L->elem == NULL ) exit(OVERFLOW); /* 分配失败,结束程序 */
94 L->Length = 0;
95 L->ListSize = MaxSize;
96 printf("动态线性表初始化成功\n");
97 return OK;
98 }
99 /**************************************/
100 Status DestoryList( CL *L ){ /* 线性表定义中三个变量都要回到初始化 */
101 free( L->elem ); /* 使用后该指针变量一定要重新指向NULL,防止野指针出现,有效 规避误操作 */
102 /* 指向NULL的指针,NULL其实就是地址为0X0000的内存,这里不可读不可写, */
103 L->elem = NULL; /* 如果释放成功,则返回NULL,无论继续free多少次都没关系,如果释放不成功,再次free会出错 */
104 L->Length = 0; /* */
105 L->ListSize = 0;
106 printf("销毁线性表成功\n");
107 return OK;
108 }
109 /**************************************/
110 Status ClearList( CL *L ){
111 L->Length = 0; /*表当前长度归0*/
112 return OK;
113 }
114 /**************************************/
115 Status ListEmpty( CL *L ){ /* 空表判断 */
116 if( L->Length == 0 ){ /* 为空表 */
117 printf("List is empty \n");
118 return TURE;
119 }
120 else{ /* 非空表 */
121 printf("List is not empty \n");
122 return FALSE;
123 }
124 }
125 /**************************************/
126 Status LisTLength( CL *L ){ /* 返回表的长度,严书这里并没有要求做表长是否合法的判断 */
127 return L->Length;
128 }
129 /**************************************/
130 Status GetElem( CL *L, int i, ElemType *e ){
131 if( i < 1 || i > MaxSize ){ /* 判断i是否合法 */
132 exit( ERROR );
133 }
134 *e = *( L->elem + i - 1 ); /* 用*e返回序号为i的元素的值 */
135 return OK;
136 }
137 /**************************************/
138 Status LocateElem( CL *L, ElemType e,Status ( *compare )( ElemType, ElemType ) ){
139 int i = 1;
140 while( i++ <= L->Length ){ /* 最多只能加 L->Length次 */
141 if( ! compare( *( L->elem ++ ) , e ) ){
142 return i;
143 }
144 }
145 return FALSE;
146 }
147 /**************************************/
148 Status PriorElem( CL *L, ElemType cur_e, ElemType *pre_e ){
149 int i = 2;
150 ElemType *p = L->elem + 1;
151 while( i++ <= L->Length ){
152 if( cur_e == *( p ++ ) ){
153 *pre_e = *( p -- );
154 return *pre_e;
155 }
156 }
157 printf("操作失败\n");
158 return FALSE;
159 }
160 /**************************************/
161 Status NextElem( CL *L,ElemType cur_e, ElemType *next_e ){
162 int i = 1;
163 ElemType *p = L->elem;
164 while( i++ < L->Length ){
165 if( cur_e == *( p ++ ) ){
166 *next_e = *( p ++ );
167 return *next_e;
168 }
169 }
170 printf("操作失败\n");
171 return FALSE;
172 }
173 /**************************************/
174 Status ListInsert ( CL *L, int i, ElemType ie ){
175
176 ElemType *newbase = 0, *p = 0, *q = 0;
177 if( i < 1 || i > MaxSize + 1 ){ /* 插入序号i不合法 */
178 printf(" 错误的插入序号\n" );
179 return ERROR;
180 }
181 if( L->Length >= L->ListSize ){ /* 存储空间满,增加分配 */
182 newbase = ( ElemType * )realloc( L->elem, ( L->ListSize + Increment ) * sizeof(ElemType) );
183 /* newbase的作用:无法提前得知旧内存后是否有足够的内存用于动态内存调整。假设没有足够内存,
184 L->elem = ( ElemType * )realloc( L->elem, ( L->ListSize + Increment ) * sizeof(ElemType) );
185 一旦realloc分配失败,返回NULL,但参数中的L->elem并没有被释放。如果直接把realloc的返回值赋给L->elem
186 则会造成L->elem原来指向的内存丢失,造成泄露
187 */
188 /* realloc 函数 如果分配失败,则第一个参数返回NULL */
189 if( !newbase ){ /* 分配失败检查 */
190 exit( OVERFLOW ); /* 存储分配失败 */
191 }
192 L->elem = newbase; /* 新基址 */
193 L->ListSize += Increment; /* 增加存储容量 */
194 }
195 q = L->elem + i - 1; /* q 为插入位置 */
196 for( p = L->elem + L->Length - 1; p >= q; --p){ /* 插入位置及元素的右移动 */
197 *( p + 1) = * p;
198 }
199 *q = ie; /* 插入ie */
200 ++L->Length; /* 表长加1 */
201 ++L->ListSize; /* 表最大长度加1 */
202 printf( "第%d位插入%d", i, ie );
203 return OK;
204 /*
205 realloc函数
206 原型:extern void *realloc(void *mem_address, unsigned int newsize);
207 功能:动态内存调整;内存是有限的。
208 分配成功:返回 指向 被分配内存的指针
209 newsize > oldsize:
210 1、malloc申请的动态内存后面有足够的续接内存。则newbase = oldbsae,size = newsize
211 2、malloc申请的动态内存后面无足够的续接内存。则申请一块新内存,把旧内存的数据复制过去
212 newbase = 新内存的首地址,size = newsize ,free(oldbase)
213 newsize < oldsize:
214 1、newbase = oldbase;size = newsize,把旧内存中的数据复制到新内存,会造成数据丢失,
215
216 oldbase = ( int* )malloc ( oldsize * sizeof(int));
217 newbase = ( int* )realloc ( oldbase, newsize * sizeof(int) );
218 1、当realloc中第一个参数为NULL,第二个参数不为0,等同与malloc函数
219 2、当第一个参数不为NULL,第二个参数为0,相当于free函数
220 分配失败:返回NULL
221 */
222 }
223 /**************************************/
224 Status ListDelete ( CL *L, int i, ElemType *de ){
225 ElemType *p = 0, *q = 0;
226 if( i < 1 || i > L->Length ){ /* 删除序号检查 */
227 printf(" 错误的删除序号\n" );
228 return ERROR;
229 }
230 p = L->elem + i - 1;
231 *de = *p; /* 用*de返回被删除的元素 */
232 q = L->elem + L->Length - 1;
233 for( ++p; p <= q; ++p ){ /* 被删除后的元素左移 */
234 *( p - 1 ) = *p;
235 }
236 L->Length--;
237 printf( "删除序号为%d的元素,其值为%d\n", i, *de );
238 return OK;
239 }
240 /**************************************/
241 void visit( ElemType *p ){
242 printf("%d ", *p );
243 }
244 /**************************************/
245 Status ListTraverse( CL *L, void ( *visit )( ElemType* ) ){
246 ElemType *p = 0;
247 int i = 0;
248 p = L->elem;
249 for( i = 1; i < L->Length; i++){
250 visit(p++);
251 }
252 printf("\n");
253 return OK;
254 }
255 /**************************************/
256 void Union(CL *La, CL *Lb){
257 int La_len = 0, Lb_len = 0;
258 int i = 0;
259 ElemType e = 0;
260 La_len = La->Length;
261 Lb_len = Lb->Length;
262 for( i; i < La_len; i++ ){
263 GetElem( Lb, i, &e ); //一次返回Lb中的所有元素
264 if( !LocateElem( La, e, compare ) ){ //与La不相同的插入La中
265 ListInsert( La, ++La_len, e );
266 }
267 }
268 }
269 /**************************************/
270 void MergeList( CL *La, CL *Lb, CL *Lc ){
271 int *pa = La->elem; /* 表a首指针 */
272 int *pb = Lb->elem; /* 表b首指针 */
273 int *pc = Lc->elem; /* 表c首指针 */
274 int *pa_last = La->elem + La->Length - 1; /* 表a尾指针 */
275 int *pb_last = Lb->elem + Lb->Length - 1; /* 表b尾指针 */
276 Lc->ListSize = Lc->Length = La->Length + Lb->Length; /* Lc的长度为(La + Lb)的长度 */
277 Lc->elem = ( ElemType* )malloc( Lc->ListSize * sizeof( ElemType ) ); /* 申请的动态内存最大长度为Lc->Length */
278 if( !Lc->elem){
279 exit( OVERFLOW ); /* 分配失败 */
280 }/* 合并算法 */
281 while( pa <= pa_last && pb <= pb_last ){ /* 此处为严蔚敏奶奶的代码,相当的有意思 */
282 if( *pa <= *pb ){ /* 忽略面向过程,直接面向了对象 */
283 *pc++ = *pa++;/* 思路:假设思考路线为个个比较过程,表的数量一旦过大,那么将十分复杂 */
284 } /* 无论表做多少容量的合并工作,都是要从两个表的首指针到尾指针,提取出来作为循环判断条件 */
285 else{ /* 有了循环条件,还需要判断排序条件,提取出来谁大谁排序。循环和判断条件都满足,第一个while就出来了 */
286 *pc++ = *pb++;
287 } /* 第一个while的判断跳出条件,为其中一个表排序完成,因此还需要为另外一个表剩余元素做排序,得到后两个while*/
288 }
289 while( pa <= pa_last ){
290 *pc++ = *pa++;
291 }
292 while( pb <= pb_last ){
293 *pc++ = *pb++;
294 }
295 }
296 /**************************************/