【数据结构】详细总结

C语言网教程

易犯错误

temp.push_back(data[rightStart]); 不是 temp.push_back(rightStart);
int nums = 56423;
int sum = 0while (nums != 0){ 
		cout << nums % 10 << ' '; //3 2 4 6 5 个位逆序输出
        sum =  sum + nums % 10; // 取个位数求和(取余数)
		nums = nums / 10; // 取个位前的数字(取除数)
	}
 nums % 10  //取个位
 nums / 10  //取个位前


在这里插入图片描述
在这里插入图片描述

  • 1、常见数据结构:链表、树(如二叉树)。(链表和二叉树是重点,图这些可以先放着)
  • 2、常见算法思想:贪心法、分治法、穷举法、动态规划,回溯法。(贪心、穷举、分治是基础,动态规划有难度,可以先放着)

1. 常见概念

1.1 时间复杂度

  • 常数阶O(1)
  • 对数阶O(logN)
  • 线性阶O(n)
  • 线性对数阶O(nlogN)
  • 平方阶O(n²)
  • 立方阶O(n³)
  • K次方阶O(n^k)
  • 指数阶(2^n)

1.2 空间复杂度

  • 比较常用的有:O(1)、O(n)、O(n²)
//空间复杂度 O(n)
int[] m = new int[n]
for(i=1; i<=n; ++i)
{
   j = i;
   j++;
}

1.3 迭代、循环和遍历的区别

  • 循环(loop) - 最基础的概念, 所有重复的行为
  • 递归(recursion) - 在函数内调用自身, 将复杂情况逐步转化为基本情况
  • (数学)迭代(iterate) - 在多次循环中逐步接近结果
  • (编程)迭代(iterate) - 按顺序访问线性结构中的每一项
  • 遍历(traversal) - 按规则访问非线性结构中的每一项

2. 哈希表(Hash Table)

  1. unordered_set
  2. unordered_multiset
  3. unordered_map
  4. unordered_multimap
  5. unordered_map (或者 unordered_set )省去了排序的过程,如果偶尔刷题时候 map 或者 set 超时了,可以考虑 unordered_map (或者 unordered_set )缩短代码运行时间、提升代码效率
//哈希查找
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) { 
        unordered_map<int,int> hash_map;  
        for(int i=0; i<nums.size(); i++) {
          //查找key是否存在,若存在,返回该键的元素的迭代器;
          //若不存在,返回m.end();
            auto it = hash_map.find(target-nums[i]);
           //unordered_map<int,int>::iterator it = hash_map.find(target-nums[i]);
            if(it != hash_map.end()) {
                return{it->second,i};
            }
            hash_map[nums[i]]=i; //构建哈希映射表,键值key和实值value对应,
        }
        return{};
    }
};

复杂链表的复制

描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。

//复杂链表的复制,请对此链表进行深拷贝,并返回拷贝后的头结点。
//struct RandomListNode {
//    int val;
//    struct RandomListNode *next, *random;
//    RandomListNode(int x) :
//            val(x), next(NULL), random(NULL) {
//    }
//};

//构建哈希表,迭代解法
RandomListNode* Clone(RandomListNode* pHead)
{
    if (pHead == nullptr) return nullptr;
    std::unordered_map<RandomListNode*, RandomListNode*> hash_map;
    
    // 若用 while(pHead != nullptr){pHead = pHead->next;},由于pHead非局部变量,会导致pHead变化
    for (RandomListNode* p = pHead; p != nullptr; p = p->next) //p相当于i,对复杂链表遍历
    {
        // 有参构造的初始化,指针均为空
        hash_map[p] = new RandomListNode(p->val); //构建哈希映射表,深拷贝重新复制开辟到堆区,p->val初始化
    }
    
    for (RandomListNode* p = pHead; p != nullptr; p = p->next)
    {
        hash_map[p]->next = hash_map[p->next];    //把拷贝后的链表链接起来
        hash_map[p]->random = hash_map[p->random];//在哈希表中random指向的地址也收录了
    }
    return hash_map[pHead];
}

