【有序表实现ADT-ONE:AVL树静态模板实现】

有序表介绍

一.有序表概述

有序表可以理解C++/Java中的抽象类; \从数据结构角度出发,
它是一种抽象数据结构。它提供实现功能需求,但没有要求严格的具体实现。

对比熟悉无序表(通常是哈希表);
哈希表特点,它的关键字 k e y key key不是有序组织的, 它通过哈希函数打乱离散分布的结果。
有序表实现的方式就是花费一些代价, 将这些关键字key顺序组织起来, 对比无序表它可以实现丰富有序的操作。

若你没有听说过有序表的概念, 那么看完上面的文字, 大概对有序表的实现有概念了吧。
比方说,尝试用一个数组有序的组织数据, 或者有序维护单向双向链表等等。
答案是否定的, 有序表还有一个特点, 它必须保证足够高的效率。
具体, 保证增删查改操作均达到 O ( l o g n ) O(logn) O(logn)的水平, 有序数组增删操作最坏是 O ( n ) O(n) O(n),有序链表查询操作最坏是 O ( n ) O(n) O(n)

数据结构的抽象描述是这样的:元素按一定顺序(一般非降序或非升序)排列、且可支持高效检索或操作的一类线性表(或广义表).

二.实现有序表的数据结构

常见实现有序表的数据结构:

  1. 有序数组: 尽管增删 O ( n ) O(n) O(n), 但支持二分查找 O ( l o g n ) O(logn) O(logn)。 实现简单,多查少改场景适用。
  2. 链表(跳表):单个有序链表组织没有查询优势,但层级有序链表即跳表, 实现全 O ( l o g n ) O(logn) O(logn)操作。
  3. (自)平衡树结构:数据结构两大阴间->AVL树,红黑树. 竞赛版:替罪羊树,Treap,FHQ Treap,Splay树; 其它的比如数据库中的B树,B*变种,2-3树,2-3-4树这些。有些场景线段树和树状数组也可以充当有序表。

前言

本篇介绍AVL树的算法模板静态数组实现, 有关于类模板(动态空间使用,释放)实现笔者暂时不更。
笔者会不断完善本篇内容, 请各位不吝赐教。

前置知识: 数据结构-二叉搜索树
编程语言:C++. 后续会补充Java版本.

本文基于如下链接实现的AVL树
Luogu3369:普通平衡树

实现了如下操作:
插入、删除、获取关键字的排名、获取排名的关键字,查找前驱和后继等基础功能.
本篇实现了有序表集而非有序表映射, 笔者可以根据下面代码扩展对应的功能。


正文部分


概述

二叉搜索树的时间复杂度依赖高度 O ( h ) O(h) O(h), 最坏是 O ( n ) O(n) O(n).
能否实现一棵自动平衡的树, 将高度h控制在下界 l o g n logn logn.
AVL树是最早提出满足这种要求的树。 它对平衡性的定义非常严格。

针对一般的二叉搜索树, 若采用动态实现的方式, 那么在经过插入或者删除可能会破坏树的平衡性。AVL树通过旋转这种检查调整的操作, 每次插入和删除都会进行这样的检测并将树调整平衡。
理解旋转操作是实现AVL树的重点。

数据结构与全局变量

const static int N = 1e6+10;//数据范围

int head = 0; // AVL树的根节点
int cnt; // 当前最后使用过节点的编号
int key[N]; // 节点的值
int height[N]; // 节点的高度
int leftChild[N]; // 节点的左子节点
int rightChild[N]; // 节点的右子节点
int key_count[N]; // 节点值的重复次数
int size[N]; // 子树大小
  • head: AVL 树的根节点编号。
  • cnt: 用于分配新的节点编号。
  • key: 存储每个节点的值。
  • height: 存储每个节点的高度。
  • leftChildrightChild: 存储每个节点的左子节点和右子节点的编号。
  • key_count: 处理重复元素,记录每个值的出现次数。
  • size: 记录每个节点所在子树的总节点数(包括重复计数)。

下面我解释一下为什么引入某些数组?

  1. 由于本题要求记录关键字, 但不需要存储键值对。 因此不需要映射关系的value数组。
  2. 由于AVL树的定义,需要记录当前节点的树高信息来维护平衡性, 需要height数组。
  3. 本题可以重复添加关键字。 针对重复关键字可以采取词频统计的方式。避免了为重复关键字分配节点编号。 引入key_count数组。
  4. 本题要计算排名引入一个size数组记录以当前节点为根节点的子树大小可以简化计算。
  5. 采用递归实现,因此不需要parent数组记录每个节点的双亲节点编号。

API

以下是下面内容介绍实现的函数

//函数声明

