双向循环链表
前面我们了解了双向链表,双向链表从任意结点都可以向前或向后进行遍历链表进行操作,但是在单一方向上的遍历会因为边界的限制从而不一定能找到目标结点,为了解决这样的问题我们引入了双向循环链表。如图:

本篇示例使用了头结点,这样可更好的表示链表为空时的状态,具体代码在这儿。
定义
对链表的结点进行定义,同时也定义了一些必要的状态。
#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 BothwayCycleChain{
// 链表的头结点,当然你也可以不使用头结点,那样你需要最首元结点进行额外操作
ChainNodePtr chain;
// 链表的长度
int count;
}BothwayCycleChain;
初始化
对于双向链表的初始化需要注意的是要对两个指针域的操作。
这部分还实现了一些其他的辅助操作。
- 初始化
/// 链表的初始化
/// @param c 链表
Status chainInit(BothwayCycleChain *c){
// 为了首元结点的操作一致性我们引出头结点。
c->chain = (ChainNodePtr)malloc(sizeof(ChainNode));
// 循环链表为空的时候头结点的前驱指针域和后去指针域都指向自己
c->chain->front = c->chain->next = c->chain;
c->count = 0;
printf("链表初始化完毕!------------------------------------\n\n");
return SUCCESS;
}
- 使用数组初始化
/// 使用一个数组来初始化一个链表
/// @param c 链表
/// @param a 数组
/// @param n 数组的长度
Status chainInitWithArr(BothwayCycleChain *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 = tail->next;
tail->next->front = k;
k->front = tail;
tail->next = k;
// 记录新的尾
tail = k;
// 长度增加
c->count++;
}
return SUCCESS;
}
- 链表的清空
/// 链表清空
/// @param c 链表
Status chainClear(BothwayCycleChain *c){
ChainNodePtr p, temp;
// 清空链表的时候保留头结点
p = c->chain->next;
// 当p == c->chain时那么已经转了一圈了
while (p != c->chain) {
temp = p;
p = p->next;
// 注意此处的处理,需要将多处指针进行置空,但其实有些多余,因为这些结点都是要释放的,但是从逻辑上应该完成这些操作。
temp->front->next = temp->next;
temp->next->front = temp->front;
// 这两步省略,因为temp会被释放。
// p->front = NULL;
// p->next = NULL;
free(temp);
}
return SUCCESS;
}
- 链表判空
/// 判断链表是否为空
/// @param c 链表
Status chainIsEmpty(BothwayCycleChain c){
// 此处你也可以使用count来进行判断
return (c.chain->next == c.chain) && (c.chain->front == c.chain);
}
- 遍历输出链表
/// 遍历并输出链表
/// @param c 链表
Status chainPrint(BothwayCycleChain c){
if (chainIsEmpty(c)) {
printf("链表为空,无需输出!\n");
return ERROR;
}
ChainNodePtr p;
p = c.chain->next;
printf("输出链表:\n");
while (p != c.chain) {
printf("%d, ", p->data);
p = p->next;
}
printf("\n\n");
return SUCCESS;
}
- 获取链表长度
/// 获取链表的长度
/// @param c 链表
int chainGetCount(BothwayCycleChain c){
return c.count;
}
插入
插入时需要注意插入位置的找寻,对最后一个位置需要使用index来判断最末位置是否是正确的位置。
/// 在特定位置插入新的元素
/// @param c 链表
/// @param index 待插入位置
/// @param e 待插入元素
Status chainInsert(BothwayCycleChain *c, int index, ElementType e){
if (NULL == c) {
printf("链表不存在,无法插入元素!\n");
return ERROR;
}
ChainNodePtr p = c->chain;
// 寻找插入位置的前驱结点
int i = 1;
for (; i < index && p->next != c->chain; i++, p = p->next) ;
// 如果 p->next == c->chain 说明找到最后了,当然这个位置也可能是一个正确的位置,需要使用index来进行判断
if (p->next == c->chain && index > i) {
printf("没有找到正确的位置,无法插入元素!\n");
return ERROR;
}
ChainNodePtr k = (ChainNodePtr)malloc(sizeof(ChainNode));
k->data = e;
k->next = p->next;
k->next->front = k;
k->front = p;
p->next = k;
c->count++;
return SUCCESS;
}
删除
删除时需要注意删除位置的找寻,对最后一个位置需要使用index来判断最末位置是否是正确的位置。
Status chainDelete(BothwayCycleChain *c, int index, ElementType *e){
if (NULL == c->chain) {
printf("链表不存在,无法操作!");
return ERROR;
}
ChainNodePtr p = c->chain;
int i = 1;
for (; i < index && p->next != c->chain; i++, p = p->next) ;
// 如果 p->next == c->chain 说明找到最后了,当然这个位置也可能是一个正确的位置,需要使用index来进行判断
if (p->next == c->chain && index > i) {
printf("没有找到正确的位置,无法操作!");
return ERROR;
}
ChainNodePtr temp = p->next;
p->next = p->next->next;
p->next->front = p;
// 带回值
*e = temp->data;
// 这一步可以省略,因为temp反正是要释放的。
// temp->front = temp->next == NULL;
free(temp);
return SUCCESS;
}
小结
双向循环链表在处理结点的时候主要需要注意:
- 对于每个结点的
两个指针域的处理; - 对最末位置的
校验。