2

上一边文章我们介绍了单链表及循环链表,现在我们了解一下双向链表及双向循环链表。

1. 双向链表

单链表中,每个结点有一个指向其直接后继结点的指针,整个单链表只能沿着一个方向操作,那么双向链表,顾名思义,在它的结点中,不仅包含直接后继结点的地址指针,还包含它的直接前驱结点地址指针。
结点定义:

#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1

typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */

//定义结点
typedef struct Node{
    ElemType data;
    struct Node *prior;
    struct Node *next;
}Node;

typedef struct Node * LinkList;

结点结构如下:
双向链表结点示意图.png
了解了结点结构后,我们看一下非空链表的结构图:
带头结点:
带头结点双线链表示意图.png
不带头结点:
不带头结点双线链表示意图.png

1.1 双向链表创建

双向链表的创建,我们默认创建带头结点的链表。
思路:

  1. 开辟空间,创建头结点,并且判断是否创建成功。
  2. 将头结点前驱指针设置为NULL,后继指针设置为NULL。
  3. 通过循环依次创建结点,并追加到链表末尾。
  4. 返回成功。

代码实现如下:

//创建双向链表
Status createLinkList(LinkList *L){
    //*L 指向头结点
    *L = (LinkList)malloc(sizeof(Node));
    if (*L == NULL) return ERROR;
    (*L)->prior = NULL;
    (*L)->next = NULL;
    (*L)->data = -1;
    
    //新增数据
    LinkList p = *L;
    for(int i=0; i < 10;i++){
        //1.创建1个临时的结点,并设置初始值。
        LinkList temp = (LinkList)malloc(sizeof(Node));
        temp->prior = NULL;
        temp->next = NULL;
        temp->data = i;
        
        //2.为新增的结点建立双向链表关系
        //temp 是p的后继
        p->next = temp;
        //temp 的前驱是p
        temp->prior = p;
        //p要记录最后的结点的位置,方便下一次插入
        p = p->next;
    }
    return OK;
}

1.2 双向链表插入结点

思路:

  1. 创建新结点temp,并进行初始化。
  2. 遍历查找插入位置结点的前驱结点位置。
  3. 如果插入的位置大于链表的总长度,返回ERROR。
  4. 判断是否插入在尾部,如果是,只需修改尾部结点与插入结点的关系。
  5. 如果不是插在尾部,则需修改插入结点与其前驱结点和后继结点的关系。
  6. 返回OK。

双向链表插入.png
代码实现如下:

//双向链表插入结点
Status ListInsert(LinkList *L, int i, ElemType data){
    //1. 插入的位置不合法 为0或者为负数
    if(i < 1) return ERROR;
    
    //2. 新建结点
    LinkList temp = (LinkList)malloc(sizeof(Node));
    temp->data = data;
    temp->prior = NULL;
    temp->next = NULL;
    
    //3.将p指向头结点!
    LinkList p = *L;
    
    //4. 找到插入位置i直接的结点
    for(int j = 1; j < i && p;j++) 
        p = p->next;
    
    //5. 如果插入的位置超过链表本身的长度
    if(p == NULL){
        return  ERROR;
    }
    
    //6. 判断插入位置是否为链表尾部;
    if (p->next == NULL) {
        p->next = temp;
        temp->prior = p;
    }else
    {
        //1 将p->next 结点的前驱prior = temp
        p->next->prior = temp;
        //2 将temp->next 指向原来的p->next
        temp->next = p->next;
        //3 p->next 更新成新创建的temp
        p->next = temp;
        //4 新创建的temp前驱 = p
        temp->prior = p;
    }
    return  OK;
}

1.3 双向链表删除指定结点

思路:

  1. 遍历查找删除位置结点的前驱结点位置,创建结点指针p指向该前驱结点。。
  2. 如果插入的位置小于1或者大于链表的总长度,返回ERROR。
  3. 创建结点指针delTemp指向待删除结点,修改结点p的next指向delTemp的next。
  4. 如果待删除结点delTemp有后继结点,那么修改其后继结点的prior指向p。
  5. 释放delTemp结点。
  6. 返回OK。

双向链表删除结点.png
代码实现如下:

