左神课程笔记——第一节课:认识复杂度和简单排序算法


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已经正确。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值