定义(たぶん?):
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
注意理解其中对于“非连续”、“非顺序”的理解。
“非连续”: 表明分配给链表数据结构的内存空间是 “非连续” 的,即链表得到的内存空间不是一块完整的内存空间。但这并不意味着链表本身非连续。
**“非顺序”:**链表结构存在顺序,但这种数据不是传统意义的线性表顺序结构。这表明链表的顺序不是单纯从1写到10这样的顺序。链表的顺序是通过指针的指向来完成顺序效果的,至于是如何完成的,下面会写到。
名词及其释义:
结点:由数据域和指针域两部分共同组成的整体。
头结点:一个数据域常常为空,指针域指向第一个存有数据的空结点。
首元结点:第一个存有数据的空结点。
头指针:永远指向链表中的第一个结点(包括头结点)的一个和结点类型相同的指针。
next:结点中的指针常用名,也因此指针域也常被叫做“next域”。
性质:
链表的非连续性:
如上文所记。链表所得到的内存空间不是一块完整的内存空间,而常常是分散的,破碎的,零散的内存空间。
这样的内存空间分配逻辑,也给了链表不同于线性表的性质:
非连续的内存分配可以使得内存分配更有效率从而避免内存的浪费。
链表的内存分配使得链表可以利用到一些在其他分配方法下难以投入利用的内存空间。
如上图中,1,3,4这种较连续的空间,可能就来自于数组等其他数据结构,而2便可以利用于链表中。
非连续性的内存分配会使得查找链表中的内容变得较数组而言更加耗费时间。
链表的非顺序性:
链表内容查找与数组这类线性顺序表不同。链表的顺序是由其在定义时所使用的指针来完成的。链表中的结点的内容包括两个部分,一个是所存储的数据和一个指向下一个结点的指针。故每次在链表中查找内容位置的时候,需要从头结点逐个到内容所在位置,相对更加耗费时间。
相关操作:
定义链表中的结点:
typedef struct link{
char elem; //代表数据域//element
struct link * next; //代表指针域,指向直接后继元素
}Link;
链表的创建:
创建一个链表,实现步骤如下:
1.定义一个头指针;
2.创建一个头结点或者首元结点,让头指针指向它;
3.每创建一个结点,都令其直接前驱结点的指针指向它。
Link* initLink() {//函数返回值为指向之前定义的Link类型的指针
int i;
Link* p = NULL;//创建头指针//创建的时候就提前指向NULL避免野指针
//创建首元结点
Link* temp = (Link*)malloc(sizeof(Link));
temp->elem = 1;//->可以理解为“的”
temp->next = NULL;//temp结构体中的“next”部分
//头指针指向首元结点
p = temp;
//每创建一个结点,都令其直接前驱结点的指针指向它
for (i = 2; i < 5; i++) {
//创建一个结点
Link* a = (Link*)malloc(sizeof(Link));
a->elem = i;
a->next = NULL;
//每次 temp 指向的结点就是 a 的直接前驱结点
temp->next = a;
//temp指向下一个结点(也就是a),为下次添加结点做准备
temp = temp->next;
}
return p;
}
打印链表内容的一种方法:
void display(Link* p) {
Link* temp = p;//temp指针用来遍历链表
//只要temp指向结点的next值不是NULL,就执行输出语句。
while (temp) {
Link* f = temp;//准备释放链表中的结点
printf("%d ", temp->elem);
temp = temp->next;
free(f);
}
printf("\n");
}
int main() {
Link* p = NULL;
printf("初始化链表为:\n");
//创建链表{1,2,3,4}
p = initLink();
//输出链表中的数据
display(p);
return 0;
}
输出的内容应该是0 1 2 3 4
以上内容只是单链表的操作方式以及构成逻辑,以后会继续补充其他链表类型的知识如双向链表、循环链表之类的。