//删除双向链表指定位置上的结点
Status ListDelete(LinkList *L, int i, ElemType *e){
    int k = 1;
    LinkList p = (*L);
    //1.判断双向链表是否为空,如果为空则返回ERROR;
    if (*L == NULL) {
        return ERROR;
    }
    //2. 将指针p移动到删除元素位置前一个结点。
    while (k < i && p != NULL) {
        p = p->next;
        k++;
    }
    //3.处理前后边界条件,如果k>i 或者 p == NULL 则返回ERROR
    if (k>i || p == NULL) {
        return  ERROR;
    }
    //4.创建临时指针delTemp指向要删除的结点,并将要删除的结点的data赋值给*e,带回到main函数
    LinkList delTemp = p->next;
    *e = delTemp->data;
    //5. p->next等于要删除的结点的下一个结点
    p->next = delTemp->next;
    //6. 如果删除结点的下一个结点不为空,则将将要删除的下一个结点的前驱指针赋值p;
    if (delTemp->next != NULL) {
        delTemp->next->prior = p;
    }
    //7.删除delTemp结点
    free(delTemp);
    return OK;
}

1.4 双向链表删除指定元素

思路:

  1. 创建结点指针p,并指向头结点。
  2. 遍历双向链表,查找与指定值相等的结点p,即待删除结点p。
  3. 若待删除结点p存在,修改结点p的前驱结点的后继指针,若p有后继结点,修改结点p的后继结点的前驱指针。
  4. 返回OK。

代码实现如下:

//删除双向链表指定的元素
Status LinkListDeletVAL(LinkList *L, int data){
    //1.判断双向链表是否为空,如果为空则返回ERROR;
    if (*L == NULL) {
        return ERROR;
    }
    LinkList p = *L;
    //2.遍历双向链表
    while (p) {
        //2.判断当前结点的数据域和data是否相等,若相等则删除该结点
        if (p->data == data) {
            
            //修改被删除结点的前驱结点的后继指针
            p->prior->next = p->next;
            //修改被删除结点的后继结点的前驱指针
            if(p->next != NULL){
                p->next->prior = p->prior;
            }
            //释放被删除结点p
            free(p);
            //退出循环
            break;
        }
        //没有找到该结点,则继续移动指针p
        p = p->next;
    }
    return OK;
}

1.5 双向链表查找元素位置

思路:

  1. 创建结点指针p,并指向首元结点,创建索引i,并设置为1.
  2. 遍历双向链表,查找与指定指相当的结点p。
  3. 如果找到,则返回索引i。
  4. 如果未找到,返回-1。

代码实现如下:

//在双向链表中查找元素位置
int selectElem(LinkList L,ElemType elem){
    LinkList p = L->next;
    int i = 1;
    while (p) {
        if (p->data == elem) {
            return i;
        }
        i++;
        p = p->next;
    }
    return  -1;
}

1.6 双向链表更新结点数据

思路:

  1. 创建结点指针p,并指向首元结点。
  2. 遍历双向链表,查找待更新结点p。
  3. 重新赋值p->next。
  4. 返回OK。

代码实现如下:

Status replaceLinkList(LinkList *L,int index,ElemType newElem){
    if (index < 1) {
        return ERROR;
    }
    LinkList p = (*L)->next;
    for (int i = 1; i < index && p->next != NULL; i++) {
        p = p->next;
    }
    p->data = newElem;
    return OK;
}

2. 双向循环链表

上面了解了双向链表,那么双向循环链表,顾名思义,就是就是将双向链表尾部结点和头结点连接起来,如下图:
空双向循环链表:
空双向循环链表示意图.png
非空双向循环链表:
非空双向循环链表示意图.png

2.1 双向循环链表创建

思路:

  1. 开辟空间,创建头结点,并且判断是否创建成功。
  2. 将头结点前驱指针设置为头结点,后继指针设置为头结点,形成一个闭环。
  3. 通过循环依次创建结点,并追加到链表末尾。
  4. 返回成功。

代码实现如下:

