c++基本数据结构用法积累

主要看的视频是b站的代码随想录:代码随想录

代码随想录

  • 数组

操作:

—— 二维数组的创立的元素添加;

String s;//s也是用vector定义的,操作同vector

vector<vector<int>> res;

vector<int> temp;

res.push_back(temp);//尾插

res.pop_back();//尾删

int n = res.back()//取最后一个元素

reverse(res);//二维数组仍然可以反转元素,就算元素是数组

——O(n)级别常用方法是双指针,一般是从左右往中间、同时从左往右、滑动窗口(满足条件时让两端移动,题号209);

——单精度浮点数7、8位精读,双精读浮点数52位。

——当值输入为2^31时,建议定义为long int

  • 链表(常用递归和迭代)

        通用思想——重要的是获得pre,即要处理节点的前一个节点;迭代:把解决一个大问题变成解决一个小问题的重复;

        虚拟头节点帮助删除、插入节点;

        递归有些难以理解,而且慢;

        类、结构体后有分号,方法后面没有;

        dummyHead初始化时别忘了指向head;

        常用双指针,左右、快慢指针;

  • 哈希表(判断元素是否出现过、相交、再现)
  • 定义

        vector<int> ans(size,value);

        unordered_set<key> set;//集合的数学定义

        unordered_map<key,value> map;//key是去重的、唯一的,所有的操作对象都是key,比如查找;key是直接查询的元素

——set的使用

unordered_set<key> set;//定义

Set.insert(s);//插入

set.count(r);//出现的的次数

Set.find(r)!=set.end();//查找r

Set.erase(first,last);//删除,[f,l)左开右闭

Set.clear();//清空

Set.empty();//判空

Set.size();//大小

——map的使用

unordered_map<key,value> map;//定义

Map.insert(make_pair(key,val));//插入,key和val不都为int类型时;

map.count(key);//出现的的次数

map.find(key)!=set.end();//查找key

map.erase(first,last);//删除,[f,l)左开右闭

map.clear();//清空

map.empty();//判空

map.size();//大小

元素是否出现,map.cout(r),返回的是出现次数,或者map.find(r)!=map.end();表示找完也找不到

尾插,v.push_back(1),v.emplace_back(1),但后者效率高;

初始化 vector<int> ans(k);//k个元素,不是方括号;//vector<int> ans(k,0);//k个0元素

        unordered_set<ListNode *> visited; 1.存储但数值键 2.无序;

        认真读题;遇到任何题目,首先考虑数据结构;

        哈希表:1.数组,值小时 2用.set 值大时,无规律 3.map 映射

        for(auto& ch:s) 指遍历串中的每一个字符,每次赋值给ch;&表示不需要复制,直接取元素;

        AddressSanitizer:DEADLYSIGNAL——可能是无限、传参错误

        ans.back()取vector数组的最后一个元素,为空时会地址报错;

        注意continue和break别用混了,一个是继续下一个循环,一个是跳出循环;

        防止数据越界, long sum = (long)nums[k]+nums[i]+nums[l]+nums[r];后面long

        数据运算剪枝要考虑整体的极限、极端情况;

        判断循环可用快慢指针;

        先考虑基本数据结构;

        小写字母哈希存储,优先使用a[26]

        Vector数组默认初始化赋值0;

  • 字符串(算法思路与数组相通)

        Swap(a,b)交换,o(1);

        Reverse(s.begin()+i,s.end()+j);//反转字符串

        将一个复杂的问题分步骤、分方法解决,一个步骤、方法解决一个小问题;

        字符串操作工具

  1. 使用 split 将字符串按空格分割成字符串数组;
  2. 使用 reverse 将字符串数组进行反转;
  3. 使用 join 方法将字符串数组拼成一个字符串。

 ——String:append():在string后面添加文本

使用append()添加文本常用方法:

直接在后面添加另一个完整的字符串:

如str1.append(str2);

添加另一个字符串的某一段子串:

如str1.append(str2, 11, 7);

添加几个相同的字符:

如str1.append(5, ‘.’);

——String :substr():

截取函数substr()的意思是:可以将字符串的某一部分,通过该函数提取出来,形成一个新的字符串

String s = a.substr(起始位置,长度(省略表示到字符串尾));

Str.erase();对字符串进行删除操作

Str.erase(str.begin()+9);//删除指定位置的元素,写数值3会删除位置3后的元素

