参考
- 公众号:程序员贺先生——C++八股
- 面试让我手写红黑树?! - 掘金 (juejin.cn)
说⼀下平衡⼆叉树、⾼度平衡⼆叉树(AVL)
-
⼆叉树:任何节点最多只允许有两个⼦节点,称为左⼦节点和右⼦节点,以递归的⽅式定义⼆叉树为,⼀个⼆叉树如果不为空,便是由⼀个根节点和左右两个⼦树构成,左右⼦树都可能为空。
-
⼆叉搜索树:⼆叉搜索树可以提供对数时间的元素插⼊和访问。
- 节点的放置规则是:任何节点的键值⼀定⼤于其左⼦树的每⼀个节点的键值,并⼩于其右⼦树中的每⼀个节点的键值。因此 ⼀直向左⾛可以取得最⼩值,⼀直向右⾛可以得到最⼤值。
- 插⼊:从根节点开始,遇键值较⼤则向左,遇键值较⼩则向右,直到尾端,即插⼊点。
- 删除:如果删除点只有⼀个⼦节点,则直接将其⼦节点连⾄⽗节点。如果删除点有两个⼦节点,以右⼦树中的最⼩值代替要删除的位置。
-
平衡⼆叉树:其实对于树的平衡与否没有⼀个绝对的标准,“平衡”的⼤致意思是:没有任何⼀个节点过深,不同的平衡条件会造就出不同的效率表现。以及不同的实现复杂度。有数种特 殊结构例如 AVL-tree, RB-tree, AA-tree,均可以实现平衡⼆叉树。
-
AVL-tree:⾼度平衡的平衡⼆叉树(严格的平衡⼆叉树)AVL-tree 是要求任何节点的左右⼦树⾼度相差最多为 1 的平衡⼆叉树。 当插⼊新的节点破坏平衡性的时候,从下往上找到第⼀个不平衡点,需要进⾏单旋转,或者双旋转进⾏调整。
说⼀下红⿊树(RB-tree)
-
定义: 红黑树是一种含有红黑结点并能自平衡的二叉查找树。但相比于AVL树,高度平衡所带来的时间复杂度,红黑树对平衡的控制要宽松一些,红黑树只需要保证黑色节点平衡即可。也正因红黑树在插入和删除时不需要太多的平衡操作,也让它成为;Java中HashMap的元素碰撞后的转换、Linux的CFS进行调度算法、多路复用技术的Epoll等各类底层的数据结构实现。
-
性质:
- 每个节点要么是⿊⾊,要么是红⾊。
- 黑色决定平衡,红色不决定平衡。这对应了2-3树中一个节点内可以存放1~2个节点。
- 根节点是⿊⾊。
- 这条规则有时会被省略。由于根总是可以从红色变为黑色,但不一定相反,因此该规则对分析几乎没有影响。
- 每个叶⼦节点(NIL)是⿊⾊。
- 这里指的是红黑树都会有一个空的叶子节点,是红黑树自己的规则。
- 每个红⾊结点的两个⼦结点⼀定都是⿊⾊。
- 通常这条规则也叫不会有连续的红色节点。这体现在2-3树中,一个节点最多临时会有3个节点,中间是黑色节点,左右是红色节点。2-3树中出现这样的情况后,会进行节点迁移,中间节点成为父节点,左右节点成为子节点。
- 任意⼀结点到每个叶⼦结点的路径都包含数ᰁ相同的⿊结点。
- 对应2-3树中,每一层都只是有一个节点贡献了树高决定平衡性,也就是对应红黑树中的黑色节点。
- 每个节点要么是⿊⾊,要么是红⾊。
B树和B+树的区别
-
B树的所有节点都是存储数据的,B+树是B树的扩展或者变种,B+树的内节点不存储数据,只做索引,所有的数据都存储在叶子节点。
-
B树的每个节点都包含键和对应值,而B+树的非叶子节点只包含键,所有的值都存储在叶子节点,这样的设计使得B+树的叶子节点形成有序链表,方便范围查询遍历。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。
B+树的优点:
- 层级少,查询速度更快
- 查询速度稳定:关键字数据地址都存在叶子节点上
- 天然具备排序功能:叶子节点数据构成了有序链表,数据紧密性高
- 全节点遍历快:无需遍历整棵树,只需遍历叶子节点即可,有利于全表扫描
十大排序算法
1. 选择排序
2. 冒泡排序
3. 插入排序
4. 归并排序
vector<int> sortArray(vector<int>& nums) {
vector<int> tmp(nums.size());
mergeSort(nums, tmp, 0, nums.size()-1);
return nums;
}
void mergeSort(vector<int> &nums, vector<int> &tmp,int left, int right){
if(left >= right) {return;}
// 递归排序好左右两部分
int mid = left+(right-left)/2;
mergeSort(nums, tmp, left , mid);
mergeSort(nums, tmp, mid+1, right);
// merge
for(int i=left;i<=right; i++){tmp[i] = nums[i];}
int i=left, j=mid+1;
for(int k=left;k<=right;k++){
if(i==mid+1 || (j!=right+1&&tmp[j]<tmp[i])){ //i满 or j没满且j小
nums[k] = tmp[j++];
}else{
nums[k] = tmp[i++];
}
}
5. 快速排序
#include <cstdlib>
#include <ctime>
#include <algorithm>
vector<int> sortArray(vector<int>& nums) {
srand((unsigned)time(NULL));
quickSort(nums, 0, nums.size()-1);
return nums;
}
void quickSort(vector<int> &nums, int left, int right){
if(left >= right){ return;}
// rand_partition- 见LC颜色分类
int pos = left + rand()%(right-left+1);
int target = nums[pos];
int p0 = left, p2 = right;
for(int i=left;i<=p2; i++){
while( nums[i]>target && i<=p2 ){ // [i] > target
swap(nums[p2--], nums[i]);
}
if(nums[i] < target){ // [i] < target
swap(nums[p0++], nums[i]);
}
}
// 递归处理[start:p0-1]和[p2+1:end]左右两部分
quickSort(nums, left, p0-1);
quickSort(nums, p2+1, right);
}
6. 堆排序
class myMinHeap{
public:
//构造函数
myMinHeap(int ms){
maxSize = ms;
curSize=0;
heapArr = new int[maxSize]{}; }
void myPush(int v){
heapinsert(v); }
void myPop(){
heapify();}
int myTop(){
return heapArr[0];}
int mySize(){
return curSize;}
private:
int maxSize;
int curSize; //堆的所有元素个数,初始化为0
int *heapArr;//完全二叉树的层序遍历
void heapinsert(int value){
// 默认放在数组最后位置arr[i](完全二叉树的最后)
// 放之前与父节点(arr[(i-1)/2]))比较,保证父节点一定比左右孩子小于等于(小根堆)
curSize++;
int i = curSize-1;
heapArr[i] = value;
if(value < heapArr[(i-1)/2]){
//交换
heapArr[i] = heapArr[(i-1)/2];
heapArr[(i-1)/2] = value;
}
}
void heapify(){ //删掉根节点,让剩下的节点构成小根堆
//1. 堆根节点(arr[0])与最后节点(arr[curSize-1])交换
int temp = heapArr[0];
heapArr[0] = heapArr[curSize-1];
heapArr[curSize-1] = temp;
//2. 删掉最后节点(最初的根节点);
curSize--;
//3. 调整剩下的堆: arr[0]与左右孩子(2i+1,2i+2)比较,不符合小根堆交换
// 交换后的节点作为新的父节点在于其左右孩子比较
// 直到某一个子树下无需交换了or到达叶子节点为止
int i=0;
while( (2*i+1)<curSize ) { //非叶子节点
int curV = heapArr[i];
if( curV < heapArr[2*i+1] ){ //与左孩子交换
heapArr[i] = heapArr[2*i+1];
heapArr[2*i+1] = curV;
i = 2*i+1;
}else if( curV <heapArr[2*i+2]){ //与右孩子交换
heapArr[i] = heapArr[2*i+2];
heapArr[2*i+2] = curV;
i = 2*i+2;
}else{ //无需交换
break;}
7. 桶排序
8. 计数排序
O(n) 时间复杂度的排序算法
前提条件:
- 待排序的数是满⾜⼀定的范围的整数,⽽且计数排序需要⽐较多的辅助空间
- 只能给⾮负整数排序,如果要排序的数据是其他 类型的,要将其在不改变相对⼤⼩的情况下,转化为⾮负整数