1 / 378

# 高级数据结构 - PowerPoint PPT Presentation

I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.

## PowerPoint Slideshow about ' 高级数据结构' - kathy

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.

- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript

### 高级数据结构

JYP

class Bag {

public:

Bag ( int MaxSize = DefaultSize ); // 假设DefaultSize已定义

int Add (const int x ); // 将整数x加入容器中

int Delete (const int k ); // 从容器中删除并打印k 个整数

private:

int top; // 指示已用空间

int *b; // 用数组b存放整数

int n; // 容量

};

JYP

Bag::Bag ( int MaxSize = DefaultSize ):n(MaxSize) {

b = new int[n];

top = -1;

}

int Bag::Add (const int x) {

if (top = = n-1) return 0; // 返回0表示加入失败

else {

b[++top] = x;

return 1;

}

}

JYP

int Bag::Delete (const int k) {

if (top + 1 < k ) return 0; //容器内元素不足k个，删除失败

else {

for (int i = 0; i < k; i++) cout << b[top – i] << “ ” ;

top = top - k;

return 1;

}

}

JYP

JYP

JYP

ai = ti + (Si) – (Si-1)

JYP

JYP

JYP

JYP

JYP

lcs(xa, ya) = lcs(x, y)a，即xa和ya的最长公共子序列由x和y的最长公共子序列后接a构成。

JYP

int String::lcs ( String y ) { // 驱动器

int n = Length( ), m = y.Length( );

return lcs( n, m, y.str );

}

int String::lcs (int i, int j, char *y ) { // 递归核心

if ( i == 0 | | j == 0) return 0;

if ( str[i-1] ==y[j-1] ) return ( lcs( i-1, j-1, y) + 1);

return max( lcs( i-1, j, y), lcs( i, j-1, y));

}

JYP

w(n, m) =

c （c为常数） n = 0或m = 0

w(n, m-1) + w(n-1, m) 否则

JYP

JYP

int String::Lcs ( String y ) {

int n = Length( ), m = y.Length( );

int lcs[MaxN][MaxM]; // MaxN和MaxM 是已定义的常数

int i, j;

for ( i = 0; i <= n; i++) lcs[i][0] = 0; // 初始值

for ( j = 0; j <= m; j++) lcs[0][j] = 0; // 初始值

for ( i = 1; i <= n; i++)

for ( j = 1; j <= m; j++)

if ( str[i-1] ==y.str[j-1] ) lcs[i][j] = lcs[i-1][j-1] + 1;

else lcs[i][j] = max(lcs[i-1][j], lcs[i][j-1]);

return lcs[n][m];

}

JYP

JYP

• 用软件模仿另一个系统的行为。
• 将研究对象表示为数据结构，对象动作表示为对数据的操作，控制动作的规则转换为算法。
• 通过更改数据的值或改变算法设置，可以观察到计算机模拟的变化，从而使用户能够推导出关于实际系统行为的有用结论。
• 在计算机处理一个对象的动作期间，其它对象和动作需等待。
• 队列在计算机模拟中具有重要应用。

JYP

• 只有一个跑道。
• 在每个时间单元，可起飞或降落一架飞机，但不可同时起降。
• 飞机准备降落或起飞的时间是随机的，在任一时间单元，跑道可能处于空闲、降落或起飞状态，并且可能有一些飞机在等待降落或起飞。
• 飞机在地上等待的代价比在空中等待的小，只有在没有飞机等待降落的情况下才允许飞机起飞。
• 当出现队列满的情况时，则拒绝为新到达的飞机服务。

JYP

struct plane {

int id; // 编号

int tm; // 到达队列时间

};

enum action { ARRIVE, DEPART };

JYP

• 时间单元:1 — endtime，并产生关于机场行为的重要统计信息，如处理的飞机数量，平均等待时间，被拒绝服务飞机的数量等。
• 采用基于泊松分布的随机整数决定在每个时间单元有多少架新飞机需要降落或起飞。
• 假设在10个时间单元中到达的飞机数分别是：2，0，0，1，4，1，0，0，0，1，那么每个时间单元的平均到达数是0.9。

JYP

• 在模拟中还需要建立新到达飞机的数据，处理被拒绝服务的飞机，起飞、降落飞机，处理机场空闲和总结模拟结果。
• 下面是机场模拟类定义：

JYP

class AirportSimulation {

// 机场模拟。一个时间单元 = 起飞或降落的时间

public:

AirportSimulation( ); // 构造函数

void RunSimulation( ); // 模拟运行

private:

Queue<plane> landing(6); // 等待降落飞机队列，假设用环

// 型队列，实际长度为5

Queue<plane> takeoff(6); // 等待起飞飞机队列，同上

double expectarrive; //一个时间单元内期望到达降落飞机数

double expectdepart; //一个时间单元内期望到达起飞飞机数

int curtime; // 当前时间

int endtime; // 模拟时间单元数

int idletime ; // 跑道空闲时间单元数

int landwait ; // 降落飞机的总等待时间

JYP

int nland ; // 降落的飞机数

int nplanes; // 处理的飞机数

int nrefuse; // 拒绝服务的飞机数

int ntakeoff; // 起飞的飞机数

void Randomize( ); // 设置随机数种子

int PoissionRandom(double& expectvalue);

// 根据泊松分布和给定期望值生成随机非负整数

plane* NewPlane(plane& p, action kind);

// 建立新飞机的数据项

void Refuse(plane& p, action kind); // 拒绝服务

void Land(plane& p); // 降落飞机

void Fly(plane& p); // 起飞飞机

void Idle( ); // 处理空闲时间单元

void Conclude( ); // 总结模拟结果

};

JYP

AirportSimulation::AirportSimulation( ) { // 构造函数

Boolean ok;

cout << “请输入模拟时间单元数：”; cin >> endtime;

idletime = landwait = nland = nplanes = 0;

nrefuse = ntakeoff = takoffwait = 0; // 初值

Randomize( ); // 设置随机数种子

do {

cout << “请输入一个时间单元内期望到达降落飞机数：”;

cin >> expectarrive;

cout << “请输入一个时间单元内期望到达起飞飞机数：”;

cin >> expectdepart;

JYP

if (expectarrive < 0.0 || expectdepart < 0.0) {

cout << “这些数不能为负！请重新输入。”<< endl;

ok = FALSE;

} else if (expectarrive + expectdepart > 1.0) {

cout << “机场将饱和！请重新输入。”<< endl;

ok = FALSE;

} else ok = TRUE;

} while (ok == FALSE);

}

