Bootstrap

【数据结构】双向链表

目录

1. 双向链表

1.1特性

1.2双向链表相关操作

1.3 双向循环链表


1. 双向链表

1.1特性

逻辑结构:线性结构

存储结构:链式存储

操作:增删改查

typedef int datatype;
typedef struct node
{
    datatype data;      //数据域
    struct node *next;  //指向后继节点指针
    struct node *prior; //指向前驱节点指针
} link_node_t, *link_node_p;

//将头尾指针封装到一个结构体里,思想上有点像链式队列
typedef struct doublelinklist
{
    link_node_p head; //链表头指针
    link_node_p tail; //链表尾指针
    int len;          //用来保存当前链表的长度
} doublelinklist_t, *doublelinklist_p;

1.2双向链表相关操作

创空:

插入:

删除:

删除指定数据

需要注意的是,与单链表不同,双链表创建过程中,每创建一个新节点都要与其前驱节点建立两次联系,分别是:

(1) 将新节点的 prior 指针指向直接前驱节点。

(2) 将直接前驱节点的 next 指针指向新节点。

#include <stdio.h>
#include <stdlib.h>

typedef int datatype;
typedef struct node
{
    datatype data;      //数据域
    struct node *next;  //指向后继节点指针
    struct node *prior; //指向前驱节点指针
} link_node_t, *link_node_p;

//将头尾指针封装到一个结构体里,思想上有点像链式队列
typedef struct doublelinklist
{
    link_node_p head; //链表头指针
    link_node_p tail; //链表尾指针
    int len;          //用来保存当前链表的长度
} doublelinklist_t, *doublelinklist_p;

//创建一个空双向链表
doublelinklist_p createEmptyDoubleLinkList()
{
    //1.申请空间存放头尾指针结构体
    doublelinklist_p p = (doublelinklist_p)malloc(sizeof(doublelinklist_t));
    if (NULL == p)
    {
        perror("p malloc err");
        return NULL;
    }
    //2. 初始化结构体空间
    p->len = 0;
    p->head = p->tail = (link_node_p)malloc(sizeof(link_node_t));
    if (NULL == p->head)
    {
        perror("p->head malloc err");
        return NULL;
    }
    //3. 初始化头节点
    p->head->next = NULL;
    p->head->prior = NULL;

    return p;
}

//向双向链表的指定位置插入数据 post位置, data数据
int insertIntoDoubleLinkList(doublelinklist_p p, int post, datatype data)
{
    int i;
    link_node_p temp = NULL;
    //1. 容错判断
    if (post < 0 || post > p->len)
    {
        printf("insertIntoDoubleLinkList err\n");
        return -1;
    }
    //2. 新建一个节点,用来保存数据
    link_node_p pnew = (link_node_p)malloc(sizeof(link_node_t));
    if (NULL == pnew)
    {
        perror("pnew malloc err");
        return -1;
    }
    pnew->data = data;
    pnew->next = NULL;
    pnew->prior = NULL;

    //3. 将新节点链接到链表中
    //分情况:尾插和中间插入
    if (post == p->len) //尾插
    {
        //将新节点链接到链表尾部
        pnew->prior = p->tail;
        p->tail->next = pnew;
        //移动尾指针
        p->tail = pnew;
    }
    else //中间插入(分前后半段)
    {
        //(1) 将temp移动到插入位置
        if (post < p->len / 2) //前半段
        {
            temp = p->head;
            for (i = 0; i <= post; i++)
                temp = temp->next;
        }
        else //后半段
        {
            temp = p->tail;
            for (i = p->len - 1; i > post; i--)
                temp = temp->prior;
        }
        //(2)将新节点插入到链表中(先连前面,再连后面)
        pnew->prior = temp->prior;
        temp->prior->next = pnew;
        pnew->next = temp;
        temp->prior = pnew;
    }
    //长度加一
    p->len++; //新多了节点,链表长+1
    return 0;
}

//遍历双向链表
void showDoubleLinkList(doublelinklist_p p)
{
    link_node_p temp = NULL;
    //正向遍历
    printf("正向遍历: ");
    temp = p->head;
    while (temp->next != NULL) //类似于遍历有头单向链表
    {
        temp = temp->next;
        printf("%d ", temp->data);
    }
    printf("\n");
    //反向遍历
    printf("反向遍历: ");
    temp = p->tail;
    while (temp != p->head) //类似于遍历无头单向链表
    {
        printf("%d ", temp->data);
        temp = temp->prior;
    }
    printf("\n");
}

