双向链表
在单链表中,我们可以通过next指针方便找到当前结点的后继结点,但是在寻找前驱结点的时候就颇为费力,只能通过遍历的方式来进行查找。由此出现了一种新的链表双向链表。
相比于单向链表,双向链表增了一个指向前驱的prior指针,通过prior可以方便的找到当前结点的前驱结点。
双向,指的是各节点之间的逻辑关系是双向的,但通常头指针只设置一个。
双向链表的结点结构如下:
//定义结点
typedef struct Node{
ElemType data;
struct Node *prior; //前驱指针
struct Node *next; //后继指针
}Node;
typedef struct Node* LinkList;
双向链表的初始化
- 创建头结点
- 创建新结点并建立关系
Status createLinkList(LinkList *L){
//头结点初始化
*L = (LinkList)malloc(sizeof(struct Node));
if(*L == NULL) return ERROR;
(*L)->next = NULL;
(*L)->data = -1;
(*L)->prior = NULL;
LinkList p = *L;
//循环插入新结点
LinkList temp;
for(int i = 0; i < 10; i++) {
temp = (LinkList)malloc(sizeof(struct Node));
if(temp == NULL) return ERROR;
temp->next = NULL;
temp->data = i;
//建立关系
p->next = temp;
temp->prior = p;
//p位移方便下一次插入
p = p->next;
}
return OK;
}
为了方便使用,定义如下函数:
/*
寻找某一索引的前驱结点。
存在index越界,但是其前驱存在的情况。
比如1-2-3-4,那么index为5的前驱就是4,但是5的结点并不存在
*/
LinkList findPriorNode(LinkList L, int index) {
int j;
LinkList target = L;
for(j = 1; j < index && target->next != NULL; j++) {
target = target->next;
}
//前驱为末尾结点,并且j < index,就越界了
if(target->next == NULL && j < index) return NULL;
return target;
}
双向链表的插入
- 插入位置不是末尾
- 插入位置在末尾
Status ListInsert(LinkList *L, int i, ElemType data){
if(i < 1) return ERROR;
LinkList target = findPriorNode(*L, i);
//如果没有找到前驱结点
if(target == NULL) return printError("插入越界了", ERROR);
LinkList temp = (LinkList)malloc(sizeof(struct Node));
if(temp == NULL) return printError("创建新结点失败", ERROR);
temp->data = data;
temp->next = NULL;
//插入位置不在末尾,需要首先将后继结点与插入结点建立关系
if(target->next != NULL) {
temp->next = target->next;
target->next->prior = temp;
}
//前驱结点和插入结点建立关系
target->next = temp;
temp->prior = target;
return OK;
}
双向链表的删除
- 删除结点不是末尾
- 删除结点在末尾
Status ListDelete(LinkList *L, int index, ElemType *e){
//如果是空链表,返回错误
if(*L == NULL) return printError("链表为空", ERROR);
if(index < 1) return printError("删除位置必须从1开始", ERROR);
LinkList target = findPriorNode(*L, index);
//如果没有找到前驱结点,或者前驱结点为尾结点
if(target == NULL || target->next == NULL) return printError("删除位置越界", ERROR);
//标记目标结点
LinkList temp = target->next;
//建立新的关系
target->next = temp->next;
//如果目标结点不是末尾结点,后继结点的prior指向前驱结点
if(temp->next != NULL) {
target->next->prior = target;
}
*e = temp->data;
free(temp);
//如果只剩头结点,则释放头结点,指针置NULL
if((*L)->next == NULL) {
free(*L);
*L = NULL;
}
return OK;
}
双向链表打印
考虑到是双向的,正向打印一次,逆向打印一次,确保next和prior的正确性。
void traverseLinkList(LinkList L) {
if(L == NULL) return;
printf("正向\n");
//找到首元结点
LinkList temp = L->next;
if(temp == NULL) return;
//循环结束位置为尾结点。
while(temp->next) {
printf("%5d", temp->data);
temp = temp->next;
}
//打印尾结点
printf("%5d", temp->data);
printf("\n");
printf("逆向\n");
//逆序
while(temp != L) {
printf("%5d", temp->data);
temp = temp->prior;
}
printf("\n");
}
双向循环链表
双向循环链表和双向链表的唯一区别在于循环,尾结点的next指针会指向头结点,s头结点的prior指针会指向尾结点
双向循环链表的初始化
- 创建头结点
- 插入新结点
双向循环链表初始化
Status creatLinkList(LinkList *L, int num){
*L = (LinkList)malloc(sizeof(struct Node));
if(*L == NULL) return printError("链表创建失败", ERROR);
(*L)->next = *L;
(*L)->prior = *L;
(*L)->data = -1;
LinkList temp;
LinkList p = *L;
for(int i = 0; i < num; i++) {
temp = (LinkList)malloc(sizeof(struct Node));
if(temp == NULL) return printError("链表创建失败", ERROR);
temp->data = i;
//建立当前结点与后继结点的关系
temp->next = *L;
temp->next->prior = temp;
//建立当前结点与前驱结点的关系
temp->prior = p;
p->next = temp;
p = temp;
}
return OK;
}
为了方便使用,定义如下函数:
/*
寻找某一索引的前驱结点。
存在index越界,但是其前驱存在的情况。
比如1-2-3-4,那么index为5的前驱就是4,但是5的结点并不存在
*/
LinkList findPriorNode(LinkList L, int index) {
int j;
LinkList target = L;
for(j = 1; j < index && target->next != L; j++) {
target = target->next;
}
//前驱为末尾结点,并且j<index,就越界了
if(target->next == L && j < index) return NULL;
return target;
}
双向循环链表的插入
Status LinkListInsert(LinkList *L, int i, ElemType e){
if(i < 1) return printError("插入位置必须从1开始", ERROR);
//如果链表为空,创建空链表
if((*L) == NULL) {
creatLinkList(L, 0);
}
LinkList target = findPriorNode(*L, i);
//没找到前驱
if(target == NULL) return printError("插入位置越界", ERROR);
LinkList temp = (LinkList)malloc(sizeof(struct Node));
if(temp == NULL) return printError("创建结点失败", ERROR);
temp->data = e;
//创建与next的关系,因为循环链表的next不会为NULL,因此不用判断是否在末尾
temp->next = target->next;
temp->next->prior = temp;
//创建与前驱的关系
target->next = temp;
temp->prior = target;
return OK;
}
双向循环链表删除
//双向循环链表删除结点
Status LinkListDelete(LinkList *L,int index,ElemType *e){
if(*L == NULL) return printError("链表为空", ERROR);
if(index < 1) return printError("删除必须从1开始", ERROR);
LinkList target = findPriorNode(*L, index);
//没找到前驱或者前驱为末尾结点
if(target->next == *L || target == NULL) return printError("删除位置越界", ERROR);
LinkList temp = target->next;
*e = temp->data;
target->next = temp->next;
temp->next->prior = target;
free(temp);
//如果只剩头结点,则释放头结点,指针置空
if((*L)->next == *L) {
free(*L);
*L = NULL;
}
return OK;
}
双向循环链表打印
考虑到是双向的,正向打印一次,逆向打印一次,确保next和prior的正确性,和双向链表不同的是要确保头结点的前一个结点为尾结点,尾结点的后一个结点头结点
。
Status traverseLinkList(LinkList L) {
if(L == NULL) return printError("链表为空", OK);
printf("正向\n");
//找到首元结点
LinkList temp = L->next;
//循环结束位置为尾结点。
while(temp->next != L) {
printf("%5d", temp->data);
temp = temp->next;
}
//打印尾结点
printf("%5d", temp->data);
printf("%*s尾结点的下一个结点为%d\n", 5, "", temp->next->data);
printf("逆向\n");
//逆序
while(temp != L) {
printf("%5d", temp->data);
temp = temp->prior;
}
printf("%*s首结点的前一个结点为%d\n", 5, "", temp->prior->data);
return OK;
}