//递归解法
//关键是保存住映射关系,可以说是哈希表和链表的组合吧
    unordered_map<RandomListNode*,RandomListNode*> hash_map;
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if(pHead == nullptr) return nullptr;
        // 有参构造的初始化
        RandomListNode* newHead = new RandomListNode(pHead->val);
        hash_map[pHead] = newHead; //这里需要保存的是 pHead -> newHead 的映射关系,必须在这里保存
        newHead->next = Clone(pHead->next);//到这一步,其实所有的点已经全部生成了
        newHead->random = nullptr;//其实默认已经是nullptr了,有没有这一步其实没什么关系
        if(pHead->random != nullptr)  newHead->random = hash_map[pHead->random];//这一步,真的是灵魂所在了
        return newHead;
    }

删除链表的重复节点

描述

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

// 哈希解法
class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead) {
        if(pHead == nullptr) return nullptr;
        vector<ListNode*> vec; //记录新链表
        unordered_map<int, int> hash;
        // 由于pHead非局部变量,会导致其变化
        //while(pHead != nullptr) { 
            //hash[pHead->val]++;
            //pHead = pHead->next;
        //}
        for(ListNode* p = pHead; p != nullptr; p = p->next) {
            hash[p->val]++;
        }
        // 由于已排序,重复节点一定是连续重复的
        ListNode* newHead;
        for(ListNode* p = pHead; p != nullptr; p = p->next) {
            if(hash[p->val] == 1){
                // 进行深拷贝复制节点(next为空),浅拷贝会导致(next指向下一节点)
                newHead = new ListNode(p->val); 
                vec.push_back(newHead);
            }
        }
        if(vec.size() == 0) return nullptr;
        if(vec.size() == 1) return vec[0];
        for(int i = 0; i < vec.size()-1; ++i) { //数组两个以上元素的情况
            // 链接两个链表节点,最后一个节点会带有之间的节点,故1.拷贝节点来杜绝  2.或最后节点置为空
            vec[i]->next = vec[i+1]; 
        }
        //vec[vec.size() - 1]->next = nullptr; //2.或最后节点置为空
        return vec[0];
    }
};

2. 二叉树

2.1 二叉树的基本概念