Str.erase(9,10);//删除指定位置开始10长度的所有元素,参数必须是数值

Str.erase(str.begin()+9,str.end()-4);//删除范围内元素,左开右闭,参数必须是迭代器

KMP算法:求最大相等前后缀长度,为next数组;

Str.find()函数:

Str.find(s串,起始位置);

Str.find(s串,起始位置,出现次数);

Str.find(c,起始位置);

如果查找成功,find()函数返回子串或字符在字符串中第一次出现的位置;否则,返回一个特殊值string::npos,表示查找失败。

  • 队列、栈
  • 基本定义  

存储元素对的栈       

stack<pair<TreeNode*,int>> st;//栈的每个元素为<节点,数值>对

st.push(make_pair(root,0));//加入元素

auto [node,cnt] = st.top();//推测类型

 ——队列操作

加入队尾  q.push(x); 获得长度,int size =q.size();;

queue<int> q;//队列

queue<TreeNode*> q{rt};//队列元素为treenode类型,并初始加入rt

m=q.front()->val;//这都可以

返回队前元素 q.front();

删除队首元素, q.pop();

交换两个队列的元素 swap(q1,q2);

std::string 类本身就提供了类似「入栈」和「出栈」的接口

string st;

st.empty();//判空

st.back();//尾元素

st.pop_back();//尾删

st.push_back(s[i]);//尾插

——stoi()函数(string to int),把string 型转化为int型stoi(123) = 123;

      }; int i = strll(1)强制转换为long long 型;

atoi()//将char*型转化为int型;stoi//将string 型转化为int型;s.c_str(),将string 类型转化为char *类型

——  双端队列deque,C++中deque是stack和queue默认的底层实现容器;

deque<int> q;//声明
    q.push_front(1);//前插
    q.push_back(2);//尾插
    int n1 = q.front();//读取前元素

int n2 = q.back();//读取尾元素
    q.pop_front();//前出队
    q.pop_back();//尾出队

int len = q.size();//获取大小

q.empty()//判断空

—— 优先队列priority_queue,默认存储容器是vector数组,逻辑实现是大根堆。大根堆每棵树满足根大于孩子;(s.begin(),s.end())是左开右闭;

(1)小根堆原则是每棵树满足根小于孩子;

逻辑结构:完全二叉树;

物理结构:一维数组;

左孩子下标 = 父*2+1,右孩子下标=父*2+2;

父节点下标= (孩子节点下标-1)/2;

  1. 使用。Priority_queue<int> q;//基于大根堆的优先队列,左大

Priority_queue<int,vector<int>,less<int>> q//int为元素类型,less表示基于大根堆;

Priority_queue<double,vector<double>,greater<double>>q//doubl

e为元素类型,greater表示基于小根堆;

q.empty() //判空

auto x = q.top()//读取头元素

q.pop()//删除头元素

Int len = q.size();//读取长度

q.push(x);//加入x,并排序

K auto/decltype;类型推导

Int a=1;

auto b= a;//自行推到类型

decltyp(a) c = a;//获取a的类型

lq.top().first//优先队列的首元素对的第一个元素

  1. 注意

调用本类的方法是 this->pop();区分与q1.pop() 是调用q1的封装方法

思考边界情况;

理清思路,写代码之前,写好算法思想,分解为几个步骤,理清思路。