//判断双向链表是否为空
int isEmptyDoubleLinkList(doublelinklist_p p)
{
    return p->len == 0;
}

//删除双向链表指定位置数据
int deletePostDoubleLinkList(doublelinklist_p p, int post)
{
    int i;
    //1. 容错判断
    if (isEmptyDoubleLinkList(p) || post < 0 || post >= p->len)
    {
        printf("deletePostDoubleLinkList err\n");
        return -1;
    }
    //2. 对删除位置进行分析,分两种情况
    if (post == p->len - 1) //删除尾巴节点
    {
        //(1)往前移动尾指针
        p->tail = p->tail->prior;
        //(2)释放要被删除节点,也就是此时尾指针所指节点后一个节点
        free(p->tail->next);
        //(3)此时尾巴节点next为空
        p->tail->next = NULL;
    }
    else //中间删除
    {
        //定义一个指针,移动到被删除节点
        link_node_p temp = NULL;
        if (post < p->len / 2) //前半段
        {
            temp = p->head;
            for (i = 0; i <= post; i++)
                temp = temp->next;
        }
        else //后半段
        {
            temp = p->tail;
            for (i = p->len - 1; i > post; i--)
                temp = temp->prior;
        }
        //进行删除操作,也就是跨过要被删除节点。
        temp->prior->next = temp->next;
        temp->next->prior = temp->prior;
        free(temp);
        temp = NULL;
    }
    //3.长度-1
    p->len--;
}

//修改指定位置的数据,post修改的位置 data被修改的数据
int changeDataDoubleLinkList(doublelinklist_p p, int post, datatype data)
{
    int i;
    link_node_p temp = NULL;
    //1. 容错判断
    if (isEmptyDoubleLinkList(p) || post < 0 || post >= p->len)
    {
        printf("deletePostDoubleLinkList err\n");
        return -1;
    }
    //2. 将指针temp移动到要修改节点
    if (post < p->len / 2) //前半段
    {
        temp = p->head;
        for (i = 0; i <= post; i++)
            temp = temp->next;
    }
    else //后半段
    {
        temp = p->tail;
        for (i = p->len - 1; i > post; i--)
            temp = temp->prior;
    }
    //3. 修改数据
    temp->data = data;
    return 0;
}

//查找指定数据出现的位置 data被查找的数据
int searchPostDoubleLinkList(doublelinklist_p p, datatype data)
{
    link_node_p temp = p->head;
    int post = 0;
    while (temp->next != NULL)
    {
        temp = temp->next;
        if (temp->data == data)
            return post;
        post++;
    }
    return -1;
}

//删除双向链表中的指定数据 data代表删除所有出现的data数据
//思想:从头节点后节点开始用指针h遍历,相当于遍历无头链表,遇到需要删除节点的就用h指向它然后删除,如果不需要删除则h继续往后走一个。这里因为是双向链表可以找到前驱,所以不需要每次指向被删除节点的前一个然后跨过了。
void deleteDataDoubleLinkList(doublelinklist_p p, datatype data)
{
    link_node_p pdel = NULL;       //用于删除节点
    link_node_p h = p->head->next; //用于相当于遍历无头链表
    //相当于遍历无头链表,遇见相等的就删除,否则向后遍历
    while (h != NULL)
    {
        if (h->data == data) //相等
        {
            if (h == p->tail) //尾节点
            {
                //删除尾巴节点
                p->tail = p->tail->prior;
                free(p->tail->next);
                p->tail->next = NULL;
            }
            else //删除中间节点
            {
                h->prior->next = h->next;
                h->next->prior = h->prior;
                pdel = h;
                h = h->next; //释放前要让h向后走一个以便继续遍历
                free(pdel);
            }

            p->len--;
        }
        else //不等
        {
            h = h->next; //往后继续遍历
        }
    }
}