图片名称 图片名称
  • 父节点、子节点:如果一个节点下面连接多个节点,那么该节点称为父节点,它下面的节点称为子节点。
  • 叶子节点:没有任何子节点的节点称为叶子节点。
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点。
  • 节点度:节点拥有的子树个数。
  • 树的度:所有结点的度数的最大值。二叉树的度<=2
  • 树的深度/高度:树中结点的最大层次数称为树的深度或高度。(树的深度和高度一样,不是节点的深度和高度
  • 二叉树深度定义:从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的节点个数为树的深度(等于层数
  • 满二叉树:各层都是满的
  • 完全二叉树:除了最后一层,其余各层都是满的,并且最后一层的节点集中在左边
  • 二叉排序树(二叉搜索树、二叉查找树)BST可以为空树,若它的左子树不空,则左子树上的所有结点的值均小于根节点的值;若它的右子树不空,则右子树上的所有结点的值均大于根节点的值,左右子树分别为二叉排序树。
  • 平衡二叉树AVL:是二叉查找树的一个进化体,又被称为AVL树,它是一颗空树或左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树
  • 红黑树RB-Tree:是一种平衡二叉查找树的变体,它的左右子树高差有可能大于1,所以红黑树不是严格意义上的平衡二叉树(AVL),其平均统计性能要强于 AVL树
通常堆(完全二叉树)是通过一维数组来实现的。在数组起始位置为0的情形中,父结点和子结点的位置关系如下:
1.索引为i的左孩子的索引是 (2* i+1)
2.索引为i的右孩子的索引是 (2* i+2)
3.索引为i的父结点的索引是 (i -1)/2 
(若len = vec.size()不是下标,而是元素大小,则(len-1-1)/2 = len/2 - 1

2.2 四种基本的遍历思想

  • 前序遍历:根结点 —> 左子树 —> 右子树(根左右)
  • 中序遍历:左子树 —> 根结点 —> 右子树(左根右)
  • 后序遍历:左子树 —> 右子树 —> 根结点(左右根)
  • 层次遍历:仅仅需按层次遍历就可以
图片名称
  • 前序遍历:1 2 4 5 7 8 3 6
  • 中序遍历:4 2 7 5 8 1 3 6
  • 后序遍历:4 7 8 5 2 6 3 1
  • 层次遍历:1 2 3 4 5 6 7 8
  • 遍历的实现方式主要是:递归和非递归
  • 递归遍历的实现非常容易,非递归的实现需要用到栈,难度系数要高一点
  • 根是相对的,对于整棵树而言只有一个根,但对于每棵子树而言,又有自己的根
//二叉树节点的定义
class TreeNode{
public:
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
}
TreeNode* node; node->val = 1;
TreeNode node(1); //开辟到栈区
TreeNode* node = new TreeNode(1); //开辟到堆区

2.3 前序遍历(迭代:dfs,辅助栈)

// 递归解法,不重新构造函数
class Solution {
public:
    vector<int> vec;
    vector<int> preorderTraversal(TreeNode* root) {
        if(root == nullptr) return vec;
        
        vec.push_back(root->val);
        preorderTraversal(root->left); //不需要返回
        preorderTraversal(root->right);
        return vec;
    }
};
// 递归解法,重新构造函数
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> vec;
        preorder(root,vec);
        return vec;
    }
    void preorder(TreeNode* root, vector<int> &vec){
        if(root==nullptr){
            return;
        }
        vec.push_back(root->val);
        preorder(root->left,vec);
        preorder(root->right,vec);
    }
};
// dfs,借助辅助栈
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        // 迭代解法,dfs
        vector<int> vec;
        if(root == nullptr) return vec;
        stack<TreeNode*> s; //辅助栈
        s.push(root);
        while(!s.empty()) {
            root = s.top();
            vec.push_back(root->val);
            s.pop();
            if(root->right != nullptr) s.push(root->right);
            if(root->left != nullptr) s.push(root->left); 
        }
        return vec;
    }
};

2.4 中序遍历(迭代: dfs,辅助栈)

// 中序遍历
// 递归解法
class Solution {
public:
    vector<int> vec;
    vector<int> inorderTraversal(TreeNode* root) {
        if(root == nullptr) return vec;
        inorderTraversal(root->left); //无返回
        vec.push_back(root->val);
        inorderTraversal(root->right);
        return vec;
    }
};
// 构造函数,递归
   vector<int> inorderTraversal(TreeNode* root) {
       vector<int> vec;
       inorder(root, vec);
       return vec;
    }
    void inorder(TreeNode* root, vector<int> &vec) { //传值是本身的拷贝
        if(root == nullptr) return;
        inorder(root->left, vec);
        vec.push_back(root->val);
        inorder(root->right, vec);
    }

// 迭代 dfs
 vector<int> inorderTraversal(TreeNode* root) {
        // 借助辅助栈
        stack<TreeNode*> s; // 只能存储指针,这样才能找到上一级的根节点
        vector<int> vec;
        while(root != nullptr || !s.empty()) { //只要有一个1就是1
            while(root != nullptr) {
                 s.push(root);
                 root = root->left;
            }
            root = s.top(); //返回上一级的根节点
            vec.push_back(root->val);
            s.pop();
            root = root->right;
        }
        return vec;
    }

2.5 后序遍历(迭代跟前序遍历类似)

