数据结构与算法基础(王卓)(6):带尾指针(单向)循环链表的合并;双链表及其插入和删除的详细操作

目录

带尾指针(单向)循环链表的合并

双向循环链表

双向链表的插入

(1):关于插入算法(的)四个操作(的)前后语序问题

(2):缺少确定p指向后继节点真正的语句

(3):真正实现用辅助函数返回后继节点地址

(4):最后

双向链表的删除

删除链表第i位元素(已知第i位结点)

删除链表第i位元素(未给出结点位序)


位置:PPT第二章:156


带尾指针(单向)循环链表的合并

PPT(157):

操作前初始设定预设的条件​​​​

我们要做的,即:

让A表尾节点指向B表首结点,让B表尾节点指向A表首结点:

Project 1:

Status 合并链表(Lnode A,Lnode B)
{
    //前置条件声明:
    LinkList Ta, Tb;//两表的尾指针

    LinkList temp = Ta;//
    Ta->next = Tb->next;//&B;
    Tb->next = temp;
    return true;
}

问题:

临时变量temp应该指向的,是尾结点指向的地址,而不是尾结点本身的地址;

两表合并以后不应存在原B表的头结点(理论上说,应该删除)

另外:

Ta和Tb是两表本身的名称,只是两表都采用尾指针而已,不存在什么指向表B的说法

Project 2:

Status 合并链表(LinkList Ta, LinkList Tb)
{
    LinkList temp = Ta->next;
    //
    Ta->next = Tb->next->next;//指向b1
    delete Tb->next;
    Tb->next = temp;
    //
    return true;
}

前提:两表非空

当我们试图去简化程序,看看能不能不用temp,结果发现:(根本不可能实现)

尾指针Ta指向表Tb的首元结点时,Tb肯定不能被销毁,且Tb->next也不能变动

要不然就找不到表Tb的首元结点了

此后,想要执行让“尾指针Tb指向表Ta的头结点”的操作,就必须再搞出(设置)一个变量来储存Ta的头结点的地址,要不然根本就无法实现程序的基本功能

双向循环链表

简而言之,双向循环链表的前置定义如下:

struct DLnode
{
    Elemtype data;
    DLnode* next,* prior;
};
typedef DLnode* LinkList;

双向链表的插入

在链表的第i位插入新元素e:

Project 1:

前置条件:

#include<iostream>
using namespace std;
#include<stdlib.h>//存放exit  
#include<math.h>//OVERFLOW,exit

#define MAXlength 100  //初始大小为100,可按需修改

typedef int Status;         //函数调用状态

struct K
{
    float a;
    int b;
    string c;
    bool operator==(K& t)
    {
        return t.a == a && t.b == b;
        //&& t.c = c;
    }
    bool operator!=(K& t)
    {
        return t.a != a || t.b != b;
        //|| t.c = c;
    }
};
typedef K Elemtype;         //函数调用状态

struct DLnode
{
    Elemtype data;
    DLnode* next, * prior;
};
typedef DLnode* DLinkList;

DLinkList p;
//为了后面双链表插入函数里方便使用,我们把p写为全局函数
Status 取第i个元素地址(DLinkList L, int i)//, DLinkList e)
{
    
    p = L->next;
    int j = 1;
    while (p && i > j)
    {
        p = p->next;
        j++;
    }
    if (i < 0 || i < j || !p)
        return false;
    //e = p;
    return true;
}

算法函数实现: 

Status 双链表插入(DLinkList L, int i, Elemtype e)
{
    取第i个元素地址(L, i);
    auto s = new DLnode;
    //auto s = new DLinkList;
    s->data = e;//别忘了写
    s->prior = p->prior;
    s->next = p;
    p->prior->next = s;
    p->prior = s;
    return true;
}

问题:

(1):关于插入算法(的)四个操作(的)前后语序问题

在程序设计时,本来出于要保留(插入结点的)前驱结点的next指针和后继结点的prior指针

于是就先给新结点的prior和next指针赋值

但是这样就会导致在执行语句    p->prior->next = s;时,由于前面有对新结点next指针赋值的操作

此时p->prior有两个地址(新节点和前驱节点都指向后继节点)容易让程序产生错乱

所以,对于该(此)处的程序,语句执行限制如下:

    s->data = e;别忘了写
    s->prior = p->prior; 执行之前,p->prior(前驱节点的)地址必须保留不能改变(丢失)
    p->prior->next = s;执行之前,不宜对p->prior进行赋值

至于语句    s->next = p; 和    p->prior = s;,地址s和p本身在这几个语句里又不会发生改变,所以不必在意

所以,综上所述,题目对这四句语句的限制要求:

    s->prior = p->prior;   :前面不能有p->prior = s;
    p->prior->next = s;   :前面不能有p->prior = s;
    s->next = p;   :任意位置
    p->prior = s;   :不能在那两句的前面

若将上述语句分别标号为1,2,3,4的话,根据排列组合的知识,可以正确运行的程序,无非就是下列几种情况:

