数据结构
数据在内存中的存储方式(存储结构)
程序 = 数据 + 算法
算法 = 操作的步骤
- 算法的时间复杂度
- 动态数组
- 链表
- 迭代器
- 栈和队列
- 二叉搜索树
- 二叉平衡树
- 哈希表
1. 时间复杂度
考量算法的时间复杂度有一个前提就是控制变量, 语句的执行时间相同, 数据的样本量相同……
算法的时间复杂度为大O记法, 通常分为以下时间复杂度
1.1 常见阶数及图像
- 常量阶 O(1)
- 对数阶 O(logn)
- 线性阶 O(n)
- 线性对数阶 O(nlogn)
- 平方阶 O(n^2)
- 立方阶 O(n^3)
- 指数阶 O(2^n)
在对比花费时间可以通过函数图像观察, 增长率越高的算法时间复杂度越高
注意: 当我们面临两个算法, 时间短空间大和时间长空间小
选择算法时, 我们优先选择时间短空间大的算法, 用空间换时间
在嵌入式等各别领域可能需要更节省的空间, 其余的都优选时间短的
3.2 示例
假设每条语句的执行时间是t
1.
void Foo()
{
int i = 1; // 1t
int j = 2; // 1t
++i;// 1t
j++;// 1t
int m = i + j;//1t
}
执行总时间: 5t
复杂度: O(1)
2.
int Foo(int n)
{
int j = 0; // 1t
for(i=1; i<=n; ++i)//(1+2n)t
{
j *= i; //nt
}
return j*n;//1t
}
执行总时间: (3n+3)t
复杂度: O(n)
3.
int Foo(int n)
{
for(x=1; x<=n; x++)//(2n+1)t
{
for(i=1; i<=n; i++)//(2n+1)*nt
{
j = i;//n*nt
j++; //n*nt
}
j*=2;//nt
}
int j*n; //1t
}
执行总时间: (4n*n+4n+2)t
复杂度: O(n)
4. //归纳推理
void Foo(int n)
{
int i = 1;
while(i<n)
{
i = i * 2;
}
}
时间复杂度: O(logn)
5.
void Foo(int n)
{
for(m=1; m<n; m++)
{
i = 1;
while(i<n)
{
i = i * 2;
}
}
}
时间复杂度: O(nlogn)
void sum(int a[],int n)
{
int s=0,i; // 1
for(i=0;i<n;i++) //
{
s=s+a[i];//
}
printf(“%d”,s); // 1
}
时间复杂度: O(n)
求解:
long sum_of_fac(int n)
{
long s=0,t,i,j; //1
for(i=1;i<=n;i++) //2n+1
{
t=1; //
for(j=1;j<=i;j++) //
t*=j; //
s+=t; //
}
return (s); //
}
时间复杂度: O(n^2)
void sum(int m,int n)
{
int i,j,s=0; //
for(i=1;i<=m;i++) //
{
for(j=1;j<=n;j++) //
{
s++; //
printf(“%d”,s); //
}
}
}
时间复杂度: O(n^2)
我们在估算时间复杂度时, 根据的是代码的执行次数, 省去最高次幂以外的表达式, 除掉常数, 则为该算法的时间复杂度
1 冒泡排序的C语言算法
// 对数组a中n个数按递增次序作冒泡排序
Void bubble1(int a[],int n)
{
int i,j,temp;
for(i=0;i<n-1;i++) // 次
for(j=0;j<n-1-i;j++) // 次
if (a[j]>a[j+1]) // ? 次
{
temp=a[j]; // ? 次
a[j]=a[j+1]; // ? 次
a[j+1]=temp; // ? 次
}
for(i=0;i<n;i++) //
printf(“%d”,a[i]); //
}
思考:在最好情况下 O(n^2)
在最坏情况下 O(n^2)
2. 冒泡排序的C语言算法
//冒泡排序
void BubbleSort(int a[],int n)
{
int i,j,temp,flag;
for(i=0;i<n-1;i++)
{
flag=1;//表示本趟是否发生交换
for(j=n-1;j>i;j--)
{
if(a[j-1]>a[j])
{
temp=a[j];
a[j]=a[j-1];
a[j-1]=temp;
flag=0;//改变交换标志
}
}
if(flag)
{//表明本趟遍历后没有发生交换
return;//说明已经有序
}
}
思考:在最好情况下 O(n)
在最坏情况下 O(n^2)
2. 动态数组
2.1 数组的操作方法
-
插入
-
头部插入
-
尾部插入
-
指定位置插入
-
-
删除
-
头删
-
尾删
-
指定位置
-
-
修改
-
查询
-
排序
-
访问
-
元素个数
-
清空
// 容器
template<typename T>
class CArray
{
public:
CArray();
CArray(const CArray& obj);
CArray(CArray&& obj);
CArray& operator=(const CArray& obj);
virtual ~CArray();
size_t Size()const;
void Clear();
//增
CArray& Insert(size_t nIdx, const T& val);
CArray& InsertHead(const T& val);
CArray& InsertTail(const T& val);
//删
CArray& Remove(size_t nIdx);
CArray& RemoveHead();
CArray& RemoveTail();
//访问
T operator[](size_t nIdx);
T Front();
T Tail();
//查,返回下标
size_t Find(const T& val);
//排序,true 从小到大 false 从大到小
CArray& Sort(bool bSmallToBig);
private:
void DeepCopy(const CArray<T>& obj);
private:
T* m_pBuf = nullptr; //数组缓冲区
size_t m_nLen = 0; //元素个数
size_t m_nBufSize = 0; //缓冲区大小
};
2.2 操作的代码实现
为了兼容字符串或其他包含指针的数据类型, 在拷贝构造和一些赋值操作是, 我们需要单独将每个元素单个赋值, 用于使用=
运算符重载来完成字符串或指针的赋值操作
使用循环赋值操作代替memcpy()
template<typename T>
CArray<T>::CArray()
{
}
template<typename T>
CArray<T>::CArray(const CArray& obj)
{
DeepCopy(obj);
}
template<typename T>
void CArray<T>::DeepCopy(const CArray<T>& obj)
{
// 检查
if (!m_pBuf)
{
return;
}
m_nLen = obj.m_nLen;
m_nBufSize = obj.m_nBufSize;
m_pBuf = new T[m_nBufSize];
// 拷贝
memcpy(m_pBuf, obj.m_pBuf, sizeof(T) * obj.m_nLen);
}
template<typename T>
CArray<T>::CArray(CArray&& obj)
: m_nLen(obj.m_nLen)
, m_nBufSize(obj.m_nBufSize)
, m_pBuf(obj.m_pBuf)
{
obj.m_pBuf = nullptr;
}
template<typename T>
CArray<T>& CArray<T>::operator=(const CArray<T>& obj)
{
// 判断
if (this == &obj)
{
return *this;
}
// 释放原来的内存
Clear();
DeepCopy(obj);
//++(*m_pCnt);
return *this;
}
template<typename T>
CArray<T>::~CArray()
{
Clear();
}
template<typename T>
size_t CArray<T>::Size() const
{
return m_nLen;
}
template<typename T>
void CArray<T>::Clear()
{
if (m_pBuf != nullptr)
{
delete[] m_pBuf;
m_pBuf = nullptr;
m_nBufSize = 0;
m_nLen = 0;
}
}
template<typename T>
CArray<T>& CArray<T>::Insert(size_t nIdx, const T& val)
{
// 判断参数
if (nIdx > m_nLen)
{
throw std::out_of_range("nIdx is out of range");
}
// 判断内存是否足够
if (m_nLen >= m_nBufSize)
{
// 不够,申请内存
int nNewBufSize = (m_nBufSize + 1) * 2;
T* pNew = new T[nNewBufSize];
// 拷贝原来的数据,释放原内存空间
if (m_pBuf != nullptr)
{
//memcpy(pNew, m_pBuf, sizeof(T) * m_nLen);
for (size_t i = 0; i < m_nLen; i++)
{
pNew[i] = std::move(m_pBuf[i]);
}
delete[] m_pBuf;
}
// 更新
m_nBufSize = nNewBufSize;
m_pBuf = pNew;
}
// 移动元素
//memcpy(m_pBuf + nIdx + 1, m_pBuf + nIdx, sizeof(T) * (m_nLen - nIdx));
for (size_t i = m_nLen; i > nIdx; i--)
{
m_pBuf[i] = std::move(m_pBuf[i - 1]);
}
// 填入新值
m_pBuf[nIdx] = val;
m_nLen++;
return *this;
}
template<typename T>
CArray<T>& CArray<T>::InsertHead(const T& val)
{
return Insert(0, val);
}
template<typename T>
CArray<T>& CArray<T>::InsertTail(const T& val)
{
return Insert(m_nLen, val);
}
template<typename T>
CArray<T>& CArray<T>::Remove(size_t nIdx)
{
// 判断参数
if (nIdx > m_nLen)
{
throw std::out_of_range("nIdx is out of range");
}
// 移动元素
//memcpy(m_pBuf + nIdx, m_pBuf + nIdx + 1, sizeof(T) * (m_nLen - nIdx - 1));
for (size_t i = nIdx; i < m_nLen - 1; i++)
{
m_pBuf[i] = std::move(m_pBuf[i + 1]);
}
// 更新
m_nLen--;
return *this;
}
template<typename T>
CArray<T>& CArray<T>::RemoveHead()
{
return Remove(0);
}
template<typename T>
CArray<T>& CArray<T>::RemoveTail()
{
return Remove(m_nLen - 1);
}
template<typename T>
T CArray<T>::operator[](size_t nIdx)
{
if (m_pBuf == nullptr || nIdx >= m_nLen)
{
throw std::out_of_range("array is empty or nIdx is out of range");
}
return m_pBuf[nIdx];
}
template<typename T>
T CArray<T>::Front()
{
return operator[](0);
}
template<typename T>
T CArray<T>::Tail()
{
return operator[](m_nLen - 1);
}
template<typename T>
size_t CArray<T>::Find(const T& val)
{
size_t nIdx = 0;
while (nIdx < m_nLen && m_pBuf[nIdx] != val)
{
nIdx++;
}
if (nIdx == m_nLen)
{
throw std::out_of_range("out of range");
return -1;
}
return nIdx;
}
template<typename T>
void quick_sort(T q[], int l, int r, bool bSmallToBig)
{
if (l >= r)
{
return;
}
T x = q[l];
int i = l - 1;
int j = r + 1;
if (bSmallToBig)
{
while (i < j)
{
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j)
{
T t = q[i];
q[i] = q[j];
q[j] = t;
}
}
}
else
{
while (i < j)
{
do i++; while (q[i] > x);
do j--; while (q[j] < x);
if (i < j)
{
T t = q[i];
q[i] = q[j];
q[j] = t;
}
}
}
quick_sort(q, l, j, bSmallToBig);
quick_sort(q, j + 1, r, bSmallToBig);
}
template<typename T>
CArray<T>& CArray<T>::Sort(bool bSmallToBig)
{
quick_sort(m_pBuf, 0, m_nLen - 1, bSmallToBig);
return *this;
}
3. 链表
链表与数组比较
-
链表的时间复杂度
- 插入O(1), 删除O(1), 查询O(n), 随机访问O(n)
-
数组的时间复杂度
- 插入O(n), 删除O(n), 查询O(n), 随机访问O(1)
-
占用内存
- 链表的占用内存比数组多,但是插入和删除的效率高,这就是空间换时间的思想
3.1 链表的逻辑
-
结点
- 每个结点包含至少两部分内容,一个是数据,一个是指针
-
链表
- 多个结点链接起来构成链表。
- 链表中的第一个结点称之为头结点,最后一个结点称之为尾结点。
- 链表中保存了指向第一个结点的指针,称之为头结点指针。
-
- 单链表
- 如果结点只有一个指针,指向此结点的后继结点,则这个链表称之为单向链表
- 双链表
- 如果结点有两个指针,一个指向此结点的后继结点,一个指向此结点前驱结点,则这个链表称之为双向链表
- 循环链表
- 如果尾结点的指针指向了头结点,则这个链表称之为循环链表。循环列表分为双向循环链表和单向循环链表
- 单链表
在实现时, 我们需要将这个节点结构使用一个结构体, 包含有当前节点存储的数据, 指向前驱节点的指针和执行后继节点的指针
struct Node
{
int m_val; //数据
Node* m_pre = nullptr; //前驱指针
Node* m_next = nullptr; //后继指针
};
注意: 在结构体中不能包含有结构体类型(递归定义), 此时编译器无法推导出结构体占用的空间大小
当我们对头指针和尾指针操作时, 总是需要改很多的代码去调整头尾指针位置, 于是我们可以设置一个头尾哨兵, 头哨兵的后继指针指向头结点, 尾哨兵的前驱指针指向尾节点, 这样当我们需要操作头尾节点的时候, 可以省去很多步骤
3.2 链表的操作方法
- 插入
- 前插
- 后插
- 头插
- 尾插
- 修改(访问)
- 头部访问
- 尾部访问
- 指定访问
- 删除(断链)
- 头删
- 尾删
- 指定位置
- 清空
- 搜索
- 获取个数
- 是否为空
-
插入
- 插入的逻辑实现, 修改节点的链表指针, 加入新的节点
-
删除
- 删除的逻辑实现
template<typename T>
class CList
{
private:
//节点
struct Node
{
Node() {}
Node(T val) { m_val = val; }
T m_val; //数据
Node* m_pre = nullptr; //前驱指针
Node* m_next = nullptr; //后继指针
};
public:
using POSTION = Node*;
public:
CList(); // 默认构造
CList(const CList<T>& obj); // 拷贝构造
CList(CList<T>&& obj); // 移动构造
virtual ~CList(); // 析构
CList<T>& operator=(const CList<T>& obj); // =运算符重载:深拷贝
CList<T>& operator=(CList<T>&& obj); // =运算符重载:移动拷贝
// 内部接口
size_t Size()const; // 获取个数
bool IsEmpty()const; // 判断是否为空
void Clear();
// 插入
POSTION Insert(POSTION pos, const T& val); // 前插
POSTION InsertHead(const T& val); // 头插
POSTION InsertTail(const T& val); // 尾插
// 删除
CList<T>& Remove(POSTION pos);
CList<T>& RemoveHead();
CList<T>& RemoveTail();
// 访问
T& Front();
T& Tail();
T& Access(POSTION pos);
// 查询
POSTION Find(const T& val);
private:
void DeepCopy(const CList<T>& obj);
void InitGuard();
void MoveList(CList&& obj);
private:
Node* m_pHeadGuard = nullptr; //头哨兵
Node* m_pTailGuard = nullptr; //尾哨兵
size_t m_nSize = 0; //个数
};
3.3 链表的代码实现
五件套
- 无参构造
- 拷贝构造
- 移动构造
- 运算符重载
- 析构
内部接口
- 获取个数
- 判断是否为空
- 清除
功能
- 插入
- 删除
- 访问
- 查询
//--------五件套--------
// 无参构造
template<typename T>
CList<T>::CList()
{
InitGuard();
}
// 拷贝构造
template<typename T>
CList<T>::CList(const CList& obj)
{
InitGuard();
DeepCopy(obj);
}
// 移动构造
template<typename T>
CList<T>::CList(CList&& obj)
{
MoveList(std::move(obj));
}
// 析构
template<typename T>
CList<T>::~CList()
{
// 清空元素
Clear();
// 销毁哨兵
delete m_pHeadGuard;
delete m_pTailGuard;
m_pHeadGuard = nullptr;
m_pTailGuard = nullptr;
m_nSize = 0;
}
// =运算符重载 : 拷贝
template<typename T>
CList<T>& CList<T>::operator=(const CList& obj)
{
// 判断自身
if (this == &obj)
{
return *this;
}
// 清空自身
Clear();
// 深拷贝
DeepCopy();
return *this;
}
// =运算符重载 : 移动
template<typename T>
CList<T>& CList<T>::operator=(CList&& obj)
{
// 清空自身
Clear();
// 移动
MoveList(std::move(obj));
return *this;
}
//--------内部函数--------
// 深拷贝
template<typename T>
void CList<T>::DeepCopy(const CList<T>& obj)
{
Node* pNode = obj.m_pHeadGuard->m_next;
while (pNode != obj.m_pTailGuard)
{
InsertTail(pNode->m_val);
pNode = pNode->m_next;
}
}
// 初始化哨兵节点
template<typename T>
void CList<T>::InitGuard()
{
// 初始化哨兵节点
m_pHeadGuard = new Node();
m_pTailGuard = new Node();
m_pHeadGuard->m_next = m_pTailGuard;
m_pTailGuard->m_pre = m_pHeadGuard;
}
// 移动
template<typename T>
void CList<T>::MoveList(CList&& obj)
{
m_pHeadGuard = obj.m_pHeadGuard;
m_pTailGuard = obj.m_pTailGuard;
m_nSize = obj.m_nSize;
obj.m_pHeadGuard = nullptr;
obj.m_pTailGuard = nullptr;
obj.m_nSize = 0;
}
//--------接口--------
// 获取个数
template<typename T>
size_t CList<T>::Size() const
{
return m_nSize;
}
// 判断是否为空
template<typename T>
bool CList<T>::IsEmpty() const
{
return m_nSize == 0;
}
// 清空
template<typename T>
void CList<T>::Clear()
{
while (!IsEmpty())
{
RemoveTail();
}
}
//--------插入--------
// 默认前插
template<typename T>
typename CList<T>::POSTION CList<T>::Insert(POSTION pos, const T& val)
{
// 参数检查
if (pos == nullptr)
{
throw std::invalid_argument("pos is null");
}
// 创建节点
Node* pNode = new Node(val);
// 插入
Node* pPre = pos->m_pre;
pPre->m_next = pNode;
pos->m_pre = pNode;
pNode->m_pre = pPre;
pNode->m_next = pos;
// 更新长度
++m_nSize;
return pNode;
}
// 头插
template<typename T>
typename CList<T>::POSTION CList<T>::InsertHead(const T& val)
{
return Insert(m_pHeadGuard->m_next, val);
}
// 尾插
template<typename T>
typename CList<T>::POSTION CList<T>::InsertTail(const T& val)
{
return Insert(m_pTailGuard, val);
}
//--------删除--------
// 指定删除
template<typename T>
CList<T>& CList<T>::Remove(POSTION pos)
{
// 参数检查
if (pos == nullptr || IsEmpty())
{
throw std::invalid_argument("pos is null or list is empty");
}
Node* pPre = pos->m_pre;
Node* pNext = pos->m_next;
pPre->m_next = pNext;
pNext->m_pre = pPre;
delete pos;
--m_nSize;
return *this;
}
// 头删
template<typename T>
CList<T>& CList<T>::RemoveHead()
{
return Remove(m_pHeadGuard->m_next);
}
// 尾删
template<typename T>
CList<T>& CList<T>::RemoveTail()
{
return Remove(m_pTailGuard->m_pre);
}
//--------访问--------
// 头部访问
template<typename T>
T& CList<T>::Front()
{
if (IsEmpty())
{
throw std::logic_error("list is empty");
}
return m_pHeadGuard->m_next->m_val;
}
// 尾部访问
template<typename T>
T& CList<T>::Tail()
{
if (IsEmpty())
{
throw std::logic_error("list is empty");
}
return m_pTailGuard->m_pre->m_val;
}
// 指定访问
template<typename T>
T& CList<T>::Access(POSTION pos)
{
if (pos == nullptr || IsEmpty())
{
throw std::invalid_argument("pos is empty or list is empty");
}
return pos->m_val;
}
//--------查询--------
template<typename T>
typename CList<T>::POSTION CList<T>::Find(const T& val)
{
Node* pNode = m_pHeadGuard->m_next;
while (pNode != m_pTailGuard)
{
if (pNode->m_val == val)
{
return pNode;
}
pNode = pNode->m_next;
}
return nullptr;
}
4. 迭代器
统一了所有容器的访问方式
4.1 <list>链表
STL库中的迭代器 (iterator) , 头文件为<list>
#include <list>
int main()
{
list<int> lst;
for (size_t i = 0; i < 10; i++)
{
lst.push_back(i); // 0 1 2 3 4 5 6 7 8 9
}
list<int>::iterator itr = lst.begin(); //头结点的位置
// 运算符重载
*itr = 10; // 访问该节点
itr++; // 取后继节点
*itr = 20;
itr--; // 取前驱节点
*itr = 30;
// 使用迭代器遍历链表
for (auto itr = lst.begin(); itr != lst.end(); ++itr)
{
cout << *itr << endl;
}
itr = lst.end(); //end()指向的是尾哨兵,不能被修改,否则报错断言
--itr;
*itr = 500;
list<int>::reverse_iterator ritr = lst.rbegin();
for (auto ritr = lst.rbegin(); ritr != lst.rend(); ++ritr)
{
cout << *ritr << endl;
}
/*
for (auto itr = lst.begin(); itr != lst.end(); ++itr)
{
int n = *itr;
cout << n << endl;
}
*/
// 范围迭代遍历链表
for (int n : lst)
{
cout << n << endl;
}
return 0;
}
正向
list<int>::iterator itr = lst.begin();
创建一个链表的迭代器itr
, 指向lst
的头结点
begin()
获取头结点, end()
获取尾哨兵
反向(reverse)
list<int>::reverse iterator ritr = lst.rbegin();
创建一个链表的反向迭代器ritr
, 指向lst
的头结点
rbegin()
获取尾节点, rend()
获取头哨兵 —— 用于反向输出
运算符重载
++
: 访问后继节点
--
: 访问前驱节点
*
: 访问该节点数据
4.2 统一接口例子
<vector>动态数组
#include <vector>
int main()
{
// 正向迭代器
vector<int> vct;
for (size_t i = 0; i < 10; i++)
{
vct.push_back(i);
}
// 反向迭代器
vector<int>::iterator itr = vct.begin();
for (auto itr = vct.begin(); itr != vct.end(); ++itr)
{
cout <<*itr<< endl;
}
// 范围迭代
for(auto n:vct)
{
cout << n <<endl;
}
}
<set>红黑树
#include <set>
int main()
{
set<int> st;
for (size_t i = 0; i < 10; i++)
{
st.insert(i);
}
for (set<int>::iterator itr = st.begin(); itr != st.end(); ++itr)
{
cout << *itr <<endl;
}
for(auto n:st)
{
cout << n <<endl;
}
}
<forward_list>反向链表
#include <forward_list>
#include <set>
int main()
{
set<int> st;
for (size_t i = 0; i < 10; i++)
{
st.insert(i);
}
vector<int> vct(st.begin(), st.end());
list<int> lst(++st.begin(), --st.end());
forward_list<int> lst0;
for (size_t i = 0; i < 5; i++)
{
lst0.push_front(i);
}
}
作业: 在动态数组和链表中添加迭代器
4.3 迭代器的内部方法
- 迭代器返回类对象
=
运算符重载++
运算符重载--
运算符重载!=
运算符重载*
运算符重载->
运算符begin
和end
rbegin
和rend
- 反向
4.3.1 链表
template<typename T>
class CList
{
private:
// 节点
struct Node
{
Node() {}
Node(T val) { m_val = val; }
T m_val; //数据
Node* m_pre = nullptr; //前驱指针
Node* m_next = nullptr; //后继指针
};
public:
// 迭代器
class Postion
{
public:
Postion() {}
Postion(Node* pNode); // 带参构造
Postion& operator++(); // 前缀++
Postion& operator--(); // 前缀--
Postion operator++(int); // 后缀++
Postion operator--(int); // 后缀--
Postion& operator=(const Postion& obj); // =重载
bool operator!=(Postion pos); // !=重载
T& operator*(); // *重载
Node* operator->(); // ->重载
private:
Node* m_pNode = nullptr;
};
public:
CList(); // 默认构造
CList(const CList<T>& obj); // 拷贝构造
CList(CList<T>&& obj); // 移动构造
virtual ~CList(); // 析构
CList<T>& operator=(const CList<T>& obj); // =运算符重载:深拷贝
CList<T>& operator=(CList<T>&& obj); // =运算符重载:移动拷贝
// 内部接口
size_t Size()const; // 获取个数
bool IsEmpty()const; // 判断是否为空
void Clear(); // 清空
// 迭代器接口
Postion begin() { return Postion(m_pHeadGuard->m_next); }
Postion rbegin() { return Postion(m_pTailGuard->m_pre); }
Postion end() { return Postion(m_pTailGuard); }
Postion rend() { return Postion(m_pHeadGuard); }
// 插入
Postion Insert(POSTION pos, const T& val); // 前插
Postion InsertHead(const T& val); // 头插
Postion InsertTail(const T& val); // 尾插
// 删除
CList<T>& Remove(POSTION pos);
CList<T>& RemoveHead();
CList<T>& RemoveTail();
// 访问
T& Front();
T& Tail();
T& Access(POSTION pos);
// 查询
Postion Find(const T& val);
private:
void DeepCopy(const CList<T>& obj);
void InitGuard();
void MoveList(CList&& obj);
private:
Node* m_pHeadGuard = nullptr; //头哨兵
Node* m_pTailGuard = nullptr; //尾哨兵
size_t m_nSize = 0; //个数
};
链表迭代器的实现
// 正向迭代器
class Postion
{
public:
Postion(){}
Postion(Node* pNode):m_pNode(pNode) {}
Postion& operator++()
{
//判断是否是尾哨兵
if (m_pNode->m_next == nullptr)
{
throw std::logic_error("end cannot ++");
}
//后继
m_pNode = m_pNode->m_next;
return *this;
}
Postion& operator--()
{
//判断是否是头结点
if (m_pNode->m_pre->m_pre == nullptr)
{
throw std::logic_error("begin cannot --");
}
//前驱
m_pNode = m_pNode->m_pre;
return *this;
}
Postion operator++(int)
{
//判断是否是尾哨兵
if (m_pNode->m_next == nullptr)
{
throw std::logic_error("end cannot ++");
}
//保存原来的位置
Node* pOld = m_pNode;
//后继
m_pNode = m_pNode->m_next;
//后++返回原来的位置
return Postion(pOld);
//return Postion(m_pNode->m_pre);
}
Postion operator--(int)
{
//判断是否是头结点
if (m_pNode->m_pre->m_pre == nullptr)
{
throw std::logic_error("begin cannot --");
}
//保存旧位置
auto pOld = m_pNode;
//前驱
m_pNode = m_pNode->m_pre;
return Postion(pOld);
}
bool operator!=(Postion pos)
{
return m_pNode != pos.m_pNode;
}
T& operator*()
{
if (m_pNode == nullptr)
{
throw std::logic_error("invalid postion");
}
return m_pNode->m_val;
}
T* operator->()
{
if (m_pNode == nullptr)
{
throw std::logic_error("invalid postion");
}
return &m_pNode->m_val;
}
private:
Node* m_pNode = nullptr;
friend class CList;
};
// 反向迭代器
class ReversPostion
{
public:
ReversPostion() {}
ReversPostion(Node* pNode) :m_pNode(pNode) {}
ReversPostion& operator--()
{
//判断是否是尾节点
if (m_pNode->m_next->m_next == nullptr)
{
throw std::logic_error("rend cannot --");
}
//后继
m_pNode = m_pNode->m_next;
return *this;
}
ReversPostion& operator++()
{
//判断是否是头哨兵
if (m_pNode->m_pre == nullptr)
{
throw std::logic_error("rbegin cannot ++");
}
//前驱
m_pNode = m_pNode->m_pre;
return *this;
}
ReversPostion operator--(int)
{
//判断是否是尾节点
if (m_pNode->m_next->m_next == nullptr)
{
throw std::logic_error("rend cannot --");
}
//保存原来的位置
Node* pOld = m_pNode;
//后继
m_pNode = m_pNode->m_next;
//后++返回原来的位置
return ReversPostion(pOld);
//return Postion(m_pNode->m_pre);
}
ReversPostion operator++(int)
{
//判断是否是头哨兵
if (m_pNode->m_pre == nullptr)
{
throw std::logic_error("rbegin cannot ++");
}
//保存旧位置
auto pOld = m_pNode;
//前驱
m_pNode = m_pNode->m_pre;
return ReversPostion(pOld);
}
bool operator!=(ReversPostion pos)
{
return m_pNode != pos.m_pNode;
}
T& operator*()
{
if (m_pNode == nullptr)
{
throw std::logic_error("invalid postion");
}
return m_pNode->m_val;
}
private:
Node* m_pNode = nullptr;
};
4.3.2 数组
// 正向迭代器
class Postion
{
public:
Postion() {}
Postion(T* pBuf, size_t nLen, size_t nIdx = 0)
:m_pBuf(pBuf), m_nLen(nLen), m_nIdx(nIdx) {}
Postion& operator++()
{
//判断是否到最后一个元素后面的位置
if (m_nIdx >= m_nLen)
{
throw std::logic_error("end cannot ++");
}
//后继
++m_nIdx;
return *this;
}
Postion operator++(int)
{
//判断是否到最后一个元素后面的位置
if (m_nIdx >= m_nLen)
{
throw std::logic_error("end cannot ++");
}
//后继
++m_nIdx;
return Postion(m_pBuf, m_nLen, m_nIdx-1);
}
Postion& operator--()
{
//判断是否是第一个元素
if (m_nIdx == 0)
{
throw std::logic_error("begin cannot --");
}
//前驱
--m_nIdx;
return *this;
}
bool operator!=(Postion pos)
{
return m_nIdx != pos.m_nIdx;
}
T& operator*()
{
if (m_nIdx >= m_nLen)
{
throw std::logic_error("invalid postion");
}
return m_pBuf[m_nIdx];
}
T* operator->()
{
if (m_nIdx >= m_nLen)
{
throw std::logic_error("invalid postion");
}
return &m_pBuf[m_nIdx];
}
private:
T* m_pBuf = nullptr;
size_t m_nLen = 0;
size_t m_nIdx = 0;
};
4.4 迭代器失效案例
int main()
{
list<int> lst;
lst.push_back(4);
lst.push_back(4);
for (size_t i = 0; i < 5; i++)
{
lst.push_back(i);
}
lst.push_back(4);
for (auto itr = lst.begin();
itr != lst.end();
++itr)
{
if (*itr == 4)
{
lst.erase(itr);
}
}
}
当链表删除的时候, 断链后对节点的内存释放, 会导致该迭代器类型指向的内存空间被释放, 无法找到前驱和后继节点
解决方式
itr = lst.erase(itr)
for (auto itr = lst.begin();
itr != lst.end();
++itr)
{
if (*itr == 4)
{
itr = lst.erase(itr);
}
}
但是在删除第一个节点4之后, 迭代器指向的链表第二节点, 但是for
循环完成一次后迭代器itr++
, 指向了第三节点, 第二节点就漏掉了判断, 所以我们要注意写循环判断时, 要使用手动自增迭代器, 否则判定会有漏判
auto itr = lst.begin();
while (itr != lst.end())
{
if (*itr == 4)
{
itr = lst.erase(itr);
continue;
}
++itr;
}
数组增加的时候会动态申请内存, 再申请新内存空间后会释放原内存, 此时指向原内存的迭代器类型指向的内存空间被释放, 导致迭代器失效
5. 栈和队列
栈和队列都是线性结构
5.1 栈
5.1.1 定义
栈是仅限定在线性表的一端进行插入和删除的线性结构
允许进行插入和删除的一端称之为栈顶,另一端则称之为栈底
-
先进后出 ( FILO )
- 因为最先入栈的数据,最后出栈,所以栈被称作先进后出结构(FILO -- first in last out),或者后进先出(LIFO -- last in first out)
-
入栈
- 在栈顶插入数据叫做进栈,也叫做压栈,入栈
- 出栈
- 从栈顶删除数据叫做出栈,也叫做弹栈
-
空栈
- 如果栈中没有数据元素,则称之为空栈
5.1.2 操作
- 入栈push
- 出栈pop
- 访问栈顶top
- 获取个数size
- 清空栈clear
- 是否为空empty
入栈
出栈
5.1.3 实现
5.1.4 应用
-
递归
- 解决函数调用栈递归函数导致的栈溢出问题 ( 函数调用栈大小为1MB )
-
后缀表达式计算
- 遇到操作数 –> 入栈, 遇到运算符 –> 取出两个数值, 运算结果, 结果入栈
-
匹配检查
-
使用栈结构, 括号可能有多层嵌套, 但是从里到外是同一层级的
遇到左括号 push 入栈, 遇到右括号出栈, 比较是否左右匹配, 直到结束
-
匹配检查
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <fstream>
#include "CStack.h"
using namespace std;
int main()
{
fstream fs;
CStack<char> stk;
char chbuf = 0;
char ch = 0;
fs.open("./test1.cpp",
ios_base::in | ios_base::out);
if (!fs.is_open())
{
std::cout << "文件打开失败" << endl;
return -2;
}
while (!fs.eof())
{
ch = fs.get();
switch (ch)
{
case'(':
{
stk.Push('(');
break;
}
case'{':
{
stk.Push('{');
break;
}
case'[':
{
stk.Push('[');
break;
}
case']':
{
chbuf = 0;
chbuf = stk.Top();
if (chbuf != '[')
{
fs.close();
std::cout << "括号不匹配" << endl;
return -1;
}
stk.Pop();
break;
}
case'}':
{
chbuf = 0;
chbuf = stk.Top();
if (chbuf != '{')
{
fs.close();
std::cout << "括号不匹配" << endl;
return -1;
}
stk.Pop();
break;
}
case')':
{
chbuf = 0;
chbuf = stk.Top();
if (chbuf != '(')
{
fs.close();
std::cout << "括号不匹配" << endl;
return -1;
}
stk.Pop();
break;
}
default:
break;
}
}
fs.close();
std::cout << "括号匹配" << endl;
return 0;
}
5.2 队列
5.2.1 定义
队列是限定于仅在一端进行插入,在另一端进行删除的线性表。
允许插入的一端称之为队尾,允许删除的一端称之为队头
- 先进先出(FIFO)
- 最先入队的元素最先出队,所以队列被称作先进先出结构(FIFO - first in first out),或者叫做后进后出(LILO - last in last out)
5.2.2 操作
- 入队push
- 出队pop
- 访问队首front
- 获取个数size
- 清空队列clear
入队
出队
6. 树
6.1 定义
-
树
-
树中相连的结点具有父子关系
- 每个结点只有一个父节点,多个子结点
-
没有父结点的结点称之为根结点
-
没有子节点的结点称之为叶子结点
-
有父亲有儿子的结点称之为分支结点
-
以某个结点为根节点向下延申出来的树叫做这棵树的子树
-
-
结点的高度
- 结点到叶子的最长路径的边数,叶子结点的高度为0
- 树的高度为根结点的高度
- 结点到叶子的最长路径的边数,叶子结点的高度为0
-
结点的深度
- 结点到根结点所经历的边数,根结点的深度为0
- 树的深度为深度最大的叶子结点的深度
- 结点到根结点所经历的边数,根结点的深度为0
-
结点的层
- 结点的层从根节点开始数,根节点为第一层,根的孩子是第二层,依此类推……
-
有序树和无序树
-
如果规定树中结点的子节点从左向右是有次序的,不能互换的,则该树为有序树,否则为无序树
-
在编程中, 我们所创建的树都是有序树, 有序数才能被我们使用, 并对其进行操作
-
6.2 二叉树
- 每个结点最多只有两个子节点
- 左子树和右子树是有顺序的
1. 二叉树的定义
- 二叉树是另一种树形结构,其特点是每个结点至多只有两棵子树( 即二叉树中不存在度大于2的结点),并且二叉树的子树有左右之分,其次序不能任意颠倒
与树相似,二叉树也以递归的形式定义。二叉树是n (n≥0) 个结点的有限集合 :- 或者为空二叉树,即n=0
- 或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一棵二叉树
- 二叉树是有序树,若将其左、右子树颠倒,则成为另一棵不同的二叉树。即使树中结点只有一棵子树,也要区分它是左子树还是右子树
2. 特殊的二叉树
-
斜树
- 所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树
-
满二叉树
- 在一棵二叉树中,如果所有的分支结点都存在左孩子和右孩子,并且所有的叶子都在最底层,这样的树成为满二叉树
- 完全二叉树
- 高度为h 、有n 个结点的二叉树,当且仅当其每个结点都与高度为h 的满二叉树中编号为1~n的结点一一对应时,称为完全二叉树
- 若i ≤ n / 2 , 则结点i 为分支结点,否则为叶子结点
- 叶子结点只可能在层次最大的两层上出现。对于最大层次中的叶子结点,都依次排列在该层最左边的位置上
- 若有度为1 的结点,则只可能有一个,且该结点只有左孩子而无右孩子(重要特征)
- 按层序编号后,一旦出现某结点(编号为i ii)为叶子结点或只有左孩子,则编号大于i ii的结点均为叶子结点
- 若n nn为奇数,则每个分支结点都有左孩子和右孩子;若n 为偶数,则编号最大的分支结点(编号为n / 2 )只有左孩子,没有右孩子,其余分支结点左、右孩子都有
- 高度为h 、有n 个结点的二叉树,当且仅当其每个结点都与高度为h 的满二叉树中编号为1~n的结点一一对应时,称为完全二叉树
-
二叉搜索树
- 左子树上所有结点的关键字均小于根结点的关键字;右子树上的所有结点的关键字均大于根结点的关键字;左子树和右子树又各是一棵二叉排序树
-
平衡二叉树
- 树上任一结点的左子树和右子树的深度之差不超过1
3. 二叉树的遍历
-
前序遍历
-
访问根结点
-
先序遍历左子树
-
先序遍历右子树
-
-
中序遍历
-
中序遍历左子树
-
访问根结点
-
中序遍历右子树
-
-
后序遍历
-
后序遍历左子树
-
后序遍历右子树
-
访问根结点
-
-
层序遍历
- 按照层数, 自左向右依次遍历
满二叉树的性质
$$
在满二叉树的第n层上有 2^{n-1} 个结点
$$
$$
深度为n的满二叉树上有 2^n -1 个结点
$$
$$
含有n个结点的满二叉树有 log_2{(n+1)} 层
$$
6.2 二叉搜索树
对于二叉树中的任意结点,左孩子的值小于这个结点的值,右孩子的值大于这个结点的值,左子树的值小于这个节点的值,右子树大于这个节点的值
6.2.1 二叉搜索树的操作
- 插入
- 遍历
- 删除
- 修改
- 查询
二叉搜索树的声明
class CBST
{
private:
struct Node
{
Node() {}
Node(const int val, Node* parent = nullptr)
:m_val(val), m_parent(parent) {}
int m_val; // 数据
Node* m_left = nullptr; // 左孩子
Node* m_right = nullptr; // 右孩子
Node* m_parent = nullptr; // 双亲
};
static const int LEFTSON = 1;
static const int RIGHTSON = 2;
static const int ERROR = -1;
public:
CBST();
CBST(const CBST& obj);
CBST(CBST&& obj);
CBST& operator=(const CBST& obj);
CBST& operator=(CBST&& obj);
virtual ~CBST();
size_t Size();
bool IsEmpty()const;
void Clear();
// 增删改查
void Insert(const int& val);
bool Delete(const int& val);
bool Update(const int& val);
Node* Find(const int& val);
private:
bool IsLeafNode(const Node* pNode);
bool IsBranchNode(const Node* pNode);
bool IsRootNode(const Node* pNode);
int Son(const Node* pNode);
// 循环遍历
void InOrder();
void InOrderPrev(Node* pNode);
private:
Node* m_pRoot = nullptr; // 根结点
size_t m_nSize = 0; // 元素个数
};
6.2.2 二叉搜索树的实现
二叉搜索树的实现
#pragma once
#include <stdexcept>
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
class CBST
{
private:
struct Node
{
Node() {}
Node(const int val, Node* parent = nullptr)
:m_val(val), m_parent(parent) {}
int m_val; // 数据
Node* m_left = nullptr; // 左孩子
Node* m_right = nullptr; // 右孩子
Node* m_parent = nullptr; // 双亲
};
static const int LEFTSON = 1;
static const int RIGHTSON = 2;
static const int ERROR = -1;
public:
CBST() {}
CBST(const CBST& obj)
{
DeepCopy(obj);
}
CBST(CBST&& obj)
{
m_pRoot = obj.m_pRoot;
m_nSize = obj.m_nSize;
obj.m_pRoot = nullptr;
obj.m_nSize = 0;
}
CBST& operator=(const CBST& obj)
{
// 判断自身
if (this == &obj)
{
return *this;
}
// 清空自身
Clear();
// 拷贝
DeepCopy(obj);
return *this;
}
CBST& operator=(CBST&& obj)
{
// 清空自身
Clear();
m_pRoot = obj.m_pRoot;
m_nSize = obj.m_nSize;
obj.m_pRoot = nullptr;
obj.m_nSize = 0;
return *this;
}
virtual ~CBST()
{
Clear();
}
size_t Size() // 获取长度
{
return m_nSize;
}
bool IsEmpty()const // 判断是否为空
{
return m_nSize == 0;
}
void Clear()
{
while (!IsEmpty())
{
Delete(m_pRoot->m_val);
}
}
// 递归清空
void Clear(Node* pNode)
{
if (pNode == nullptr)
{
return;
}
Clear(pNode->m_left);
Clear(pNode->m_right);
delete pNode;
--m_nSize;
}
// 增删改查
void Insert(const int& val) // 插入
{
// 判断是否为空树
if (IsEmpty())
{
m_pRoot = new Node(val);
++m_nSize;
return;
}
// 查找位置
Node* pNode = m_pRoot;
while (true)
{
// 大于该结点的值,往右找
if (val > pNode->m_val)
{
// 找到没有右子树(叶子结点),插入
if (pNode->m_right == nullptr)
{
pNode->m_right = new Node(val, pNode);
++m_nSize;
break;
}
pNode = pNode->m_right;
}
// 小于该结点的值,往左找
else if (val < pNode->m_val)
{
// 找到没有左子树(叶子结点),插入
if (pNode->m_left == nullptr)
{
pNode->m_left = new Node(val, pNode);
++m_nSize;
break;
}
pNode = pNode->m_left;
}
else
{
throw std::logic_error("val has been exist");
}
}
}
bool Delete(const int& val) // 删除
{
// 判断是否为空树
if (IsEmpty())
{
m_pRoot = new Node(val);
++m_nSize;
throw std::logic_error("tree is empty");
}
// 查找要被删除的结点
Node* pNode = FindNode(val);
if (pNode == nullptr)
{
return false;
}
// 叶子
if (pNode->m_left == nullptr && pNode->m_right == nullptr)
{
DelLeaf(pNode);
return true;
}
// 单分支结点
if (pNode->m_left == nullptr || pNode->m_right == nullptr)
{
DelSingle(pNode);
return true;
}
// 两个子结点
if (pNode->m_left != nullptr && pNode->m_right != nullptr)
{
DelDouble(pNode);
return true;
}
}
bool Update(const int& oldval, const int& newval)
{
if (FindNode(oldval) != nullptr)
{
Delete(oldval);
Insert(newval);
return true;
}
return false;
}
bool Find(const int& val)
{
return FindNode(val) != nullptr;
}
Node* FindNode(const int& val)
{
// 判断是否为空树
if (IsEmpty())
{
throw std::logic_error("tree is empty");
}
// 查找位置
Node* pNode = m_pRoot;
while (pNode != nullptr)
{
// 大于该结点的值,往右找
if (val > pNode->m_val)
{
pNode = pNode->m_right;
}
// 小于该结点的值,往左找
else if (val < pNode->m_val)
{
pNode = pNode->m_left;
}
else if (val == pNode->m_val)
{
return pNode;
}
}
return nullptr;
}
// 递归遍历
void InOrder()
{
InOrderPriv(m_pRoot);
}
void InOrderPriv(Node* pNode)
{
if (pNode == nullptr)
{
return;
}
cout << pNode->m_val << endl; // 自身
InOrderPriv(pNode->m_left); // 左子
InOrderPriv(pNode->m_right); // 右子
}
// 循环实现遍历
void PreOrderLoop()// 前序
{
std::stack<Node*> stk;
Node* pNode = m_pRoot;
while (true)
{
// 沿着左孩子向下, 沿途右孩子入栈
while (pNode->m_left != nullptr)
{
cout << pNode->m_val << endl;
if (pNode->m_right != nullptr)
{
stk.push(pNode->m_right);
}
pNode = pNode->m_left;
}
// 栈为空,处理完毕
if (stk.empty())
{
break;
}
// 从栈顶取出
pNode = stk.top();
stk.pop();
}
}
void InOrderLoop()
{
while (true)
{
std::stack<Node*> stk;
Node* pNode = m_pRoot;
while (pNode != nullptr)
{
// 沿着左孩子向下, 沿途结点入栈
stk.push(pNode);
pNode = pNode->m_left;
}
// 处理完毕
if (stk.empty())
{
break;
}
// 从栈上取出
pNode = stk.top();
stk.pop();
cout << pNode->m_val << endl;
// 处理右子树
pNode = pNode->m_right;
}
}
void PostOrderLoop()
{
std::stack<Node*> stk;
Node* pNode = m_pRoot;
Node* pLast = nullptr;
while (true)
{
// 向左下走, 沿途节点入栈
while (pNode != nullptr)
{
stk.push(pNode);
pNode = pNode->m_left;
}
if (stk.empty())
{
break;
}
// 访问栈顶
pNode = stk.top();
if (pNode->m_right == nullptr || pNode->m_right == pLast)
{
cout << pNode->m_val << endl;
pLast = pNode;
stk.pop();
pNode = nullptr;
continue;
}
// 处理右子树
pNode = pNode->m_right;
}
}
// 层序遍历
void InOrderCeng()
{
std::queue<Node*> que;
que.push(m_pRoot);
while (!que.empty())
{
Node* pNode = que.front();
if (pNode->m_left != nullptr)
{
que.push(pNode->m_left);
}
if (pNode->m_right != nullptr)
{
que.push(pNode->m_right);
}
cout << pNode->m_val << endl;
que.pop();
}
}
private:
void DeepCopy(const CBST& obj)
{
std::queue<Node*> que;
que.push(obj.m_pRoot);
while (!que.empty())
{
Node* pNode = que.front();
if (pNode->m_left != nullptr)
{
que.push(pNode->m_left);
}
if (pNode->m_right != nullptr)
{
que.push(pNode->m_right);
}
Insert(pNode->m_val);
que.pop();
}
}
void DeepCopy(Node*& pNode, const CBST& obj, Node* pObjNode)
{
if (pObjNode == nullptr)
{
return;
}
Insert(pObjNode->m_val); // 构建结点
DeepCopy(pNode->m_left, obj, pObjNode->m_left); // 左子
DeepCopy(pNode->m_right, obj, pObjNode->m_right); // 右子
}
void DelLeaf(Node* pDel)
{
// 判断是否是跟结点(只剩一个跟结点)
if (pDel == m_pRoot)
{
delete pDel;
--m_nSize;
return;
}
// 删除叶子结点
// 判断是左孩子还是右孩子, 将双亲指针的后继置空
Node* pParent = pDel->m_parent;
if (pParent->m_left == pDel)
{
pParent->m_left = nullptr;
}
else
{
pParent->m_right = nullptr;
}
delete pDel;
--m_nSize;
}
void DelSingle(Node* pDel)
{
// 判断是否是跟结点
if (pDel == m_pRoot)
{
m_pRoot = (pDel->m_left != nullptr ? pDel->m_left : pDel->m_right);
m_pRoot->m_parent = nullptr;
delete pDel;
--m_nSize;
return;
}
// 删除 将双亲的孩子指针指向该结点的孩子,孩子的双亲指针指向该结点的双亲
Node* pParent = pDel->m_parent;
Node* pChild = (pDel->m_left != nullptr ? pDel->m_left : pDel->m_right);
pChild->m_parent = pParent;
if (pDel == pParent->m_left)
{
pParent->m_left = pChild;
}
else
{
pParent->m_right = pChild;
}
delete pDel;
--m_nSize;
return;
}
void DelDouble(Node* pDel)
{
// 找到左子树的最大值
Node* pMaxInLeft = pDel->m_left;
while (pMaxInLeft->m_right != nullptr)
{
pMaxInLeft = pMaxInLeft->m_right;
}
// 替换
pDel->m_val = pMaxInLeft->m_val;
// 删除
if (pMaxInLeft->m_left != nullptr)
{
DelSingle(pMaxInLeft);
}
else
{
DelLeaf(pMaxInLeft);
}
}
private:
Node* m_pRoot = nullptr; // 根结点
size_t m_nSize = 0; // 元素个数
};
6.3 二叉平衡树
avl
树
6.3.1 概念
二叉搜索树在一定程度上可以提高搜索效率,但是当序列是有序时:
斜树
此时二叉搜索树退化成单链表,搜索效率退化为O(n)
为了解决这个问题科学家引入了AVL树,又称平衡搜索二叉树
AVL简称平衡二叉树。由前苏联的数学家 Adelse-Velskil 和 Landis 在 1962 年提出的高度平衡的二叉树,根据科学家的英文名也称为 AVL 树。它具有如下几个性质:
- 可以是空树。
- 假如不是空树,任何一个结点的左子树与右子树都是平衡二叉树,并且高度之差的绝值不超过 1。
平衡之意,如天平,即两边的分量大约相同
非平衡搜索二叉树
平衡搜索二叉树
6.3.2 平衡因子
定义:某节点的左子树与右子树的高度(深度)差即为该节点的平衡因子(BF,Balance Factor),平衡二叉树中不存在平衡因子大于 1 的节点。在一棵平衡二叉树中,节点的平衡因子只能取 0 、1 或者 -1 ,分别对应着左右子树等高,左子树比较高,右子树比较高。
6.3.3 AVL树的四种旋转
左旋
失衡结点单次左旋
右旋
失衡结点单次右旋
左右双旋
失衡结点的左子结点先左旋, 失衡结点再右旋
右左双旋
失衡结点的右子结点先右旋, 失衡结点再左旋
我们调整平衡树有四种旋转方式, 单次左旋, 单次右旋, 左右双旋, 右左双旋, 针对四种不同的旋转方式, 有对应的情况使用这四种旋转方式
我们比较是否该旋转调整平衡树的依据为平衡因子, 当左右子树的高度相差大于1 ( 平衡因子 > 1 或 < -1 ) , 则需要我们调整平衡树
- 当左子树的高度过高时 (平衡因子 > 1) , 比较失衡节点的左子结点的左右子树高度
- 左子结点的左子树更高 (平衡因子 > 0) 右旋
- 左子结点的右子树更高 (平衡因子 < 0) 左右双旋
- 当右子树的高度过高时 (平衡因子 < -1) , 比较失衡节点的右子结点的左右子树高度
- 右子结点的右子树更高 (平衡因子 < 0) 左旋
- 右子结点的左子树更高 (平衡因子 > 0) 右左双旋
左旋的实现
void RotateLeft(Node* pNode)
{
// 左旋
// 1. 右子结点的左子树成为该结点的右子树
// 2. 该结点成为右子结点的左子树
// 3. 该结点的右子结点替换该结点
/*
* [P] [P]
* | |
* N 左旋 NR*
* / \ ---> / \
* NL NR N* NRR
* / \ / \
* [NRL] NRR NL [NRL]*
*/
Node* pP = pNode->m_parent;
Node* pNR = pNode->m_right;
Node* pNRL = pNR->m_left;
// 修改P
if (pP == nullptr)
{
m_pRoot = pNR;
}
else
{
pP->m_left == pNode ?
pP->m_left = pNR :
pP->m_right = pNR;
}
// 修改N
pNode->m_parent = pNR;
pNode->m_right = pNRL;
// 修改NR
pNR->m_parent = pP;
pNR->m_left = pNode;
// 修改NRL
if (pNRL != nullptr)
{
pNRL->m_parent = pNode;
}
// 调整高度
pNode->m_height = max(Height(pNode->m_left), Height(pNode->m_right)) + 1;
pNR->m_height = max(Height(pNR->m_left), Height(pNR->m_right)) + 1;
}
右旋的实现
void RotateRight(Node* pNode)
{
/*
* [P] [P]
* | |
* N 右旋 NL*
* / \ ---> / \
* NL NR NLL* N
* / \ / \
* NLL [NLR] [NLR]* NR
*/
Node* pP = pNode->m_parent;
Node* pNL = pNode->m_left;
Node* pNLR = pNL->m_right;
// 修改P
if (pP == nullptr)
{
m_pRoot = pNL;
}
else
{
pP->m_left == pNode ?
pP->m_left = pNL :
pP->m_right = pNL;
}
// 修改N
pNode->m_parent = pNL;
pNode->m_left = pNLR;
// 修改NL
pNL->m_parent = pP;
pNL->m_right = pNode;
// 修改NLR
if (pNLR != nullptr)
{
pNLR->m_parent = pNode;
}
// 调整高度
pNode->m_height = max(Height(pNode->m_left), Height(pNode->m_right)) + 1;
pNL->m_height = max(Height(pNL->m_left), Height(pNL->m_right)) + 1;
}
6.4 STL 中的树
<map> 和 <set> 红黑树
标签:Node,结点,return,val,nullptr,笔记,pNode,数据结构 From: https://www.cnblogs.com/xmwblogs/p/18090465