//递归解法
class Solution {
public:
    vector<int> vec;
    vector<int> postorderTraversal(TreeNode* root) {
        if(root == nullptr) return vec;
        postorderTraversal(root->left);
        postorderTraversal(root->right);
        vec.push_back(root->val);
        return vec;
    }
};
 
//递归解法,构建函数
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> vec;
        postorder(root,vec);
        return vec;

    }
    void postorder(TreeNode* root, vector<int> &vec){
        if(root == nullptr){return;}
        postorder(root->left, vec);
        postorder(root->right, vec);
        vec.push_back(root->val);
    }
};
//迭代,与前序遍历类似
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
       vector<int> vec;
       if(root == nullptr) return vec;
       stack<TreeNode*> s; //借助辅助栈
       s.push(root);
       while(!s.empty()) {
           root = s.top();
           vec.push_back(root->val);
           s.pop();
           if(root->left != nullptr) s.push(root->left);
           if(root->right != nullptr) s.push(root->right); //根右左
       }
       reverse(vec.begin(), vec.end()); //反转,变为左右根
       return vec;
    }
};

2.6 层序遍历(bfs,辅助队列,重点记忆,细节太多)

 // 层序遍历,bfs解法
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if(root == nullptr) return res;  //边界条件
        queue<TreeNode*> q;   //构造辅助队列,利用先进先出的特性
        
        q.push(root);
        while(!q.empty()){
            vector<int> vec;
            // 遍历队列,采用取头部数据的方式
            int len = q.size(); //必须存起来,size是动态变化的
            for(int i = 0; i < len; ++i) {
                root = q.front();  //很重要,调整root指针的指向
                vec.push_back(root->val);
                q.pop();
                //每遍历一个节点就插入左右节点到队列中
                if(root->left != nullptr) q.push(root->left);
                if(root->right != nullptr) q.push(root->right);
            }
            res.push_back(vec); //插入数组
        }
        return res;
    }
};

2.7 平衡二叉树(两重递归解法)

//是否为平衡二叉树
 int highCalculate(TreeNode* pRoot) {
            if(pRoot == nullptr) return 0;
            int leftHigh = highCalculate(pRoot->left);
            int rightHigh = highCalculate(pRoot->right);
            return 1+max(leftHigh, rightHigh);
    }
     
    bool IsBalanced_Solution(TreeNode* pRoot) {
        if(pRoot == nullptr) return true; //空树也是平衡二叉树
        bool leftBalanced = IsBalanced_Solution(pRoot->left);
        bool rightBalanced = IsBalanced_Solution(pRoot->right);
        if(leftBalanced && rightBalanced && 
           abs(highCalculate(pRoot->left) - highCalculate(pRoot->right)) <= 1)
            return true;
        else
            return false;
    }

