初级班第一课

时间复杂度

常数时间的操作:一个操作如果和数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。譬如数组寻址或者加减乘除运算等操作。
时间复杂度:在一个算法流程中,常数操作数量的指标,常用O(big O)来表示。具体来说,在常数操作数量的表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分如果记为f(N),那么时间复杂度为O(f(N))。
注意:评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本下的实际运行时间,也就是常数项的时间。
详细的注解:如何理解算法时间的复杂度表示法

冒泡排序

冒泡排序:按照顺序比较相邻的数并交换位置(如果不是预期的顺序)的一种算法。顺序可以是升序或者降序。冒泡排序的本质就是从一个元素开始,不断比较相邻位置的数,如果不是预期的顺序就交换,直到交换到最后一个位置(这时候我们已经把最大的数放到了最后的位置)。然后我们进行N-1次循环,每次仅排列好一个数,我们就能得到一个排好序的数组。每经过一次循环排列好的数就多一个,所以需要比较的数的数量在每次循环中也相应的减少。
详细的注解冒泡排序

# Bubble sort in Python
def bubbleSort(array):
	for i in range(len(array)):
		for j in range(0, len(array)-i-1):
			if array[j] > array[j+1]:
				(array[j], array[j+1]) = (array[j+1], array[j])

时间复杂度:O(N2)

选择排序

选择排序:在每次循环中记录最小位置的下标,然后交换到相应最小的位置。
第一次循环的例子
详细的注解选择排序

# Selection sort in Python
def selectionSort(array):
	for step in rage(len(array)):
		min_idx = step
		for i in range(step+1, len(array)):
			if array[i] < array[min_idx]:
				min_idx = i
		(array[step], array[min_idx]) = (array[min_idx], array[step])

时间复杂度:O(N2)
注意: 冒泡排序和选择排序在工程的实践中已经基本不用了。

插入排序

插入排序:插入排序是在每次循环中排列好一个区间范围内的数。在具体排列的时候,第一次循环的区间范围为1,所以它已经是排列好的。第二次循环的区间范围为2,然后将新加入的数依次向前比较,将其插入到合适的位置。之后依次循环,直至区间范围为整个数组。第三次循环
详细的注解插入排序

# Insertion sort in Python
def insertionSort(array):
	for step in range(1, len(array)):
		key = array[step]:
		j = step - 1
		while j>=0 and key < array[j]:
			array[j+1] = array[j]
			j = j - 1
		array[j+1] = key

时间复杂度

  • 最坏情况:O(N2)
  • 最好情况:O(N)

冒泡排序和选择排序都是严格的O(N2)算法,和数据的状况无关。但是插入排序是和数据状况相关的。

归并排序

归并排序是分治算法(divide and conquer)的一种。分治算法我会再另写一个博客来描述常见的分治问题。归并排序其实主要分为两步:第一步是将问题划分为子问题,第二部是通过试用类似于外排的方式来讲子数组排列好。归并排序图解
归并排序的分治策略:
假设我们需要排列一个无序数组A。子问题会变成排列这个数组的一部分:索引从p开始,从r结束,表示为A[p…r]。

  • Divide:如果q是p…r的中点,那么我们把数组A给划分成两部分:A[p…q]和A[q+1, r]。
  • Conquer:我们分别排列A[p…q]和A[q+1, r]这两部分。如果我们没有达到基线条件(base condition),我们继续划分子问题直到他们满足基线条件。
  • Combine:在conquer那一步中我们最终会得到两个排列好的子数组A[p…q]和A[q+1, r],然后我们组合这两个结果然后得到最红的有序数组A[p…r]。(使用类似外排的方式来merge/ combine)
    Merge的策略

详细的注解归并排序

