循环链表与双链表:
循环链表:
循环链表是一种特殊的链式存储结构。它的特点是表中最后一个节点的指针域指向头节点,使得整个链表形成一个环,这种结构的优点是从任何一个节点出发都可以访问到其他所有节点,增加了链表的灵活性。
一个循环链表的实例:
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
int main() {
int n, i, data;
struct Node* head, * new_node, * temp;
printf("Enter the number of nodes: ");
scanf("%d", &n);
// 创建第一个节点
head = (struct Node*)malloc(sizeof(struct Node));
if (!head) {
printf("Memory allocation failed.");
return 0;
}
printf("Enter the data for node 1: ");
scanf("%d", &data);
head->data = data;
head->next = NULL;
temp = head;
// 创建剩余节点
for (i = 2; i <= n; ++i) {
new_node = (struct Node*)malloc(sizeof(struct Node));
if (!new_node) {
printf("Memory allocation failed.");
return 0;
}
printf("Enter the data for node %d: ", i);
scanf("%d", &data);
new_node->data = data;
new_node->next = NULL;
temp->next = new_node;
temp = new_node;
}
//循环连接
new_node->next = head;
// 输出链表中的数据元素
printf("\nCreated Linked List is: ");
temp = head;
struct node* k = head;
int l = 1;
while (temp != k||l==1) {
printf("%d ", temp->data);
l = 0;
temp = temp->next;
}
printf("\n");
// 释放内存
temp = head;
l = 1;
while (temp != k||l==1) {
head = temp->next;
free(temp);
l = 0;
temp = head;
}
return 0;
}
从上可以看出,与一般的单链表相比,多了 new_node->next = head;从而可以循环连接。
除此之外,在输出链表元素时也与一般的单链表不同,需要特殊的手段避免死循环:
struct node* k = head;//记录头结点的位置,再次循环到头结点时,停止打印
int l = 1;
while (temp != k||l==1) {
printf("%d ", temp->data);
l = 0;
temp = temp->next;
}
printf("\n");
// 释放内存
temp = head;
l = 1;
while (temp != k||l==1) {
head = temp->next;
free(temp);
l = 0;
temp = head;
}
同样,我们也可以通过一些操作,让两个有头结点的循环链表相连接(假设它们的尾指针分别为reara,rearb):
p=reara->next;
reara->next=rearb->next->next;
q=rearb->next;
rearb->next=p;
free(q);
要注意的是,第一个链表的尾指针连接的不是第二个链表的头结点,而是之后的节点。
下面再来看看双链表:
什么是双链表:
如图所示:
双链表,也被称为双向链表,是链表的一种。在双链表中,每个数据节点都有两个指针,分别指向直接后继和直接前驱,与单链表相比,双链表的主要区别在于其结构的构造有所不同,在单链表中,每个节点都有一个储存数据的部分和一个指向后继节点的指针。这意味着单链表的遍历操作必须从前节点到后节点进行然而,在双链表中,每个节点除了有储存数据的部分和一个指向后继节点的指针(next
)外,还有一个指向前驱节点的指针(pre
)这样,双链表就可以实现从后节点到前节点的遍历操作。这种结构使得双链表在进行某些操作时,如插入和删除节点,比单链表更加高效。因为在双链表中,可以直接找到任何节点的前驱节点,而在单链表中,则需要从头开始遍历才能找到前驱节点。当然,在换取方便的同时,这也使得双链表在内存使用上比单链表更加消耗资源,因为它需要额外的空间来存储pre
指针。
双链表的使用:
双链表的大多操作都与单链表类似,只不过多了一个前驱指针。
以下是一个双链表节点的C语言定义示例:
struct dnode{
int data;
struct next*;
struct pre*;
};
当然,我们也可以创建一个头结点:
struct dnode*head=(struct dnode*)malloc(sizeof(struct dnode));
head->next=NULL;
head->pre=NULL;
也可以增加一个节点,这与单链表相比,有一定的变化:
struct dnode*new=(struct dnode*)malloc(sizeof(struct dnode));
//假如在当前节点p后面添加。
new->next=p->next->next;//1
new->pre=p;//2
p->next->pre=new;//3
p->next=new;//4
注意3,4步都使用到了p->next;如果先进行了第四步,那么第三步就会出错。所以,顺序很重要,
不过我们可以将三四步这样写,就避免了顺序错误的问题:
new->next->pre=new;
new->pre->next=new;
也可删除一个节点,比如删除p节点:
p->next->pre=p->pre;
p->pre->next=p->next;
free(p);
用完之后,别忘了释放空间,可以用双链表的整表删除:
void free_dnode(struct dnode* head) {
struct dnode* p = head;
while (p->next) {
struct dnode* k = p->next;
free(p);
p = k;
}
free(p);
}
完整的代码如下;
#include <stdio.h>
#include <stdlib.h>
struct dnode {
int data;
struct dnode* pre;
struct dnode* next;
};
struct dnode* init_head(void) {
struct dnode* head = (struct dnode*)malloc(sizeof(struct dnode));
head->next = NULL;
head->pre = NULL;
return head;
}
void add_dnode(struct dnode* head, int a, int b) {
struct dnode* new = (struct dnode*)malloc(sizeof(struct dnode));
new->data = b;
struct dnode* p = head;
while (a > 0 && p->next != NULL) {
p = p->next;
a--;
}
new->pre = p;
new->next = p->next;
if (p->next != NULL) {
p->next->pre = new;
}
p->next = new;
}
void print(struct dnode* head) {
struct dnode* p = head->next;
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
void del_dnode(struct dnode* head, int place) {
struct dnode* p = head;
while (place > 0 && p->next != NULL) {
p = p->next;
place--;
}
if (p->next != NULL) {
struct dnode* to_delete = p->next;
p->next = to_delete->next;
if (to_delete->next != NULL) {
to_delete->next->pre = p;
}
free(to_delete);
}
}
void free_dnode(struct dnode* head) {
struct dnode* p = head;
while (p->next) {
struct dnode* k = p->next;
free(p);
p = k;
}
free(p);
}
int main() {
struct dnode* head = init_head();
int k;
scanf("%d", &k);
for (int i = 0; i < k; i++) {
int a, b;
scanf("%d%d", &a, &b);
add_dnode(head, a, b);
}
int place;
scanf("%d", &place);
del_dnode(head, place);
print(head);
free_dnode(head);
head = NULL;//避免出现悬挂指针
return 0;
}
我们也可以像单链表一样,用数组来模拟一个双链表:
先来看看一个实例:
#include <stdio.h>
#include <string.h>
int l[10000];
int r[10000];
int data[10000];
int idx = 2;
void init() {
r[0] = 1; // 左头节点
l[1] = 0; // 右尾结点
}
void add_l(int x) { // 头插
data[idx] = x;
l[idx] = 0;
r[idx] = r[0];
l[r[0]] = idx;
r[0] = idx;
idx++;
}
void add_r(int x) { // 尾插
data[idx] = x;
r[idx] = 1;
l[idx] = l[1];
r[l[1]] = idx;
l[1] = idx;
idx++;
}
void insert_r(int pos, int x) { // 右插
pos++;
data[idx] = x;
l[idx] = pos;
r[idx] = r[pos];
l[r[pos]] = idx;
r[pos] = idx;
idx++;
}
void insert_l(int pos, int x) { // 左插
pos++;
pos = l[pos];
data[idx] = x;
l[idx] = pos;
r[idx] = r[pos];
l[r[pos]] = idx;
r[pos] = idx;
idx++;
}
void del(int k) { // 删除
k++;
r[l[k]] = r[k];
l[r[k]] = l[k];
}
int main() {
int m;
scanf("%d", &m);
getchar();
init();
while (m--) {
char str[1000];
gets(str);
if (str[0] == 'L') {
int x;
sscanf(str, "%*s %d", &x);
add_l(x);
}
else if (str[0] == 'R') {
int x;
sscanf(str, "%*s %d", &x);
add_r(x);
}
else if (str[0] == 'D') {
int x;
sscanf(str, "%*s %d", &x);
del(x);
}
else if (str[0] == 'I') {
int x, pos;
sscanf(str, "%*s %d %d", &pos, &x);
if (str[1] == 'L') {
insert_l(pos, x);
}
else if (str[1] == 'R') {
insert_r(pos, x);
}
}
}
for (int i = r[0]; i != 1; i = r[i]) {
printf("%d ", data[i]);
}
return 0;
}
数组模拟的的双链表不但有双链表的所有功能,还避免了频繁分配空间,在处理大数据时,更加快速,而相比与数组,又具备了链表可以插入元素的功能。
当然,我们也可以把它变成双向循环链表,改动如下:
void make_circular(struct dnode* head) {
struct dnode* p = head;
while (p->next != NULL) {
p = p->next;
}
p->next = head;
head->pre = p;
}
输出也会相应变化,以下是完整版本:
#include <stdio.h>
#include <stdlib.h>
struct dnode {
int data;
struct dnode* pre;
struct dnode* next;
};
struct dnode* init_head(void) {
struct dnode* head = (struct dnode*)malloc(sizeof(struct dnode));
head->next = NULL;
head->pre = NULL;
return head;
}
void add_dnode(struct dnode* head, int a, int b) {
struct dnode* new = (struct dnode*)malloc(sizeof(struct dnode));
new->data = b;
struct dnode* p = head;
while (a > 0 && p->next != NULL) {
p = p->next;
a--;
}
new->pre = p;
new->next = p->next;
if (p->next != NULL) {
p->next->pre = new;
}
p->next = new;
}
void print(struct dnode* head) {
struct dnode* p = head->next;
while (p != NULL && p != head) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
void del_dnode(struct dnode* head, int place) {
struct dnode* p = head;
while (place > 0 && p->next != NULL) {
p = p->next;
place--;
}
if (p->next != NULL) {
struct dnode* to_delete = p->next;
p->next = to_delete->next;
if (to_delete->next != NULL) {
to_delete->next->pre = p;
}
free(to_delete);
}
}
void free_dnode(struct dnode* head) {
struct dnode* p = head;
while (p->next && p->next != head) {
struct dnode* k = p->next;
free(p);
p = k;
}
free(p);
}
void make_circular(struct dnode* head) {
struct dnode* p = head;
while (p->next != NULL) {
p = p->next;
}
p->next = head;
head->pre = p;
}
int main() {
struct dnode* head = init_head();
int k;
scanf("%d", &k);
for (int i = 0; i < k; i++) {
int a, b;
scanf("%d%d", &a, &b);
add_dnode(head, a, b);
}
make_circular(head);
int place;
scanf("%d", &place);
del_dnode(head, place);
print(head);
free_dnode(head);
head = NULL;//避免出现悬挂指针
return 0;
}