//up整合子树信息更新当前头节点
void up(int i);
//左旋操作
int leftRotate(int i);
//右旋操作
int rightRotate(int i);
//查询当前值的排名
int getRank(int val);
//查询当前值的排名: 辅助函数
int getRank(int i,int val);
//查询排名为x的关键字
int getIndex(int x);
//查询排名为x的关键字:辅助函数
int getIndex(int i,int x);

//获取节点的前驱节点关键字
int pre(int i);
//pre的辅助函数
int pre(int i,int num);

//获取节点的后继节点关键字
int post(int i);
//post的辅助函数
int post(int i,int num);

//添加元素, 若有多个则增加词频。
void add(int val);
int add(int i,int val);

//删除元素, 若有多个则减少一个词频即可
void remove(int val);
int remove(int i,int val);

辅助函数

更新节点信息 (up)

//更新size,height信息。 根据孩子更新信息
void up(int i){
   
  size[i] = size[leftChild[i]]+size[rightChild[i]]+key_count[i];
  height[i] = max(height[leftChild[i]], height[rightChild[i]]) + 1;
}
  • 功能: 根据当前节点的左右子节点信息,更新当前节点的 sizeheight
  • 参数: i - 当前节点编号。

左旋操作 (leftRotate)

左旋操作
左旋操作的理解要点是:
节点i和它右孩子的颠倒父子关系; 同时这种操作不会破坏二叉搜索树的性质;理解这过程左右树高的变化。

如下代码流程:

  1. 定义变量r, r=rightChild[i]
  2. r的左孩子成为i的右孩子。(i对r的单向父亲关系解除了)
  3. 让i成为r的左孩子(r对i绑定父子关系)。
  4. i在r下面, 先对下面的i整合信息更新 u p ( i ) up(i) up(i), 再对 u p ( r ) up(r) up(r)的正确。
  5. 由于原先以i节点为根的子树变成r为根节点, 向上返回r为左旋后的新节点。
int leftRotate(int i){
   
  int r = rightChild[i];
  rightChild[i] = leftChild[r];
  leftChild[r] = i;
  up(i);
  up(r);
  return r;
}
  • 功能: 对节点 i 进行左旋,保持 AVL 树的平衡。
  • 返回值: 旋转后新的根节点编号。

右旋操作 (rightRotate)

右旋操作
右旋操作的理解要点和左旋操作一致。 可以视作左旋的对称过程。

int rightRotate(int i){
   
  int l = leftChild[i];
  leftChild[i] = rightChild[l];
  rightChild[l] = i;
  up(i);
  up(l);
  return l;
}
  • 功能: 对节点 i 进行右旋,保持 AVL 树的平衡。
  • 返回值: 旋转后新的根节点编号。

维护平衡 —四种旋转方式(maintain)

int maintain(int i){
   
  int lh = height[leftChild[i]];
  int rh = height[rightChild[i]];
  if(lh - rh > 1){
   
    // 左子树高,需要右旋
    if(height[leftChild[leftChild[i]]] >= height[rightChild[leftChild[i]]]){
   
      i = rightRotate(i);
    } else {
   
      leftChild[i] = leftRotate(leftChild[i]);
      i = rightRotate(i);
    }
  }
  else if(rh - lh > 1){
   
    // 右子树高,需要左旋
    if(height[rightChild[rightChild[i]]] >= height[leftChild[rightChild[i]]]){
   
      i = leftRotate(i);
    } else {
   
      rightChild[i] = rightRotate(rightChild[i]);
      i = leftRotate(i);
    }
  }
  return i;
}
  • 功能: 检查并维护节点 i 的平衡,通过旋转操作确保 AVL 树的性质。
  • 返回值: 维护平衡后的节点编号。

主要操作函数

插入操作 (add)

int add(int i, int val){
   
  if(i == 0){
   
    key[++cnt] = val;
    height[cnt] = key_count[cnt] = size[cnt] = 1;
    return cnt;
  }
  if(key[i] == val){
   
    key_count[i]++;
  }
  else if(key[i] > val){
   
    leftChild[i] = add(leftChild[i], val);
  }
  else{
   
    rightChild[i] = add(rightChild[i], val);
  }
  up(i);
  return maintain(i);
}

void add(int val){
   
  head = add(head, val);
}
  • 功能: 向 AVL 树中插入值 val
  • 参数:
    • val - 要插入的值。
  • 说明:
    • 如果当前节点为空,则创建新节点。
    • 如果值已存在,增加 key_count
    • 根据值的大小决定插入到左子树还是右子树。
    • 更新节点信息并维护平衡。

删除操作 (remove)

int removeMostLeft(int i, int mostLeft){
   
  if(i == mostLeft){
   
    return rightChild[mostLeft];
  } else {
   
    leftChild[i] = removeMostLeft(leftChild[i], mostLeft);
    up(i);
    return maintain(i);
  }
}

int remove(int i, int val
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值