数据结构(链表-链栈-二叉排序树BST-平衡二叉树AVL-红黑树-B树)
引言
本文的主要内容为线性链表、链栈、二叉排序树、平衡二叉树等几种数据及C++实现,主要参考了李长河主编的《C++程序设计》教材,其中线性链表、链栈、二叉排序树的代码可从该主编的GitHub上的第8.3节处获取:https://github.com/Changhe160/book-cplusplus/tree/master/code。
本文记录如有错误,万望指正。笔者的邮箱为:wuxiaofang555555@163.com 。本文的代码亦可以从笔者的GitHub上获取:https://github.com/wuerfang/data_structure。
线性链表
线性链表
-
也称为单链表(
singly linked list
),每个数据元素占用一个结点(node
),链表(SList
)由多个结点组成 -
结点(
node
)含一个数据域和一个指针域,其中指针域存放下一个结点的地址 -
链表(
SList
)含有一个头指针(head
)、尾指针(tail
)、多个结点;如下所示
结点 :Node
template<typename T>
class Node {
T m_data;
Node *m_next = nullptr;
friend class SList<T>; //将SList声明为Node的友元
public:
Node(const T &val) :m_data(val) { }
Node(const Node &) = delete; //禁止复制
Node& operator=(const Node &) = delete; //禁止赋值
const T& data() const { return m_data; }
T& data() { return m_data; }
Node* next() { return m_next; }
};
单链表C++代码:SList
template<typename T>
class SList {
Node<T> *m_head = nullptr, *m_tail = nullptr;
public:
SList() = default;
~SList();
SList(const SList &) = delete; //禁止复制
SList& operator=(const SList &) = delete;//禁止赋值
void clear();
void push_back(const T &val);
Node<T>* insert(Node<T> *pos, const T &val);
void erase(const T &val);
Node<T>* find(const T &val);
friend std::ostream& operator<< <T>(std::ostream &os, const SList<T> &list);
};
template<typename T>
SList<T>::~SList() {
clear();
}
template<typename T>
void SList<T>::clear() {
Node<T> *p = nullptr;
while (m_head != nullptr) {
p = m_head;
m_head = m_head->m_next;
delete p;
}
m_tail = nullptr;
}
template<typename T>
std::ostream& operator<<(std::ostream &os, const SList<T> &list) {
Node<T> *p = list.m_head;
while (p != nullptr) {
os << p->data() << " ";
p = p->next();
}
return os;
}
插入结点
template<typename T>
void SList<T>::push_back(const T &val) {
Node<T>* node = new Node<T>(val);
if (m_head == nullptr) {
m_head = m_tail = node;
}
else {
m_tail->m_next = node;
m_tail = node;
}
}
template<typename T>
Node<T>* SList<T>::insert(Node<T> *pos, const T &val) {
Node<T>* node = new Node<T>(val);
if (pos == m_tail) { //判断插入的位置是否为尾结点
m_tail->m_next = node;
m_tail = node;
}
else {
node->m_next = pos->m_next;
pos->m_next = node;
}
return node;
}
删除结点
template<typename T>
void SList<T>::erase(const T &val) {
Node<T> *p = m_head, *q = p;
while (p != nullptr&&p->m_data != val) {
q = p;
p = p->m_next;
}
if (p)
q->m_next = p->m_next;
if (p == m_tail)
m_tail = q;
if (p == m_head && p)
m_head = p->m_next;
delete p;
}
查找元素
template<typename T>
Node<T>* SList<T>::find(const T &val) {
Node<T> *p = m_head;
while (p != nullptr && p->m_data != val) {
p = p->m_next;
}
return p;
}
链栈
栈(Stack)的结构
Node
: 链栈的结点与前述线性链表的相同
链栈C++代码:Stack
-
Stack
template<typename T> class Stack { Node<T> *m_top = nullptr; public: Stack() = default; ~Stack(); Stack(const Stack &) = delete; //禁止复制 Stack& operator=(const Stack&) = delete; //禁止赋值 void clear(); //清除链栈 bool empty() { return m_top == nullptr; } void push(const T &val); //压栈 void pop(); //弹栈 const T& top(){ return m_top->m_data; }//获得栈顶元素 }; template<typename T> Stack<T>::~Stack() { clear(); } template<typename T> void Stack<T>::clear() { Node<T> *p = nullptr; while (m_top != nullptr) { p = m_top; m_top = m_top->m_next; delete p; } } template<typename T> void Stack<T>::push(const T &val) { Node<T> *node = new Node<T>(val); node->m_next = m_top; m_top = node; } template<typename T> void Stack<T>::pop() { Node<T> *p = m_top; m_top = m_top->m_next; delete p; }
二叉树
二叉树的结构
结点:Node
- 包括一个数据域和两个指针域
template<typename T>
class Node {
T m_data;
Node *m_left = nullptr, *m_right = nullptr;
public:
Node(const T &val) :m_data(val) { }
Node(const Node &) = delete;
Node& operator=(const Node &) = delete;
const T& data() const { return m_data; }
T& data() { return m_data; }
Node* left() { return m_left; }
Node* right() { return m_right; }
};
二叉排序树
二叉排序树
二叉排序树(Binary Sort Tree
), 亦称为二叉搜索树(Binary Search Tree
)、二叉查找树
- 根结点的键值,大于左子树上的键值,小于右子树上的键值
- 中序遍历可以获得从小到大的键值序列,利于查找
删除结点
-
第一种情况:结点为叶子节点,直接删除
-
第二种情况:结点只有左子树或只有右子树,删除该结点,将其左子树或右子树直接替代它的位置
-
第三种情况:结点既有左子树又有右子树,找到它的中序遍历的前序或后继,用其替换该节点,然后删除前驱或是后继。
-
找该结点的后继结点的方法
- 第一种情况:该结点有右子树,则后继为右子树的最左结点
- 第二种情况:该结点没有右子树,且其为其父亲结点的左结点,则后继为其父亲结点
- 第三种情况:该结点没有右子树,且其为其父亲结点的右结点,沿着父节点寻找,直到找到 一个结点为其父亲结点的左结点,则所寻找的后继为该父亲结点,
-
找该结点的前驱结点的方法
- 第一种情况:该结点有左子树,则前驱为左子树的最右结点
- 第二种情况:该结点没有左子树,且其为其父亲结点的右结点,则前驱为其父亲结点
- 第三种情况:该结点没有左子树,且其为其父亲结点的左结点,沿着父节点寻找,直到找到 一个结点为其父亲结点的右结点,则所寻找的前驱为该父亲结点
-
二叉树搜索C++代码:BSTree
-
BSTree
template<typename T> class BSTree { Node<T> *m_root; public: BSTree() = default; ~BSTree() { destory(m_root); } Node<T>* root() { return m_root; } Node<T>* insert(const T &val) { return insert_(m_root, val); } Node<T>* search(const T &val) { return search_(m_root, val); } void inOrder() { inOrder_(m_root); } private: void destory(Node<T> *p); Node<T>* insert_(Node<T> * &p, const T &val); Node<T>* search_(Node<T> *p, const T &val); void inOrder_(Node<T> *p); void visit(T &val) { std::cout << val << " "; } }; template<typename T> void BSTree<T>::destory(Node<T> *p) { while (p != nullptr) { destory(p->m_left); destory(p->m_right); delete p; } } template<typename T> Node<T>* BSTree<T>::insert_(Node<T> * &p, const T &val) { if (p == nullptr) //找到插入的位置,插入结点,若new失败,std::nothrow能够保证返回空指针 return p = new (std::nothrow) Node<T>(val); else if (val < p->m_data) //从左子树中查找 return insert_(p->m_left, val); else //从左子树中查找 return insert_(p->m_right, val); } template<typename T> Node<T>* BSTree<T>::search_(Node<T> *p, const T &val) { while (p != nullptr && val != p->m_data) { if (val < p->m_data) p = p->m_left; else p = p->m_right; } return p; } template<typename T> void BSTree<T>::inOrder_(Node<T> *p) { if (p != nullptr) { inOrder_(p->m_left); visit(p->m_data); inOrder_(p->m_right); } }
平衡二叉树(AVL树)
平衡二叉树
- 平衡二叉树(
Self-Balancing Binary Search Tree
或Height-Balancing Binary Search Tree
)- 是一种特殊的二叉排序树,其中每个节点的左子树和右子树的高度差至多为1,是高度平衡的
平衡因子BF
(Balance Factor
)指结点的左子树深度减去右子树深度的值,AVL树上的结点的平衡因子只可能为 1 , 0 , − 1 1,\; 0,\; -1 1,0,−1
- 平衡二叉树查找的时间复杂度优于二叉排序树,其查找、删除和插入的时间复杂度均为 O ( l o g n ) O(logn) O(logn)
结点:Node
-
多了一个平衡因子
template<typename T> class Node { T m_data; Node *m_left = nullptr, *m_right = nullptr; int m_bf; //平衡因子 friend class BalanceBinaryTree<T>; public: Node(const T &val) :m_data(val), m_bf(0) { } Node(const Node &) = delete; Node& operator=(const Node &) = delete; const T& data() const { return m_data; } T& data() { return m_data; } const int& bf() const { return m_bf; } int& bf() { return m_bf; } Node* left() { return m_left; } Node* right() { return m_right; } };
插入节点
- 插入结点时,为了保持平衡性,需要旋转操作(平衡调整),共有以下四种旋转方式
平衡二叉树C++代码
BanlanceBinaryTree
(这里只给出了插入操作,即创建平衡二叉树的必要函数)
template<typename T>
class BalanceBinaryTree {
Node<T> *m_root = nullptr; //根结点
public:
BalanceBinaryTree() = default;
void insert(const T &val);
void insert_(Node<T> *& root, Node<T> *node); //将指针S所指节点插入二叉排序中
int getDepth(Node <T> * T); //求树的高度
int getNodeFactor(Node<T> *node); //求树中节点的平衡因子
void bfForTree(Node<T> *&root); //求树中的每个节点的平衡因子
void bfIsTwo(Node<T> *&root, Node<T> *&f, Node<T>* &p);
void LLAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f); //LL调整
void LRAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f); //LR调整
void RLAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f); //RL调整
void RRAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f); //RR调整
void allAdjust(Node<T> *&root); //集成四种调整,并实时更新平衡因子
};
template<typename T>
void BalanceBinaryTree<T>::insert(const T &val) {
Node<T>* node = new Node<T>(val);
insert_(m_root, node);
allAdjust(m_root);
}
template<typename T>
void BalanceBinaryTree<T>::insert_(Node<T> *& root, Node<T> * node) {
if (root == nullptr)
root = node;
else if (node->m_data<root->m_data)
insert_(root->m_left, node);
else
insert_(root->m_right, node);
}
template<typename T>
int BalanceBinaryTree<T>::getDepth(Node<T> * root) {
if (root == nullptr)
return 0;
else {
int ldepth = getDepth(root->m_left);
int rdepth = getDepth(root->m_right);
int depth = ldepth > rdepth ? ldepth : rdepth;
depth += 1;
return depth;
}
}
template<typename T>
int BalanceBinaryTree<T>::getNodeFactor(Node<T> *node) {
int ldepth = 0, rdepth = 0;
if (node) {
ldepth = getDepth(node->m_left);
rdepth = getDepth(node->m_right);
}
return ldepth - rdepth;
}
template<typename T>
void BalanceBinaryTree<T>::bfForTree(Node<T> *&root) {
if (root) {
root->m_bf = getNodeFactor(root);
bfForTree(root->m_left);
bfForTree(root->m_right);
}
}
template<typename T>
void BalanceBinaryTree<T>::bfIsTwo(Node<T> *&root, Node<T> *&f, Node<T>* &p) {
if (root) {
if (root->m_left != nullptr) {
if (root->m_left->m_bf == 2 || root->m_left->m_bf == -2) {
f = root;
p = root->m_left;
}
}
if (root->m_right != nullptr) {
if (root->m_right->m_bf == 2 || root->m_right->m_bf == -2) {
f = root;
p = root->m_right;
}
}
bfIsTwo(root->m_left, f, p);
bfIsTwo(root->m_right, f, p);
}
}
template<typename T>
void BalanceBinaryTree<T>::LLAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f) {
Node<T> *r;
if (root == p) {
root = p->m_left; //将P的左孩子提升为新的根节点
r = root->m_right;
root->m_right = p; //将p降为其左孩子的右孩子
p->m_left = r; //将p原来的左孩子的右孩子连接其p的左孩子
}
else {
if (f->m_left == p) { //f的左孩子是p
f->m_left = p->m_left; //将P的左孩子提升为新的根节点
r = f->m_left->m_right;
f->m_left->m_right = p; //将p降为其左孩子的右孩子
p->m_left = r; //将p原来的左孩子的右孩子连接其p的左孩子
}
if (f->m_right == p){ //f的左孩子是p
f->m_right = p->m_left; //将P的左孩子提升为新的根节点
r = f->m_right->m_right;
f->m_right->m_right = p; //将p降为其左孩子的右孩子
p->m_left = r; //将p原来的左孩子的右孩子连接其p的左孩子
}
}
}
template<typename T>
void BalanceBinaryTree<T>::LRAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f){
Node<T> *l, *r;
if (root == p) { //->m_bf==2&&root->m_left->m_bf!=2
root = p->m_left->m_right; //将P的左孩子的右孩子提升为新的根节点
r = root->m_right;
l = root->m_left;
root->m_right = p;
root->m_left = p->m_left;
root->m_left->m_right = l;
root->m_right->m_left = r;
}
else {
if (f->m_right == p) { //f的左孩子是p
f->m_right = p->m_left->m_right; //将P的左孩子的右孩子提升为新的根节点
r = f->m_right->m_right;
l = f->m_right->m_left;
f->m_right->m_right = p;
f->m_right->m_left = p->m_left;
f->m_right->m_left->m_right = l;
f->m_right->m_right->m_left = r;
}
if (f->m_left == p) { //f的左孩子是p
f->m_left = p->m_left->m_right; //将P的左孩子的右孩子提升为新的根节点
r = f->m_left->m_right;
l = f->m_left->m_left;
f->m_left->m_right = p;
f->m_left->m_left = p->m_left;
f->m_left->m_left->m_right = l;
f->m_left->m_right->m_left = r;
}
}
}
template<typename T>
void BalanceBinaryTree<T>::RLAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f)
{
Node<T> *l, *r;
if (root == p) { //->m_bf==-2&&root->m_right->m_bf!=-2
root = p->m_right->m_left;
r = root->m_right;
l = root->m_left;
root->m_left = p;
root->m_right = p->m_right;
root->m_left->m_right = l;
root->m_right->m_left = r;
}
else {
if (f->m_right == p){ //f的左孩子是p
f->m_right = p->m_right->m_left;
r = f->m_right->m_right;
l = f->m_right->m_left;
f->m_right->m_left = p;
f->m_right->m_right = p->m_right;
f->m_right->m_left->m_right = l;
f->m_right->m_right->m_left = r;
}
if (f->m_left == p) { //f的左孩子是p
f->m_left = p->m_right->m_left;
r = f->m_left->m_right;
l = f->m_left->m_left;
f->m_left->m_left = p;
f->m_left->m_right = p->m_right;
f->m_left->m_left->m_right = l;
f->m_left->m_right->m_left = r;
}
}
}
template<typename T>
void BalanceBinaryTree<T>::RRAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f) {
Node<T> *l;
if (root == p){ //->m_bf==-2&&root->m_right->m_bf!=-2
root = p->m_right; //将P的右孩子提升为新的根节点
l = root->m_left;
root->m_left = p; //将p降为其右孩子的左孩子
p->m_right = l; //将p原来的右孩子的左孩子连接其p的右孩子
//注意:p->m_right->m_bf==0插入节点时用不上,删除节点时可用
}
else {
if (f->m_right == p){ //f的右孩子是p
f->m_right = p->m_right; //将P的右孩子提升为新的根节点
l = f->m_right->m_left;
f->m_right->m_left = p; //将p降为其右孩子的左孩子
p->m_right = l; //将p原来的右孩子的左孩子连接其p的右孩子
}
if (f->m_left == p) { //f的左孩子是p
f->m_left = p->m_right; //将P的左孩子提升为新的根节点
l = f->m_left->m_left;
f->m_left->m_left = p; //将p降为其左孩子的左孩子
p->m_right = l; //将p原来的右孩子的左孩子连接其p的右孩子
}
}
}
template<typename T>
void BalanceBinaryTree<T>::allAdjust(Node<T> *&root) {
Node<T> *f = nullptr, *p = nullptr;
bfForTree(root);
bfIsTwo(root, f, p);
while (p) {
bfForTree(root);
if (p->m_bf == 2 && (p->m_left->m_bf == 1 || p->m_left->m_bf == 0)) {
LLAdjust(root, p, f);
bfForTree(root);
}
else if (p->m_bf == 2 && p->m_left->m_bf == -1) {
LRAdjust(root, p, f);
bfForTree(root);
}
else if (p->m_bf == -2 && p->m_right->m_bf == 1) {
RLAdjust(root, p, f);
bfForTree(root);
}
else if (p->m_bf == -2 && (p->m_right->m_bf == -1 || p->m_right->m_bf == 0)) {
RRAdjust(root, p, f);
}
f = nullptr;
p = nullptr;
bfIsTwo(root, f, p);
}
}
- 测试代码
int main() {
BalanceBinaryTree<int> tree;
std::vector<int> v = { 4,5,6,3,2,1,9,8};
for (int i = 0; i < v.size(); ++i) {
tree.insert(v[i]);
}
return 0;
}
红黑树
红黑树概述
- 是一种特殊的二叉排序树,每个结点增加一个存储位表示结点的颜色(红色或黑色),是一种弱平衡二叉树,相对于AVL树,它的旋转此=次数少,所以对于搜索、插入、删除操作较多的情况下,通常使用红黑树。如图即为一颗红黑树。
红黑树性质
- 1.结点是红色或是黑色(插入的结点默认为红色)
- 2.根结点是黑色
- 3.每个叶子的结点都是黑色的空结点(null)
- 4.每个红色结点的两个子节点都是黑色的(即不会出现连续的红结点)
- 5.从任意结点到其每个叶子的所有路径都包含相同的黑色结点(即每一条路径上的黑色结点的数量相同)
可推导出性质
- 红黑树中最长路径中结点个数不会超过最短路径节点个数的两倍
- 每一条路径上的黑色结点的数量相同
红黑树较AVL树的优点
- AVL 树是高度平衡的,频繁的插入和删除,会引起频繁的保持平衡操作,导致效率下降;红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转。
- 红黑树在查找,插入删除的性能都是O(logn),且性能稳定,所以STL里面很多结构包括map底层实现都是使用的红黑树。
结点:RBTNode
-
RBTNode
enum Color{RED,BLACK};//标记红黑树的颜色 template<typename T> class RBTNode { T m_data; RBTNode *m_left = nullptr; RBTNode *m_right = nullptr; RBTNode *m_parent = nullptr; Color m_color = RED; public: RBTNode(const T &val) :m_data(val) { } };
插入结点
(插入的位置根据BST方式获得)
-
第一种情况:父为
红
,叔为红
,祖父为黑
,变色
-
第二种情况:父为
红
,叔为黑
(或不存在),祖父为黑
,单旋转-变色
,此处的旋转结点为祖父- 子和父均为左结点:
右旋转-变色
- 子和父均为右结点:
左旋转-变色
- 子和父均为左结点:
-
第三种情况:为第二种情况的特殊情况,
复合旋转-变色
- 父为左结点,子为右结点:
先左旋(旋转点为父结点)-再右旋(旋转点为祖父结点)-变色
- 父为右结点,子为左结点:
先右旋(旋转点为父结点)-再左旋(旋转点为祖父结点)-变色
- 父为左结点,子为右结点:
删除结点
-
第一种情况:无子节点时,删除节点可能为红色或者黑色;
- 如果为红色,直接删除即可,不会影响黑色节点的数量;
- 如果为黑色,则需要进行删除平衡的操作了;
-
第二种情况:只有一个子节点时,删除节点只能是黑色,其子节点为红色,否则无法满足红黑树的性质了。 此时用删除节点的子节点接到父节点,且将子节点颜色涂黑,保证黑色数量。
-
第三种情况:有两个子节点时,与二叉搜索树一样,使用后继节点作为替换的删除节点,情形转至为前两种情况。
B树、B+树
B树(B-tree)
- B树是一种平衡的多路查找树,2-3树和2-3-4树都是B树的特例。结点最大的孩子数目称为B树的阶(order),故2-3树的阶为3,2-3-4树的阶为4。
- B树的数据结构是为内外存(内存和硬盘(磁盘))的数据的交互准备的
- 一个m阶的B树具有如下属性
- 如果根结点不是叶子叶子结点,则其至少有两颗子树
- 每个非根的分支结点都有k-1个元素和k个孩子,每一个叶子结点都有k-1个元素,其中 ⌈ m / 2 ⌉ ≤ k ≤ m \lceil m/2 \rceil \leq k \leq m ⌈m/2⌉≤k≤m
- 所有叶子结点都位于同一层次
- 所有分支结点包含下列信息数据
(
n
,
A
0
,
K
1
,
A
1
,
K
2
,
A
2
,
.
.
.
,
K
n
,
A
n
)
(n,A_0,K_1,A_1,K_2,A_2,...,K_n,A_n)
(n,A0,K1,A1,K2,A2,...,Kn,An) ,其中
n
n
n为关键字个数、
K
i
K_i
Ki为关键字、
A
i
A_i
Ai为指向子树根节点的指针
B+树
- B+树是为了解决所有元素遍历等问题,在B树原有基础上添加新的元素组织方式;B+树是应文件系统所需要而出的一种B树的变形树。
- B+树和B树的差异
- 有n个子孩子的结点中包含n个关键字
- 所有的叶子结点包含全部关键字的信息,及指向含这些关键字机理的指针,叶子结点本身一关键字的大小自小到大顺序连接
- 所有分支可以看成是索引,结点中仅含有其子树中的最大(或最小)关键字