数据结构之路-双向链表

双向链表其实就是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。
理论再多 始终要回到代码上面 那下面我就以代码的示例来解说一个双向链表。

链表首先要创建一个结构体,这个结构体就是每个结点的数据,以及前后指针域。

typedef struct NAME {
    char   *name;
    struct NAME *pNext;
    struct NAME *pPre;
}T_Name,*PT_Name;

这里创建了一个结构体,名字叫NAME,typedef就是让以后的代码可以让T_Name代替struct NAME而PT_Name代替struct NAME * 名字取的也是很有讲究的 这里T表示结构体 P表示指针 PT那就表示指向结构体的指针 以后看到这个一目了然。
结构体里有三个成员
1.name:这里可以任意发挥 我这里创建了一个叫做name的指针 储存每个结点的名字 用术语来说就是数据域。
2.pNext 指向下一个结点的指针 如果是最后一个结点要将这个值赋值为NULL(空)。
3.pPre 指向前一个结点的指针 这样就可以从反方向的顺序找到上一个结点

添加结点

void add_one_name()
{
    PT_Name ptNew;
    char *str;
    char name[128];

    printf("enter the name:");
    scanf("%s", name);

    str  = malloc(strlen(name) + 1);
    strcpy(str, name);

    ptNew = malloc(sizeof(T_Name));
    ptNew->name = str;
    ptNew->pre  = NULL;
    ptNew->next = NULL;

    add_name(ptNew);
}

void add_name(PT_Name ptNew)
{
    PT_Name ptCur;
    /* 检查链表头是否为空 链表头为空 那直接令链表头指向结点 */
    if (g_ptNameHead == NULL)
    {
        g_ptNameHead = ptNew;
    }
    else
    {
    /* 链表头不为空 那说明还有结点 */
        ptCur = g_ptNameHead;
    /* 循环检查每个结点pNext 直到为空就说明已经到最后一个结点了 然后再在最后一个结点加入一个新的结点 */
        while (ptCur->pNext)
        {
            ptCur = ptCur->pNext;
        }
        /* 最后一个结点的的pNext指向新加入的结点  */
        ptCur->pNext = ptNew;
        /* 新加入的结点的pPre指向当前最后一个结点 */
        ptNew->pPre  = ptCur;
    }
}

添加结点很简单
设置结点的数据域也就是名字 然后调用add_name函数将这个结点添加到链表的末尾

删除一个结点

void del_one_name()
{   
    PT_Name ptFind;
    char name[128];
    /* 获得要删除结点的名字 */
    printf("enter the name:");
    scanf("%s", name);
    /* 依据这个名字取链表中查找同名的结点 然后返回这个结点 */
    ptFind = get_name(name);
    /* 返回的是一个空指针 说明没有同名的结点 */
    if (ptFind == NULL)
    {
        printf("do not have this name\n");
        return ;
    }
    /* 删除这个结点 */
    del_name(ptFind);

}

PT_Name get_name(char *name)
{
    PT_Name ptCur;
    /* 如果链表头为空 说明链表都没结点 那就没东西删除了 直接返回NULL */
    if (g_ptNameHead == NULL)
    {
        return NULL;
    }
    else
    {
        /* 循环每个结点 对比每个结点的名字 如果相同说明已经找到了 这里不处理链表中有很多同名结点的情况 */
        ptCur = g_ptNameHead;
        do {
            if (strcmp(ptCur->name, name) == 0)
                /* 找到了同名的 直接返回 */
                return ptCur;
            else
                ptCur = ptCur->pNext;
        }while (ptCur);
    }
    /* 能执行到这里 说明链表都已经循环了 都还没找到 那就返回NULL */
    return NULL;
}


