在前面的章节中讲解了C语言的基础知识、数组、指针、流程控制等,在C语言中还有一些基本的数据结构,如栈、队列和链表,这些数据结构有着自己的特性,合理利用这些数据结构可以在程序中起到事半功倍的效果,本章将针对这些基本的数据结构进行详细地讲解。
在现实生活中经常会完成一些"后进先出"的动作,例如一群人排队等一个电梯,当电梯门打开后,排在队首的人先进入电梯,排在队末的人最后进入电梯,当电梯停止时,后进入电梯的人就会先下去,先进入电梯的人后下去。在C语言中有一种数据结构也满足这种"后进先出"的原则,该数据结构就是栈,栈中可以加入元素也可以弹出元素,这些元素在存取的过程中会满足"后进先出"的原则。接下来将针对栈进行详细地讲解。
在C语言中可以用数组来模拟栈。数组的开头(下标为0的元素)被称为栈底,再利用另外一个指针指向数组的末尾,被称为栈顶:
#define STACK_SIZE1000
int stack[STACK_SIZE];//int类型的数组用来实现栈
{
tail = 0;
}
第2行定义了宏STACK_SIZE,并将它的值定为1000。这是本节中栈的默认大小;
第4行定义了一个全局的int类型数组stack,它的长度为STACK_SIZE。接下来将用这个数组来模拟栈上的操作。
在栈这一节中,约定tail指向栈中即将加入的新元素的位置,即栈中已有元素的下标为0,1,2到tail-1。按照这样的约定,当head和tail的值都指向第0个元素时,这个栈就是空的。
push函数实现了向栈中加入新元素的操作,它的定义如下:
{
tail++;
其中,参数element是要加入栈中的新元素。根据此前的约定,tail指向的是新元素加入的位置,因此stack[tail]用来保存要加入的值element。
10.1.3 弹出栈中元素
int pop()
tail--;
return stack[tail];
pop函数不接受任何参数,它返回一个int类型的数,这个数正是当前栈顶的元素。为了实现pop函数的功能,首先将tail自减,此时tail被挪到了栈中的最后一个元素,随后将这个元素返回。注意pop函数调用之后栈的长度也减一,因此tail自减之后恰好指向的就是pop之后新元素加入栈时的位置。
10.1.4 查看栈顶元素
int peek()
return stack[tail - 1];
由于tail指向了栈顶的后一个元素,因此栈顶元素的下标是tail-1,peek函数返回这个下标对应的元素。请注意比较peek函数和pop函数的区别:两个函数的返回元素其实是一样的,但是pop函数在返回栈顶元素的同时还修改了tail的值,而peek函数仅仅返回栈顶元素,栈底的位置并没有被修改。
由于一个栈为空的条件是tail=head=0,因此清空栈的操作非常简单:
void cleanStack()
tail = head;
注意到在栈中head的值自始至终为0,cleanStack函数实际上将tail的值设为了0。另一个值得留意的地方是清空一个栈并没有必要将栈中的所有元素统统pop一遍,或者将这些元素都设为0,只要将tail移到栈底就足够了。
10.1.6 打印栈中元素
void printStack()
for (int i = head; i < tail; i++)
printf("%d ", stack[i]);
printf("\n");
如前所述栈中的元素下标从head开始,到tail-1结束,printStack利用一个for循环将这些元素都打印出来,以方便观察栈的当前状态。
例程 10-1 实现一个栈
2 #define STACK_SIZE1000
4 int stack[STACK_SIZE];//int类型的数组用来实现栈
6 {
8 tail++;
10 int pop()
12 tail--;
14 }
16 {
18 }
20 {
22 }
24 {
26 {
28 }
30 }
32 {
34 tail = 0;
36 push(3);
38 push(12);
40 printf("pop %d\n", pop());
42 printf("peek %d\n", peek());
44 return 0;
程序的运行结果如图10-1所示:
例程10-1中包含了前面实现的所有函数,接下来逐行分析main函数中的程序,看看这个程序对栈都做了什么:
第35行:打印当前栈中的元素。由于此时是一个空栈,打印的结果应该也为空;
第37行:向栈中插入一个元素5;
第39行:打印当前栈中的元素;
第41行:打印当前栈中元素;
第43行:打印栈中元素。
队列
10.1.7 定义队列
例程 10-2 定义队列
47 #define QUEUE_SIZE1000
49 int queue[QUEUE_SIZE];//int类型的数组用来实现队列
51 {
53 tail = 0;
55 }
图 10-2 定义一个队列
10.1.8 进入队列
void enQueue(int element)
queue[tail] = element;
tail++;
这里可以将enQueue和栈中的push函数进行比较:由于都是从尾部加入元素,enQueue的实现和栈中的push函数完全一样。
10.1.9 离开队列
int deQueue()
head++;
return queue[head - 1];
在deQueue函数中首先将head的位置向后挪一位,这是deQueue之后head应该在的位置。随后,队首的元素现在的下标变成了head-1,deQueue函数返回这个下标的对应元素值,实现从队首弹出元素的功能。
10.1.10 清空队列
void cleanQueue()
tail = head = 0;
10.1.11 打印队列中元素
void printQueue()
for (int i = head; i < tail; i++)
printf("\n");
至此一个简单的队列就完成了,和栈一样,下面的实例直观地展示了队列先进先出的特点,如例程10-3所示:
56 #include <stdio.h>
58 int head, tail;//队首和队尾
60 void enQueue(int element)
62 queue[tail] = element;
64 }
66 {
68 return queue[head - 1];
70 void cleanQueue()
72 tail = head = 0;
74 void printQueue()
76 for (int i = head; i < tail; i++)
78 printf("\n");
80 int main()
82 head = 0;
84 printQueue();
86 enQueue(5);
88 printQueue();
90 printQueue();
92 printQueue();
94 }
图 10-3 实现一个简单的队列
第27到28行:将head和tail都初始化为0,指向队首;
第30行:向队列中插入元素3;
第32行:向队列中插入元素12;
第34行:利用deQueue函数取出队首的元素3并打印,此时队列中的元素按顺序依次是5和12;
第36行:再次利用deQueue取出队首的5,此时队列中只有12;
关于队列要牢牢把握队列先进先出的特点,本章之后会介绍如何充分利用队列的这一特点解决实际问题。
前面的章节中已经介绍了int,char,float等基本数据类型,有的时候这些基本数据类型不能满足程序员的需求,比如想要表示数学中的复数,就需要实部和虚部两个double类型;想要表示空间中的一个点就需要x,y和z三个坐标;想要表示学校的一个学生,就至少需要一个表示姓名的字符串和一个int类型的学号。C语言允许定义自己的数据类型,这种自定义的新数据类型就是结构体。
结构体的定义需要遵循下面的语法格式:
struct 结构体名
成员1的类型 变量名;
成员2的类型 变量名;
成员n的类型 变量名;
下面是几个结构体定义的例子:
struct Student
char name[50];
int studentID;
Student结构体中定义了两个成员:一个是char类型的数组name,用来保存学生的姓名(字符串形式);第二个是int类型的学号studentID。
二、点
struct Point
double x;
double y;
double z;
Point就是程序中新定义的数据类型,它包含了三个double类型的数,分别表示x,y和z坐标的值。定义了Point之后,就可以像int,float等基本数据类型一样去声明Point类型的变量。
三、栈
struct Stack
int head;
int tail;
int *stack;
本章第一节中的栈也可以定义成结构体的形式。在Stack结构体中包含四个成员变量:栈底head,栈顶tail,栈的大小size,以及一个int*类型的指针stack,用来实现栈。
本章第二节的队列也可以定义成结构体的形式,其定义和栈完全一样:
struct Queue
int head;
int tail;
int *queue;
这是因为在前面的例子中,栈和队列都是基于数组并且利用两个下标来实现的。队列结构体中也包含四个成员变量:队首head,队尾tail,队列的大小size,以及一个int*类型的指针queue用来实现队列。
定义了结构体之后就可以向定义基本数据类型int,double等的变量一样去定义一个结构体类型的变量。结构体变量的定义语法如下所示:
类似的,也可以定义结构体数组:
以上一节中的Stack为例:
这里定义了一个Stack类型的变量,它的名字是s,s里面包含了一个整数head,一个整数tail,一个整数size和一个整型指针stack。
struct 结构体名 结构变量名={成员变量1初值, 成员变量2初值,…,成员变量n初值};
一、初始化Student
这里定义了一个Student类型的变量,变量名为student,student中包含一个char数组name用来存储学生的名字,name被初始化为Zhang San。student中还包含了一个int类型的学号,在这里被初始化为20140100。
struct Point p[2] = {{1.0, 2.0, -1.0}, {0.0, 0.0, 0.0}};
三、初始化Stack
Stack类型的变量s中包含4个成员,其中int类型的head和tail被分别初始化为了0和0,int类型的长度size被初始化为了1000,int类型的指针stack被初始化为NULL。
struct Queue q = {0, 0, 1000, NULL};
10.1.14 结构体与指针
定义一个Student类型的指针:
这个指针被初始化为NULL。
struct Student s = {"Zhang San", 20140100};
这里首先定义了一个结构体变量s,并用字符串Zhang San和学号20140100去初始化它。随后定义了一个Student类型的指针p,并利用取地址&将s的地址赋值给p,p成为了指向Student类型的变量s的指针。
//num是一个int类型的变量,表示数组长度
这里num是一个int类型的变量,假定num的值只有在运行时才能确定,Student类型的指针p分配了长度为num的一个结构体数组,数组中的每个元素都是一个Student类型的结构体变量,整个要分配的内存大小就是Student的大小乘以数量num。
结构体中的数据都是保存在成员变量中的,因此要想真正使用结构体光赋值还是远远不够的,还需要知道如何去访问结构体中的每一个成员变量。访问成员变量的方法可以分为两类:
如果已经在程序中定义好了一个结构变量,那么通过"结构变量名.成员变量名"就可以访问相应的成员变量。如例程10-4所示:
95 #include <stdio.h>
97 struct Student
99 char name[50];
101 };
103 {
105 for (int i = 0; i < 2; i++)
107 printf("%s %d\n", s[i].name, s[i].studentID);
109 return 0;
程序的运行结果如图10-4所示:
在例程10-4中,main函数里定义了一个结构体数组s,它的长度为2,两个数组成员s[0]和s[1]被分别初始化。此时通过s[0].name可以访问到s[0]中的char数组name,通过s[0].studentID可以访问到s[0]中的int类型成员变量studentID。程序中利用一个for循环将两个数组成员的姓名和学号分别输出。
如果程序中有一个指向某个结构体变量的指针,那么可以利用"指针名->成员变量名"的方法来访问成员变量,如例程10-5所示:
111 #include <stdio.h>
113 struct Student
115 char name[50];
117 };
119 {
121 struct Student *p = &s;
123 printf("%s %d\n", p->name, p->studentID);
125 }
图 10-5 通过指针访问成员变量
链表
10.1.16 定义链表
struct Head
int length;
struct Node* first;
这是一个Head类型的结构体,它包含了两个成员,一个是int类型的变量length,用来表示整个链表的长度;第二个成员是一个指针,指向Node类型的变量。Node的定义如下所示:
struct Node
int val;
struct Node *next;
Node是这个链表中真正的元素,类似于数组当中的元素。Node中包含两个成员:首先是int类型的变量val,用来保存数据;其次是一个指针next,它指向一个新的Node变量。
整个链表就是由一系列指针和结构体构成的蛇形结构:从Head开始指向链表的第一个Node,第一个Node指向第二个Node,第二个Node指向第三个Node,以此类推。直到某一个Node的next指针为NULL了,链表终止。
例程 10-6 定义链表
127 #include <stdlib.h>
129 {
131 struct Node* first;
133 struct Node
135 int val;
137 };
139 {
141 h->length = 0;
143 free(h);
145 }
图 10-6 定义一个链表
第15行:利用malloc函数申请一块大小为Head的内存,初始化一个链表指针;
第17行:将Head中的first指针设为NULL;
10.1.17 访问元素
struct Node* getNode(struct Head* head, int id)
if (id < 0 || id >= head->length)
struct Node* p = head->first;
while (i < id)
p = p->next;
i++;
return p;
getNode函数的输入是一个Head指针head,以及将要访问的元素下标id。getNode函数返回一个Node类型的指针,指向链表中下标为id的Node。
如果id不在0到length-1的范围之内,getNode返回NULL。否则,函数会初始化一个指针p指向链表中下标为0的Node,并初始化当前Node的下标i为0。随后函数利用while循环来寻找下标为id的Node。在当前循环中,如果i还没有到id,那么就将指针p更新到p的下一个Node,并将i的值加1,直到i指向id为止。
在链表中插入元素采用如下的规则:如果插入位置的下标id在0到length-1的范围之内,那么就插入到元素id之前,即插入后新元素的下标应当为id。如果下标id=length,那么就将元素插入到链表末尾。其他任何情况下对链表都不作改动:
void addNode(struct Head* head, int val, int id)
if (id < 0 || id > head->length)
return;
if (id == 0)
struct Node* p = head->first;
head->first = (struct Node*)malloc(sizeof(struct Node));
head->first->next = p;
else
struct Node* p = getNode(head, id - 1);
p->next = (struct Node*)malloc(sizeof(struct Node));
p->next->next = q;
head->length++;
addNode函数接受一个Head指针,将要插入的值val,以及要插入的位置id。如果id超出了范围,就直接返回不做任何改动;否则,根据id的值分别进行处理:如果id为0,意味着新的Node要插入在Head和第一个Node之间,程序在if中进行处理;否则直接使用getNode获得id-1处的Node,利用该Node的next指针将val插入到它身后。
删除元素和插入元素类似,要求删除元素的id必须在0到length-1的范围之内,否则不做处理:
void deleteNode(struct Head* head, int id)
if (id < 0 || id >= head->length)
return;
if (id == 0)
struct Node* p = head->first;
head->first = p->next;
free(p);
else
struct Node* p = getNode(head, id - 1);
p->next = q->next;
free(q);
head->length--;
deleteNode和addNode的流程类似,都是首先检验id是否在有效范围之内,如果在的话,再根据id的值是否为0分情况讨论,选择是直接从head开始删除还是从id-1的位置开始删除。
释放链表的函数destroylinkedList定义如下。它负责释放链表中所有Node所占用的内存。
void destroylinkedList(struct Head *head)
struct Node* p = head->first;
while (p)
head->first = p->next;
p = head->first;
head->length = 0;
destroylinkedList的执行过程实际上是在不断地删除链表的第一个Node,直到所有Node都被删除为止。
打印链表的函数定义如下:
void printlinkedList(struct Head *head)
struct Node* p = head->first;
for (int i = 0; i < head->length; i++)
printf("%d ", p->val);
p = p->next;
printf("\n");
printlinkedList函数的输入参数包括一个head指针,由于我们知道链表中所有Node的数量,因此可以利用一重for循环遍历所有Node,分别输出Node中存储的val值即可。
至此一个简单的链表就算是完成了。下面的程序可以用来上面这些链表函数的实现是否正确,如例程10-7所示:
146 #include <stdio.h>
148 struct Head
150 int length;
152 };
154 {
156 struct Node *next;
158 struct Node* getNode(struct Head* head, int id)
160 if (id < 0 || id >= head->length)
162 struct Node* p = head->first;
164 while (i < id)
166 p = p->next;
168 }
170 }
172 {
174 return;
176 {
178 head->first = (struct Node*)malloc(sizeof(struct Node));
180 head->first->next = p;
182 else
184 struct Node* p = getNode(head, id - 1);
186 p->next = (struct Node*)malloc(sizeof(struct Node));
188 p->next->next = q;
190 head->length++;
192 void deleteNode(struct Head* head, int id)
194 if (id < 0 || id >= head->length)
196 if (id == 0)
198 struct Node* p = head->first;
200 free(p);
202 else
204 struct Node* p = getNode(head, id - 1);
206 p->next = q->next;
208 }
210 }
212 {
214 for (int i = 0; i < head->length; i++)
216 printf("%d ", p->val);
218 }
220 }
222 {
224 while (p)
226 head->first = p->next;
228 p = head->first;
230 head->length = 0;
232 int main()
234 struct Head *h = (struct Head*)malloc(sizeof(struct Head));
236 h->first = NULL;
238 //1
240 addNode(h, 2, 1);
242 printlinkedList(h);
244 //1 4 2
246 addNode(h, 5, 2);
248 printlinkedList(h);
250 //1 4 2
252 deleteNode(h, 1);
254 printlinkedList(h);
256 //
258 free(h);
260 }
图 10-7 实现链表
union共同体
10.1.22 定义共同体
union 名称
变量类型1 变量名1;
变量类型3 变量名3;
...
union的定义方法和结构体很类似,但是需要注意的是,union中所有的变量名实际上共用同一块内存。下面对例程的分析将会揭示这一点,如例程10-8所示:
例程 10-8 使用union
262 union Data
264 int i;
266 };
268 {
270 d.i = 15;
272 printf("%d\n", d.j);
274 printf("%d\n", d.i);
276 printf("%p\n", &d.i);
278 return 0;
程序的运行结果如图10-8所示:
在刚刚的例程中,main函数里首先定义了一个union Data变量,名称为d,在union Data中包含两个int类型的变量i和j,main函数里首先对i进行赋值,并利用printf打印出i和j的值。由于i和j数据类型完全一样,占用同一块4个字节的内存,因此对i赋值也就相当于对j进行了赋值,可以预料到两者打印的结果应该是一样的。
最后,利用printf打印出了i和j的内存地址。根据union的定义,打印的结果应该完全相同。
例程 10-9 union中包含不同类型的成员
281 union Data
283 int i;
285 };
287 {
289 d.u = 0xffffffff;
291 printf("%u\n", d.u);
293 return 0;
程序的运行结果如图10-9所示:
上面的程序中定义了一个union类型Data,但是Data中包含了两个不同类型的成员变量:unsigned int类型的u和int类型的i。在main函数中首先将u赋值为0xffffffff,这是unsigned int能够表示的最大数,它在内存中的形式是:
当程序通过d.i的形式来访问这段内存时,i的值也是0xffffffff,但是由于i的类型是int,根据前面的知识,这个值对应的有符号整数应该是-1,所以可以预计main函数中第二次printf的结果应该是-1。
10.1.23 使用不同长度的成员
例程 10-10 union包含不等长成员
296 #include <string.h>
298 {
300 char str[4];
302 int main()
304 union Data d;//定义union变量
306 printf("%s\n", d.str);
308 return 0;
程序的运行结果如图10-10所示:
在例程10-10中,union内包含了两个不等长的数据成员:成员ch的类型是char,占用1个字节;成员str是一个字符串,长度为4个char,占用4个字节。此时union的大小是字符串的大小4个字节,并且ch和str的首字节对齐,即ch和str的第一个字符占用同样位置的内存。
可以看到printf的输出支持上面的分析。这个例子从侧面揭示了union中包含不同长度的数据成员时它们在内存中的位置关系。
本节将通过两个例子介绍如何使用栈和队列解决实际问题。逆波兰表达式求值的例子利用到了栈后进先出的特点,而为像素点染色的例子利用了队列先进先出的特点。在解决实际问题时,要根据问题本身的要求和特性选择合适的数据结构,以达到事半功倍的效果。
数学书上通常将代数运算表达式表示成"操作数 操作符 操作数"的形式,比如3 + 4表示加法运算,它的两个操作数分别是3和4;5 * 2表示乘法运算,它的两个操作数分别是5和2。逆波兰表达式是将操作数按顺序依次放在操作符之前的表达式,如3 + 4在逆波兰表达式中就是3 4 +,而5 * 2在逆波兰表达式中是5 2 *。一个稍微复杂一些的例子是四则运算的混合:
在逆波兰表达式中,4 * 5首先被表示成4 5 *:
加法运算3 + 4 5 *的两个操作数现在分别是3和4 5 *,它的逆波兰表达式为3 4 5 * +:
最后减法的操作数分别是3 4 5 * +和2,因此该四则运算最终的逆波兰表达式为:
给定上述逆波兰表达式,可以在只遍历输入数据一次的情况下,利用栈求得逆波兰表达式的值,在上面的例子中,这个值等于21。解决逆波兰表达式的思路是:从左到右遍历逆波兰表达式中的所有字符,如果遇到一个操作数,就将操作数压入一个操作数栈;如果遇到一个操作符,就将操作数栈最上端的两个操作数弹出,利用操作符进行计算,并将计算结果压栈。这时,计算结果就成为了未来操作符的操作数。当整个逆波兰表达式被处理完之后,栈中只剩下唯一的一个元素,这个元素就是最后一个操作符对应的结果,也就是整个逆波兰表达式的值,如例程10-11所示:
310 #include <stdio.h>
312 int head, tail;
314 void push(int element)
316 stack[tail] = element;
318 }
320 {
322 returnstack[tail];
324 void cleanStack()
326 tail = head;
328 int peek()
330 return stack[tail - 1];
332 void printStack()
334 for (int i = head; i < tail; i++)
336 printf("\n");
338 int main()
340 head = 0;//栈底
342 char RPN[20] = "345*+2-";//逆波兰表达式
344 int op1, op2;//操作数
346 {
348 {
350 }
352 {
354 op1 = pop();
356 {
358 push(op1 + op2);
360 case '-':
362 break;
364 push(op1 * op2);
366 case '/':
368 break;
370 printf("输入错误!\n");
372 }
374 }
376 return 0;
程序的运行结果如图10-11 所示:
例程10-11中的程序相对比较复杂,这里来逐行解释main函数中的代码(main函数之前的代码和此前栈一节中的代码一致):
第33行:定义了要求值的逆波兰表达式;
第35行:定义了两个操作数;
第38到41行:如果字符是一个操作数,就将这个操作数压入栈中;
第44到45行:对于操作符,将栈中的最上面两个元素弹出,作为操作符使用的两个操作数;
第48到50行:如果操作符是加法,将两个操作数相加,并将结果压入栈中;
第54到56行:如果操作符是乘法,将两个操作数相加,并将结果压入栈中;
第60到61行:如果操作符不满足上述情况,则提示输入错误;
第66行:此时已经跳出了while函数之外,即处理完了所有的操作符和操作数。如果一切正确,现在栈中应该只有一个元素,并且这个元素是处理最后一个操作符时得到的结果——整个逆波兰表达式的值。将这个值输出;
在使用画图程序时经常会选择某种颜色,然后在一个封闭图形内部点一下鼠标,就可将图形内染上同一种颜色。在已知封闭图形边界和鼠标位置的情况下,如何将所有处于图形内部的像素点染上同一种颜色?更加具体的描述是:给定一个只包含0和1的二维数组,用1表示封闭图形的边界,0表示底色。给定一个大于1的整数(表示某一种特定颜色),以及一个二维坐标(保证在图形内部),将二维数组中处在这个封闭图形中的元素都赋值为这种颜色。
上述过程可以用队列来实现:在队列中保存探查到的尚未染色且在内部的点,只要队列非空,就开始处理队首的像素,将它染色,并检查它的邻居。如果它的邻居当中有像素点尚未染色,且还没有加入到队列中,就将这个像素加入到队列中来。下面是例程的实现,如例程10-12所示:
378 #include <stdio.h>
380 #define SIZE 8
382 {
384 int j;
386 int head, tail;
388 void enQueue(struct Pos element)
390 queue[tail] = element;
392 }
394 {
396 return queue[head - 1];
398 int main()
400 head = 0;//队首
402 int image[SIZE][SIZE] = {{0, 0, 0, 0, 0, 0, 0, 0},
404 {0, 1, 0, 0, 1, 0, 0, 0},
406 {0, 1, 0, 0, 0, 0, 1, 0},
408 {0, 0, 1, 1, 1, 1, 1, 0},
410 for (int i = 0; i < SIZE; i++)
412 for (int j = 0; j < SIZE; j++)
414 if (image[i][j] != 0)
416 else
418 }
420 }
422 struct Pos start = {2, 2};
424 int color = 2;
426 while (head != tail)
428 struct Pos p = deQueue();
430 struct Pos neighbor;
432 {
434 neighbor.j = p.j;
436 {
438 image[neighbor.i][neighbor.j] = inQueue;
440 }
442 {
444 neighbor.j = p.j;
446 {
448 image[neighbor.i][neighbor.j] = inQueue;
450 }
452 {
454 neighbor.j = p.j - 1;
456 {
458 image[neighbor.i][neighbor.j] = inQueue;
460 }
462 {
464 neighbor.j = p.j + 1;
466 {
468 image[neighbor.i][neighbor.j] = inQueue;
470 }
472 for (int i = 0; i < SIZE; i++)
474 for (int j = 0; j < SIZE; j++)
476 if (image[i][j] != 0)
478 else
480 }
482 }
484 }
图 10-12 为像素点染色
第23到24行:定义了队首和队尾,并将它们初始化为0,即一个空队列;
第33到44行:利用for循环将图片打印在控制台上;
第46行:将起点像素加入到队列中去。目前队列中只有一个元素;
第48行:定义了一个标签,用来表示像素点是否已经在队列中。这是因为一个像素点A可能是好几个像素点B、C和D的邻居,如果像素B、C和D都已经在队列中了,那么就会多次访问A,如果不加区别,A就会被重复加入到队列中。因此程序中需要一个标签来表明A像素是否处于已经加入队列的状态;
第51行:将队首的元素p取出,这是本次while循环将要处理的像素点;
第53行:定义了一个新的Pos结构体neighbor,用来表示要处理的像素的邻居。接下来的程序朝四个方向分别检查四个邻居的状态;
第64到73行:处理p下方的邻居;
第84到93行:处理p右侧的邻居;
第106行:返回。
本章小结
本章主要讲解了C语言中的基本数据结构,其中包括栈、队列、链表,还讲解结构体以及共同体等,通过本章的学习可以掌握C语言中的基本数据结构,以及在实际开发中的应用。