JYP

RunSimulation( )是模拟运行的主控程序：

void AirportSimulation::RunSimulation( ) {

int pri; // 伪随机整数

plane p;

for (curtime = 1; curtime <= endtime; curtime++) {

cout << “时间单元” << curtime << “：”;

pri = PoissionRandom(expectarrive);

for (int i =1; i <= pri; i++) { //处理新到达准备降落的飞机

p = *NewPlane(p, ARRIVE);

if (landing.IsFull( )) Refuse(p, ARRIVE);

}

pri = PoissionRandom(expectdepart);

JYP

for (int i =1; i <= pri; i++) { //处理新到达准备起飞的飞机

p = *NewPlane(p, DEPART);

if (takeoff.IsFull( )) Refuse(p, DEPART);

}

if (!landing.IsEmpty( )) { // 降落飞机

p = *landing.Delete(p);

Land(p);

} else if (!takeoff.IsEmpty( )) { // 起飞飞机

p = *takeoff.Delete(p);

Fly(p);

} else Idle( ); // 处理空闲时间单元

}

Conclude( ); // 总结模拟结果

}

JYP

void AirportSimulation::Randomize( ) {

srand((unsigned int) (time(NULL)%10000));

}

rand按照均匀分布生成随机数，还需要转化为适合机场模拟的泊松分布随机数。下面直接给出根据泊松分布和给定期望值生成伪随机整数的算法（其数学推导略） ：

JYP

int AirportSimulation::PoissionRandom(double& expectvalue) {

int n = 0; // 循环计数

double limit; // e-v, 其中，v是期望值

double x; // 伪随机数

limit = exp(-expectvalue);

x = rand( ) / (double) INT_MAX;

// rand( )生成0到INT_MAX之间的整数, x在0和1之间

while (x > limit) {

n++;

x *= rand( ) / (double) INT_MAX;

}

return n;

}

JYP

plane* AirportSimulation::NewPlane(plane& p, action kind) {

nplanes++; // 飞机总数加1

p.id = nplanes; p.tm = curtime;

switch (kind) {

case ARRIVE:

cout << “飞机” << nplanes << “准备降落。” << endl;

break;

case DEPART:

cout << “飞机” << nplanes << “准备起飞。” << endl;

break;

}

return &p;

}

JYP

void AirportSimulation::Refuse(plane& p, action kind) {

switch (kind) {

case ARRIVE:

cout << “引导飞机” << p.id << “到其它机场降落。”

<< endl;

break;

case DEPART:

cout << “告诉飞机” << p.id << “等一会儿再试。”

<< endl;

break;

}

nrefuse++; // 被拒绝飞机总数加1

}

JYP

void AirportSimulation::Land(plane& p) {

int wait;

wait = curtime – p.tm;

cout << “飞机” << p.id << “降落，该机等待时间：”

<< wait << “。”<< endl;

nland++; // 降落飞机总数加1

landwait += wait; // 累加总降落等待时间

}

JYP

void AirportSimulation::Fly(plane& p) {

int wait = curtime – p.tm;

cout << “飞机” << p.id << “起飞，该机等待时间：”

<< wait << “。”<< endl;

ntakeoff++; // 起飞飞机总数加1

takeoffwait += wait; // 累加总起飞等待时间

}

JYP

void AirportSimulation::Idle( ) {

cout << “跑道空闲。” << endl;

idletime++; // 跑道空闲时间加1

}

void AirportSimulation::Conclude( ) {

cout << “总模拟时间单元数：” << endtime << endl;

cout << “总共处理的飞机数：” << nplanes << endl;

cout << “降落飞机总数：” << nland << endl;

cout << “起飞飞机总数：” << ntakeoff << endl;

JYP

cout << “队列中剩余的准备降落飞机数：”

<< landing.Size( ) << endl;

// 假设队列成员函数Size( )返回队列中元素个数

cout << “队列中剩余的准备起飞飞机数：”

<< takeoff.Size( ) << endl;

if (endtime > 0) cout << “跑道空闲时间百分比：”

<< ((double) idletime / endtime) * 100.0 << endl;

if (nland > 0) cout << “降落平均等待时间：”

<< (double) landwait / nland << endl;

if (ntakeoff > 0) cout << “起飞平均等待时间：”

<< (double) takeoffwait / ntakeoff << endl;

}

JYP

#include “common.h”

#include “simdefs.h” // 存放模拟类定义及相关函数实现

void main( ) {

AirportSimulation s;

s.RunSimulation( );

}

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

(4.3)

(4.4)

JYP

x B2(x) – B(x) + 1 = 0

JYP

JYP

JYP

5.2.1 双端优先队列与最小最大堆

（1） 插入一个具有任意key值的元素

（2） 删除key值最大的元素

（3） 删除key值最小的元素

JYP

template <class Type>

class DEPQ {

public:

virtual void Insert(const Element<Type>&) = 0;

virtual Element<Type>* DeleteMax(Element<Type>&) = 0;

virtual Element<Type>* DeleteMin(Element<Type>&) = 0;

};

JYP

JYP

JYP

template <class Type>

class MinMaxHeap: public DEPQ <Type> {

public:

MinMaxHeap (const int); // 构造函数

~MinMaxHeap ( ); // 析构函数

void Insert (const Element<Type>&);

Element<Type>* DeleteMax(Element<Type>& x );

Element<Type>* DeleteMin(Element<Type>& x );

private:

Element<KeyType> *h;

int n; // 最小最大堆的当前元素个数

int MaxSize; // 堆中可容纳元素的最大个数

JYP

// 其它用于实现最小最大堆的私有数据成员

};

template <class Type> // 构造函数定义

MinMaxHeap<Type>::MinMaxHeap (const int sz = DefaultHeapSize) : MaxSize(sz), n(0) {

h = new Element<Type>[MaxSize+1]; // h[0] 不用

}

JYP

5.2.2 插入操作

JYP

JYP

JYP

JYP

template <class Type>

