数据结构之线性表再思考

数据结构学起来妙不可言,贼有意思。

很久没写博客了,今天来一篇长的。前面写的关于线性表的代码和思路,经过我多次反复思考,又有了新的收获,与大家一起分享。

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 ", flag );
 63
 64     for( i; i <= 10; i++ ) {    /* 插入实验 */
 65         ListInsert( &Ldemo, i, ie );
 66         flag = LisTLength( &Ldemo );
 67         printf( " 表长 %d ", flag );
 68         ie++;
 69     }/* 第一个for循环后i的值变11,因此要重新给i赋值1 */
 70     for( i = 1 ; i <= 10; i++ ){
 71         GetElem( &Ldemo, i, &gete );
 72         printf( "获取到的元素的值为%d ", 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("动态线性表初始化成功 ");
 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("销毁线性表成功 ");
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 ");
118         return TURE;
119     }
120     else{                    /* 非空表 */
121         printf("List is not empty ");
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("操作失败 ");
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("操作失败 ");
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(" 错误的插入序号 " );
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(" 错误的删除序号 " );
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 ", 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(" ");
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 /**************************************/
本文由作者原创,如需转载注明出处!
原文地址:https://www.cnblogs.com/Fsiswo/p/9785072.html