// 双向循环链表初始化
Status creatLinkList(LinkList *L){
    *L = (LinkList)malloc(sizeof(Node));
    if (*L == NULL) {
        return ERROR;
    }
    (*L)->next = (*L);
    (*L)->prior = (*L);
    //新增数据
    LinkList p = *L;
    for(int i=0; i < 10;i++){
        //1.创建1个临时的结点
        LinkList temp = (LinkList)malloc(sizeof(Node));
        temp->data = i;
        //2.为新增的结点建立双向链表关系
        //temp是p的后继
        p->next = temp;
        //temp的前驱是p
        temp->prior = p;
        //temp的后继是*L
        temp->next = (*L);
        //(*L)的前驱是新建的temp
        (*L)->prior = temp;
        //p要记录最后的结点的位置,方便下一次插入
        p = p->next;
    }
    return OK;
}

2.2 双向循环链表插入结点

思路:

  1. 创建结点指针p,并指向头结点。
  2. 遍历查找到插入位置前一个结点p。
  3. 创建新增节点temp,并赋值。
  4. 修改temp结点的前后关系。
    ①. 将结点temp的前驱结点设置为p。
    ②. 将结点temp的后继结点设置为p->next。
    ③. 将结点p的后继结点设置为temp。
    ④. 将结点temp的后继结点的前驱结点设置为temp。
  5. 返回OK。

双向循环链表插入结点.png

代码实现如下:

//双向循环链表插入元素
/*当插入位置超过链表长度则插入到链表末尾*/
Status LinkListInsert(LinkList *L, int index, ElemType e){
    //1.如果i>index 则返回error
    if (index < 1)  return ERROR;
    //2. 创建指针p,指向双向链表头
    LinkList p = (*L);
    int i = 1;
    //3.双向循环链表为空,则返回error
    if(*L == NULL) return ERROR;
    //4.找到插入前一个位置上的结点p
    while (i < index && p->next != *L) {
        p = p->next;
        i++;
    }
    //5.创建新结点temp
    LinkList temp = (LinkList)malloc(sizeof(Node));
    //6.temp 结点为空,则返回error
    if (temp == NULL) return ERROR;
    //7.将生成的新结点temp数据域赋值e.
    temp->data = e;
    //8.将结点temp 的前驱结点为p;
    temp->prior = p;
    //9.temp的后继结点指向p->next;
    temp->next = p->next;
    //10.p的后继结点为新结点temp;
    p->next = temp;
    //11.temp节点的下一个结点的前驱为temp结点
    temp->next->prior = temp;
    return OK;
}

2.3 双向循环链表删除结点

思路:

  1. 判断链表是否存在,不存在则return。
  2. 如果删除到只剩下首元结点了,则直接将*L置空。
  3. 创建结点指针,并指向首元结点,遍历查找到删除的结点temp,并将删除结点的值赋值给*e。
  4. 修改链表连接关系。
    ①. 修改被删除结点temp的前驱结点的后继指针。
    ②. 修改被删除结点temp的后继结点的前驱指针。
  5. 释放删除的结点temp。
  6. 返回OK。

双向循环链表删除结点.png

代码实现如下:

// 双向循环链表删除结点
Status LinkListDelete(LinkList *L,int index,ElemType *e){
    int i = 1;
    LinkList temp = (*L)->next;
    if (*L == NULL) {
        return  ERROR;
    }
    //如果删除到只剩下首元结点了,则直接将*L置空;
    if(temp->next == *L){
        free(*L);
        (*L) = NULL;
        return OK;
    }
    //1.找到要删除的结点
    while (i < index) {
        temp = temp->next;
        i++;
    }
    //2.给e赋值要删除结点的数据域
    *e = temp->data;
    //3.修改被删除结点的前驱结点的后继指针
    temp->prior->next = temp->next;
    //4.修改被删除结点的后继结点的前驱指针
    temp->next->prior = temp->prior;
    //5. 删除结点temp
    free(temp);
    return OK;
}

2.4 双向循环链表遍历

思路:

  1. 判断链表是否为空,若为空,直接return。
  2. 创建结点指针temp,并指向首元结点。
  3. 循环遍历temp,若temp不为头结点,输出temp结点值,若temp为头结点,链表已经遍历到结尾,退出循环。

代码实现如下:

//遍历双向循环链表
Status Display(LinkList L){
    if (L == NULL) {
        printf("打印的双向循环链表为空!\n\n");
        return ERROR;
    }
    printf("双向循环链表内容:  ");
    LinkList p = L->next;
    while (p != L) {
        printf("%d  ",p->data);
        p = p->next;
    }
    printf("\n\n");
    return OK;
}

Daniel_Coder
7 声望1 粉丝