• 《算法导论》第14章 数据结构的扩张 (1)动态顺序统计



    《数据结构扩张》是《算法导论》第三部分的最后一章。在介绍学习了这么多种数据
    结构之后,简要介绍了当这些基本数据结构不满足需求时,如何扩张它们来满足需求。
    这才是学习算法的目的,能够根据需求选择合适的数据结构和算法,并在无法满足需求
    时能够扩张它。这才是对算法的思想和本质的学习!

    可以将本章看做深入学习的前奏吧,因为紧接着就要开始进入第四部分《高级设计和分析
    技术》了。那么赶快来看看如何扩张数据结构,然后就进入高级部分的学习吧!


    1.如何扩张数据结构?

    1)选择基础数据结构
    2)确定要在基础数据结构中添加哪些信息
    3)验证可用基础数据结构上的基本操作来维护新添加的信息
    4)设计新的操作

    下面来看一个简单的数据扩张的例子 - 动态顺序统计树。《算法导论》中选用红黑树
    作为基础数据结构,这里为了简单,用最简单的二叉查找树来实现。这样我们可以集中
    注意力在数据结构扩张的方法上。


    2.动态顺序统计树

    在第九章《中位数和顺序统计》中,我们曾研究过如何求数组中的元素的顺序统计量。
    如查找最大值、最小值、第 i 小的值等等。其中求第 i 小元素时采用了随机快速排序的
    RANDOMIZED-PARTITION划分方法,从而达到了Θ(n)的期望运行时间。

    本节要研究的是动态顺序统计量,与之前的有些类似,首先我们来看面对这个需求,
    如何选择合适的数据结构,并在必要时进行简单的扩张。

    1)选择基础数据结构

    我们面对的需求是维护动态集合,即数据可能会频繁的插入和删除,固定长度的数组
    显然不适合这种情形。所以我们可以选择二叉查找树作为基础数据结构,但基本的
    二叉查找树实现顺序统计功能是很低效的,所以还要添加新的域来扩张它。

    2)确定要在基础数据结构中添加哪些信息

    我们选择在每个结点添加一个新的size域,来保存每棵子树的大小(包含的结点树,包括
    此根结点自身)。更自然的想法是新加一个rank域,直接保存每个结点的顺序统计量,
    但这样插入和删除时可能会麻烦一些,参见习题14.1-6。

    3)验证可用基础数据结构上的基本操作来维护新添加的信息

    如前所述,添加size而不是rank域是为了只对原有数据结构略作改动,就足以维护附加信息,
    这是比较理想的情况。如果添加rank域,当插入一个最小元素时,所有结点的rank域都要变。

    4)设计新的操作

    新的操作就是我们需求中所需的,OS-SELECT求顺序统计量为 i 的结点和OS-RANK求一个
    结点的顺序统计量。


    3.实现源码

    下面来看实现代码,这里只列出BST实现的改动部分,着重来看新加的操作和对BST的影响。

    typedef struct _BSTNode {
         struct _BSTNode *left, *right, *parent;
         int key;
         char *value;
         int size;
    } BSTNode;
    
    void bst_insert(BSTNode **root, BSTNode *newNode)
    {
         // Locate insert location
         BSTNode *pNode = NULL;
         BSTNode *node = *root;
         while (node != NULL) {
              pNode = node;
              node->size += 1;//维护size域
              if (newNode->key < node->key)
                   node = node->left;
              else
                   node = node->right;
         }
         
         // Link newNode to pNode
         newNode->parent = pNode;
    
         // Link pNode to newNode
         if (pNode == NULL)
              *root = newNode;
         else if (newNode->key < pNode->key)
              pNode->left = newNode;
         else
              pNode->right = newNode;          
    }
    
    BSTNode *bst_delete(BSTNode **root, BSTNode *delNode)
    {
         BSTNode *pNode = delNode;//维护size域
         while ((pNode = pNode->parent) != NULL)
              pNode->size -= 1;
    
         // Real delete node: delNode or its successor 
         // (if delNode has both left and right child)
         BSTNode *realDelNode;
         if (delNode->left && delNode->right)
              realDelNode = bst_successor(delNode);
         else
              realDelNode = delNode;
    
         // Child of real delete node
         BSTNode *childNode;
         if (delNode->left)
              childNode = realDelNode->left;
         else
              childNode = realDelNode->right;
    
         // Link realDelNode child and parent
         if (childNode)
              childNode->parent = realDelNode->parent;
    
         if (realDelNode->parent == NULL)
              *root = childNode;
         else if (realDelNode == realDelNode->parent->left)
              realDelNode->parent->left = childNode;
         else
              realDelNode->parent->right = childNode;
    
         // Copy successor data to delNode (override)
         // if real delete node is not delNode but its successor          
         if (realDelNode != delNode) {
              delNode->key = realDelNode->key;
              delNode->value = realDelNode->value;
              delNode->size = realDelNode->size;//维护size域
         }
    
         return realDelNode;
    }
    
    BSTNode *bst_os_select(BSTNode *node, int i)
    {
         if (node == NULL)
              return NULL;
    
         int r = 1;
         if (node->left != NULL)
              r = node->left->size + 1;
    
         if (i == r)
              return node;
         else if (i < r)
              return bst_os_select(node->left, i);
         else
              return bst_os_select(node->right, i - r); 
    }
    
    int bst_os_rank(BSTNode *root, BSTNode *node)
    {
         int r = 1;
         if (node->left != NULL)
              r += node->left->size;
    
         while (node != root) {
              if (node == node->parent->right)
                   r += node->parent->left->size + 1;
              node = node->parent;
         }
         return r;
    }


    4.运行情况详细分析

    现在以查找第 i = 17小的元素来看OS-SELECT的执行过程。


        |                                                                                                                                                    |
        |                                                                                                                                                    |
    [  3, 7,  10, 12, 14,14, 16, 17, 19,   20,21,      21, 26, 28,       30,35, 38, 39,41,        47]

    1.从根结点26开始,其顺序统计量 r = 26左子结点size + 1 = 13 < i,则递归OS-SELECT(26右子结点, i - r),
    即OS-SELECT(结点41,4)。

    [  3, 7,  10, 12, 14,14, 16, 17, 19,   20,21,      21, 26, 28,       30,35, 38, 39,41,        47]

    2.根结点变为41,i = 4,r = 5 + 1 = 6 > i,递归OS-SELECT(41的左子结点, i ),即OS-SELECT(结点30, 4)。

     3, 7,  10, 12, 14,14, 16, 17, 19,   20,21,      21, 26, 28,       30,35, 38, 39,41,        47]

    3.根结点变为30,i = 4,r = 1 + 1 = 2 < i,递归OS-SELECT(结点38, 2)。

    [  3, 7,  10, 12, 14,14, 16, 17, 19,   20,21,      21, 26, 28,       30,35, 38, 39,41,        47]

    4.根结点变为38,i = 2,r = 1 + 1 = 2 == i,找到了!结点38即为顺序统计量17的元素。

     3, 7,  10, 12, 14,14, 16, 17, 19,   20,21,      21, 26, 28,       30,35, 38, 39,41,        47]

    看到OS-SELECT的实现了吧,跟第九章的RANDOMIZED-SELECT多么相似。从根结点开始遍历,
    首先计算根节点的顺序统计量设为r,若比 i 小则继续处理根节点的左子树,否则处理右子树。
    我们可以将每次递归处理的根节点node看做RANDOMIZED-PARTITION返回的划分元素A[q],
    若 i 比A[q]的顺序统计量小则继续处理较小的数组部分,否则处理大的那部分。如出一辙!



  • 相关阅读:
    Git初探
    ERROR:column &quot;rolcatupdate&quot; does not exist
    Android Studio SVN使用和VisualSVN-Server配置(图解)
    你了解实时计算吗?
    AndroidAnnotations使用说明书—AndroidAnnotations是怎样工作的?
    ios_webView
     paip.android环境搭建与开发事例
    【转】JNI学习积累之一 ---- 常用函数大全
    【转】NI语法 JNI参考 JNI函数大全
    【转】Android 学习笔记——利用JNI技术在Android中调用、调试C++代码
  • 原文地址:https://www.cnblogs.com/xiaomaohai/p/6157848.html
Copyright © 2020-2023  润新知