左神课程笔记——第二节课:认识O(NlogN)的排序


1.master公式—上节课遗留

例:用递归方法求一个数组中的最大值
代码实现:

int process(vector<int>& arr, int l, int r) {
	if (l == r) {
		return arr[l];
	}
	//int mid = l + (r - l) / 2;//防止溢出
	int mid = l + ((r - l) >> 1);//速度更快,位运算符的优先级小于+ -
	int lMax = process(arr, l, mid);
	int rMax = process(arr, mid + 1, r);
	return max(lMax, rMax);
}

int getMax(vector<int>& arr) {
	return process(arr, 0, arr.size() - 1);
}

递归调用过程(利用系统栈):
在这里插入图片描述
master公式—估算递归行为的时间复杂度。使用的前提条件:子问题的规模要相同
在这里插入图片描述


2.归并排序


归并排序:将数组分成两部分,分别对两部分进行排序,将排好序的两部分利用双指针合并成一个数组(需要一个外部空间),最后将外部空间的数组拷贝到原数组中。对每部分的排序采用递归的方式进行。
在这里插入图片描述
在这里插入图片描述
代码实现:

void merge(vector<int>& arr, int l, int mid, int r) {
	int p1 = l, p2 = mid + 1;
	vector<int>temp(r - l + 1);
	int i = 0;
	while (p1 <= mid && p2 <= r) {
		temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
	}
	while (p1 <= mid) {
		temp[i++] = arr[p1++];
	}
	while (p2 <= r) {
		temp[i++] = arr[p2++];
	}
	for (i = 0; i < temp.size(); i++) {
		arr[i + l] = temp[i];
	}
}

void process(vector<int>& arr, int l, int r) {
	if (l == r) {
		return;
	}
	int mid = l + ((r - l) >> 1);
	process(arr, l, mid);
	process(arr, mid + 1, r);
	merge(arr, l, mid, r);
}

void mergeSort(vector<int>& arr) {
	if (arr.size() < 2) {
		return;
	}
	process(arr, 0, arr.size() - 1);
}

算法复杂度分析:
在这里插入图片描述
归并排序的实质:比较信息没有被浪费,因此时间复杂度变为了O(NlogN)
归并排序的拓展:小和问题和逆序对问题
小和问题:在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
在这里插入图片描述
转换思路:小和问题<–>当前数的右边有多少个数比当前数大,就产生多个当前数的小和
在这里插入图片描述
可以用归并排序求当前数的右边有多少个数比当前数大:在合并(merge)时,比较双指针所指数的大小。只有左部分的数比右部分的数小时才会产生小和,且每个部分的内部不产生小和。
注意,当两指针指向的数大小相同时,应先将右部分的数拷贝到新数组中,否则可能会漏求部分数的小和(这是与普通归并排序的区别)
在这里插入图片描述
代码实现:

int merge(vector<int>& arr, int l, int mid, int r) {
	int p1 = l, p2 = mid + 1;
	int res = 0;
	int i = 0;
	vector<int>temp(r - l + 1);
	while (p1 <= mid && p2 <= r) {
		res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
		temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
	}
	while (p1 <= mid) {
		temp[i++] = arr[p1++];
	}
	while (p2 <= r) {
		temp[i++] = arr[p2++];
	}
	for (i = 0; i < temp.size(); i++) {
		arr[l + i] = temp[i];
	}
	return res;
}

int process(vector<int>& arr, int l, int r) {
	if (l == r) {
		return 0;
	}
	int mid = l + ((r - l) >> 1);

	//左侧排序求小和+右侧排序求小和+左右合并求小和
	return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r);
}

int smallSum(vector<int>& arr) {
	if (arr.size() < 2) {
		return 0;
	}
	return process(arr, 0, arr.size() - 1);
}

逆序对问题:在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有逆序对。
在这里插入图片描述
解:小和问题是求右边的数有多少比左边的数大,逆序对问题是求左边的数有多少比右边的数大,本质上是同一个问题
注:由mergeSort改编的题每年必出

3.荷兰国旗问题–>快速排序

(1)给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)
解:用一个变量记录小于等于区域的右边界。a) 如果arr[i]<=num,那么将arr[i]和<=区域的下一个数交换,<=区域右扩,i++;b) 如果arr[i]>num,那么直接i++。:
在这里插入图片描述
(2)给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)
解:用两个变量分别记录小于区域的右边界和大于区域的左边界。a) 如果arr[i]<num,那么将arr[i]和<区域的下一个数交换,<区域右扩,i++;b) 如果arr[i]==num,那么直接i++;c) 如果arr[i]>num,那么将arr[i]和>区域的前一个数交换,>区域左扩,i保持不变
在这里插入图片描述
在这里插入图片描述
(3)快排1.0:将数组中的最后一个数当作num,将前面的区域划分成小于等于区域和大于区域,最后再将最后一个数num与小于等于区域的后一个数交换。执行完上述操作(partion)后,num所在位置就是最终排序后所在的位置。让两个区域递归上述过程。
在这里插入图片描述
(4)快排2.0:将数组中的最后一个数当作num,将前面的区域划分成小于区域、等于区域和大于区域,最后再将最后一个数num与大于区域的第一个数交换。执行完上述操作(partion)后,num所在位置就是最终排序后所在的位置。让小于区域和大于区域递归上述过程。快排2.0比快排1.0快一点,因为每次可以排好若干个相等的数。
在这里插入图片描述
快排1.0和2.0的时间复杂度都是O(N2),因为可以举出最坏的例子(给定数组就是排好序的数组)的时间复杂度是O(N2)。最好的情况是每次的划分值刚好在中间(时间复杂度为O(NlogN)),划分值很偏左/右都是不好的情况
(5)快排3.0:从数组中随机选择一个数与最后一个数交换,当成划分值。因为每次划分值都是随机选取的,因此可以保证时间复杂度就是O(NlogN)
代码实现:

void swap(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}


//默认以arr[r]做划分,<   ==   >
//返回=区域的左边界和右边界
vector<int> partition(vector<int>& arr, int l, int r) {
	int less = l - 1, more = r;//less:<区域右边界 more:>区域的左边界
	int i = l;//i:当前下标
	int num = arr[r];//选定的划分值
	while (i < more) {
		if (arr[i] < num) {
			swap(arr[i], arr[less + 1]);
			less++;
			i++;
		}else if (arr[i] > num) {
			swap(arr[i], arr[more - 1]);
			more--;
		}
		else {
			i++;
		}
	}
	swap(arr[r], arr[more]);
	return vector<int>{less + 1, more};
}

//排序arr[l...r]
void quickSort(vector<int>& arr, int l, int r) {
	if (l < r) {
		srand((unsigned)time(NULL));
		swap(arr[r], arr[rand() % (r - l + 1) + l]);
		vector<int>p = partition(arr, l, r);
		quickSort(arr, l, p[0] - 1);
		quickSort(arr, p[1] + 1, r);
	}
}

void quickSort(vector<int>& arr) {
	if (arr.size() < 2) {
		return;
	}
	quickSort(arr, 0, arr.size() - 1);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值