int main(int argc, char const *argv[])
{
    doublelinklist_p p = createEmptyDoubleLinkList();
    insertIntoDoubleLinkList(p, 0, 1);
    insertIntoDoubleLinkList(p, 1, 2);
    insertIntoDoubleLinkList(p, 2, 3);
    insertIntoDoubleLinkList(p, 3, 4);

    showDoubleLinkList(p);

    deletePostDoubleLinkList(p, 3);
    showDoubleLinkList(p);
    changeDataDoubleLinkList(p, 2, 100);
    showDoubleLinkList(p);
    printf("post = %d\n", searchPostDoubleLinkList(p, 100));
    insertIntoDoubleLinkList(p, 3, 1);
    deleteDataDoubleLinkList(p, 1);
    showDoubleLinkList(p);
    return 0;
}

1.3 双向循环链表

doublecirclelink.c

#include <stdio.h>
#include <stdlib.h>

typedef int datatype;
typedef struct node_t
{
    datatype data;
    struct node_t * prior;
    struct node_t * next;
}link_node_t,*link_node_p;

typedef struct doublelinklist
{
    link_node_p head;
    link_node_p tail;
}double_node_t,*double_list_t;


int main(int argc, const char *argv[])
{
    int i;
    int all_num = 8;//猴子总数
    int start_num = 3;//从3号猴子开始数
    int kill_num = 3;//数到几杀死猴子 
    link_node_p h = NULL;
    link_node_p pdel = NULL;//用来指向被杀死猴子的节点
    printf("请您输入猴子的总数,开始号码,出局号码:\n");
    scanf("%d%d%d",&all_num,&start_num,&kill_num);
    //1.创建一个双向的循环链表
    double_list_t p = (double_list_t)malloc(sizeof(double_node_t));//申请头指针和尾指针
    if(NULL == p)
    {
        perror("malloc failed");
        return -1;
    }
    p->head = p->tail = (link_node_p)malloc(sizeof(link_node_t));
    if(NULL == p->tail)
    {
        perror("p->tail malloc failed");
        return -1;
    }
    p->head->data = 1;
    p->head->prior = NULL;
    p->head->next = NULL;
    //将创建n个新的节点,链接到链表的尾
    for(i = 2; i <= all_num; i++)
    {
        link_node_p pnew = (link_node_p)malloc(sizeof(link_node_t));
        if(NULL == pnew)
        {
            perror("pnew malloc failed");
            return -1;
        }
        pnew->data = i;
        pnew->prior = NULL;
        pnew->next = NULL;
        //(1)将新的节点链接到链表的尾
        p->tail->next = pnew;
        pnew->prior = p->tail;
        //(2)尾指针向后移动,指向当前链表的尾
        p->tail = pnew;
    }
    //(3)形成双向循环链表 
    p->tail->next = p->head;
    p->head->prior = p->tail;
    //调试程序 
    #if 0
    while(1)
    {
        printf("%d\n",p->head->data);
        p->head = p->head->next;
        sleep(1);
    }
    #endif
    //2.循环进行杀死猴子
    h = p->head;
    //(1)先将h移动到start_num处,也就是开始数数的猴子号码处
    for(i = 1; i < start_num; i++)
        h = h->next;
    printf("start is:%d\n",h->data);
    while(h->next != h)//当h->next == h 就剩一个节点了,循环结束
    {
        //(2)将h移动到即将杀死猴子号码的位置
        for(i = 1; i < kill_num; i++)
            h = h->next;
        //(3)进行杀死猴子,经过上面的循环后,此时的h指向即将杀死的猴子
        h->prior->next = h->next;
        h->next->prior = h->prior;
        pdel = h;//pdel指向被杀死猴子的位置
        printf("kill is -------%d\n",pdel->data);
        h = h->next;//需要移动,从杀死猴子后的下一个位置开始数
        free(pdel);
    }
    printf("猴王是%d\n",h->data);
    return 0;
}

单向链表与双向(循环)链表的区别:

存储空间方面:双向存储空间大,因为每个节点中有两个指针。

效率方面:双向链表效率高,因为可以从后往前遍历。

请完成如下双向链表的删除过程: (云尖软件开发笔试试题)

A.p-> next=p->prior->next;

p->prior=p->next->prior;

free(p);

B. P->prior = p->next;

p->nest = p->prior;

free(p);

C. p->prior->next = p->next;

p->next >prior = p->prior:

free(p);

D.p->prior->next = p->next->prior;

p->next->prior=p->prior->next;

free(p);

;