双向链表
在前面对于链表的学习中,我们可以发现对于链表的各个结点来说,我们可以通过next指针十分轻松地获取结点的后继结点,但是如果我们知道一个 k 结点要反过来找寻它的前驱结点 p 就会显得十分困难。通常我们会使用 p->next != k 来进行遍历找到 p,显然这是一个比较耗时的操作。
为了解决这样的问题,我们可以给链表的结点添加一个新的指针域,使其指向当前结点的前驱结点,如图所示:

双向链表。如图:

这是一个典型的使用
空间换取时间的做法。
为了保证首元结点的操作一致性,本篇示例是使用了头结点的,具体代码在这儿。
定义
对链表的结点进行定义,同时也定义了一些必要的状态。
#define MAXSIZE 20
#define SUCCESS 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int ElementType;
typedef int Status;
typedef struct ChainNode{
// 数据域
ElementType data;
// 前驱指针域
struct ChainNode *front;
// 后继指针域
struct ChainNode *next;
}ChainNode, *ChainNodePtr;
//定义双向链表
typedef struct BothwayChain{
// 链表的头结点,当然你也可以不使用头结点,那样你需要最首元结点进行额外操作
ChainNodePtr chain;
// 链表的长度
int count;
}BothwayChain;
初始化
对于双向链表的初始化需要注意的是要对两个指针域的操作。
这部分还实现了一些其他的辅助操作。
- 初始化
/// 链表的初始化
/// @param c 链表
Status chainInit(BothwayChain *c){
// 为了首元结点的操作一致性我们引出头结点。
c->chain = (ChainNodePtr)malloc(sizeof(ChainNode));
c->chain->front = c->chain->next = NULL;
c->count = 0;
printf("链表初始化完毕!------------------------------------\n\n");
return SUCCESS;
}
- 使用数组初始化
/// 使用一个数组来初始化一个链表
/// @param c 链表
/// @param a 数组
/// @param n 数组的长度
Status chainInitWithArr(BothwayChain *c, int a[], int n){
chainInit(c);
// 记录链表的尾
ChainNodePtr tail;
tail = c->chain;
for (int i = 0; i < n; i++) {
ChainNodePtr k = (ChainNodePtr)malloc(sizeof(ChainNode));
k->data = a[i];
k->next = NULL;
// 尾插法插入数据,注意对于操作处俩端的指针丢需要进行处理。
k->front = tail;
tail->next = k;
// 记录新的尾
tail = k;
// 长度增加
c->count++;
}
return SUCCESS;
}
- 链表的清空
/// 链表清空
/// @param c 链表
Status chainClear(BothwayChain *c){
ChainNodePtr p, temp;
// 清空链表的时候保留头结点
p = c->chain->next;
while (p) {
temp = p;
p = p->next;
// 这里的逻辑类似于删除
temp->front->next = temp->next;
if (temp->next) {
temp->next->front = temp->front;
}
// 这一步省略,temp已经被释放了
temp->front = temp->next = NULL;
free(temp);
}
return SUCCESS;
}
- 链表判空
/// 判断链表是否为空
/// @param c 链表
Status chainIsEmpty(BothwayChain c){
// 此处你也可以使用count来进行判断
return NULL == c.chain->next;
}
- 遍历输出链表
/// 遍历并输出链表
/// @param c 链表
Status chainPrint(BothwayChain c){
if (chainIsEmpty(c)) {
printf("链表为空,无需输出!\n");
return ERROR;
}
ChainNodePtr p;
p = c.chain->next;
printf("输出链表:\n");
while (p) {
printf("%d, ", p->data);
p = p->next;
}
printf("\n\n");
return SUCCESS;
}
- 获取链表长度
/// 获取链表的长度
/// @param c 链表
int chainGetCount(BothwayChain c){
return c.count;
}
插入
/// 在特定位置插入新的元素
/// @param c 链表
/// @param index 待插入位置
/// @param e 待插入元素
Status chainInsert(BothwayChain *c, int index, ElementType e){
if (NULL == c) {
printf("链表不存在,无法插入元素!\n");
return ERROR;
}
ChainNodePtr p = c->chain;
// 寻找插入位置的前驱结点
for (int i = 1; i < index && NULL != p; i++, p = p->next) ;
if (NULL == p) {
printf("没有找到正确的位置,无法插入元素!\n");
return ERROR;
}
ChainNodePtr k = (ChainNodePtr)malloc(sizeof(ChainNode));
k->data = e;
k->next = p->next;
// 注意此处可能插在最后一位,此时 k->next = NULL 所以不能处理它的前驱指针域。
if (NULL != k->next) {
k->next->front = k;
}
k->front = p;
p->next = k;
c->count++;
return SUCCESS;
}
删除
/// 删除特定位置的结点
/// @param c 链表
/// @param index 待删除位置
/// @param e 删除结点值带回
Status chainDelete(BothwayChain *c, int index, ElementType *e){
if (NULL == c->chain) {
printf("链表不存在,无法操作!");
return ERROR;
}
ChainNodePtr p = c->chain;
for (int i = 1; i < index && NULL != p; i++, p = p->next) ;
if (NULL == p) {
printf("没有找到正确的位置,无法操作!");
return ERROR;
}
ChainNodePtr temp = p->next;
p->next = p->next->next;
// 注意此处可能删除在最后一位,此时 p->next = NULL 所以不能处理它的前驱指针域。
if (p->next) {
p->next->front = p;
}
// 带回值
*e = temp->data;
// 这一步可以省略,因为temp反正是要释放的。
// temp->front = temp->next == NULL;
free(temp);
return SUCCESS;
}
小结
双向链表在处理结点的时候主要需要注意:
- 对于每个结点的
两个指针域的处理; - 对于
首元结点和尾结点的处理。