# Merge sort in Python
def mergeSort(array):
	if len(array) > 1:
		mid = len(array) // 2
		L = array[:mid] # Dividing the array into 2 parts
		R = array[mid:]
		# Recursion is used here
		mergeSort(L)
		mergeSort(R)
	
	# The following is the process of merging	
		i = j = k = 0
	# Copy data to temp arrays L[] and R[]
	while i < len(L) and j < len(R):
		if L[i] < R[j]:
			arr[k] = L[i]
			i += 1
		else:
			arr[k] = R[j]
			j += 1
		k += 1
		
	# Checking if any element was left
	while i < len(L):
		arr[k] = L(i)
		i += 1
		k += 1
	while j < len(R):
		arr[k] = R[j]
		j += 1
		k += 1

时间复杂度:O(N*logN)

归并排序算法思路应用

小和问题

在一个数组中,每一个元素左边比当前元素值小的元素值累加起来,叫做这个数组的小和。
例如:[2,3,4,1,5]
2左边比2小的元素:无
3左边比3小的元素:2
4左边比4小的元素:2,3
1左边比1小的元素:无
5左边比5小的元素:2,3,4,1
小和small_sum = 2 + 2 + 3 + 2 + 3 + 4 + 1 = 17

思路:要计算一组数的小和,只需要计算左边的小和加上右边的小和再加上合并时候的小和。合并时候的小和求解的时候已经排好序了,计算小和时可以用这一策略进行加速。

def small_sub(arr):
	return merge_sort(arr, 0, len(arr)-1)

def merge_sort(arr, left, right):
	if left == right:
		return 0

	mid = (left + right) // 2

	return merge_sort(arr, left, mid) + merge_sort(arr, mid+1, right) + merge(arr, left, mid, right)

def merge(arr, left, mid, right):
	res = [] # 辅助数组
	small_sub = 0
	left_index = left
	right_index = mid + 1

	while (left_index<=mid and right_index<=right):
		if arr[left_index] <= arr[right_index]:
			small_sub += arr[left_index] * (right - right_index + 1)
			res.append(arr[left_index])
			left_index += 1
		else:
			res.append(arr[right_index])
			right_index += 1

	res += arr[left_index:mid+1]
	res += arr[right_index:right+1]

	for i in range(len(res)):
		arr[left+i] = res[i] # 将原数组变得有序

	return small_sub
	
if __name__ == '__main__':
    x = [1,3,4,2,5]
    res = small_sub(x)
    print(res)

Source

逆序对儿问题

于一个序列a1, a2, a3…an, 若存在i, j,使得i<j且ai>aj,则称为一个逆序对儿。输入为一个数组,输出为数组中逆序对儿数目。

class inverseCount:
	def __init__(self):
		self.count = 0

	def mergeSort(self, array):
		if len(array) < 2:
			return array
		
		mid = len(array) // 2
		left = self.mergeSort(data[:mid])
		right = self.mergeSort(data[mid:])
		i, j = 0
		res = []
		while (i < len(left) and j < len(right)):
			if left[i] <= right[j]:
				res.append(left[i])
				i += 1
			else:
				res.append(right[j])
				j += 1
				self.count += (len(left)-i)
		return res + left[i:] + right[j:]

	def InversePairs(self, array):
		if len(data) < 2:
			return 0
		selg.mergeSort(data)
		return self.count

对数器

对数器是通过用大量测试数据来验证算法是否正确的一种方式。
步骤

  1. 有一个你想要测的方法a;
  2. 实现一个绝对正确但是复杂度不好的b;
  3. 实现一个随机样本产生器;
  4. 实现对比算法a和b的方法;
  5. 把方法a和方法b比对多次来验证方法a是否正确;
  6. 如果有一个样本使得比对出错,打印样本分析是哪个方法出错;
  7. 当样本数量很多时比对测试依然正确,可以确定方法a已经正确。

注意

  • 随机产生的样本应该是小数据集,但是要进行多次(10w+)的对比。小数据集是因为方便对比分析,多次比对是要覆盖所有的随机情况。
  • 算法b要保持正确性。

主定理分析(master theorem)

主定理分析可以用来分析递归算法的时间复杂度。套公式就能得出来算法复杂度。注意n/b中b代表着子过程中的样本量,a代表子过程中发生了几次,f(n)代表除去调用子过程之外,剩下的代价是多少。(只有当子过程的样本量规模一样时,才可以调用下面的公式)
在这里插入图片描述
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值