目录
位置: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)