[ C++编程 ] 编程细节与增效技巧随记

  • 该文章记录笔者在学习 C++ 中所接触到的 需要注意的细节 以及 可以提升编程效率 或是 可以使代码变得简洁易懂的小技巧 ,随学习进程不定时更新
  • 系统环境:Win10,Visual Studio 2019

先进行无脑的堆砌,如果后续条目数变得很多再分类整理 


遍历字符串,修改字符串

C++ 11中新增的用法,在for循环中可以这么写,遍历或打印字符串更为方便

string s = "abcdefg";
for (char c : s){
	cout << c ;
}
cout << endl;
//@result: abcdefg

也可像下述操作,使用 “&c” 来直接修改整串字符串的字符 

string s = "abcdefg";
for (char& c : s){
	c = 'A';
}
for (char c : s) {
	cout << c ;
}
cout << endl;
//@result:AAAAAAA

也可用于数组遍历 

double num[5] = { 1, 2, 3, 4, 5 };
for (double d : num){
	cout << d;
}
cout << endl;
//@result:12345

unordered_set  非排序set容器

该容器将元素以非排序的形式存储,直接存储数据的内容而不是键值,不可添加重复元素,元素不可被修改,但可以通过删除再插入的方式进行 “修改”

在使用时需要添加头文件:

#include <unordered_set>

比较有用的成员函数是 count  ,其可以返回容器中有无待添加元素的值,因其容器的不重复特性,所以返回值限定为 “1” 或 “0” ,即有或没有,可以进行如下操作:

unordered_set<int> mySet;
mySet.insert(1);
mySet.insert(2);
mySet.insert(3);
mySet.insert(4);
// 是否有"4"? 若有则插入"5"
if (mySet.count(4)) {
	mySet.insert(5);
}
else mySet.insert(4);
// 打印结果
for (int c : mySet) {
	cout << c << " ";
}
//@result: 1 2 3 4 5 

如果多次往 unordered_set 中插入重复元素,编译会通过,实际执行过程中也不会报错,但插入重复元素那条语句会被自动忽略掉

unordered_set<int> mySet;
mySet.insert(1);
mySet.insert(2);
mySet.insert(2);
mySet.insert(2);
mySet.insert(3);
// 打印结果
for (int c : mySet) {
	cout << c << " ";
}
//@result: 1 2 3

匹配子串,KMP算法模式串预处理

对于在母串中找到匹配的子串问题,我自然想到的是暴力解法,即没有什么技术含量,同时时间复杂度也较高的解法,其主要思路是整块匹配:

// hlen: 母串长度,nlen:子串长度
for (int i = 0; i <= hlen - nlen; ++i) {
	if (haystack.substr(i, nlen) == needle) {
		return i;
	}
}
return -1;

运用 KMP 算法 可以提高搜索字串的效率,这里谨浅探讨KMP算法中对于模式串的预处理工作,对于字符串 “ABCABCACA”, 可以根据其字符排列特征对其进行预处理,标记在某一位与子串匹配失败时,指针该向前移动的个数,当然,这个“个数”越小就代表前移越少,遍历相对会更快一些,理想情况是子串的所有标记都为“0”,对于上述字符串,计算后的标记结果对应为:

模式串 A B C A B C A C A
标记位 0 0 0 0 1 2 3 1 0

当上述模式串的倒数第三个元素“A”匹配失败时,则在母串前移三位继续匹配。我们可以大致看出,模式串中包含越多的重复字符和相似的字符排列会给KMP算法带来时间上的拖延,像下述字符串则很好:

模式串 b e a u t y
标记位 0 0 0 0 0 0 

二分相关问题,优化O(N)至O(logN)

在有序数组的搜索与插入相关问题中,使用二分法可以将时间复杂度由传统方法的 O(n) 优化到 O(logn) ,在编制程序时需要注意以下问题:

  • 首先在C++中,int类型整数相除所得结果并不是四舍五入,而是全舍,例如 3 / 2 = 1,1 / 2 = 0, 29 / 10 = 2 等等,因此在二分法的mid变量相关设计中要注意不要越界
  • 在mid变量与目标值不等时,下一次搜索边界值的赋予可以根据mid与目标值的大小关系,在等于mid的基础上再加一或减一,可以提升一定的效率,下方示例代码(力扣官方):
int n = nums.size();
int left = 0, right = n - 1, ans = n;
while (left <= right) {
    int mid = ((right - left) >> 1) + left;
    if (target <= nums[mid]) {
        ans = mid;
        right = mid - 1;
        } else {
            left = mid + 1;
    }
}
return ans;