1234

2134

3124

3214

1324

2314

 然而,即使研究到了这一步,程序依然还是有问题

实际上第i个元素的地址没有传进程序,修改如下:

(2):缺少确定p指向后继节点真正的语句

Project 2:(1234)

DLinkList p,a;//改动1
Status 取第i个元素地址(DLinkList L, int i, DLinkList a)//改动2
{

    p = L->next;
    int j = 1;
    while (p && i > j)
    {
        p = p->next;
        j++;
    }
    if (i < 0 || i < j || !p)
        return false;
    a = p;//改动3
    return true;
}


Status 双链表插入(DLinkList L, int i, Elemtype e)
{
    取第i个元素地址(L, i, a);
    if (a != p)//改动5
        return false;

    auto s = new DLnode;

    s->data = e;//别忘了写

    s->prior = p->prior;
    p->prior->next = s;
    s->next = p;// 
    p->prior = s;
    return true;
}

确定p指向后继节点真正的语句:改动5

(3):真正实现用辅助函数返回后继节点地址

另外,如果在前面写函数“取第i个元素地址”时,想要完全保证函数的封装性,也可以把函数改为:

DLinkList 取第i个元素地址(DLinkList L, int i, DLinkList a)
{
    DLinkList p;
    p = L->next;
    int j = 1;
    while (p && i > j)
    {
        p = p->next;
        j++;
    }
    if (i < 0 || i < j || !p) 
        return 0;
    a = p;
}

此时,插入函数内引用函数语句为:

Status 双链表插入(DLinkList L, int i, Elemtype e)
{
    DLinkList p,a;
    取第i个元素地址(L, i, a);
    if ( a!= p)
        return false;

    auto s = new DLnode;
...

或者写得更简单简洁一点:

DLinkList 取第i个元素地址(DLinkList L, int i)
{
    DLinkList p;
    p = L->next;
    int j = 1;
    while (p && i > j)
    {
        p = p->next;
        j++;
    }
    if (i < 0 || i < j || !p)
        return 0;
    return p;
}

此时,插入函数内引用函数语句为:

Status 双链表插入(DLinkList L, int i, Elemtype e)
{
    DLinkList p;
    if ( 取第i个元素地址(L, i)!= p)
        return false;

    auto s = new DLnode;

    s->data = e;//别忘了写

    s->prior = p->prior;
    p->prior->next = s;
    s->next = p;
    p->prior = s;
    return true;
}

(4):最后

关于新建的节点s,我们可以像前面所示例的程序一样:

直接创建一个新节点s,节点名直接作为指向结点的指针(这样更加方便快捷)

也可以像上课说的那样:

创立一个新的节点,创建指针指向该节点:(这也就是我们先前在学C++时介绍的最常见的用法)

    DLnode *s = new DLnode;

双向链表的删除

删除链表第i位元素(已知第i位结点)

Status 双链表删除(DLinkList L, int i, Elemtype e)
{
    DLinkList p = 取第i个元素地址(L, i);
    if (p->data != e)
        return false;

    p->next->prior = p->prior;
    //后继节点的前驱设置为前驱结点地址
    p->prior->next = p->next;
    delete p;
    return true;
}

 前置条件同(3)第二个程序版本

这里,我们设计的程序认为:e用来表示结点信息本身(已知结点信息)

而PPT想表达的意思为:e用来返回结点信息:

Status 双链表删除(DLinkList L, int i, Elemtype &e)
{
    DLinkList p;
    if (p != 取第i个元素地址(L, i))
        return false;

    e = p->data;

    p->next->prior = p->prior;
    //后继节点的前驱设置为前驱结点地址
    p->prior->next = p->next;
    delete p;
    return true;
}

 时间复杂度:O(1)

删除链表第i位元素(未给出结点位序)

既然你没有给出我们结点位序,那你至少需要给出我们结点信息data

要不然你啥也不给,我们肯定是找不到这个结点在哪的

DLinkList LocateELem(DLinkList L, Elemtype e)
{
    //在线性表L中查找值为e的数据元素
    //找到,则返回L中值为e的数据元素的地址,查找失败返回NULL
    auto p = L->next; int i = 1;
    while (p && p->data != e)
    {
        i++;
        if (e == p->data)
        {
            //cout << "地址为:  " << p << ";" << endl;
            //cout << "位置序号为:  " << i << ";" << endl;
            return p;
        }
        p = p->next;
    }
    if (p == NULL)
        return NULL;
    //return 1;
}
Status 双链表删除(DLinkList L, Elemtype &e)
{
    DLinkList p;
    if (p != LocateELem(L, e))
        return false;

    e = p->data;

    p->next->prior = p->prior;
    //后继节点的前驱设置为前驱结点地址
    p->prior->next = p->next;
    delete p;
    return true;
}

 时间复杂度:O(n)

因为查找算法本身需要的时间复杂度为O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值