带尾指针(单向)循环链表的合并
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)


















