目录
在上一篇博客里(链表的实现(C语言)),我们学到了单向链表的建立这一个知识点,而在这篇博客里,我们将学到循环链表、双向链表的建立操作以及其它相关操作。
循环链表:
在不包括头结点的单向链表中,除队首结点外,每一个结点都有一个直接前驱结点;除队尾结点外,每一个结点都有一个直接后继结点。这也就是说,在这个链表当中,没有一个结点指向队首结点,并且只有队尾结点的指针域是置空的。那可不可以让这个置空的指针域装着指向队首结点的指针,使得通过这条链中的任意一个结点,我们可以沿着这条链访问其它任意一个结点?这个过程可以用下面的图一和图二来表示:
其中加上的黑线箭头将整个单向链表连接成了一个环,这个环便被我们称为循环链表(circular linked list)。
单向链表只有从表头开始访问,才能访问到所有结点,而在循环链表中从任意结点都能访问到所有结点,这一个特点被称为循环链表的可及性。
双向链表:
单向链表有一个指针域和至少一个数据域,而与此相对应的双向链表有两个指针域和至少一个数据域,图示为:
双向链表的结点有左指针域和右指针域,左指针域储存直接前驱结点(即按顺序的前一个结点),右指针域储存直接后继结点,位置也可以互换。
这两种链表的概念都是建立在单向链表的基础上的,理解起来并不难,各给出一个示例。
循环链表示例:
#include<stdio.h>
#include<string.h>
struct stu
{
int iScore;
char cName[10];
struct stu* pNext;
};
int main()
{
struct stu* pHead = NULL;//pHead指向队首结点
struct stu* pTemp = NULL;//pTemp用来打印各个结点
struct stu* stu_new = (struct stu*)malloc(sizeof(struct stu));
//stu_new用来建立新结点
struct stu* stu_end = NULL;
//stu_end指向最后一个结点
pHead = stu_new;
pTemp = stu_new;
while (scanf("%d", &stu_new->iScore) && stu_new->iScore != -1)
//输入-1,循环停止
{
scanf("%s", stu_new->cName);
stu_end = stu_new;
stu_new = (struct stu*)malloc(sizeof(struct stu));
stu_end->pNext = stu_new;
}
stu_end->pNext = pHead;//连接队首结点和队尾结点
free(stu_new);//释放掉分配给stu_new的内存空间
printf("打印结果:\n");
printf("%d %s\n", pTemp->iScore, pTemp->cName);
pTemp = pTemp->pNext;
while (pTemp != pHead)
{
printf("%d %s\n", pTemp->iScore, pTemp->cName);
pTemp = pTemp->pNext;
}
return 0;
}
在这个示例中,我们是从队首结点开始打印节点信息的,代码在上一篇博客中的代码基础上修改了一些,建议对比一下改动。重要的改动改动有两处:
stu_end->pNext = pHead;//连接队首结点和队尾结点
while (pTemp != pHead)
第一处改动是我们添加上去的语句,原代码中是这样的:
stu_end->pNext = NULL;
目的正是连接队首结点和队尾结点。
第二处原语句为:
while (pHead != NULL)
在这篇博客看来,改成下面这样或许更好理解:
while (pTemp != NULL)
此时,用来判断继续打印的条件不再是临时指针或其它起指示作用的指针是否为空,而是临时指针或其它起指示作用的指针是否等于头指针或其它指向特定结点的指针,这一点要明确。
双向链表示例:
#include<stdio.h>
#include<string.h>
struct stu
{
struct stu* pBefo;
int iScore;
char cName[10];
struct stu* pNext;
};
int main()
{
struct stu* pTemp = NULL;
//pTemp用作顺序打印和倒序打印链表时用到的临时指针
struct stu* pHead = NULL;
//定义pHead用来指向队首结点
struct stu* pEnd = NULL;
//定义pEnd用来指向队尾结点
struct stu* stu_new = (struct stu*)malloc(sizeof(struct stu));
struct stu* stu_end = NULL;
pHead = stu_new;
//pHead指向队首结点
while (scanf("%d", &stu_new->iScore) && stu_new->iScore != -1)
//输入-1,循环停止
{
scanf("%s", stu_new->cName);
stu_end = stu_new;//新结点变成了队尾结点
stu_new = (struct stu*)malloc(sizeof(struct stu));//开辟一个新结点
stu_end->pNext = stu_new;
stu_new->pBefo = stu_end;
//连接新结点与旧结点的前后关系
}
stu_end->pNext = NULL;
free(stu_new);
pEnd = stu_end;
//pEnd指向队尾结点
printf("顺序打印结果:\n");
pTemp = pHead;
while (pTemp != NULL)
{
printf("%d %s\n", pTemp->iScore, pTemp->cName);
pTemp = pTemp->pNext;
}
printf("倒序打印结果:\n");
pTemp = pEnd;
while (pTemp != NULL)
{
printf("%d %s\n", pTemp->iScore, pTemp->cName);
pTemp = pTemp->pBefo;
}
return 0;
}
这个示例不难理解,只是在单向链表的基础上添加了一个新的指针域。在这个示例中,pHead 和 pEnd 分别指向这条双向链表队首结点和队尾结点,在两个相反方向上都有一条通路,图示如下:
图中蓝线箭头指示了一条顺序的通路,红线箭头指示了一条逆序的通路。
运行一下:
栈的链式实现:
在学习了单向链表和双向链表后,我们可以发现双向链表的图示只是在单向链表的图示的基础上加了一条逆序通路。在按照这条逆序通路打印出链表的各个结点的内容时,会发现一个现象,我们在后面输入的信息反而先打印出来,这是不是符合栈存储结构后进先出(LIFO,last in first out)的特性?
没错,我们可以用这样的链式存储结构来实现栈结构,只需要在双向链表的基础上只保留其逆向通路。只需要将上面的代码稍稍修改:
#include<stdio.h>
#include<string.h>
struct stu
{
struct stu* pBefo;
int iScore;
char cName[10];
};
int main()
{
struct stu* pTemp = NULL;
//pTemp顺序打印和倒序打印链表时用到的临时指针
struct stu* pEnd = NULL;
//定义pEnd用来指向队尾结点
struct stu* stu_new = (struct stu*)malloc(sizeof(struct stu));
struct stu* stu_end = NULL;
while (scanf("%d", &stu_new->iScore) && stu_new->iScore != -1)
//输入-1,循环停止
{
scanf("%s", stu_new->cName);
stu_end = stu_new;//新结点变成了队尾结点
stu_new = (struct stu*)malloc(sizeof(struct stu));//开辟一个新结点
stu_new->pBefo = stu_end;
//连接新结点与旧结点的前后关系
}
free(stu_new);
pEnd = stu_end;
//pEnd指向队尾结点
printf("倒序打印结果:\n");
pTemp = pEnd;
while (pTemp != NULL)
{
printf("%d %s\n", pTemp->iScore, pTemp->cName);
pTemp = pTemp->pBefo;
}
return 0;
}
需要记住的是,在计算机系统中,栈是一个动态内存区域,是一个物理结构;而当它作为一种数据结构时,它实际上是作为一个逻辑结构的。在这个实例中,通过逆序通路我们成功实现了栈的LIFO的特性。
好了,本篇博客到此结束。
欢迎指正我的上一篇博客:链表的实现(C语言)
我的下一篇博客:函数×指针(C语言) 补充