数据结构与算法(待)

42 阅读6分钟

参考

  1. 公众号:程序员贺先生——C++八股
  2. 面试让我手写红黑树?! - 掘金 (juejin.cn)

说⼀下平衡⼆叉树、⾼度平衡⼆叉树(AVL)

  • ⼆叉树:任何节点最多只允许有两个⼦节点,称为左⼦节点和右⼦节点,以递归的⽅式定义⼆叉树为,⼀个⼆叉树如果不为空,便是由⼀个根节点和左右两个⼦树构成,左右⼦树都可能为空。

  • ⼆叉搜索树:⼆叉搜索树可以提供对数时间的元素插⼊和访问。

    • 节点的放置规则是:任何节点的键值⼀定⼤于其左⼦树的每⼀个节点的键值,并⼩于其右⼦树中的每⼀个节点的键值。因此 ⼀直向左⾛可以取得最⼩值,⼀直向右⾛可以得到最⼤值。
    • 插⼊:从根节点开始,遇键值较⼤则向左,遇键值较⼩则向右,直到尾端,即插⼊点。
    • 删除:如果删除点只有⼀个⼦节点,则直接将其⼦节点连⾄⽗节点。如果删除点有两个⼦节点,以右⼦树中的最⼩值代替要删除的位置。
  • 平衡⼆叉树:其实对于树的平衡与否没有⼀个绝对的标准,“平衡”的⼤致意思是:没有任何⼀个节点过深,不同的平衡条件会造就出不同的效率表现。以及不同的实现复杂度。有数种特 殊结构例如 AVL-tree, RB-tree, AA-tree,均可以实现平衡⼆叉树。

  • AVL-tree:⾼度平衡的平衡⼆叉树(严格的平衡⼆叉树)AVL-tree 是要求任何节点的左右⼦树⾼度相差最多为 1 的平衡⼆叉树。 当插⼊新的节点破坏平衡性的时候,从下往上找到第⼀个不平衡点,需要进⾏单旋转,或者双旋转进⾏调整。

说⼀下红⿊树(RB-tree)

  • 定义: 红黑树是一种含有红黑结点并能自平衡的二叉查找树。但相比于AVL树,高度平衡所带来的时间复杂度,红黑树对平衡的控制要宽松一些,红黑树只需要保证黑色节点平衡即可。也正因红黑树在插入和删除时不需要太多的平衡操作,也让它成为;Java中HashMap的元素碰撞后的转换、Linux的CFS进行调度算法、多路复用技术的Epoll等各类底层的数据结构实现。

  • 性质:

    1. 每个节点要么是⿊⾊,要么是红⾊。
      • 黑色决定平衡,红色不决定平衡。这对应了2-3树中一个节点内可以存放1~2个节点。
    2. 根节点是⿊⾊。
      • 这条规则有时会被省略。由于根总是可以从红色变为黑色,但不一定相反,因此该规则对分析几乎没有影响。
    3. 每个叶⼦节点(NIL)是⿊⾊。
      • 这里指的是红黑树都会有一个空的叶子节点,是红黑树自己的规则。
    4. 每个红⾊结点的两个⼦结点⼀定都是⿊⾊。
      • 通常这条规则也叫不会有连续的红色节点。这体现在2-3树中,一个节点最多临时会有3个节点,中间是黑色节点,左右是红色节点。2-3树中出现这样的情况后,会进行节点迁移,中间节点成为父节点,左右节点成为子节点。
    5. 任意⼀结点到每个叶⼦结点的路径都包含数ᰁ相同的⿊结点。
      • 对应2-3树中,每一层都只是有一个节点贡献了树高决定平衡性,也就是对应红黑树中的黑色节点。

B树和B+树的区别

  1. B树的所有节点都是存储数据的,B+树是B树的扩展或者变种,B+树的内节点不存储数据,只做索引,所有的数据都存储在叶子节点。

  2. B树的每个节点都包含键和对应值,而B+树的非叶子节点只包含键,所有的值都存储在叶子节点,这样的设计使得B+树的叶子节点形成有序链表,方便范围查询遍历。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

B+树的优点:

  1. 层级少,查询速度更快
  2. 查询速度稳定:关键字数据地址都存在叶子节点上
  3. 天然具备排序功能:叶子节点数据构成了有序链表,数据紧密性高
  4. 全节点遍历快:无需遍历整棵树,只需遍历叶子节点即可,有利于全表扫描

十大排序算法

1678710991634.png 1678711201263.png

1678711236729.png

1678711254659.png

1. 选择排序

1678711074699.png

2. 冒泡排序

1678711095414.png

3. 插入排序

1678711123706.png

4. 归并排序

1678711281407.png

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. 快速排序

1678711308169.png

#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. 堆排序

1678711345091.png 1678711356484.png

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. 桶排序

1678711380739.png

8. 计数排序

O(n) 时间复杂度的排序算法

前提条件:

  1. 待排序的数是满⾜⼀定的范围的整数,⽽且计数排序需要⽐较多的辅助空间
  2. 只能给⾮负整数排序,如果要排序的数据是其他 类型的,要将其在不改变相对⼤⼩的情况下,转化为⾮负整数

对10TB数据文件排序方法

1697892653119.png