void MinMaxHeap<Type>::Insert(const Element<Type>&x ) {

if (n == MaxSize ) { MinMaxFull( ); return;}

n++;

int p = n/2; // p是新结点的双亲

if (!p) {h[1] = x; return;} // 插入初始时为空的堆

switch (level(p)) {

case MIN:

if (x.key < h[p].key) { // 沿着最小层检查

h[n]=h[p];

VerifyMin(p, x);

}

JYP

else VerifyMax(n, x); // 沿着最大层检查

break;

case MAX:

if (x.key > h[p].key) { // 沿着最大层检查

h[n]=h[p];

VerifyMax(p, x);

}

else VerifyMin(n, x); // 沿着最小层检查

} // switch结束

} // Insert结束

JYP

JYP

template <class Type>

void MinMaxHeap<Type>::VerifyMax (int i, const

Element<KeyType>&x ) { // 沿着从最大结点i

// 到根结点的路径检查最大结点，将x插入正确位置

for (int gp = i/4; gp && (x.key > h[gp].key); gp /=4) {

// gp是 i的祖父

h[i] = h[gp]; // 将h[gp]移到h[i]

i = gp;

}

h[i] = x; // 将x插入结点i

}

JYP

JYP

5.2.3 删除最小元素操作

JYP

（1） 根结点无子女，这时可将x插入根结点中。

（2） 根结点至少有一个子女。这时堆中的最小元素（不算元素x）位于根结点的子女结点或孙子女结点（最多6个）之一。假设该结点的编号为k，则还需要考虑下列可能性：

(a) x.key≤h[k].key。由于h中不存在比x更小的元素，所以x可插入根。

JYP

(b) x.key > h[k].key且k是根的子女。由于k是最大结点，其后代的key值都不大于h[k].key，因而也不大于x.key。所以可将元素h[k]移到根，并将元素x插入结点k。

(c) x.key > h[k].key且k是根的孙子女。这时也可将元素h[k]移到根。设p是k的双亲。若x.key > h[p].key，则将x 和h[p]交换。这时，问题转化为将x插入以k为根的子堆中，且h[k]已腾空。这与初始插入根结点为空的最小最大堆h的情形类似。

JYP

JYP

template <class Type>

Element<Type>* MinMaxHeap<Type>::

DeleteMin(Element<Type>&y) {

// 从最小最大堆中删除并返回最小元素

if (!n) { MinMaxEmpty( );return 0; }

y = h[1]; // 保存根元素

Element<Type> x = h[n--];

int i = 1, j = n/2; // 为重新插入x作初始化

// 寻找插入x的位置

while (i <= j) { // i 有子女，情况(2)

int k = MinChildGrandChild(i);

if (x.key <= h[k].key)

break; // 情况 2(a)，可将x 插入h[i]

JYP

else { // 情况2(b) 或 (c)

h[i] = h[k];

if (k <= 2*i+1) { // k 是i的子女，情况2(b)

i = k; // 可将x插入h[k]

break;

}

else { // k是i的孙子女，情况2(c)

int p = k/2; // p是k的双亲

if (x.key > h[p].key) {

Element<Type> t = h[p]; h[p] = x; x = t;

}

} // if (k<=2*i+1)结束

i = k;

} // if (x.key<=h[k].key)结束

JYP

} // while结束

h[i] = x; // 注意，即使现在n == 0，对h[1] 赋值也没

// 错，这样简化边界判断

return &y;

}

JYP

5.3.1 双堆定义

（1） 根结点不含元素。

（2） 左子树是最小堆。

（3） 右子树是最大堆。

JYP

（4） 若右子树不空，设i为左子树中的任意结点，j为右子树中的对应结点。若这样的j不存在，则令j为右子树中对应i的双亲的结点。结点i中的key小于或等于结点j中的key。

JYP

JYP

j = i + 2log2i -1;

if (j > n + 1) j /= 2;

JYP

template <class Type>

class Deap: public DEPQ <Type> {

public:

Deap (const int);

~Deap ( );

void Insert (const Element<Type>&);

Element<Type>* DeleteMax(Element<Type>& );

Element<Type>* DeleteMin(Element<Type>& );

private:

Element<Type> *d;

int n; // 当前元素个数

int MaxSize; // 可容纳的最大元素个数

// 其它私有数据成员

JYP

};

template <class Type>

Deap <Type> ::Deap (const int sz =

DefaultHeapSize):MaxSize(sz), n(0) {

d = new Element<Type>[MaxSize+2]; // d[0] 和d[1]不用

}

JYP

5.3.2 插入操作

JYP

JYP

JYP

（1） Deap::DeapFull( )。

（2） Deap::MaxHeap(int p)。判断p是否最大堆结点。对于p > 1，当2log2 p + 2log2 p -1 ≤ p < 2log2 p时，p是最大堆的结点。

（3） Deap::MinPartner(int p)。p是双堆的最后一个结点且为最大堆结点，计算与p对应的最小堆结点，这可由p – 2log2p -1确定。

JYP

（4） Deap::MaxPartner(int p)。p是双堆的最后一个结点且为最小堆结点，计算与p对应的最大堆结点，这可由(p + 2log2p-1 ) / 2确定。

（5） 函数Deap::MinInsert和Deap::MaxInsert分别将一个元素插入最小堆和最大堆的指定位置。与最小堆或最大堆的插入操作的区别仅仅在于此处最小堆的根是结点2，最大堆的根是结点3。

JYP

template <class Type>

void Deap<Type>::Insert (const Element<Type>&x ) {

// 将元素x插入双堆

int i;

if (n == MaxSize ) { DeapFull( ); return;}

n++;

if (n == 1) { d[2] = x; return;} // 插入空双堆

int p = n + 1; // p是双堆中新的最后位置

switch (MaxHeap(p)) {

case TRUE: // p是最大堆结点

i = MinPartner(p);

if (x.key < d[i].key) {

d[p] = d[i];

MinInsert(i, x);

}

JYP

else MaxInsert(p, x);

break;

case FALSE: // p是最小堆结点

i = MaxPartner(p);

if (x.key > d[i].key) {

d[p] = d[i];

MaxInsert(i, x);

}

else MinInsert(p, x);

} // switch结束

} // Insert结束

JYP

j = i + 2log2i -1;

if (j > n + 1) j /= 2;

5.3.3 删除最小元素