void del_name(PT_Name ptDel)
{
    PT_Name ptCur;  
    PT_Name ptPre;  
    PT_Name ptNext; 

    /* 如果链表头就是要删除的 那直接链表头指向被删除的下一个结点 然后返回退出  */
    if (g_ptNameHead == ptDel)
    {
        g_ptNameHead = ptDel->next;
        /* 释放 */
        return;
    }
    else
    {
        /*  被删除的结点不是第一个结点 那就要循环找到 */
        ptCur = g_ptNameHead->next;
        while (ptCur)
        {
            if (ptCur == ptDel)
            {
                /* 找到了要删除的结点啦 */
                /* 用ptPre,ptNext保存要删除结点的pPre,pNext */
                ptPre  = ptCur->pPre;
                ptNext = ptCur->pNext;
                /* 上一个结点的指向下一个结点 */
                ptPre->pNext = ptNext;
                if (ptNext)
                {
                    /* 如果下一个结点存在就将下一个结点的pPre指向上一个结点 */
                    ptNext->pPre = ptPre;
                }
                break;
            }
            else
            {
                ptCur = ptCur->pNext;
            }
        }
    }

    free(ptDel->name);
    free(ptDel);
}

有了上面的两个功能 就可以从一个链表中 增加结点和删除指定的结点了
列出整个链表所有结点

void list_all_name(void)
{
    PT_Name ptCur;
    int i = 0;
    ptCur = g_ptNameHead;
    while (ptCur)
    {
        printf("%02d : %s\n", i++, ptCur->name);
        ptCur = ptCur->next;
    }
}

这个函数也还是很简单的 循环每个结点 然后打印每个结点的数据就可以了。

如何插入一个结点? 我们这里都是直接在末尾加入一个结点
熟悉了链表的操作 其实都是很简单的 但是就是考虑下编程思想 主要是解决下面两个问题
插入一个结点 无非就考虑几个点
1.在哪里插入
2.怎么插入
分析第一个问题
哪里插入 那就要找到前面或者后面的结点 这样我们才能找到要插入的位置
这里我们就设定加入在一个已知结点的后面 所以两个步骤
1.根据这个已知结点的名字 找到这个结点的位置
2.将要加入的结点加入到这个结点的后面

void add_one_name()
{
    PT_Name ptFind;
    char name[128];
    PT_Name ptNew;
    char name1[128];
    char *str;

    printf("enter the name you want:");
    scanf("%s", name);
    /* 确定你要加入在哪个结点的后面 */
    ptFind = get_name(name);
    if (ptFind == NULL)
    {
        printf("do not have this name\n");
        return ;
    }

    printf("Enter the name\n");
    scanf("%s",name);

    str  = (char *)malloc(strlen(name1) + 1);
    strcpy(str,name1);
    /* 设置这个即将要插入的结点的信息 */
    ptNew = (PT_Name)malloc(sizeof(T_Name));
    ptNew->name  = str;
    ptNew->pNext = NULL;
    ptNew->pPre  = NULL;

    /* 插入到链表中 */
    add_name(ptFind,ptNew);

}

PT_Name get_name(char *name)
{
    PT_Name ptCur;
    if (g_ptNameHead == NULL)
    {
        return NULL;
    }
    else
    {
        ptCur = g_ptNameHead;
        do {
            if (strcmp(ptCur->name, name) == 0)
                return ptCur;
            else
                ptCur = ptCur->pNext;
        }while (ptCur);
    }
    return NULL;
}

void add_name(PT_Name ptFind,PT_Name ptNew)
{
    PT_Name ptCur;  

    PT_Name ptNext; 
    /* 如果这个结点是链表中的最后一个结点的话 那直接加入到这个结点后面就可以了 */
    if (ptFind->pNext == NULL)
    {
        ptFind->pNext = ptNew;
        ptNew->pPre   = ptFind;
        /* 释放 */
        return;
    }
    else
    {
        ptCur = g_ptNameHead->next;
        while (ptCur)
        {
            if (ptCur == ptFind)
            {
                /* 从链表中删除 */
                ptNext = ptCur->next;;
                ptNew->pNext = ptNext;
                ptNew->ppre  = ptCur
                ptCur->pNext  = ptNew;
                ptNext->ptPre = ptNew;


            }
            else
            {
                ptCur = ptCur->next;
            }
        }
    }
    }
}

双向链表的所有基本操作就这样了 无非就是指针的运用而已

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值