1.时间复杂度
常数操作:一个操作和样本的数据量没有关系,每次都是固定时间(每种算法的固定时间可能不同)内完成的操作
时间复杂度:一个算法流程中,常数操作数量的一个指标。写出一个算法流程中常数操作数量的表达式,舍弃表达式中的低阶项,以及高阶项的系数,剩下的部分如果是f(N),那么时间复杂度为为O(f(N))。
评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本下的实际运行时间,也就是“常数项时间”。
2.选择排序
选择排序:每次选择待排序数据中最大/小的元素放到已排序数据的后面
时间复杂度分析:O(N^2)
实现代码:
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
vector<int> selectionSort(vector<int>& arr) {
if (arr.size() < 2) {
return arr;
}
for (int i = 0; i < arr.size() - 1; ++i) {//i~N-1上找最小值的下标
int minIndex = i;
for (int j = i + 1; j < arr.size(); ++j) {
minIndex = arr[minIndex] > arr[j] ? j : minIndex;
}
swap(arr[i], arr[minIndex]);
}
return arr;
}
3.冒泡排序
冒泡排序:每次比较相邻两个数的的大小,将大的放到右边,这样每一轮都将找到待排序数据中的最大值,并放到排好序的位置
时间复杂度:O(N^2)
代码实现:
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
vector<int> bubbleSort(vector<int>& arr) {
if (arr.size() < 2) {
return arr;
}
for (int i = arr.size()-1; i > 0; --i) {//未排序的范围:0~i
for (int j = 0; j < i; ++j) {
if (arr[j] > arr[j + 1]) {
swap(arr[j], arr[j+1]);
}
}
}
return arr;
}
4.异或运算 - 不进位加法
性质:(1) 0^N=N; N^N=0;(2) 满足交换律和结合律:ab=ba; (ab)c=a(bc),用不进位加法的思想理解;(3) 一堆数进行异或可以随意排列异或的顺序
交换两个数的值可以用异或来完成(不建议使用):
注意:这样做的前提是,a和b是两个不同的变量,即他们在内存中是两块不同的区域,但他们的值可以相同
例:要求时间复杂度O(N),空间复杂的O(1)
(1) 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这一个数
解:让所有的数异或,最后的结果就是那个出现奇数次的数
代码实现:
void evenTimes(vector<int>& arr) {
int eor = 0;
for (int i = 0; i < arr.size(); i++) {
eor = eor ^ arr[i];
}
cout << eor << endl;
}
(2)一个数组中有两种数(a和b)出现了奇数次,其他数都出现了偶数次,怎么找到这两个数
解:让所有的数异或,其值等于a异或b,且结果必不为0,因此二进制形式至少有一位为1;找出最右侧1的位置,a和b在这个位置取值不同(否则该位置不可能为1);将数组中的数按照该位置是0or1进行分类,则a和b会被分成不同的两类,将其中一类进行异或则可以求出a(或者b);将a在与所有数异或的结果a^b进行异或,则可以求出b
代码实现:
void oddTimes(vector<int>& arr) {
int eor = 0;
for (int i = 0; i < arr.size(); i++) {
eor = eor ^ arr[i];
}
int rightOne = eor & (~eor + 1);//把某个数最右侧的1提取出来(该位置位1,其余位置为0)
int a = 0;
for (int i = 0; i < arr.size(); i++) {
if ((arr[i] & rightOne) == 0) {//说明arr[i]在这个位置上为1。加括号的原因:关系运算符的优先级高于位运算符
a = a ^ arr[i];
}
}
int b = eor ^ a;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
}
5.插入排序
插入排序:每次将未排序数据的第一个数与已排序数据进行比较,将其插入到合适的位置(如果该数比当前位置的数小,则交换两数,直到比当前位置的数大或者该数已经移到了最左侧)
时间复杂度:根据原始数据的状态不同而不同,最好时间复杂度为O(N),最坏时间复杂度为O(N2)。注意,一般时间复杂度是按照最坏时间复杂度估计的,因此插入排序的时间复杂度为O(N^2)
代码实现:
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
vector<int> insertionSort(vector<int>& arr) {
if (arr.size() < 2) {
return arr;
}
//0-0 有序的
//0-i 想有序
for (int i = 1; i < arr.size(); ++i) {
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; --j) {
swap(arr[j], arr[j + 1]);
}
}
return arr;
}
6.二分法
(1)在一个有序数组中,找某个数是否存在——时间复杂度:O(logN)
(2)在一个有序数组中,找>=某个数最左侧的位置——二分到不能再分!
(3)局部最小值问题:无序数组,相邻两个数不相等,求出一个局部最小位置——时间复杂度好于O(N)
局部最小:0位置要比1位置的数小,则0位置是局部最小;N-1位置要比N-2位置的数小,则N-1位置为局部最小;i位置要比i-1位置的数小并且要比i+1位置的数小,则i位置为局部最小
不是只有有序才能二分,无序也可以,只要中间值可以将数组明确分成两部分即可
7.对数器
有一个想要测的方法a和一个实现复杂度不好但是容易实现的方法b。实现一个随机样本产生器,把方法a和方法b跑相同的随机样本,看看得到的结果是否一样。如果有一个随机样本使得比对结果不一致,打印样本进行人工干预,改对方法a或者方法b。当样本数量很多时比对测试依然正确,可以确定方法a已经正确。