JYP

JYP

template <class Type>

Element<Type>* Deap<Type>::DeleteMin (Element<Type>&x ) { // 删除并返回双堆的最小元素

if (!n) { DeapEmpty( ); return;}

x = d[2]; // 最小元素

int p = n+1;

Element<Type> t = d[p]; // 最后一个结点中的元素

n--;

for (int i = 2; i 至少有一个子女; i = j) {

d[i] = d[j];

}

return &x;

}

JYP

JYP

JYP

JYP

JYP

0 若x是一个外部结点

shortest(x) =

1 + min{ shortest(LeftChild(x))，

shortest(RightChild(x))} 否则

JYP

JYP

（a） n≥2shortest(x) – 1。

（b） 最右边的从根到外部结点路径是最短的从根到外部结点路径，其长度为shortest(x)。

（a）根据shortest(x)定义，左偏树的前shortest(x)层不存在外部结点

JYP

shortest(x) levels

JYP

（b）可由左偏树的定义直接得出。

JYP

template <class Type> class MinLeftistTree;

template <class Type>

class LeftistNode {

friend class MinLeftistTree<Type>;

private:

Element<Type> data;

LeftistNode *LeftChild, *RightChild;

int shortest;

};

template <class Type>

class MinLeftistTree: public MinPQ <Type> {

public:

JYP

// 左偏树的三个操作

void Insert (const Element<Type>&);

Element<Type>* Delete(Element<Type>&); // 删除最小元素

void MinCombine(MinLeftistTree<Type>*);

private:

LeftistNode<Type>*MinUnion(LeftistNode<Type>*,

LeftistNode<Type>*);

LeftistNode<Type>* root;

};

JYP

（1）将元素x插入一棵最小左偏树时，可先建立一棵包含单个元素x的最小左偏树，再合并这两棵最小左偏树。

（2）从一棵非空最小左偏树中删除最小元素时，可合并这棵最小左偏树的左、右子树，再删除其根结点。

JYP

a

a

b

2

2

5

+

c

=>

(b)

JYP

• 比较结点a和b的key值，若a的key值不大于b的key值，则a是合并所得树的根。
• a的左子树暂时不变。递归调用算法，将a的右子树与以b为根的最小左偏树合并，所得最小左偏树成为a的新右子树。
• 若该新右子树的根结点的shortest值大于a的左子树的根结点的shortest值，则交换a的左、右子树。
• 最后计算结点a本身的shortest值。

JYP

a的key值大于b的key值的情况也类似处理，只是这时b是合并所得树的根。

JYP

JYP

JYP

JYP

JYP

template <class Type>

void MinLeftistTree<Type>::MinCombine

(MinLeftistTree<Type> b) { // 合并最小左偏树b和

// *this，并将b设置为空最小左偏树

if (!root) root = b.root;

else if (b.root) root = MinUnion(root, b.root);

b.root = 0;

}

template <class Type>

LeftistNode<Type>* MinLeftistTree<Type>::MinUnion

(LeftistNode<Type>*a, LeftistNode<Type>*b) {

// 合并以a和b为根的非空最小左偏树，并返回

// 所得最小左偏树的根。

JYP

if (adata.key > bdata.key) { // 令a指向根结点中key值

LeftistNode<Type>*t = a; // 较小的最小左偏树

a = b;

b = t;

}

// 构建最小二叉树

if (!aRightChild) aRightChild = b;

else aRightChild = MinUnion(aRightChild, b);

// 维护左偏树性质

if (!aLeftChild) { // 左子树为空，交换左、右子树

aLeftChild = aRightChild;

aRightChild = 0;

}

JYP

else if (aLeftChildshortest < aRightChild shortest) {

LeftistNode<Type>* t = aLeftChild; // 交换左、右子树

aLeftChild = aRightChild;

aRightChild = t;

}

// 设置shortest 数据成员

if (!aRightChild) ashortest = 1;

else ashortest = aRightChildshortest + 1;

return a;

}

JYP

JYP

5.5.1 二项式堆定义

JYP

template <class Type>

class BinomialHeap; // 向前声明

template <class Type>

class BinomialNode {

friend class BinomialHeap<Type>;

private:

Element<Type> data;

int degree;

};

JYP

template <class Type>

class BinomialHeap: public MinPQ <Type> {

public:

BinomialHeap(BinomialNode<Type>*init = 0): min(init) { }; // 构造函数

void Insert (const Element<Type>&); // 插入操作

Element<Type>* Delete(Element<Type>&); // 删除最小

// 元素操作

void MinCombine(BinomialHeap<Type>*); // 合并操作

private:

BinomialNode<Type>* min;

};

JYP

JYP

JYP

5.5.2 插入操作

JYP

5.5.3 合并操作

JYP

5.5.4 删除最小元素

JYP

JYP

JYP

JYP

JYP

template <class Type>

Element<Type> * BinomialHeap<Type>::DeleteMin

(Element<Type>& x) // 删除B-堆的最小元素

min可指向新的环表中的任意一个结点; 如果无剩余

JYP

JYP

for (d = pdegree; tree[d]; d++) {

JoinMinTree(p, tree[d]); // 结合两棵最小二项式树

tree[d] = 0; // 树tree[d] 已被结合进p中

}

tree[d] = p; // 结合后，度比原来增加1

JYP

JYP

5.5.5 分析

JYP

JYP

JYP

JYP

JYP

JYP

5.6.1 斐波纳契堆定义

（1） 减少key：将指定结点的key减去一个正值。

（2） 删除：删除指定结点的元素。

JYP

B堆是F堆的特殊情况，上一节的B堆实例也是F堆实例。

JYP

F堆的表示：

• 在B堆的每个树结点中增加两个数据成员：parent和ChildCut。parent用于指向该结点的双亲（若存在的话）。ChildCut用于控制最小树的修剪。
• 插入、删除最小元素与合并这三个基本操作的实现与在B堆中的一样。
• 下面重点考虑两个新操作：（1）从F堆中删除任意结点b；（2）将任意结点b的key减去一个正值。

JYP

（1） 若min = b，则执行删除最小元素操作；否则执行下列第（2）、（3）和（4）步。

（2） 将b从其所在双链表中删除。