vector<int>容器最大值的寻找 

可以使用遍历方法,花费O(n), 也可以利用STL中的相关功能,没研究过源码,但估计对于无序数组的查找应该也是O(n)

首先添加头文件:

#include <algorithm>

其次对于vector容器进行如下操作:

int maxVal = 0;
vector<int> nums = { -2, 1, -3, 4, -1, 2, 1, -5, 4 };
maxVal = *max_element(nums.begin(), nums.end());
cout << maxVal << endl;
//@result: 4

其中 max_element 函数的参数为容器的首尾地址(范围),返回值为最值元素的地址,故需要对返回值进行取 * 处理


跳出多重循环 (使用goto语句)

有时在内循环已经达成我们想要的目的时,我们希望直接跳出外循环并接着执行后续的业务逻辑,此时可以使用goto语句进行实现

for (int i = 0; i < 10; ++i) {
    for (int i = 0; i < 10; ++i) {
	    if (true) {
		    goto outloop;
        }    
	}
}
	
outloop:
	// do something

在《C++ Primer》中,作者不建议我们使用goto语句,因为其会使程序变得复杂难懂


循环语句中 int 溢出问题

在 C++ 中,普通有符号 int 类型可以表示的数介于 - 2,147,483,648 ~ 2,147,483,647 之间,换言之也就是 4字节32位带符号整数 (在C++ Primer中有描述“C++中规定的尺寸的最小值”中,int类型占位是16位,但这个问题应该是取决于编译器实现的,在vs中将int类型的大小设定在了32位),用其中一位来表示正负,其余位最多可以表示 2^31 体量的数字,若在编程中出现溢出问题,则程序会报错,若是在循环语句中,我们可以进行如下操作:

while ((long long)i * i <= x) {
	i += 1;
}
// 数据类型 "long long" 自C++ 11中新定义
while (size_t (i * i) <= x) {
	i += 1;
}

或者将循环中的局部变量提前先声明为容量更大的数据类型,也可解决问题


vector数组的下标问题

在利用vector数组作为一维线性存储结构时,我们声明数组时可以不进行初始化(例如直接声明成 vector<int> s;),在需要插入元素时直接使用 push_back

当需要进行类二维操作时,我们应用下标对vector数组进行操作,则要事先声明数组的大小(维度),例如(vector<int> s(2);),此举可以让我们将vector中的元素进行单个操作,比如一个string类型vector,我么既可以用下标方式对其中的每一个string进行操作,string类型的vector亦可以使用for(string c: s)的方式进行遍历输出

力扣第六题是一个很好的应用了该技术的例子:

static string convert(string s, int numRows) {
	if (numRows == 1) return s;
	vector<string> rows(min(numRows, int(s.size()))); // 防止s的长度小于行数
	int curRow = 0;
	bool goingDown = false;

	for (char c : s) {
		rows[curRow] += c;
		if (curRow == 0 || curRow == numRows - 1) {// 当前行curRow为0或numRows -1时,箭头发生反向转折
			goingDown = !goingDown;
		}
		curRow += goingDown ? 1 : -1;
	}

	string ans;
	for (string row : rows) {// 从上到下遍历行
		ans += row;
	}

	return ans;
}

// 参考用户"Mia"

利用Sort或HashMap寻找数组中的众数

假设有一vector数组,其中必有一众数,该众数在数组中出现的次数大于数组元素数量的一半,设计算法找出该众数

由于其众数出现次数大于元素数量的一半,故若将数组按序排列,则下标为 n/2 (n为数组元素个数)的元素必为该众数,故先添加算法头文件:

#include <algorithm>

使用sort指令对数组进行排序并返回众数值,注意sort的成员变量中二者均为迭代器:

sort(nums.begin(), nums.end());
return nums[nums.size() / 2];

亦可使用哈希表来解决该问题,首先添加无序映射头文件:

#include <unordered_map>

进行表的构建与众数的查找输出:

//        元素 ↓     ↓ 频率
unordered_map<int, int> record;
for (int i = 0; i < nums.size(); ++i)
{
	// 数组中的数字为键值,键值对应着一个出现次数
    // 每次循环均找到键值所对应的出现次数并+1
	record[nums[i]]++; //*
	if (record[nums[i]] > nums.size() / 2)return nums[i];
}
return -1;

注意:标 * 的一步,对图的nums[i]键值进行++赋值时,自动创建配对关系,如第一次出现“2”的时候,则创建对[2,1],第二次出现“2”的时候则该对变为[2,2] 


猜你喜欢

转载自blog.csdn.net/Norths_/article/details/125646657
今日推荐