《算法导论》— Chapter 12 二叉查找树

查找树是一种数据结构,它支持多种动态集合操作,包括Search、Minimum、Maximum、PreDecessor、Successor、Insert、Delete等。它既可以用作字典,也可以用作优先级队列;在二叉查找树(Binary Search Tree)上执行基本操作的时间与树的高度成正比,对于一颗含有n个结点的完全二叉树,基本操作的最坏情况运行时间为floor(logn)。

本章讨论二叉查找树的基本性质以及上面提及的基本操作的实现。

GitHub 程序实现代码

1 二叉查找树

1.1 性质

如下图所示,一颗二叉查找树是按照二叉树结构来组织的。这样的树一般用链表结构表示,每一个结点是一个对象,包含关键字key、父亲结点parent、左儿子结点left以及右儿子结点right四个属性。

明显的,对于二叉查找树中关键字的存储方式总是满足这样的性质:

设x为二叉查找树中的一个结点,如果y是x左子树中的一个结点,则y?>key<=x?>key,如果y是x右子树中的一个结点,则y?>key>=x?>key。

1.2 基本操作

1.2.1 遍历

根据二叉查找树的性质,可以用一个递归算法按照排列顺序依次输出所有关键字,这就是中序遍历,遍历顺序为根、左子树、右子树。同样的,有前序遍历,根的关键字在左右子树之前输出;后序遍历,根的关键字在其左右子树之后输出。

1.2.2 查找

对于二叉查找树,最常见的操作就是查找树中的某个关键字。查找操作同样采用递归的形式实现,其复杂度等于树的高度。

操作过程如下图所示:

1.2.3 求最大、最小关键字

对于用作优先级队列的结构,求最大最小关键字是必不可少的操作。

要查找二叉查找树中的最小关键字,根据树的性质,只要从根节点开始,沿着各个结点的left指针查找下去,直到遇到NULL为止。

同理,要查找二叉查找树中的最大关键字,根据树的性质,只要从根节点开始,沿着各个结点的right指针查找下去,直到遇到NULL为止。

这两个操作的运行时间都是O(h)。

1.2.4 求前驱、后继

我们知道,中序遍历二叉查找树得到的是一组有序序列,有时候需要求指定结点的前驱与后继。对于给定结点x的后继结点是具有大于或等于x?>key中关键字的最小结点;同理,对于给定结点x的前驱结点是具有小于x?>key中关键字的最大结点。根据二叉查找树的性质,不用对关键字做任何比较就可以得到给定结点的前驱和后继结点。

1.2.5 插入

插入和删除操作会引起整个二叉查找树表示的集合的动态变化。要反应出这种变化,就要修改数据结构,但是在修改的同时,还要保持整棵树的性质不变。

将新值插入到一颗二叉查找树中的过程如下:

1.2.6 删除

相对于插入操作,删除更加复杂一些,下图详细展示了删除不同结点需要的步骤:

对高度为h的二叉查找树,动态集合操作Insert与Delete的运行时间都是O(n)。

2 二叉查找树程序实现

下面给出的程序实现,包括了所有以上提及的基本操作:

(1)BinarySearchTree.h

#ifndef _BINARYSEARCHTREE_H_
#define _BINARYSEARCHTREE_H_

#include <iostream>

typedef struct BSTNode{
    BSTNode *left;
    BSTNode *right;
    BSTNode *parent;

    int key;

    BSTNode(int data) : left(NULL), right(NULL), parent(NULL), key(data){}
};

class BinarySearchTree{
public:
    BinarySearchTree();
    ~BinarySearchTree();

    //插入删除操作
    void Insert(int data);
    BSTNode *Delete(int data);
    BSTNode *root;
};

//查找操作
BSTNode *Search(BSTNode * node, int data);

//遍历操作
void InOrderWalk(BSTNode * node);
void PreOrderWalk(BSTNode * node);
void PostOrderWalk(BSTNode * node);

//查询最大最小值
BSTNode *Maximum(BSTNode * node);
BSTNode *Minimum(BSTNode * node);

//查找前驱与后继
BSTNode *PreDecessor(BSTNode *node);
BSTNode *Successor(BSTNode *node);

#endif

(2)BinarySearchTree.cpp

#include "BinarySearchTree.h"
#include <iostream>

BinarySearchTree::BinarySearchTree()
{
    root = NULL;
}

BinarySearchTree::~BinarySearchTree()
{
    delete root;
}

//向二分查找树中插入数据data
void BinarySearchTree::Insert(int data)
{
    BSTNode *node = new BSTNode(data);
    BSTNode *p = root, *q = NULL;
    while (p != NULL)
    {
        q = p;
        if (p->key > data)
            p = p->left;
        else
            p = p->right;
    }
    node->parent = q;
    if (q == NULL)
        root = node;
    else if (q->key > data)
        q->left = node;
    else
        q->right = node;
}