（3） 将b的子女的双链表与由min指向的根结点的双链表合并，并将b的子女结点的parent字段设置为0，形成一个新的根结点双链表。与删除最小元素操作不同的是，在此不结合度相同的树。

（4） 释放结点b。

5.6.2 删除操作

JYP

JYP

（1） 减少b中的key值。

（2） 若b  min且b中的key值小于其双亲中的key值，则将b从其所在双链表中删除并将其插入最小树根结点的双链表。

（3） 若b中的key值小于min中的key值，则将min指向b。

5.6.3 关键字减少操作

JYP

JYP

5.6.4 瀑布修剪

JYP

JYP

（a） b的度≤logm。

（b） MaxDegree≤logn，删除最小元素操作的实际代价是O(log n + s)。

5.6.5 分析

JYP

JYP

JYP

JYP

#insert的定义不变。

JYP

#insert个代价单位可分摊到形成#insert计数的插入操作，每个插入操作分摊1个代价单位。

LastSize个代价单位可分摊到形成LastSize计数的删除最小元素、删除任意结点和减少key操作。这使得每个删除最小元素和删除任意结点操作分摊最多logn的代价，减少key操作分摊1个代价单位（瀑布修剪增加的最小树代价将在后面另外分摊）。因此，删除最小元素操作的分摊代价是O(log n)。

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

low(w) = min {dfn(w), min {low(x)| x是w的一个子女},

min {dfn(x)| (w, x) 是一条回边}}

JYP

JYP

void Graph::DfnLow (const int x ) { // 在顶点x开始DFS

num = 1; // num是Graph 的类型为int的数据成员

dfn = new int[n]; low = new int[n]; // dfn和low都是Graph 的

// 类型为int *的数据成员

for ( int i = 0; i < n; i++ ) { dfn[i] = low[i] = 0; }

DfnLow ( x, -1 ); // x是根，其双亲是伪顶点-1

delete [ ] dfn; delete [ ] low;

}

JYP

void Graph::DfnLow ( int u, int v ) { // 从u开始深度优先搜索并

// 计算dfn和low。v是u的双亲

dfn[u] = low[u] = num++;

for (每一个与u邻接的顶点w) // 具体代码与图的表示有关

if (dfn[w] == 0) { // w 是未被访问顶点

DfnLow (w, u);

low[u] = min2 (low[u], low[w]); // min2(x,y)返回x和y的

// 较小者

}

else if (w != v) low[u] = min2 ( low[u], dfn[w] ); // 回边。注

// 意(v, u)不是回边

}

JYP

JYP

JYP

（1）w是未被访问的顶点

（2）w是已被访问的顶点而且是u的祖先

dfn[w] < dfn[u]。

JYP

2

u

3

x

w

4

JYP

void Graph::Biconnected ( ) {

num = 1;

dfn = new int[n]; low = new int[n];

for ( int i = 0; i < n; i++ ) { dfn[i] = low[i] = 0; }

Biconnected (0, -1); // 从顶点0开始

delete [ ] dfn;

delete [ ] low;

}

void Graph::Biconnected (int u, int v) { // 计算dfn和low，并输

// 出各双连分量的边。v是u 的双亲，栈S是Graph的数

// 据成员，并被初始化为空。假设n>1。

dfn[u] = low[u] = num++;

JYP

for (每一个与u邻接的顶点w) { // 具体代码与图的表示有关

if (v != w && dfn[w] < dfn[u]) 将边(u,w) 加入栈S中;

if (dfn[w] == 0 ) { // w是未被访问的顶点

Biconnected (w, u); low[u] = min2 (low[u], low[w]);

if (low[w] >= dfn[u]) {

cout <<“新双连分量：”<< endl;

do {

cout << x << "," << y <<endl;

} while ( (x, y)与(u, w)不相同);

}

}

else if (w != v) low[u] = min2 (low[u], dfn[w]); // 回边

} // for结束

}

JYP

Biconnected的时间复杂性是O(n + e)。

JYP

JYP

TV = {0}; // 从顶点0开始构造，假设G至少有一个顶点

