数据结构与算法-双向链表-Day3

377 阅读6分钟

双向链表

在单链表中,我们可以通过next指针方便找到当前结点的后继结点,但是在寻找前驱结点的时候就颇为费力,只能通过遍历的方式来进行查找。由此出现了一种新的链表双向链表

相比于单向链表,双向链表增了一个指向前驱prior指针,通过prior可以方便的找到当前结点的前驱结点

双向,指的是各节点之间的逻辑关系是双向的,但通常头指针只设置一个。

双向链表的结点结构如下:

//定义结点
typedef struct Node{
    ElemType data;
    struct Node *prior;     //前驱指针
    struct Node *next;      //后继指针
}Node;

typedef struct Node* LinkList;

双向链表的初始化

  1. 创建头结点

  1. 创建新结点并建立关系

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;
}

双向链表打印

考虑到是双向的,正向打印一次,逆向打印一次,确保nextprior的正确性。

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指针会指向尾结点

双向循环链表的初始化

  1. 创建头结点

  1. 插入新结点

双向循环链表初始化
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;
}

双向循环链表打印

考虑到是双向的,正向打印一次,逆向打印一次,确保nextprior的正确性,和双向链表不同的是要确保头结点的前一个结点为尾结点,尾结点的后一个结点头结点 。

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;
}