//从二分查找树中删除数据
BSTNode *BinarySearchTree::Delete(int data)
{
    BSTNode *node = Search(root, data);
    BSTNode *ret , *tmp;

    if (node == NULL)
        return node;

    //如果目标结点只有一个子女则删除该结点,否则删除其后继结点
    if (node->left == NULL || node->right == NULL)
        ret = node;
    else
        ret = Successor(node);

    //如果被删结点有左右孩子,将其链接到被删结点的父节点
    if (ret->left != NULL)
        tmp = ret->left;
    else
        tmp = ret->right;

    if (tmp != NULL)
        tmp->parent = ret->parent;
    //如果被删结点的父节点为空,则说明要删的是根节点
    if (ret->parent == NULL)
        root = tmp;
    else if (ret == ret->parent->left)
        ret->parent->left = tmp;
    else
        ret->parent->right = tmp;

    if (ret != node)
        node->key = ret->key;

    return ret;
}

//查找以node结点为根的子树中值为data的结点
BSTNode *Search(BSTNode * node, int data)
{
    if (node == NULL || node->key == data)
        return node;
    if (data < node->key)
        return Search(node->left, data);
    else
        return Search(node->right, data);
}

//遍历操作
void InOrderWalk(BSTNode * node)
{
    if (node != NULL)
    {
        InOrderWalk(node->left);
        std::cout << node->key << "\t";
        InOrderWalk(node->right);
    }
}

void PreOrderWalk(BSTNode * node)
{
    if (node != NULL)
    {
        std::cout << node->key << "\t";
        InOrderWalk(node->left);
        InOrderWalk(node->right);
    }
}

void PostOrderWalk(BSTNode * node)
{
    InOrderWalk(node->left);
    InOrderWalk(node->right);
    std::cout << node->key << "\t";
}

//查询最大最小值
BSTNode *Maximum(BSTNode * node)
{
    while (node->left != NULL)
        node = node->left;
    return node;
}

BSTNode *Minimum(BSTNode * node)
{
    while (node->right != NULL)
        node = node->right;
    return node;
}

//查找前驱与后继
BSTNode *PreDecessor(BSTNode *node)
{
    if (node->left != NULL)
        return Maximum(node->left);

    BSTNode *p = node->parent;
    while (p != NULL && node == p->left)
    {
        node = p;
        p = node->parent;
    }

    return p;
}

BSTNode *Successor(BSTNode *node)
{
    if (node->right != NULL)
        return Minimum(node->right);
    BSTNode *p = node->parent;
    while (p != NULL && node == p->right)
    {
        node = p;
        p = node->parent;
    }

    return p;
}

(3)main.cpp

#include "BinarySearchTree.h"
#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

const int MAX = 101;
const int N = 10;

int main()
{
    BinarySearchTree *bst = new BinarySearchTree();

    //设置随机化种子,避免每次产生相同的随机数
    srand(time(0));

    //构造一个随机数组成的二分查找树
    for (int i = 0; i < N; i++)
        bst->Insert(rand() % MAX);

    //遍历查找树
    cout << "先序遍历二分查找树bst:" << endl;
    PreOrderWalk(bst->root);

    //遍历查找树
    cout << endl << "中序遍历二分查找树bst:" << endl;
    InOrderWalk(bst->root);

    //遍历查找树
    cout << endl << "后序遍历二分查找树bst:" << endl;
    PostOrderWalk(bst->root);

    int x = 45;
    BSTNode *node = Search(bst->root, x);
    if (node)
    {
        cout << endl << "二分查找树bst中存在结点x = " << x << endl;
        bst->Delete(x);

        cout << endl << "删除二分查找树中结点x = " << x << "成功!" << endl;

        //遍历查找树
        cout << endl << "先序遍历二分查找树bst:" << endl;
        PreOrderWalk(bst->root);

        //遍历查找树
        cout << endl << "中序遍历二分查找树bst:" << endl;
        InOrderWalk(bst->root);

        //遍历查找树
        cout << endl << "后序遍历二分查找树bst:" << endl;
        PostOrderWalk(bst->root);
        cout << endl << endl;
    }
    else{
        cout << endl << "二分查找树bst中不存在结点x = " << x << endl;
        cout << endl << endl;
    }

    system("pause");
    return 0;
}

测试结果(查找失败的情况):

测试结果(查找成功并删除):

3 随机构造的二叉查找树

在本章的最后介绍了一种随机构造二叉查找树的理论方法,这主要是针对普通二叉查找树基本操作运行时间O(h)考虑的,这种方式可以使得:

一棵在n个关键字上随机构造的二叉查找树的期望高度为O(logn)。

这一部分感觉应用不多吧,木有仔细看,^||^~~

时间: 2024-12-29 12:21:02

《算法导论》— Chapter 12 二叉查找树的相关文章

算法导论——lec 12 平摊分析与优先队列