for ((T = ; T包含的边少于n -1条;将(u, v)加入T) {

if (不存在这样的边) break;

}

if (T包含的边少于n-1条)

cout <<“不存在最小生成树” << endl ;

JYP

cost(near[v],v) = 。

JYP

JYP

JYP

JYP

1 void Graph::ShortestPath(const int n, const int v) {

2 for (int i = 0; i < n; i++) {

3 s[i] = FALSE; dist[i] = length[v][i];

4 if ( i != v && dist[i] < LARGEINT) path[i] = v;

else path[i] = – 1;

5 }

6 s[v] = TRUE; dist[v] = 0;

7 for (i = 0; i < n-2; i++) { // 确定从v开始的n – 1条路径

8 int u = choose(n); // 选择u，使得对于所有s[x] =

// FALSE，dist[u] = 最小的dist[x]

9 s[u] = TRUE;

JYP

10 for ( int w = 0; w < n; w++)

11 if (!s[w])

12 if (dist[u] + length[u][w] < dist[w]) {

dist[w] = dist[u] + length[u][w];

path[w] = u;

}

13 } // for (i = 0;…) 结束

14}

JYP

JYP

JYP

JYP

JYP

JYP

template <class KeyType>

void list(Element<KeyType> *list, const int n, int first) {

// 重新排序由first指向的链表中的记录，使list[0],…,list[n-1]

// 中的关键字按非递减次序排列

for (int i = 0; i < n – 1; i++) {

// 找到应放到位置i的记录。由于位置0, 1, …, i-1的记录已

// 就位，该记录下标一定≥i

while (first < i) first = list[first].link; // 经过虚拟结点

JYP

int q = list[first].link; // list[q]是按非递减次序的下一个

// 记录，可能是虚拟记录

if (first != i) { // 交换list[i] 和list[first]，并将list[i].link

// 设置为原list[i]的新位置

Element<KeyType> t = list[i]; list[i] = list[first];

list[first] = t;

}

first = q;

}

}

JYP

JYP

JYP

JYP

JYP

JYP

template <class KeyType>

void table(Element<KeyType> *list, const int n, int *t) {

// 重新排列list[0], …, list[n-1]，使其对应序列list[t[0]], …,

// list[t[n-1]], n≥1

for (int i = 0; i < n – 1; i++)

if (t[i] != i) { // 存在一个开始于i的非平凡环路

Element<KeyType> p = list[i]; int j = i;

do {

int k = t[j]; list[j] = list[k]; t[j] = j;

j = k;

} while ( t[j] != i );

list[j] = p; // p中的记录应该移到位置j

t[j] = j;

}

}

JYP

for循环执行了n–1次。如果对于某些i的取值，t[i]  i，则存在一个包含k > 1个不同记录Ri, Rt[i], …, Rtk-1[i]的环路。重新排列这些记录需要k+1次移动。

JYP

JYP

7.9.1 概述

7.9 外排序

JYP

• 寻找时间：将读写头定位于正确的柱面所用时间。
• 等待时间：本磁道中所需块旋转到读写头下所用时间。
• 传输时间：将块中数据读入内存或写到磁盘所用时间。
• 其中，就数据传输而言，寻找和等待都是辅助性的，但其时间却较长。为了提高传输效率，IO块的容量一般都较大，通常可包含数千字节。

JYP

（1）根据内存容量将输入记录表分为若干段，并利用某种内排序方法逐个对这些段排序。这些已排序的段又称为归并段（runs）。

（2）归并第一阶段生成的归并段，直到最终只剩一个归并段。

JYP

JYP

tseek = 最长寻找时间

tlatency = 最长等待时间

trw = 读写一个IO块（250个记录）所需时间

tIO = tseek + tlatency + trw

tIS = 内排序750个记录所需时间

ntm = 将n个记录从输入缓冲区归并到输出缓冲区所

JYP

JYP

JYP

7.9.2 k路归并

JYP

JYP

• k路归并所需的缓冲区个数随着k的增大而增加；
• 在内存容量给定情况下，缓冲区容量随着k的增大而减小；
• 这又导致IO块的有效容量减小，从而使一遍数据扫描需要更多的IO块操作。
• 因此，当k超过一定值时，输入输出时间反而会随着k的增大而增加。k值的最佳选择与磁盘参数和可用于缓冲区的内存容量有关。

JYP

7.9.3 生成初始归并段

JYP

JYP

• 败者树是组织这些记录的有效结构：
• 每个非叶结点i有一个字段l[i]（1≤i<k），表示在结点i比赛的败者。
• rn[i]表示r[i]所属的归并段号（0≤i<k）。
• l[0]和q都存放整个比赛的胜者记录下标。
• rc存放当前归并段号。
• rq存放r[q]所属的归并段号。

JYP

rmax存放将生成的实际归并段总数。

• LastKey存放当前最后一个输出记录的关键字值。
• 当输入记录表已读空时，我们可以构造归并段号为rmax + 1的虚拟记录，以将败者树中的实际记录“顶”出。

JYP

1 template <class KeyType>

2 void runs(const int k) {

3 Element<KeyType> *r = new Element[k];

4 int *rn = new int[k]; int *l = new int[k];

5 for ( int i = 0; i < k; i++ ) { // 输入记录

6 ReadRecord( r[i] ); rn[i] = 1;

7 }

8 InitializeLoserTree(r, l, rn, k); // 初始化败者树，可用类

// 似第4.6.2小节的方法

9 int q = l[0]; // 整个比赛的胜者

10 int rq = 1; int rc = 1; int rmax = 1; KeyType LastKey;

JYP

11 while (1) { // 输出归并段

• 12 if ( rq != rc ) { // 当前段结束
• 13 输出归并段结束标记;
• 14 if ( rq > rmax ) break; // 遇到虚拟记录，说明实际
• // 记录已输出完，跳出循环
• 15 else rc = rq;
• 16 }
• 17 WriteRecord(r[q]); LastKey = r[q].key;
• 18 // 输入新记录
• 19 if (输入结束) rn[q] = rmax + 1; // 生成虚拟记录，以把实
• // 际记录“顶”出败者树
• 20 else {
• 22 if ( r[q].key < LastKey) rn[q] = rmax = rq + 1;// 新记录
• // 属于下一个归并段

JYP

23 else rn[q] = rc; // 新记录仍然属于当前归并段

• }
• 25 rq = rn[q];
• 26 // 重新调整败者树
• 27 for (int t = (k + q)/2; t; t /= 2) // t初始化为r[q]的父结点
• 28 if ((rn[l[t]] < rq) || (rn[l[t]] == rq && r[l[t]].key <
• r[q].key)) { // t是胜者
• 29 int temp = q; q = l[t]; l[t] = temp;
• 30 rq = rn[q];
• 31 }
• 32 } // while循环结束
• 33 delete [ ] r; delete [ ] rn; delete [ ] l;
• 34}

JYP

JYP

7.9.4 归并段的最佳归并和哈夫曼树

JYP

JYP

33 + 43 + 82 + 211 = 58 和

32 + 42 + 82 + 212 = 72。

JYP

JYP

JYP

JYP

class BinaryTreeNode {

friend class BinaryTree;

private:

BinaryTreeNode *LeftChild;

int weight;

BinaryTreeNode *RightChild;

};

JYP

class BinaryTree {

public:

BinaryTree(BinaryTree bt1, BinaryTree bt2) {

root = new BinaryTreeNode;

rootLeftChild = bt1.root;

rootRightChild = bt2.root;

rootweight = bt1.rootweight + bt2.rootweight;

}

private:

BinaryTreeNode *root;

};

JYP

void huffman(List<BinaryTree> list) {

int n = list.Size( ); // 表list中的二叉树棵数

for (int i = 0; i < n – 1; i++) { // 循环n-1次，以结合n个结点

BinaryTree first = list.DeleteMinWeight( );

BinaryTree second = list.DeleteMinWeight( );

BinaryTree *bt = new BinaryTree(first, second);

list.Insert(bt);

}

}

JYP

list.DeleteMinWeight( )返回list中权重最小的树并将其从list中删除，list.Size( )返回list中元素个数，list.Insert(bt)将新二叉树bt加入表list中。

JYP

JYP

24 + 34 + 43 + 72 + 82 + 132 = 88

JYP

JYP

（1） 每个元素有一个关键字，且任何两个不同的元素的关键字不相等（即关键字唯一）；

（2） 左子树（如果存在的话）中的关键字小于根结点中的关键字；

（3） 右子树（如果存在的话）中的关键字大于根结点中的关键字；

（4） 左、右子树也是二叉查找树。

JYP

JYP

（1） C.ThreeWayJoin(A, x, B)：

（2） C.TwoWayJoin(A, B)：

JYP

（3） A.Split(i, B, x, C)：

JYP

3-路结合的实现：

JYP

2-路结合可通过3-路结合实现：

JYP

JYP

JYP

JYP

template <class Type>

Element<Type>* BST<Type>::Split(Type i, BST<Type>&B, Element<Type>&x, BST<Type>&C) { // 根据关键字i分裂

if (!root) {B.root = C.root = 0;return 0;} // 空树

BstNode<Type> *Y = new BstNode<Type>; // B的头结点

BstNode<Type> *R = Y;

BstNode<Type> *Z = new BstNode<Type>; // C的头结点

BstNode<Type> *L = Z;

JYP

BstNode<Type> *t = root;

while (t)

if (i == tdata.key) { // 在结点t分裂

RRightChild = tLeftChild;

LLeftChild = tRightChild;

x = tdata;delete t;

B.root = YRightChild;delete Y; // 删除头结点

C.root = ZLeftChild;delete Z; // 删除头结点

root = 0;

return &x;

}

else if (i < tdata.key) {

LLeftChild = t;

L = t; t = tLeftChild;

}

JYP

}

else {

RRightChild = t;

R = t; t = tRightChild;

}

RRightChild = LLeftChild = 0; // 注意这是必要的

B.root = YRightChild;delete Y; // 删除头结点

C.root = ZLeftChild;delete Z; // 删除头结点

root = 0;

return 0;

}

