常见的数据结构
数据结构概述
数据结构需要从三方面考虑,第一个是逻辑结构,第二个是存储结构,第三个就是你需要实现一些什么。
我们一般在思考问题的时候,观察事物的内在联系,事物与事物之间可能是层级关系,或者平等…,数据有在程序中用什么存储结构来存储,使用数组,结构体还是其他的。
一、 顺序表
适用数据查找,适用数组来存储,为了方便起见,我们这次使用的是整型数组来表示一个个的数据,因为数据实时增减,需要使用一个整型变量来记录当前数据所在数组中的位置,为了更好的增删改查,创建三个文件
sqlist.c sqlist.h test.c
开始实现以下函数
首先在sqlist.h的头文件中:
typedef int date_t;
2 #define N 128
3 typedef struct sqlist_t
4 {
5 date_t date[N];
6 int last;
7 }sqlist, *sqlink;
last用来存储当前数据的个数,每一次的增删都需要更新这个值
再声明需要实现的函数
9 sqlink list_creat();//空的顺序表的创建
10 int list_clear(sqlink L);//清空数据
11 int list_isempty(sqlink L);//判断是否为空
12 int list_insert(sqlink L,date_t date,int pos);//按位置插入数据
13 int list_free(sqlink* L);//释放顺序表
14 int list_purge(sqlink L , int pos);//删除某一个位置上的元素
15 int list_show(sqlink L);//顺序表的显示
16 int list_merge(sqlink L1,sqlink L2);//顺序表的合并
17 int list_find(sqlink L,date_t date);//查找数据
18 int List_PurgeRepeat(sqlink L);//顺序表删除重复数据
~
1、创建一个空的顺序表
sqlink list_creat();//空的顺序表的创建
示例:
sqlink list_creat()
11 {
12 sqlink ptr = (sqlink)malloc(sizeof(sqlist));
13 if(ptr == NULL)
14 {
15 printf("list is error");
16 return ptr;
17 }
18 memset(ptr,0,sizeof(sqlist));
19 //初始化的时候别忘了last要初始化
20 ptr->last = -1;
21 return ptr;
22 }
注意事项
1、malloc申请的空间没有初始化,很可能其中的数据是随机值,需要使用memset进行清零操作。也可以使用calloc来代替,用法
void *calloc(unsigned int num, unsigned int size)
>num:需要申请的个数,size:每一个所占字节数-----总内存:num*size
示例:
sqlink ptr = (sqlink)calloc(sizeof(sqlist)/sizeof(int) , sizeof(int));
2、前面都弄完之后,一定要记得把last置-1。
2、顺序表的插入和删除
int list_insert(sqlink L,date_t date,int pos);//按位置插入数据
int list_purge(sqlink L , int pos);//按位置删除数据
int list_insert_tail(sqlink L,date_t date)
int List_PurgeRepeat(sqlink L)//删除重复元素
示例:
列表数据的插入
int list_insert(sqlink L,date_t date,int pos)
59 {
60 if(L == NULL)
61 {
62 printf("list is error\n");
63 return -1;
64 }
65 if(pos < 0||pos >L->last+1)
66 {
67 printf("pos is ilegal\n");
68 return -1;
69 }
70 if(L->last >= N)
71 {
72 printf("list has been filled");
73 }
74 int i;
75 for(i = L->last;i >=pos;i--)
76 {
77 L->date[i+1] = L->date[i];
78 }
79 L->date[pos] = date;
80 L->last++;
81 return 0;
82 }
列表数据的删除
int list_purge(sqlink L , int pos)
119 {
120 if(L == NULL)
121 {
122 printf("list is error\n");
123 return -1;
124 }
125 if(L->last == -1 || pos < 0 || pos > L->last)
126 {
127 printf("error\n");
128 return -1;
129 }
130 int i = pos;
131 while(i <=L->last-1)//考虑到列表一开始是满的话,就不能操作last+1上的数据
132 {
133 L->date[i] = L->date[i+1];
134 i++;
135 }
136 L->date[i] = 0;
137 //更新长度
138 L->last--;
139 return 0;
140 }
列表数据的尾部插入
int list_insert_tail(sqlink L,date_t date)
168 {
169 if(L == NULL)
170 {
171 printf("%s list is ilegal\n",__FUNCTION__);
172 return -1;
173 }
174 if(L->last == N)
175 {
176 printf("%s list is full ,cannot insert",__FUNCTION__);
177 return -1;
178 }
179 int temp = L->last;
180 L->date[temp+1] = date;
181 L->last++;
182 return 0;
183 }
列表删除重复的数据
3 int List_PurgeRepeat(sqlink L)//删除重复元素
204 {
205 if(L == NULL)
206 {
207 printf("list is error");
208 return -1;
209 }
//列表中仅有一个数据是不会重复的,我们以第一个数据为标准
//之后的数据依次和前面的数据进行比较,重复则删除
210 int i = 1,j;
211 while(i <= L->last)
212 {
213 j = 0;
214 while(j < i)
215 {
//前面的数据依次往后,j++,直到遇到重复的了,在删除,
//则继续本次循环可以提前退出了
216 if(L->date[i]==L->date[j])
217 {
218 list_purge(L,i);
219 break;
220 }
221 j++;
222 }
//如果没有提前退出,也就是说while循环走完全程,这个时候,j==i;
//后面的数据需要往后移动了i++,为啥数据重复了就不用i++呢,那是
//因为使用list_purge这个函数,后面的数据删除后,不会空着这一块,而是
//后面的数据需要整体向前移动,这也就是顺序表在增删的时候麻烦之处。
223 if(j == i)
224 {
225 i++;
226 }
227 }
228 return 0;
229 }
注意事项
1、在进行对任何数据的增加和删除时,都需要检查用户传入的位置是否正确,其次,数据的数量last需要实时更新。
2、在进行插入的时候,需要考虑列表是否已满。
3、在进行删除的时候,在遍历数据的时候,如果列表已满,不能访问到last+1的位置上。
4、再进行编程时需要先构思好,可以在纸上先画个图
3、数据的查找和显示
int list_find(sqlink L,date_t date)
int list_show(const sqlink L)
数据的查找
int list_find(sqlink L,date_t date)
89 {
90 if(L == NULL)
91 {
92 printf("list is erorr");
93 return 0;
94 }
95 int i = 0;
96 while(i <= L->last)
97 {
98 if(L->date[i] == date)
99 {
100 return i;
101 }//跳出循环则说明没有找到数据
102 i++;
103 }
106 printf("date: %d is not found\n",date);
107 return -1;
110 }
数据的显示
int list_show(const sqlink L)
147 {
148 if(L == NULL)
149 {
150 printf("list is error");
151 return -1;
152 }
153 int i = 0;
154 while(i <= L->last)
155 {
156 printf("%4d",L->date[i]);
157 i++;
158 }
159 putchar('\n');
160 return 0;
161 }
4、列表的合并和排序
int list_merge(sqlink L1,sqlink L2)
191 {
//这里需要判断一下这两个列表是否合法,此处省略。
192 int i = 0;
193 while(i <= L2->last)//这是需要跟在后面的列表
194 {
195 if(list_find(L1,L2->date[i])==-1)//==-1就是没有重复的元素
196 {
197 list_insert_tail(L1,L2->date[i]);
198 }
199 i++;
200 }
201 return 0;
202 }
这个合并使用的是∪合并,也就是A = A∪B,如果B中的数据,A没有,则放到A中
,相反,则不放。
5、列表的释放和显示
int list_show(const sqlink L)
int list_free(sqlink* L)
列表的显示
int list_show(const sqlink L)
147 {
148 if(L == NULL)
149 {
150 printf("list is error");
151 return -1;
152 }
153 int i = 0;
154 while(i <= L->last)
155 {
156 printf("%4d",L->date[i]);
157 i++;
158 }
159 putchar('\n');
160 return 0;
161 }
列表的释放
int list_free(sqlink* L)
113 {
114 free(*L);
115 *L = NULL;
116 return 0;
117 }
注意事项
1、在释放内存时,很容易造成悬空指针,我们发现,当我们将指针P指向的空间释放掉后,指针保存的地址还是存在的,这就造成了悬空指针,这里也是我们使用地址的地址来操作,目的就是将我们在主函数新建的指针所指向的内存释放掉,同时置NULL;
当然也可以返回NULL,用指针来接收也可以达到目的。
二、链表
链表和顺序表都是一种线性表,他的优点是不需要连续的存储空间,而且增删都比较方便,不用大动干戈的整体移动。但是缺点是查找就没有顺序表那么便捷了。
解释一下,我们为什么使用动态申请的方式来创建这些顺序表还是链表,都是因为这些空间资源是可以自己定义什么时候释放,它一直在那,总不能在一个函数中创建完,函数调用结束就销毁吧(栈)。
引入链表的定义:
1 #include <stdio.h>
2 typedef int date_t;
3 typedef struct link_t
4 {
5 date_t date;
6 struct link_t*node_next;
7 }link_node,*link_p;
因为空间不是连续的,不能用下标来找到某一个节点,顺表那一套在这就不适用了。
链表相关函数的声明:
9 link_p list_creat();
10 int list_tail_insert(link_p H, date_t value);//head
11 link_p list_get(link_p H, int pos);
12 int list_insert(link_p H, date_t value, int pos);
13 int list_show(link_p H);
14 int list_merge(link_p H1,link_p H2);
15 link_p list_adimax(link_p H1,date_t*date);
16 int list_sort(link_p H);
1、创建链表
link_p list_creat()
10 {
11 link_p H = (link_p)malloc(sizeof(link_node));
12 if(H == NULL )
13 {
14 printf("get heal failed\n");
15 return NULL;
16 }
17 //初始化其中的数据,date可以随意,node_next为NULL;
18 H->node_next = NULL;
19 H->date = 0;
20 return H;
21 }
返回值:头节点。
链表的插入
int list_tail_insert(link_p H, date_t value);//头部插入
int list_insert(link_p H, date_t value, int pos);
思路:1、检测参数是否正确(插入点是否合理,链表是否合理,不需要判断满)
2、创建一个节点,使用形参初始化date,next指针置空,不要引入野指针。
3、找到待插入点,再保存待插入点下一个节点的地址
4、待插入点的next指向新节点,新节点的next指向刚刚保存的地址。
示例:
int list_tail_insert(link_p H, date_t value)
23 {
24 link_p P_New;
25 if(H == NULL)
26 {
27 printf("%s --space is error\n",__FUNCTION__);
28 return -1;
29 }
30 //创建一个节点
31 if(( P_New = (link_p)malloc(sizeof(link_node)))==NULL)
32 {
33 printf("%s--malloc failed\n",__FUNCTION__);
34 return -1;
35 }
36 link_p P = H;//保存头地址,P移动找最后一个节点
37 while(P->node_next!=NULL)
38 {
39 P = P->node_next;
40 }
41 P->node_next = P_New;
42 //给新节点赋值
43 P_New->date = value;
44 P_New->node_next = NULL;
45
46 return 0;
47 }
链表的删除和清空
思路1
1、先用P_old保存需要删除的节点
2、找到要删除节点位置的前一个节点P_last(这个P_last需要判断一下是不是NULL或者已经是最后一个节点,是的话就不能删除下一个哦)
3、P_last的next = P_old的next
4、释放P_old
思路2
1、先找到需要删除的节点,保存需要删除的节点的下一个地址作为P_old;
2、把P_old的数据(date和next)拷贝给需要删除的节点
3、释放P_old
链表的排序(从小到大)
思路1:
1、将除了头节点的第一个节点 和 其他节点断开,形成两个链表。
2、后链表的每一个节点和前链表的节点依次进行比较,如果出现后面的节点比前面某一个要小,就插到这个节点的前面(这个貌似需要保存前一个节点的地址)可以使用p->node_next来比较,这样既可以比较,也可以保存p;
示例:
int list_sort(link_p H)
321 {
322 link_p temp_back;
323 link_p temp;
324 if(H == NULL)
325 {
326 printf("link is error\n");
327 return -1;
328 } //空链表和仅有一个节点就不需要排序了
329 if(H->node_next == NULL || H->node_next->node_next== NULL)
330 {
331 return 0;
332 }
333 //首先保存后链表的地址,P_Back也是用来遍历后面链表的
334 link_p P_Back = H->node_next->node_next;
335 //然后置空实现断开
336 H->node_next->node_next = NULL;
337 link_p P = H;//用来遍历链表
338 while(P_Back != NULL)
339 {
340 //初始P指针,因为后面的操作为改变P的指向
341 P = H;
342 //P_Back和P遍历的节点进行比较
343 while(P->node_next != NULL && P->node_next->date < P_Back->date)
344 {
345 P = P->node_next;
346 }
347 temp_back = P_Back;
348 //首先保存需要插入的节点,然后遍历节点P_Back可以往后移动了
349 P_Back = P_Back->node_next;
350 //插入之前先保存下一个地址
351 temp = P->node_next;
352 P->node_next = temp_back;
353 temp_back->node_next = temp;
354 }
355
356 }
思路2:
可以将链表先保存到数组中,然后使用快速排序,然后再将数组的数据依次放会链表中。
链表的反转
思路1:
也是使用上面的插入的方法,先断开,再插入,但这次插入是头部插入。
示例:
int link_reverse(link_p H)
358 {
359 //参数的检测忽略
360 //....
361 link_p temp,temp_Head;
362 //先初始化一下
363 temp_Head = H->node_next;
364 //断开
365 link_p P_Back;
366 P_Back = H->node_next->node_next;
367 H->node_next->node_next = NULL;
368 while(P_Back != NULL)
369 {
370 temp = P_Back;
371 P_Back = P_Back->node_next;
372 H->node_next = temp;
373 temp->node_next = temp_Head;
374 //上面的操作修改了temp_Head,P_Back的插入使得H不再指向temp_Head
375 temp_Head = H->node_next;
376 }
return 0;
377 }
思路2:
一个指针 i 指向第一个节点,另一个指针j 指向最后一个节点。然后依次交换数据,可以整个节点交换当然也行。