出栈时注意-和/的操作数顺序,先出栈为右操作数;

  • 树(递归定义、操作

做题:一、确定遍历方式 二、递归、迭代

树的定义是递归实现的,所有操作都可以通过递归实现。

树的一切操作都基于遍历(遍历后才能处理):

  1. DFS,共6种=前中后*2(交换左右顺序),递归、迭代+队列都可以。
  2. BFS,迭代+队列,队列为主,少数情况下用栈。

建议画图变成,照着图调试;

树是递归定义的,理论上树的一切操作都可以递归实现,树是适合递归的最佳体质。

  1. 基础理论

A树种:

完全二叉树:每一层都满节点;

满二叉树:共k层,前k-1层满,第k层左孩子节点连续;

二叉搜索树(b树):左孩子节点<根节点<右孩子节点;

平衡二叉搜索树(b+树):在b树基础上,满足递归定义-左右子树高度差不超过1;b+树是map set的底层实现;unordered_map底层实现是哈希表;

B存储方式:链式(指针)、线性(数组)

链式  val,next;二叉树一般由链表实现,传输根节点;

线式  完全二叉树或者满二叉树,lch=p*2+1,rch=p*2+2;p=(ch-1)/2

C二叉树遍历

深度优先搜索:孩子优先;前中后序,递归法(方法自调用)、迭代法-用栈模拟递归(leetcode 144)。前中后是根的顺序,左孩子一定在右孩子的左边。

广度优先遍历:兄弟优先;迭代法,使用队列。

D定义

struct TNode

{

Int val;

TNode* lch,*rch; //指针指向左右孩子,左右孩子同样是节点,递归定义

TNode(int x):val(x),lch(NULL),rch(NULL){}  //不用分号,是一个构造方法
};

TNode *p = new TNode(x);//创建对象,->是链表节点的专属;

m叉树:Rt是根节点,rt的children是一个节点数组,这是m叉树的定义结构

//m叉树

struct Node{
    int val;
    vector<Node*> ch;
    Node():val(0),ch(){}
    Node(int x):val(x),ch(){}
    Node(int x,vector<Node*> pl):val(x),ch(pl){}
};

int main()
{
    Node *p1 = new Node(1);
    Node *p2 = new Node(2);
    Node *p = new Node(1,{p1,p2});
    for(auto c:p->ch) cout<<c->val<<endl;
    return 0;
}

  1. 遍历

迭代法实现遍历二叉树,看leetcode94;

迭代法使用栈模拟递归;

DFS(深度优先搜索)前序遍历(中左右):遍历顺序等于处理顺序,迭代好写;

后序遍历(左右中):前序遍历中左右颠倒,然后反转res数组

中序遍历(左中右):遍历顺序不等于处理顺序。

BFS(广度优先遍历)层序遍历,树结构借助队列;

BFS 的使用场景总结:层序遍历、(无权)最短路径问题。102的讲解中详细介绍。

Dijkstra 算法适合带权最短路径问题。

遍历树,如果每层只有头,一般是传参修改未覆盖;

传指针参数并覆盖操作:void handle(Node* &last,Node* &p,Node* &nextstart)

        递归三要素:(1)确认传参和返回值(2)确定终止条件  (3)确定单层递归的逻辑(每次递归重复处理的信息)。写递归不必追求代码简短,主要是易于理解为主。

        迭代三要素类似递归:(1)确认参数和迭代值(2)确定中止、终止条件(3)确定单层递归的逻辑

        不要定义与方法同名的变量,可以定义与类的成员相似的变量,尽量区分。

        要有成对思想,有入口就要考虑出口,有加入,就要考虑到删除,有追求就要考虑到失败。

        二叉树递归演示,最简单的 3个节点就够了;

        完全二叉树可以利用其由满二叉树组成的性质计算节点数(222);

        完全二叉树的子树是完全二叉树。

        求高度建议后序遍历,求深度建议前序遍历。

        abs取绝对值,absolute。

        一般题目,后序遍历居多,也就是先计算,递归最末尾处返回。

        中序+(前序/后序)可以确定一颗二叉树,前序+后序不能确定一颗二叉树。

——二叉搜索树

递归定义,左子树所有val<根节点val<右子树所有val。

递归和迭代都比较简单,因为二叉搜素树严格有序。

验证:中序遍历,单调递增。

回溯、向上返回,是递归的后序遍历。

delete node;//可以释放内存

  • (递归)回溯(与递归相辅相成)

有递归就有回溯,通常出现在递归的下面。

纯暴力的搜索:组合(无序)、切割、子集、排列、棋盘问题。

如何理解:想象成图形结构,所有回溯法都可以抽象为一个树形结构-n叉树(回溯也是递归过程),宽度是集合的大小,深度是递归的深度。

void backtracking(参数) {

    void backtracking(参数) {

    if (终止条件) {

        存放结果;

        return;

    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {

        处理节点;

        backtracking(路径,选择列表); // 递归前后操作对称-回溯

        回溯,撤销处理结果

    }

}

回溯三部曲:
1 确定递归的参数和返回值

2 确定终止条件

3 确定单层递归逻辑

剪枝:for循环内,处理n;

常用剪枝:i<=n-(k-path.size())+1

—— if(path.back()==target) //取元素之前要先保证非空,要不然会报空指针异常

——回溯用target-num[i]作为下一层递归值时,最终出口是target==0,不是target==path.back();

——横向树层去重,针对数值:数组中有重复元素,先排序,然后同一树层去重,if(i>index&&nums[i]==nums[i-1]) continue;

————注意递归参数 find(nums,i+1);//i+1不是index+1

————纵向全树树枝去重,针对下标vector<bool> set(size+1,false);//使用vector或者set使得每一层用过的元素作为标记,如果当前值出现过,那就把他跳过;

————全排列去重:used<bool>数组;还有一种去重是用过的设置为超出额度的值;需要区分的是整棵树(即整个答案,如不重复数组元素的全排列)不能有重复的值,还是同层内不可有重复的值(如数组内元素可以相同);

————for(i=row-1,j=col+1;i>=0&&j<n;i--,j++)//&&如果换成逗号会报地址越界错误,操蛋

————通过写方法把一个复杂问题分解成一步步简单的操作和方法;

————难题就是有一个个简单题组成的,是简单思维的有机组合,多花些时间拆解和组装而已;

  • 贪心算法

算法思想:每次找到局部最优,最后达到全局最优。如每次取面额最大的钱,最后使得面额最大。思考大概找到最优,结果推到全局最优,想不出反例即可。实用主义,差不多就行。

贪心无套路。

需要左右都满足情况时,可以考虑分别满足,“左一层,右一层”,使得同时满足两种情况,即分开讨论;

分类讨论。思路清晰再写代码好些;

不要两边一起贪;

 题号406

static bool cmp(vector<int> &a,vector<int> &b)//取地址能减少复制的时间和空间

    {//按身高排序,保证身高最高靠前

        if(a[0]==b[0]) return a[1]<b[1];//如果第一属性相等,第二属性逆序

        else return a[0]>b[0];//第一属性顺序

    }

    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {

        sort(people.begin(),people.end(),cmp);

        list<vector<int>> que;//链表,元素是数组

        for(int i=0;i<people.size();i++)

        {

            int pos=people[i][1];

            auto it = que.begin();//list<vector<int>>::iterator it

迭代器

             while(pos--) it++;//迭代器没法直接加数字

            que.insert(it,people[i]);//it位置插入

        }

        vector<vector<int>> res(que.begin(),que.end());//把链表的元素复制给二维数组

题目一定有答案,我们努力靠直觉、思考去靠近就行了;

  • 动态规划

表示状态并进行转移,即规划动态的转移

状态转移

有:基础问题、背包问题、打家劫舍、股票问题、子序列问题

  1. dp[i]的含义,一、二维度的状态转移的数组
  2. 递推公式,dp[i]从何而来?
  3. dp数组的初始化
  4. 遍历顺序
  5. 打印dp数组(debug)
  6. 状态压缩

规范化、过程化。

越嫌麻烦越麻烦,越求快越慢。不要嫌麻烦,慢慢扎实推导。慢慢推到递推公式,一定能成功,不用着急,不会太难,也不会太简单。

————0-1背包问题()

建议听讲课程后写出代码。

0-1即两种选择,背包即容器。

根据dp意义去推导dp推导公式;

  • 单调栈

找到第一个更大、更小的元素。

用去Mod模拟转圈、首尾相连。

for(int i=0;i<s.size()*2;i++)

s[i%s.size()];

形成一个中间大、两边小或者相反的形状,保证了数据的有序性;

     heights.insert(heights.begin(),0);

     heights.push_back(0);

顺入逆出;

  • 图论

DFS,深度优先搜索,回溯是一种dfs;dfs,每一条道走到头,递归+回溯,一条条射线,主要是搜索所有路径。

void dfs(参数) {

    if (终止条件) {

        存放结果;

        return;

    }

    for (选择:本节点所连接的其他节点) {

        处理节点;

        dfs(图,选择的节点); // 递归

        回溯,撤销处理结果//回溯

   }}

BFS,广度优先搜索,一圈一圈向外搜索,寻找最短路径,光源向外同步发散。地图可以有障碍。用栈和队列存储都可以,不用递归,也不用回溯。

void bfs(参数)

{

queue<类型> q;

q.push();

visited 数组:

while(!q.empty())

{

获取首元素;

for(所有延申方向)

{

入队;

if(不合法) continue;

if(目标)

{

处理。

}

}

}}

代码就是益智小游戏。

队列元素成对:

        queue<pair<int,int>> q;

        q.push({x,y});

        pair<int,int> cur = q.front();q.pop();

        int nextx = cur.first+dir[i][0];

        int nexty = cur.second+dir[i][1];

visited数组访问了,立刻设为true,防止重复访问;

正难则反;

  

  • 19
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值