在平摊分析中,执行一系列数据结构操作所需要的时间是通过对执行的所有操作求平均得出,反映在任何情况下(即最坏情况下),每个操作具有平均性能.掌握了平摊分析主要有三种方法,聚集分析.记账方法.势能方法.掌握了平摊分析的方法以后,我们就可以利用他来分析一些优先队列. 一. 平摊分析 [问题一]对作用于一个初始为空的栈上的n个PUSH.POP和MULTIPOP组成的序列进行分析. [问题二]考虑一个由0开始向上计数的k位二进制计数器.作用于一个初始为零的计数器上的n个INCREMENT操作的时间分析.

算法导论 第12章 二叉查找树

二叉查找树是一种树数据结构,它与普通的二叉树最大的不同就是二叉查找树满足一个性质:对于树中的任意一个节点,均有其左子树中的所有节点的关键字值都不大于该节点的关键字值,其右子树中的任意一个节点的关键字值都不小于该节点的关键字值. 在二叉查找树上可以进行搜索.取最小值.取最大值.取指定节点的前驱.取指定节点的后继以及插入和删除节点操作,因此二叉查找树和堆(大顶堆和小顶堆)一样,也可以做优先队列,都能够在 O(lgn) 的时间内取得集合的最大值和最小值.一个二叉查找树的期望高度为O(lgn),因此在二

算法导论5.1-2

题目摘要:利用random(0,1):提供一个random(a,b)的函数 分析:利用random(0,1)产生2进制数,这可以产生所有0,1,2……n的数,当这些数在[a,b]中时,我们就返回它,不在就不返回.实现时n只要包含b-a就可以了然后加上a后再判断. 1 #include<iostream> 2 #include<ctime> 3 4 using namespace std; 5 6 int RandomCopy(int a, int b) 7 { 8 srand(ti

算法导论自学1-2章

AKamai??????????????? 上界(最坏情况分析worst-case-analysis):对用户承诺 average case:T(n)是所有输入的加权平均值,期望时间 bast case(假象):有些算法对特定输入的时间时间更短,但是平均时间和上界可能很长 速度:计算机(速递,相对速度,绝对速度) 渐进分析:忽略依赖于计算机的东西, 关注时间的 -增长- 情况 插入排序(n*n):n小很快, 归并排序(n lg n):n大于30左右就快于插入 递归树 我的归并排序http://m

《算法导论》12.3节习题

12.3-1 二叉搜索树insert操作的递归版本 void insert1(Node* pRoot, Node* pAdd) { bool bLeft = pAdd->key < pRoot->key; Node* pNextRoot = bLeft ? pRoot->left : pRoot->right; if(pNextRoot) insert1(pNextRoot, pAdd); else { pAdd->parent = pRoot; if(bLeft) p

算法导论 10.1-2 用一个数组实现两个栈

一.题目 用一个数组A[ 1....N ]实现两个栈,除非数组的每一个单元都被使用,否则栈例程不能有溢出,注意PUSH和POP操作的时间应为O(1). 二.解法 对于一个数组,由它的两端作为栈底,栈向数组中间扩展.当数组中每个元素被用到时,栈满. 三.代码 struct Node; typedef Node *ComStack; struct Node { int Capacity; int TopL; int TopR; ElementType *Array; }; ComStack Crea

算法导论(Introduction to Algorithms )— 第十二章 二叉搜索树— 12.1 什么是二叉搜索树

搜索树数据结构支持许多动态集合操作,如search(查找).minmum(最小元素).maxmum(最大元素).predecessor(前驱).successor(后继).insert(插入).delete(删除),这些都是基本操作,可以使用一颗搜索树当做一个字典或者一个优先队列. 12.1.什么事二叉搜索树 二叉搜索树是以一棵二叉树来组织的,可以用一个链表数据结构来表示,也叫二叉排序树.二叉查找树: 其中的每个结点都是一个对象,每个对象含有多个属性(key:该结点代表的值大小,卫星数据:不清楚

【算法导论】学习笔记——第12章 二叉搜索树

搜索树数据结构支持多种动态集合操作,包括SEARCH.MINIMUM.MAXIMUM.PREDECESSOR.SUCCESSOR.INSRT和DELETE操作等.基本的搜索树就是一棵二叉搜索树.12.1 什么是二叉搜索树1. 二叉搜索树的性质:设x是二叉搜索树中的一个结点.如果y是x左子树中的一个结点,那么y.key<=x.key.如果y是x右子树中的一个结点,那么y.key>=x.key.三种遍历时间复杂度是O(n),这是显然的. 12.1-3 1 void Inorder_Tree_Wal

《算法导论》 — Chapter 7 高速排序

序 高速排序(QuickSort)也是一种排序算法,对包括n个数组的输入数组.最坏情况执行时间为O(n^2). 尽管这个最坏情况执行时间比較差.可是高速排序一般是用于排序的最佳有用选择.这是由于其平均性能相当好.期望的执行时间为O(nlgn).且O(nlgn)中隐含的常数因子非常小.另外它还能够进行就地排序在虚拟环境中也能非常好的工作. GitHub chapter 7 程序代码下载 原理 高速排序也和合并排序一样,基于分治法,分为分解.解决.合并三个步骤. 分解:数组array[low-hig