数据结构之线性表再思考
Posted fsiswo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构之线性表再思考相关的知识,希望对你有一定的参考价值。
数据结构学起来妙不可言,贼有意思。
很久没写博客了,今天来一篇长的。前面写的关于线性表的代码和思路,经过我多次反复思考,又有了新的收获,与大家一起分享。
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 /**************************************/
以上是关于数据结构之线性表再思考的主要内容,如果未能解决你的问题,请参考以下文章