时间复杂度与简单排序算法
时间复杂度 O
- 一个操作如果和样本的数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。
- 时间复杂度为一个算法流程中,常数操作数量的一个指标。常用O(读作big O)来表示。具体来说,先要对一个算法流程非常熟悉,然后去写出这个算法流程中,发生了多少常数操作, 进而总结出常数操作数量的表达式。
- 在表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分如果为f(N),那么时间复杂度为O(f(N))。
- 评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本下的实际运行 时间,也就是“常数项时间”。
补充说明:
- 时间复杂度的O表示一个最坏情况,算法的上限
- 两个同时间复杂度的算法进行比较要实际测试
- O(log2^N)默认为以2为底
异或(^)
异或(^)可以理解为无进位相加(可解释异或的交换性质)。
0^1=1
1^1=0
0^0=0
运算性质与拓展
- 0 ^ N = N N ^ N = 0
- 异或运算满足交换律和结合律:异或的顺序不影响结果
a = 1^5^2^3^5^5^3^2^2^1^3^4
= 1^1^2^2^2^3^3^3^4^5^5^5
= 2^3^4^5
应用
第 1 题:数值交换
不用额外变量交换两个数
void swap(int& a,int& b){
a=a^b; // a = a^b b = b
b=a^b; // a = a^b b = (a^b)^b
a=a^b; // a = (a^b)^(a^b^b) b = (a^b)^b
}
注意:用异或运算可以省空间(无需中间变量),但要求两个变量在不同的内存空间里,否则会变成零
第 2 题:
题目 :一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这一个数?
思路分析:利用异或运算,计算性质,N ^ N=0,交换律结合律,偶数次的数字最终会全部消掉,剩下奇数次的数
void printOddTimesNum(vector<int> array) {
int arrLen = array.size();
int eor = 0;
for (int i = 0; i < arrLen; i++) {
eor ^= array[i];
}
cout << eor << endl;
}
第 3 题:
题目 :一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到这两个数
思路分析:由于只有两个数出现了奇数次,不妨设出现奇数次的两个数分别为 a , b
则由题意可得,所有数 异或起来可得 eor = a ^ b 且不等于0 ,则必存在a,b(二进制)中至少有一位不相等,
假设 a,b 中第6位(最右边)不同,且 a 中第6位为1,b 中第6位为0,则 eor 的 第6位(最右边)为1,
提取 eor 最右边的 1 (二进制数) 为 rightOne (0000 0100),
然后根据第6位是否为 1 (rightOne),可以将给定数组分成两堆,第6位为1的(含a堆)和第6位为0的(含b堆) ,
将含a堆的所有数异或,偶数次的数 异或 依然为0,因此得到其中一个奇数 onlyOne = a
另一个奇数 otherOne = eor ^ onlyOne = (a ^ b) ^ a = b
void printOddTimesNum(vector<int>& array) {
// 假设数目为奇数的两种,分别为啊 a ,b
int arrLen = array.size();
int eor = 0;
for (int i = 0; i < arrLen; i++) {
eor ^= array[i];
}
// eor = a ^ b;
int rightOne = eor & (~eor + 1); // 提取eor最右边的1
int onlyOne = 0;
for (int i = 0; i < arrLen; i++) {
if ((array[i] & rightOne) != 0) {// 将数组分成两堆
onlyOne ^= array[i];
}
}
// onlyOne = a | b;
int otherOne = eor ^ onlyOne;
cout << onlyOne << "\t" << otherOne << endl;
}
提取一个数最右边的1 :
res = n &(~n+1);
例:0100 0110(70) ----> 0000 0010(2)
int n = 70;
int res = n &(~n+1); // res = 2
二分法
第 1 题:
题目:在一个有序数组中找一个数是否存在。
bool isExist(vector<int>& array,int target){
int arrLen = array.size();
int L = 0;
int R = arrLen - 1;
while(L <= R){
int mid = L + ((R -L) >> 1);
if(array[mid] == target){
return true;
}
if(array[mid] > target){
R = mid - 1;
}else{
L = mid + 1;
}
}
return false;
}
第 2 题:
题目:在一个有序数组中,找>=某个数最左侧的位置。(或者找<=某个数最右侧的位置)
int findLeftNum(vector<int>& array, int target) {
int index = -1;
int arrLen = array.size();
int L = 0;
int R = arrLen - 1;
while (L <= R) {
int mid = L + ((R - L) >> 1);
if (array[mid] >= target) {
index = mid;
R = mid - 1;
}
else {
L = mid + 1;
}
}
return index;
}
int findRightNum(vector<int>& array, int target) {
int index = -1;
int arrLen = array.size();
int L = 0;
int R = arrLen - 1;
while (L <= R) {
int mid = L + ((R - L) >> 1);
if (array[mid] <= target) {
index = mid;
L = mid + 1;
}
else {
R = mid - 1;
}
}
return index;
}
第 3 题:
题目:局部最小值问题。(数组A无序,相邻数一定不相等,找一个局部最小值)
int findMinNum(vector<int>& array) {
int arrLen = array.size();
int L = 0;
int R = arrLen - 1;
if (array[0] < array[1]) {
return 0;
}
if (array[R] < array[R - 1]) {
return R;
}
// 前面都不满足则一定存在局部最小值在中间
while (L <= R) {
// 先判断首位元素是否满足要求
int mid = L + ((R - L) >> 1);
if (array[mid] < array[mid - 1] && array[mid] < array[mid + 1]) {
return mid;
}
// 这里位置替换和普通二分法不一样
// 不可以用 R = mid - 1;L = mid + 1; 来更新边界位置,会发生越界
// 例如 数组大小为6的时候 {1, -5, 2, 10, 6, -3}
// L=0,R=5 -> mid=2 更新后 L=0,R=1 -> mid=0
// 存在条件判断 array[mid - 1] 就会越界
else if (array[mid] > array[mid - 1]) {
R = mid;
}
else {
L = mid;
}
}
return -1;
}
对数器
- 有一个你想要测的方法a
- 实现复杂度不好但是容易实现的方法b
- 实现一个随机样本产生器
- 把方法a和方法b跑相同的随机样本,看看得到的结果是否一样。
- 如果有一个随机样本使得比对结果不一致,打印样本进行人工干预,改对方法a或者方法b
- 当样本数量很多时比对测试依然正确,可以确定方法a已经正确
随机Vector<int>数组生成
头文件 RandomArray.h
#pragma once
#include <iostream>
using namespace std;
#include <vector>
class RandomArray {
public:
//声明静态成员函数
static void generateRandomArray(vector<int>& randomArray, int maxSize, int maxValue);
static void printArrayInfo(vector<int> array);
};
源文件 RandomArray.cpp
#include "RandomArray.h"
void RandomArray::generateRandomArray(vector<int>& randomArray, int maxSize, int maxValue) {
if (!randomArray.empty()) {
randomArray.clear();
}
int randomSize = rand() % (maxSize + 1) + 1; // 生成 1~maxSize 之间的一个随机整数
randomArray.resize(randomSize);
for (int i = 0; i < randomSize; i++) {
int randomValue = (rand() % (maxValue + 1)+1) - (rand() % (maxValue + 1));
randomArray[i] = randomValue;
}
}
void RandomArray::printArrayInfo(vector<int> array) {
int arraySize = array.size();
for (int i = 0; i < arraySize; i++) {
cout << array[i] << " ";
}
cout << endl;
}
排序算法
选择排序
时间复杂度O(n2)
,额外空间复杂度O(1)
- 0 - n-1 选择一个最小的排在0号位置
- 1 - n-1选择一个最小的排在1号位置
- 2 - n -1选择一个最小的排在2号位置
...
以此类推,代码如下:
// 选择排序不适合使用 异或 来互换值
// array[i], array[minIndex] 有可能指向同一个内存空间 , 异或结果会为0
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
void selectionSort(vector<int>& array) {
for (int i = 0; i < array.size() - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < array.size(); j++) {
minIndex = array[j] < array[minIndex] ? j : minIndex;
}
swap(array[i], array[minIndex]);
}
}
冒泡排序
时间复杂度O(n2)
空间复杂度O(1)
稳定
void swap(int& a, int& b) {
a = a ^ b; // a = a^b b = b
b = a ^ b; // a = a^b b = (a^b)^b
a = a ^ b; // a = (a^b)^(a^b^b) b = (a^b)^b
}
void bubbleSort(vector<int>& array) {
for (int i = 0; i < array.size() - 1; i++){
for (int j = 0; j < array.size() - 1 - i; j++){
if (array[j] > array[j + 1]){
swap(array[j], array[j + 1]);
}
}
}
}
插入排序
时间复杂度O(n2)
空间复杂度O(1)
稳定
void swap(int& a, int& b) {
a = a ^ b; // a = a^b b = b
b = a ^ b; // a = a^b b = (a^b)^b
a = a ^ b; // a = (a^b)^(a^b^b) b = (a^b)^b
}
void insertSort(vector<int>& array) {
for (int i = 1; i < array.size(); ++i){
for (int j = i-1; j >= 0 && array[j] > array[j + 1]; j--){
swap(array[j], array[j + 1]);
}
}
}