3. 链表

  • 头指针:顾名思义是一个指针,指向链表的开始地址;(是链表必需的
  • 头结点:第一个节点,该节点只有地址信息,改地址指向下一个结点,数据域无信息;(不是链表所必需的
  • 首元结点:含第一个元素的结点,为链表的实际开始位置,数据域包含第一个数据信息,指针指向下一个结点。

作用

  • 头指针:头指针就是链表的名字,头指针具有标识作用。头指针仅仅是个指针而已。无论链表是否为空,头指针均不为空。但是头指针为空,链表肯定为空
  • 头结点:有了头结点后,对在第一个元素结点前插入结点和删除第一个结点,其操作与对其它结点的操作统一了。
  1. 不带头结点的单链表对于第一个节点的操作与其他节点不一样,需要特殊处理,这增加了程序的复杂性和出现bug的机会,因此,通常在单链表的开始结点之前附设一个头结点。
  2. 带头结点的单链表,初始时一定返回的是指向头结点的地址,所以一定要用二维指针,否则将导致内存访问失败或异常。
  3. 带头结点与不带头结点初始化、插入、删除、输出操作都不一样,在遍历输出链表数据时,带头结点的判断条件是while(head->next!=NULL),而不带头结点是while(head!=NULL),虽然头指针可以在初始时设定,但是如1所述,对于特殊情况如只有一个节点会出现问题。

在这里插入图片描述

在这里插入图片描述

3.1 单向链表

  • 单向链表(单链表)是链表的一种,它由节点组成,每个节点都包含下一个节点的指针,下图就是一个单链表,头节点为空(有时候头节点不一定为空)
图片名称

单链表删除节点

图片名称

单链表添加节点

单链表添加节点

3.2 双向循环链表

  • 双向链表(双链表)是链表的一种。和单链表一样,双链表也是由节点组成,它的每个数据结点中都有两个指针,分别指向直接后继(节点的右侧 next)和直接前驱(节点的左侧 pre)。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表
图片名称

双向链表删除节点

图片名称
//删除节点pindex
pindex->next->prev = pindex->prev;
pindex->prev->next = pindex->next;
free(pindex); //注意释放节点

双向链表添加节点

单链表添加节点
//将pnode节点插入到pindex之前
pnode->prev = pindex->prev;
pnode->next = pindex;
pindex->prev->next = pnode;
pindex->prev = pnode;

4. 数组(二维数组)

4.1 数组插入值

vector<int> B(len, 0);
B[i] = tempA; //如果没有开辟数组空间,不能【】来插入数值

vector<int> B;
B.push_back(tempA);

4.2 数组初始化

vector<vector<bool>> dp(m, vector<bool>(n, false)); // m X n 
vector<int>({array[ret.first], array[ret.second]}); // 匿名对象初始化,初始化两个值
1.vector二维数组的创建和初始化
vector <int> vec(10,90); //将10个一维动态数组初始为90
vector<vector<int>> vec(row,vector<int>(col,0)); 
//初始化row * col二维动态数组,初始化值为0
vector<vector<int>> vec(3,vector<int>(4,0)); //代表三行四列
0 0 0 0 
0 0 0 0
0 0 0 0

2.获取一维数组的长度
int size = vec.size();

3.获取二维数组的长度
int size_row = vec.size(); //获取行数row
int size_col = vec[0].size(); //获取列数col
 
4.给vector二维数组赋值
简单的就直接赋值
ans[0][0]=1;
ans[0][1]=2;
ans[1][0]=3;
ans[1][1]=4;

5. 字符串(char*和string)

单链表添加节点
// 数字字符限定为 0-9
//一、char 转 int
char c = '0';
int i3 = c - '0';              // 0
//int i1 = c;                    // 48
//int i2 = c - 0;                // 48
//int i4 = c + '0';              // 96

//二、int 转 char
int i = 5;
char c4 = i + '0';            // 5
//char c1 = i;                  // 越界
//char c2 = i - 0;              // 越界
//char c3 = i - '0';            // 越界

注意

cout << 'test'; // Note the single quotes.
//给我的输出1952805748.
通常,这是四个字符合并的ASCII值。
't' == 0x74; 'e' == 0x65; 's' == 0x73; 't' == 0x74;
所以0x746573741952805748

5.1 字符串长度获取

  • 用string的成员方法length()获取字符串长度,length()比较直观,表示的就是该字符串的长度。
string str="abc";
len = str.length()
//结果为3
  • 用string的成员方法size()获取字符串长度,size()表示的是string这个容器中的元素个数。那么size()表示的就是这个vector(容器)中char的个数。
string str="abc";
len = str.size();
//结果为3
  • 用strlen获取字符串长度。strlen同样也可以用于C++的string。但是需要用str.c_str()将C++ string转换为char*类型。
string str = "abc";
 const char* c = str.c_str(); //常量指针是指针(指向可改,值不可改)
 char* c = (char*)str.c_str(); 
 const char* c = str.data();
 int len = strlen(c);

5.2 C++ 中 string和char* 的区别

  • char*:char *是一个指针,可以指向一个字符串数组,至于这个数组可以在栈上分配,也可以在堆上分配,堆得话就要你手动释放了。
//vs版本兼容性问题,新版本vs2017,2019都会遇到这个问题,新版本vs对此有更严格的要求
const char* s1 = "str111";
1.char* s1 = "str111";  //内容是不可以改的(vs2019报错)
  s1++; *s1; //可以用移动指针的方式来访问

2.char s2[] = "string"; //内容是可以改的
  s2[0] = 'j';
  for (int i = 0; s2[i] != '\0'; ++i) {
	  cout << s2[i];
  }

3.char* p = new char[10]; //开辟到堆区

5.2.1 string转char*

  • 有如下三种方法实现类型转换,分别是:c_str(); data(); copy();
//1. c_str()方法
//注意:若不添加const,会报错invalid conversion from const char*  to char *。char*要开辟新的空间,可以加上const或强制转化成char*。
string str=“world”;
const char *c = str.c_str(); //加const或等号右边用char*  //常量指针是指针(指向可改,值不可改)
char *p = (char*)str.c_str();  p[0] = 'a'; //内容是可以改的
//c的值不可改

//2. data()方法
string str = "hello";
const char* p = str.data();//加const或等号右边用char*
char * p=(char*)str.data();

//3.copy()方法
string str="qweqwe";
char data[30];
str.copy(data, 3, 0);//0表示复制开始的位置,3代表复制的字符个数

5.2.2 char * 转string

//直接赋值
string s;
char *p = "hello";
s = p;

注意

  • 当我们定义了一个string,就不能用scanf(“%s”,s)和printf(“%s”,s)输入输出。主要是因为%s要求后面是对象的首地址

5.3 C 字符串函数

strcpy(str1,str2);//将str2里面的字符串复制到str1数组中

5.4 string和int的相互转换

一、int转换成string
//c++11标准增加了全局函数std::to_string:
string to_string (int val);
string to_string (long val);
string to_string (long long val);
string to_string (unsigned val);
string to_string (unsigned long val);
string to_string (unsigned long long val);
string to_string (float val);
string to_string (double val);
string to_string (long double val);
二、string转换成int
int stoi (string str);
float stof(string str);
long stol(string str);
long long stoll(string str);
// 1.atoi是 C 语言的库函数,atoi()不会做范围检查,如果超出范围的话,超出上界,则输出上界,超出下界,则输出下界;
// 2.stoi()会做范围检查,默认范围是在int的范围内的,如果超出范围的话则会runtime error!
stoi();//函数原型 int atoi(const string& str);
atoi();//函数原型 int atoi(const char* str);
atof();//浮点型
atol();//long型

#include<cstring> // 头文件
string s1("1234567");
char* s2 = "1234567";
char s3[] = "123";
int a = stoi(s1); //string
int a1 = stoi(s2); // char*

int b = atoi(s2);
int c = atoi(s1.c_str()); //string
int d = atoi(s3);

注意事项

//1. 在字符串尾插一个'A'字符,参数必须是字符形式
str1.puch_back ('A');      

//2. vector<string> 不能拷贝到 string
vector<string> v = {"aa","bb"};
string a(v.begin(), v.end()); //报错,不能区间拷贝

6. 栈、队列

// 判断栈不为空很重要,防止栈溢出(s.top()溢出)

7. 堆

完全二叉树:除了最后一层,其余各层都是满的,并且最后一层的节点集中在左边

7.1 堆的特点

  • 将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等
  • 大部分时间我们是使用完全二叉树来存储堆的,但是堆并不等于完全二叉树,例如二项堆,斐波那契堆,就不属于二叉树
  • 一般的操作进行一次的时间复杂度在O(1)~O(logn)之间
  • 注意:堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。例如,在一个最大堆中,最大的那一个元素总是位于 index 0 的位置,但是最小的元素则未必是最后一个元素。唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个。

7.2 堆的使用情况

  • 构建优先队列
  • 支持堆排序
  • 快速找出一个集合中的最小值(或者最大值)
通常堆(完全二叉树)是通过一维数组来实现的。在数组起始位置为0的情形中,父结点和子结点的位置关系如下:
(1)索引为i的左孩子的索引是 (2* i+1)2)索引为i的右孩子的索引是 (2* i+2)3)索引为i的父结点的索引是 (i -1)/2 
(若len = vec.size()不是下标,而是元素大小,则(len-1-1)/2 = len/2 - 1
注意:这里得到的关系由数组的起始位置来决定。
因此,在第一个元素的索引为0时:
对于大顶堆有:arr(i)>arr(2* i+1) && arr(i)>arr(2* i+2)
对于小顶堆有:arr(i)<arr(2* i+1) && arr(i)<arr(2* i+2)

7.3 优先队列

  • STL 中的 priority_queue 其实就是一个大根堆(默认大顶堆)
#include <queue> //要包含头文件
#include<functional> // 涉及到仿函数,要包含内建的仿函数

优先队列具有队列的所有特性,包括基本操作:
//top 访问队头元素 (普通 queue 为 front())
//empty 队列是否为空
//size 返回队列内元素个数
//push 插入元素到队尾 (并排序)
//emplace 原地构造一个元素并插入队列
//pop 弹出队头元素
//swap 交换内容

//小顶堆 (开头是小) (与 set 和 sort 相反)
priority_queue <int,vector<int>,greater<int> > q; //greater和less是std实现的两个仿函数
//大顶堆(开头是大)
priority_queue <int,vector<int>,less<int> >q;  

//默认是大顶堆,直接输入元素则使用默认容器和比较函数
priority_queue<int> a; 
//等价 priority_queue<int, vector<int>, less<int> > a;

与往常的初始化不同,优先队列的初始化涉及到一组而外的变量,这里解释一下初始化:

  1. T就是Type为数据类型
  2. Container是容器类型,(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector
  3. Compare是比较方法,类似于sort第三个参数那样的比较方式,对于自定义类型,需要我们手动进行比较运算符的重载。与sort直接Bool一个函数来进行比较的简单方法不同,Compare需要使用结构体的运算符重载完成,直接bool cmp(int a,int b){ return a>b; } 这么写是无法通过编译的。
struct cmp
{ //这个比较要用结构体表示
    bool operator()(int &a, int &b) const
    {
        return a > b;   //小顶堆
    }
};
  
priority_queue<int,vector<int>,cmp> q;    //使用自定义比较方法
priority_queue<int> pq;
// pair类型
 priority_queue<pair<int, int> > a; 
    pair<int, int> b(1, 2);
    pair<int, int> c(1, 3);
    pair<int, int> d(2, 5);
    a.push(d);
    a.push(c);
    a.push(b);

// 自定义数据类型
struct tmp2 //重写仿函数
{
    bool operator() (tmp1 a, tmp1 b) 
    {
        return a.x < b.x; //大顶堆
    }
};

最小的K个数

29 最小的K个数

// 优先队列priority_queue
class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> vec;
        if(k == 0 || k > input.size()) return vec;
        // 构建优先队列
        priority_queue<int, vector<int>, greater<int> > pq; //默认大顶堆,换成小顶堆
        for(int i = 0; i < input.size(); ++i) {
            pq.push(input[i]);
        }
        while(k--){
            vec.push_back(pq.top());
            pq.pop();
        }
        return vec;
    }
};

8. 图论

  • 图是用来对对象之间的成对关系建模的数学结构,由"节点"或"顶点"(Vertex)以及连接这些顶点的"边"(Edge)组成。
  • 图的顶点集合不能为空,但边的集合可以为空。图可能是无向的,这意味着图中的边在连接顶点时无需区分方向。否则,称图是有向的。
  • 图的分类:无权图和有权图,连接节点与节点的边是否有数值与之对应,有的话就是有权图,否则就是无权图。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宇光_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值