JYP

JYP

JYP

n个结点的二叉树有n + 1个外部结点。不成功的查找都会在某一个外部结点结束，所以也称外部结点为失败结点。

I = 0 + 1 + 1 + 2 = 4

E = 2 + 2 + 2 + 3 + 3 = 12

JYP

JYP

E – 2(k + 1) + k = E – k – 2

E – k – 2 = (I – k) + 2m

E = I + 2(m + 1)

JYP

JYP

1 最坏情况

I = =

Sn = = (8.1)

JYP

2 最好情况

0 + 2  1 + 4  2 + 8  3 + … +

I =

JYP

= (n + 1)q – 2(q + 1) + 2，

Sn = =

 log2(n + 1) –1log2n–1 (8.2)

JYP

3 平均情况

Sn — 平均成功查找所需的比较次数

Un — 平均不成功查找所需的比较次数

Sn = 1 +

JYP

Sn = =

JYP

Sn = 1 +

I = U0 + U1 + …+ Un-1

Un = =

JYP

(n + 1)Un = 2n + U0 + U1 + …+ Un-1

n Un-1 = 2(n – 1) + U0 + U1 + …+ Un-2

Un = Un-1 +

JYP

Un = 2 = 2 Hn+1 – 2

Hn+1 =

Un  2 (ln 2) (log2n)  1.39 log2n (8.4)

JYP

Sn  1.39 log2n – 1。

JYP

JYP

JYP

E0包含所有满足x < a1的标识符x，Ei包含所有满足ai < x < ai+1的标识符x，1≤i < n，En包含所有满足x > an的标识符x。

JYP

(8.5)

JYP

JYP

JYP

JYP

JYP

wij = qi +

JYP

JYP

L是左子树，包含标识符ai+1, …, ak-1；R是右子树，包含标识符ak+1, …, aj。

cij = pk+L的代价+R的代价+L的权重+R的权重

cij = pk+ci,k-1+ckj+wi,k-1+wkj = wij+ci,k-1+ckj (8.7)

JYP

wij + ci,k-1 + ckj =

ci,k-1 + ckj = (8.8)

JYP

w01 = w00 + p1 + q1 = 0.15 + 0.5 + 0.05 = 0.7

c01 = w01 + min{c00 + c11} = 0.7

r01 = 1

w12 = w11 + p2 + q2 = 0.05 + 0.05 + 0.1 = 0.2

c12 = w12 + min{c11 + c22} = 0.2

r12 = 2

JYP

w23 = w22 + p3 + q3 = 0.1 + 0.1 + 0.05 = 0.25

c23 = w23 + min{c22 + c33} = 0.25

r23 = 3

JYP

T13的根结点含标识符a3，其左子树是T12，右子树是T33。

T12的根结点含标识符a2，其左子树是T11，右子树是T22。由此可以构造T03，如右图所示。

JYP

JYP

= O(n3)

Knuth的研究成果表明，(8.8)式中的最佳u的选择可限定在ri,j-1≤u≤ri+1,j的范围。

≤2n – m + 1 = O(n)

JYP

n – m + 1 + = O(n2)

JYP

template <class Type>

void BST<Type>::obst(float *p, float *q, Element<Type>*a, int n) { // 给定n个标识符a1 < a2 < … < an，和pj（1  j  n）和

// qi（0  i  n），计算表示ai+1,…, aj的Tij的cij，同时计算

// rij和wij。最后调用build构造最佳二叉查找树。

for (int i = 0; i < n; i++) {

w[i][i] = q[i]; c[i][i] = 0; // 初始化

w[i][i+1] = q[i] + q[i+1] + p[i+1]; // 只含一个结点的情况

r[i][i+1] = i+1;

c[i][i+1] = w[i][i+1];

}

w[n][n] = q[n]; c[n][n] = 0;

JYP

for (int m = 2; m <= n; m++) // 含m个结点的最佳二叉查找树

for (i = 0; i <= n - m; i++) {

int j = i + m;

w[i][j] = w[i][j-1] + p[j] + q[j];

int k; float s = MAX; // 设MAX是已定义的最大值常数

for (int u = r[i][j-1]; u <= r[i+1][ j]; u++) {

float t = c[i][u-1] + c[u][j];

if (t < s) {s = t; k = u;} // 求k

}

c[i][j] = w[i][j] + c[i][k-1] + c[k][j]; // (8.7)式

r[i][j] = k;

}

root = build(a, 0, n);

} // obst结束

JYP

template <class Type>

BstNode<Type>* BST<Type>::build(Element<Type>*a, int i, int j) { // 根据rij构造最佳二叉查找树

if (i == j) return 0; // 空树

BstNode<Type>*p = new BstNode<Type>;

int k = r[i][j];

pdata = a[k];

pLeftChild = build(a, i, k-1);

pRightChild = build(a, k, j);

return p;

}

