數據結構之線性表再思考
數據結構學起來妙不可言,賊有意思。
很久沒寫博客了,今天來一篇長的。前面寫的關於線性表的代碼和思路,經過我多次反復思考,又有了新的收獲,與大家一起分享。
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 /**************************************/
數據結構之線性表再思考