算法基础随笔
一、数据结构
1.map
1、优点:
(1)有序性,这是map结构最大的有点,其元素的有序性在很多应用中都会简化很多的操作。
(2)红黑树,内部实现一个红黑树使得map的很多操作在log2n的时间复杂度下就可以实现,因此效率非常的高。
2、缺点:空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间。
3、适用处:对于那些有顺序要求的问题,用map会更高效一些。
4、map遍历
// c++ 98
map<string, int>::iterator it;
for (it = m2.begin(); it != m2.end(); it++) {
string s = it->first;
printf("%s %d\n", s.data(), it->second);
}
// c++ 11
for(auto it : map1){
cout << it.first <<" "<< it.second <<endl;
}
5.插入元素
// 定义一个map对象
map<int, string> mapStudent;
// 第一种 用insert函數插入pair
mapStudent.insert(pair<int, string>(000, "student_zero"));
// 第二种 用insert函数插入value_type数据
mapStudent.insert(map<int, string>::value_type(001, "student_one"));
// 第三种 用"array"方式插入
mapStudent[123] = "student_first";
6.查找元素
// find 返回迭代器指向当前查找元素的位置否则返回map::end()位置
iter = mapStudent.find("123");
if(iter != mapStudent.end())
cout<<"Find, the value is"<<iter->second<<endl;
else
cout<<"Do not Find"<<endl;
7.删除与清空元素
//迭代器刪除
iter = mapStudent.find("123");
mapStudent.erase(iter);
//用关键字刪除
int n = mapStudent.erase("123"); //如果刪除了會返回1,否則返回0
//用迭代器范围刪除 : 把整个map清空
mapStudent.erase(mapStudent.begin(), mapStudent.end());
//等同于mapStudent.clear()
2.unordered_map
1、优点:因为内部实现了哈希表,因此其查找速度非常的快。
2、缺点:哈希表的建立比较耗费时间
3、适用处:对于查找问题,unordered_map 会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map
在使用上map和unordered_map是一样的
3.priority_queue
优先队列与队列一样,只能从队尾插入元素,从队首删除元素。但是它有一个特性,就是队列中最大的元素总是位于队首,所以出队时,并非按照先进先出的原则进行,而是将当前队列中最大的元素出队。这点类似于给队列里的元素进行了由大到小的顺序排序。元素的比较规则默认按元素值由大到小排序,可以重载“<”操作符来重新定义比较规则。
和队列基本操作相同:
top() 访问队头元素
empty() 队列是否为空
size() 返回队列内元素个数
push() 插入元素到队尾 (并排序)
emplace() 原地构造一个元素并插入队列
pop() 弹出队头元素 swap 交换内容
定义:priority_queue<Type, Container, Functional>:
Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式。
//升序队列
priority_queue <int,vector<int>,greater<int> > q;
//降序队列
priority_queue <int,vector<int>,less<int> >q;
实例:
#include<iostream>
#include <queue>
using namespace std;
int main()
{
//对于基础类型 默认是大顶堆
priority_queue<int> a;
//等同于 priority_queue<int, vector<int>, less<int> > a;
// 这里一定要有空格,不然成了右移运算符
priority_queue<int, vector<int>, greater<int> > c; //这样就是小顶堆
priority_queue<string> b;
for (int i = 0; i < 5; i++)
{
a.push(i);
c.push(i);
}
while (!a.empty())
{
cout << a.top() << ' ';
a.pop();
}
cout << endl;
while (!c.empty())
{
cout << c.top() << ' ';
c.pop();
}
cout << endl;
b.push("abc");
b.push("abcd");
b.push("cbd");
while (!b.empty())
{
cout << b.top() << ' ';
b.pop();
}
cout << endl;
return 0;
}
疑问:二叉堆实现优先队列:优先队列是基于大根堆和无序数组实现的????
4.deque
Deque(双端队列)是一种具有队列和栈的性质的数据结构。双端队列的元素可以从两端弹出,其限定插入和删除操作在表的两端进行
1.头文件
#include <deque>
2.定义
a) deque<int>s1;
b) deque<string>s2;
c) deque<node>s3; /*node为结构体,可自行定义。*/
3.常用操作
//a) 构造函数
deque<int> ideq
//b)增加函数
ideq.push_front( x):双端队列头部增加一个元素X
ideq.push_back(x):双端队列尾部增加一个元素x
//c)删除函数
ideq.pop_front():删除双端队列中最前一个元素
ideq.pop_back():删除双端队列中最后一个元素
ideq.clear():清空双端队列中元素
//d)判断函数
ideq.empty() :向量是否为空,若true,则向量中无元素
//e)大小函数
ideq.size():返回向量中元素的个数
4.示例
#include <deque>
#include <cstdio>
#include <algorithm>
using namespace std;
int main()
{
deque<int> ideq(20); //Create a deque ideq with 20 elements of default value 0
deque<int>::iterator pos;
int i;
//使用assign()赋值 assign在计算机中就是赋值的意思
for (i = 0; i < 20; ++i)
ideq[i] = i;
//输出deque
printf("输出deque中数据:\n");
for (i = 0; i < 20; ++i)
printf("%d ", ideq[i]);
putchar('\n');
//在头尾加入新数据
printf("\n在头尾加入新数据...\n");
ideq.push_back(100);
ideq.push_front(i);
//输出deque
printf("\n输出deque中数据:\n");
for (pos = ideq.begin(); pos != ideq.end(); pos++)
printf("%d ", *pos);
putchar('\n');
//查找
const int FINDNUMBER = 19;
printf("\n查找%d\n", FINDNUMBER);
pos = find(ideq.begin(), ideq.end(), FINDNUMBER);
if (pos != ideq.end())
printf("find %d success\n", *pos);
else
printf("find failed\n");
//在头尾删除数据
printf("\n在头尾删除数据...\n");
ideq.pop_back();
ideq.pop_front();
//输出deque
printf("\n输出deque中数据:\n");
for (pos = ideq.begin(); pos != ideq.end(); pos++)
printf("%d ", *pos);
putchar('\n');
return 0;
}
5.set
set 容器内的元素会被自动排序,set 与 map 不同,set 中的元素即是键值又是实值,set 不允许两个元素有相同的键值。不能通过 set 的迭代器去修改 set 元素,原因是修改元素会破坏 set 组织。当对容器中的元素进行插入或者删除时,操作之前的所有迭代器在操作之后依然有效。
1.头文件
#include <set> // set属于std命名域的,因此需要通过命名限定,例如using std::set;
2.定义
set<int> a; // 定义一个int类型的集合a
// set<int> a(10); // error,未定义这种构造函数
// set<int> a(10, 1); // error,未定义这种构造函数
set<int> b(a); // 定义并用集合a初始化集合b
set<int> b(a.begin(), a.end()); // 将集合a中的所有元素作为集合b的初始值
3.常规操作
###容量函数###
容器大小:st.size();
容器最大容量:st.max_size();
容器判空:st.empty();
查找键 key 的元素个数:st.count(key);
###添加函数###
在容器中插入元素:st.insert(const T& x);
任意位置插入一个元素:st.insert(iterator it, const T& x);
###删除函数###
删除容器中值为 elem 的元素:st.pop_back(const T& elem);
删除it迭代器所指的元素:st.erase(iterator it);
删除区间 [first,last] 之间的所有元素:st.erase(iterator first, iterator last);
清空所有元素:st.clear();
###访问函数###
查找键 key 是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end(): st.find(key);
4.示例
容量函数
#include <iostream>
#include <set>
using namespace std;
int main(int argc, char* argv[])
{
set<int> st;
for (int i = 0; i<6; i++){
st.insert(i);
}
cout << st.size() << endl; // 输出:6
cout << st.max_size() << endl; // 输出:214748364
cout << st.count(2) << endl; // 输出:1
if (st.empty())
cout << "元素为空" << endl; // 未执行
return 0;
}
添加函数
#include <iostream>
#include <set>
using namespace std;
int main(int argc, char* argv[])
{
set<int> st;
// 在容器中插入元素
st.insert(4);
// 任意位置插入一个元素
set<int>::iterator it = st.begin();
st.insert(it, 2);
// 遍历显示
for (it = st.begin(); it != st.end(); it++)
cout << *it << " "; // 输出:2 4
cout << endl;
return 0;
}
删除函数
#include <iostream>
#include <set>
using namespace std;
int main(int argc, char* argv[])
{
set<int> st;
for (int i = 0; i < 8; i++)
st.insert(i);
// 删除容器中值为elem的元素
st.erase(4);
// 任意位置删除一个元素
set<int>::iterator it = st.begin();
st.erase(it);
// 删除[first,last]之间的元素
st.erase(st.begin(), ++st.begin());
// 遍历显示
for (it = st.begin(); it != st.end(); it++)
cout << *it << " "; // 输出:2 3 5 6 7
cout << endl;
// 清空所有元素
st.clear();
// 判断set是否为空
if (st.empty())
cout << "集合为空" << endl; // 输出:集合为空
return 0;
}
访问函数
#include <iostream>
#include <set>
using namespace std;
int main(int argc, char* argv[])
{
set<int> st;
for (int i = 0; i < 6; i++)
st.insert(i);
// 通过find(key)查找键值
set<int>::iterator it;
it = st.find(2);
cout << *it << endl; // 输出:2
return 0;
}
6.string
string str;
str.push_back('s'); // 向末尾插入一个字符
str.pop_back(); // 向末尾删除一个字符
7.queue
-
头文件
#include<queue>
2. 定义
queue<type> val;
3. 方法
q.empty() 如果队列为空返回true,否则返回false
q.size() 返回队列中元素的个数
q.pop() 删除队列首元素但不返回其值
q.front() 返回队首元素的值,但不删除该元素
q.push() 在队尾压入新元素
q.back() 返回队列尾元素的值,但不删除该元素
4. 示例
#include <queue>
#include <iostream>
using namespace std;
int main(){
queue<int> q;
for (int i = 0; i < 10; i++){
q.push(i);
}
if (!q.empty()){
cout << "队列q非空!" << endl;
cout << "q中有" << q.size() << "个元素" << endl;
}
cout << "队头元素为:" << q.front() << endl;
cout << "队尾元素为:" << q.back() << endl;
for (int j = 0; j < 10; j++){
int tmp = q.front();
cout << tmp << " ";
q.pop();
}
cout << endl;
cout << "队头元素为:" << q.front() << endl;
if (!q.empty()){
cout << "队列非空!" << endl;
}
return 0;
}
二、算法
1.sort函数
-
sort函数包含在头文件为#include<algorithm>的c++标准库中,调用标准库里的排序方法可以实现对数据的排序。其实STL中的sort()并非只是普通的快速排序,除了对普通的快速排序进行优化,它还结合了插入排序和堆排序。根据不同的数量级别以及不同情况,能自动选用合适的排序方法。
-
sort函数的模板有三个参数:
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
(1)第一个参数first:是要排序的数组的起始地址。
(2)第二个参数last:是结束的地址(最后一个数据的后一个数据的地址)
(3)第三个参数comp是排序的方法:可以是从升序也可是降序。如果第三个参数不写,则默认的排序方法是从小到大排序。
实例:
#include<iostream>
#include<algorithm>
using namespace std;
bool cmp(int a,int b);
main(){
//sort函数第三个参数自己定义,实现从大到小
int a[]={45,12,34,77,90,11,2,4,5,55};
sort(a,a+10,cmp);
for(int i=0;i<10;i++)
cout<<a[i]<<" ";
}
//自定义函数
bool cmp(int a,int b){
return a>b;
}
字符串的比较
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main(){
string str1 = "ABC";
string str2 = "ABD";
string str3 = "ABC";
string str4 = "CAB";
sort(str4.begin(),str4.end());
if(str1 == str2){
cout <<"TRUE"<<endl;
}
if(str1 == str3)
cout <<"TRUE"<<endl;
if(str1 == str4)
cout <<"TRUE--"<<endl;
int m = str1.compare(str3);
cout <<m << endl;
string str = "abcdefgh";
cout << str.substr(0,3) << endl;
return 0;
}
2.字符分割
(1).用strtok函数进行字符串分割
原型: char *strtok(char *str, const char *delim);
功能:分解字符串为一组字符串。
参数说明:str为要分解的字符串,delim为分隔符字符串。
返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。
其它:strtok函数线程不安全,可以使用strtok_r替代。
//借助strtok实现split
#include <string.h>
#include <stdio.h>
int main()
{
char s[] = "Golden Global View,disk * desk";
const char *d = " ,*";
char *p;
p = strtok(s,d);
while(p)
{
printf("%s\n",p);
p=strtok(NULL,d);
}
return 0;
}
(2).用find() 和 substr() 函数进行字符串分割
功能:string的find()函数用于找出字母在字符串中的位置。
原型:int find(string str,int position)
str:是要找的元素
position:字符串中的某个位置,表示从从这个位置开始的字符串中找指定元素。可以不填第二个参数,默认从字符串的开头进行查找。
返回值为目标字符的位置,当没有找到目标字符时返回npos。
//实例
string s = "hello world!";
cout << s.find("e") << endl;
结果为:1
string s = "hello world!";
if (s.find("a") == s.npos) {
cout << "404 not found" << endl;
}
结果为:404 not found
string s = "hello world!";
cout << s.find("l",5) << endl;
结果为:9
string s = "hello world!";
cout << "first time occur in s:"<<s.find_first_of("l") << endl; //第一次
cout << "last time occur in s:" << s.find_last_of("l") << endl; //最后一次
结果为:
first time occur in s:2
last time occur in s:9
原型:string substr ( size_t pos = 0, size_t n = npos ) const;
功能:获得子字符串。
参数说明:pos为起始位置(默认为0),n为结束位置(默认为npos)
返回值:子字符串
#include<string>
#include<iostream>
using namespace std;
int main()
{
string s("12345asdf");
string a = s.substr(0,5); //获得字符串s中从第0位开始的长度为5的字符串
cout << a << endl;
}
//输出结果为:12345
#include <iostream>
#include <string>
#include <vector>
//字符串分割函数
std::vector<std::string> split(std::string str, std::string pattern)
{
std::string::size_type pos;
std::vector<std::string> result;
str += pattern;//扩展字符串以方便操作
int size = str.size();
for (int i = 0; i < size; i++)
{
pos = str.find(pattern, i);
if (pos < size)
{
std::string s = str.substr(i, pos - i);
result.push_back(s);
i = pos + pattern.size() - 1;
}
}
return result;
}
int main()
{
std::string str;
std::cout<<"Please input str:"<<std::endl;
//std::cin>>str;
getline(std::cin,str);
std::string pattern;
std::cout<<"Please input pattern:"<<std::endl;
//std::cin>>pattern;
getline(std::cin,pattern);//用于获取含空格的字符串
std::vector<std::string> result=split(str,pattern);
std::cout<<"The result:"<<std::endl;
for(int i=0; i<result.size(); i++)
{
std::cout<<result[i]<<std::endl;
}
std::cin.get();
std::cin.get();
return 0;
}
(3) istringstream
stringstream是C++的一个输入输出控制类,作用是将字符串按照空格分割。
头文件 #include <sstream>
#include <sstream>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
string s = "a/b/c/d";
istringstream iss(s);
string buffer;
while(getline(iss, buffer, '/'))
{
cout<<buffer<<endl;
}
return 0;
}
可以将字符串分割简单地封装成一个split
函数
#include <sstream>
#include <iostream>
#include <vector>
using namespace std;
/**
*分割字符串,输入待分割的字符串s以及分割符sep;
*这里没有对分割可能得到的空串进行处理,可以直接删除空串
*/
vector<string> split(string s, char sep)
{
istringstream iss(s);
vector<string> res;
string buffer;
while(getline(iss, buffer, sep)){
res.push_back(buffer);
}
return res;
}
int main()
{
string s = "a/b/c/d";
vector<string> res = split(s, '/');
for(int i=0; i<res.size(); i++) cout<<res[i]<<" ";
cout<<endl;
return 0;
}
3.二分查找法
参考:(89条消息) 【二分查找】详细图解Charon_cc的博客-CSDN博客二分查找
模板:左闭有闭的写法
重点:就是循环条件和后续的区间赋值问题
总结:在有序的序列中,时间复杂度是logN。
int search(int nums[], int size, int target) //nums是数组,size是数组的大小,target是需要查找的值
{
int left = 0;
int right = size - 1; // 定义了target在左闭右闭的区间内,[left, right]
while (left <= right) { //当left == right时,区间[left, right]仍然有效
int middle = left + ((right - left) / 2);//等同于 (left + right) / 2,防止溢出
if (nums[middle] > target) {
right = middle - 1; //target在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; //target在右区间,所以[middle + 1, right]
} else { //既不在左边,也不在右边,那就是找到答案了
return middle;
}
}
//没有找到目标值
return -1;
}
//
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
for (const auto& row: matrix) {
//lower_bound() 二分查找法
auto it = lower_bound(row.begin(), row.end(), target);
if (it != row.end() && *it == target) {
return true;
}
}
return false;
}
};
以下是二分查找法的变种题型(check条件)
在排序数组中查找数字 I_(剑指 Offer)
class Solution {
public:
int search(vector<int>& nums, int target) {
if(!nums.size())
return 0;
int i = 0,j = nums.size() - 1;
while(i <= j){
int m = (i + j)/2;
if(nums[m] <= target)
i = m + 1;
else
j = m - 1;
}
int right = i;
if(j >= 0 && nums[j] != target)
return 0;
i = 0;
j = nums.size() - 1;
while(i <= j){
int m = (i + j)/2;
if(nums[m] < target){
i = m + 1;
}else
j = m - 1;
}
int left = j;
return right - left - 1;
}
};
0~n-1中缺失的数字_(剑指 Offer)
class Solution {
public:
int missingNumber(vector<int>& nums) {
int l = 0,r = nums.size() - 1;
while(l <= r){
int mid = (l + r) >> 1;
if(nums[mid] == mid)
l = mid + 1;
else
r = mid - 1;
}
return nums[r] != r ? r : r + 1;
}
};
4.计算二位数组的行列数
#include<iostream>
using namespace std;
int main(){
int a[2][3] = {{1,2,3},{4,5,6}};
int lines = sizeof(a) / sizeof(a[0][0]);
int row = sizeof(a) / sizeof(a[0]); // 行
int col = lines / row; // 列
cout << row << col;
return 0;
}
5.计算vector二位矩阵的行列数
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main(){
//创建4*5二维数组,并将数组元素的值都设为1
vector<vector<int>> dp(4, vector<int>(5, 1));
//打印二维数组
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 5; j++)
cout << dp[i][j] << " ";
cout << endl;
}
cout << endl;
int m = dp.size();//行数
int n = dp[0].size();//列数
cout << "m = " << m << endl;
cout << "n = " << n << endl;
return 0;
}
###############################################################################
str.find(i) == str.end() //说明没到
str.find(i) != str.end() //说明找到了
这是标准库里迭代器部分的内容,简单点说,就是用find这个函数,去找str这个序列中的i元素,如果序列中所找的这个元素不存在,就会返回end()。
6.求vector 和 数组的最值问题
1)vector容器
例 vector<int> v;
最大值:int maxValue = *max_element(v.begin(),v.end());
最小值:int minValue = *min_element(v.begin(),v.end());
2)普通数组
例 a[]={1,2,3,4,5,6};
最大值:int maxValue = *max_element(a,a+6);
最小值:int minValue = *min_element(a,a+6);
7.求vector的和
#include<numeric> //使用accumulate函数要包含这个头文件
vector<float> result = {1.2, 2.3, 3.4};
float scoreSum = accumulate(result.begin(), result.end(), 0.0);
8.求最大公约数
int func(int n,int m)
{
int a,b,c;
a = n;b = m;
if(a < b)
swap(a,b);
c=a % b;
while(c != 0){
a=b;
b=c;
c=a%b;
}
return b;
}
9.int 与 string 相互转化
int max = num;
//int -> string
string str = std::to_string(num);
//string -> int
int shu = stoi(str);
10. 链表的头插法和尾插法
// C 语言的写法
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define ElemType int
typedef struct LNode{
ElemType Data;
struct LNode *Next;
}LNode,*LinkList;
//1.链表的创建 (头插法)
LinkList CreateList_Head(LinkList L)
{
LinkList s;int x;
L = (LNode*)malloc(sizeof(LNode));
L->Next=NULL;
scanf("%d",&x);
while(x!=9999){
s=(LNode*)malloc(sizeof(LNode));
s->Data=x;
s->Next=L->Next;
L->Next=s;
scanf("%d",&x);
}
return L;
}
//2.尾插发
LinkList CreateList_Tail(LinkList L)
{
int x;
L = (LNode*)malloc(sizeof(LNode));
LNode *s,*r=L;
scanf("%d",&x);
while(x!=9999){
s=(LNode*)malloc(sizeof(LNode)); //创建新的结点
s->Data=x;
r->Next=s;
r=s;
scanf("%d",&x);
}
r->Next=NULL;
return L;
}
void PrintList(LinkList L)
{
LinkList p;
p=L->Next;
printf("链表元素如下:\n");
while(p!=NULL)
{
printf("%d ",p->Data);
p=p->Next;
}
printf("\n");
}
int main()
{
LinkList L;//结构变量L即表示整个链表,也是头指针指向头结点
printf("尾插法建立单链表,输入值(9999结束)\n");
L=CreateList_Head(L);
PrintList(L);
printf("头法建立单链表,输入值(9999结束)\n");
L=CreateList_Tail(L);
PrintList(L);
return 0;
}
Leetcode的写法
ListNode* temp1 = new ListNode(-1);
ListNode* temp2 = new ListNode(-1);
//尾插法--建立链表
ListNode* L = temp1;
for(ListNode* temp = head; temp != nullptr; temp = temp->next){
ListNode* s = new ListNode(temp->val);
L->next = s;
L = s;
}
//头插法--反转链表
for(ListNode* temp = head; temp != nullptr; temp = temp->next){
ListNode* s = new ListNode(temp->val);
s->next = temp2->next;
temp2->next = s;
}
temp1 = temp1->next;
temp2 = temp2->next;