JYP

B树只有利于单个关键字查找，而在数据库等许多应用中常常也需要范围查询。

B+树就是这种改进的结果，也是现实中最常用的一种B树变种。

B+树与B树的最大区别是：B+树的元素只存放在叶结点中。

8.6.6 B+树

JYP

（1） 中间结点最多有m棵子树，且具有下列结构：

n, A0, (K1, A1), (K2, A2), …, (Kn, An)

（2） Ki < Ki+1，1≤i < n，且所有中间结点的关键字互不相同。

（3） Ai中的所有关键字都大于等于Ki并小于Ki+1，0 < i < n。

JYP

（4） An中的所有关键字都大于等于Kn，A0中的所有关键字都小于K1。

（5） 根结点至少有2棵子树，其它中间结点至少有m/2棵子树。

（6） 所有叶结点都处于同一个层次，包含了全部关键字以及相应的元素或元素地址，叶结点中的关键字从小到大排序，且互不相同。

（7） 叶结点中存放的关键字个数可以大于或小于m。设mleaf是叶结点可容纳的最大失败结点个数，则其中的实际关键字个数n应满足：1≤mleaf/2 – 1≤n < mleaf。

JYP

B+树的叶结点被链接成双链表，以便范围查询。

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

JYP

8.9.1 带目录动态散列

JYP

JYP

JYP

JYP

JYP

JYP

（1）通过散列函数确定目录项，并得到页面地址；

（2）访问该页面。

JYP

hashi: key  {0, …, 2i – 1}，1≤i≤d

JYP

JYP

JYP

template <class Type>

struct record { // 记录结构

Type key;

// 其它数据字段

};

template <class Type>

struct page { // 页面结构

record names[PageSize]; // 假设PageSize是已定义的常数

int NumRecords; // 本页面的记录个数

};

JYP

template <class Type>

struct DirEntry { // 目录项结构

int LocalDepth; // 该目录项所指页面的局部深度

};

template <class Type>

DirEntry rdirectory[MaxDir]; // 目录，假设MaxDir是已

// 定义的常数

int gdepth; // 全局深度，要求1≤gdepth≤KeySize，

// 假设KeySize是已定义的常数

int NumGdepthPage; // 局部深度达到全局深度的页面数

JYP

template <class Type>

int hash(const Type& key, const int i); // 用均匀散列将key

//转换为二进制位序列，返回与i个最低位对应

// 的整数，并作为目录项下标

int buddy(const int index); // 根据指向一个页面的目录项下

// 标index，返回指向其伙伴页面的目录项下标，这可

// 以通过对index的首个二进制位作取反操作实现

template <class Type>

int size(const page<Type>* p); // 返回页面p中记录个数

template <class Type>

void coalesce(const page<Type>* p, const page<Type>* buddy);

// 将页面p及其伙伴buddy的记录合并到页面p，并释放页

// 面buddy

JYP

template <class Type>

Boolean PageSearch(const Type& key, const page<Type>* p);

// 在页面p中查找关键字key。找到返回TRUE，否则返回

// FALSE

template <class Type>

void enter(const record& r, const page<Type>* p); // 将新记录r

//插入页面p，并执行pNumRecords++;

template <class Type>

void PageDelete(const Type& key, const page<Type>* p); // 从

// 页面p中删除关键字为key的记录，并执行pNumRecords--;

JYP

template <class Type>

page<Type>* find(const Type& key) { // 在整个表中查找关键

// 字为key的记录。找到返回该记录所在页面的地址，

//否则返回0

int index = hash(key, gdepth);

if (PageSearch(key, p) return p;

else return 0;

}

template <class Type>

void insert(const record<Type>& r, const Type& key) { // 将新

// 记录r插入整个表中

page<Type>* p = find(key); // 检查key是否已存在

if (p) return; // key已存在

int index = hash(key, gdepth);

JYP

Boolean StillFull = FALSE;

if (pNumRecords != PageSize) enter(r, p); // 页面未满，插

// 入新记录

else do {

rdirectory[index].LocalDepth++;

if (rdirectory[index].LocalDepth > gdepth) {

gdepth++;

if (gdepth > KeySize) {报告错误; return;}

NumGdepthPage = 0; // 初始化局部深度达到全

// 局深度的页面数

}

JYP

int buddyindex = buddy(index);

rdirectory[buddyindex].LocalDepth =

rdirectory[index].LocalDepth;

if (rdirectory[index].LocalDepth == gdepth)

NumGdepthPage += 2;

int index = hash(key, gdepth); // 通过散列函数得到记录r

// 的目录项下标

// 必是原来的页面p和新分配页面q之一

if (pNumRecords != PageSize) enter(r, p);

else StillFull = TRUE;

} while (StillFull == TRUE);

}

JYP

template <class Type>

void Delete(const Type& key) { // 从整个表中删除关键字

// 为key的记录

page<Type>* p = find(key);

if (p) {

PageDelete(key, p);

int index = hash(key, gdepth);

int buddyindex = buddy(index);

if (size(p)+size(bp) <= PageSize) {

if (index < buddyindex) { // 总是合并到小下标目录

// 项所指页面

coalesce(p, bp);

}

JYP

else {

coalesce(bp, p);

}

rdirectory[index].LocalDepth--;

rdirectory[buddyindex].LocalDepth--;

if (rdirectory[index].LocalDepth + 1 == gdepth)

NumGdepthPage – = 2;

}

JYP

while (!NumGdepthPage && gdepth > 1) {

gdepth--;

// 录项所指页面，前面一半目录项已设置好

}

}

}

void main( ) {

gdepth = 1;

}

JYP

L(k)与页面的容量p有关。当所有记录都可存入一个页面时，L(k) = 1。

JYP

JYP

Mendelson的研究结果表明

JYP

Fagin等人的实验研究结果表明，可扩展散列的性能至少与B树同样好或更好。在查找和插入时间方面，可扩展散列明显更好；在空间利用率方面，两者几乎相同。

JYP

8.9.2 无目录动态散列

JYP

JYP

JYP

JYP

if ( hash(key, r) < q) page = hash(key, r + 1);

else page = hash (key, r);

JYP