数组
数组是存放在连续内存空间上的相同类型数据的集合。
数组可以方便的通过下标索引的方式获取到下标对应的数据。
需要两点注意的是:数组下标都是从0开始的。。数组内存空间的地址是连续的。
正是因为数组在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。
数组的元素是不能删的,只能覆盖。
在C++中二维数组是连续分布的。像Java是没有指针的,同时也不对程序员暴露其元素的地址,寻址操作完全交给虚拟机。而对于python,使用嵌套列表实现的二维数组,每个一维数组内部是连续分布的,但各个数组分布不连续,使用NumPy库实现的多维数组,它们被存储在一段连续的内存块中,因此它们的地址是连续的。
复杂度
选择排序
实现数组升序
过程描述:从第i个数开始,从i+1到数组最后一个数依次与i比较,将最小的数与i互换位置。
.....i+1...................i+2........................................................................i+1.............
...
时间复杂度O(n^2):(i历遍数组n+n-1+....1)+(j历遍i后数n-1+n-2+...+1)+(i与最小数互换n)
空间复杂度O(1):只对i, j, min_num赋值
class select_min:
def __init__(self,ary):
self.ary = ary
def num_select(self):
##历遍整个数组,获得第i个值
for i in range(len(self.ary)):
##数组无值直接返回
if self.ary == None :
return self.ary
##使用中间变量储存值,方便操作
min_num = self.ary[i]
##历遍i后的所有值,进行比较,将最小值与第i个值位置互换
for j in range(i+1,len(self.ary)):
if self.ary[j] < min_num:
min_num = self.ary[j]
self.ary[i], self.ary[j] = self.ary[j], self.ary[i]
return self.ary
if __name__ == "__main__":
ary = [1,9,5,6,8,3,4,22,4,5,66]
s = select_min(ary)
print(s.num_select())
冒泡排序
实现数组升序
过程描述:历遍数组,两两进行比较,并将大的数往右移动一格。
历遍数组过程:第一轮0~N-1,第二轮0~N-2....
时间复杂度:O(n^2):(i历遍数组n-1+n-2+...)+(最坏情况交换n-1+n-2+....)
class bubble_sort(object):
def __init__(self, ary):
self.ary = ary
def sort(self):
if self.ary == None or len(self.ary) == 0: # 如果数组为空或长度为0
return self.ary
for k in range(len(self.ary)-1): # 遍历数组,最后一个数移动后不动
for i in range(len(self.ary)-k-1): # 遍历数组
if self.ary[i] > self.ary[i+1]: # 如果当前元素大于下一个元素
self.ary[i], self.ary[i+1] = self.ary[i+1], self.ary[i] # 交换两个元素的位置
return self.ary
if __name__ == '__main__':
ary = []
print(bubble_sort(ary).sort())
插入知识点
异或运算:两数不同为1,两数相同为0。可理解为无进位相加,即1^1=0等同于二进制1+1=10,但不进位,等于0。
异或运算的数学性质:
(1)0^N=N,N^N=0
(2)符合交换律与结合律:a^b = b^a,a^b^c = a^(b^c)
(3)异或的结果与顺序无关。
由此我们可以重写交换数模块:
class bubble_sort(object):
def __init__(self, ary):
self.ary = ary
def sort(self):
if self.ary == None or len(self.ary) == 0: # 如果数组为空或长度为0
return self.ary
for k in range(len(self.ary)-1): # 遍历数组,最后一个数移动后不动
for i in range(len(self.ary)-k-1): # 遍历数组
if self.ary[i] > self.ary[i+1]: # 如果当前元素大于下一个元素
self.ary[i], self.ary[i+1] = self.exchange_num(self.ary[i], self.ary[i+1]) # 交换两个元素
return self.ary
##重写交换数模块
def exchange_num(self, a, b):
a = a ^ b #a=a^b,b=b
b = a ^ b #a=a^b,b=a^b^b=a^0=a
a = a ^ b #a=a^b^a=b^0=b,b=a
return a, b
if __name__ == '__main__':
ary = [5,2,9,6,8,3,4,4]
print(bubble_sort(ary).sort())
第一种交换方法只适用于python编程语言,因为它利用了Python中的元组解包(tuple unpacking)特性。在 Python 中,可以在一行代码中同时交换两个变量的值,而不需要临时变量。而在许多编程语言中,变量赋值并不直接支持像 Python 这样的元组解包。例如,在 C、Java 和 C++ 中,必须使用临时变量来交换两个值,因为它们不具备像 Python 那样的解包能力。在这些语言中,你不能在一行代码中直接用多元赋值交换变量的值。
异或运算的其他运用:
已知有一个数组,有一种数出现了奇数次,其他数均出现了偶数次。
(1)如何找到奇数次的那个数?
(2)如果奇数次的数变为了两种,如何找到奇数次的那两个数?
要求:时间复杂度O(n),空间复杂度O(1)
(1)
思路:
偶数次异或=0,奇数次异或=奇数次的数。
如[1,2,1,3,1,1,2,3,3],用异或计算时等同1^1^1^1^2^2^3^3^3=0^0^3=3
class find_single_odd_num:
def __init__(self, ary):
self.ary = ary
def EO(self):
a = 0
for b in self.ary:
a ^= b
return a
if __name__ == '__main__':
ary = [1,2,1,3,1,1,2,3,3]#1^1^1^1^2^2^3^3^3=0^0^3=3
print(find_single_odd_num(ary).EO())
(2)
思路:
我们将这个数组设为ori,用8位表示一个字节,设a,b为奇数次数,经过异或运算后最后会剩a^b不等于0,即结果的二进制表示必有1,a与b的二进制表示也不同,一定会有第k位一个是0一个是1,所以我们可以用第k为为1或0来区分a,b。
我们再设一个oth数组,这个数组只包含k位为1(为0同理)的数,对这个数组进行异或运算肯定会是a(或b),再将该数组(oth)的异或计算结果与原(ori)异或计算结果进行异或计算,得到的数会是a^a^b=b(或b^a^b=a)。
class find_twice_odd_num:
def __init__(self, ary):
self.ary = ary
def EO(self):
a = 0
b = 0
for i in self.ary:
b ^= i #a^b
right_one = b & (~b + 1) #寻找结果的二进制中最右侧的1,如b = 11101000, right_one = 00001000
for j in self.ary:
##将数组中与right_one与运算结果为1的数异或到a中
if (j & right_one) == 1:
a ^= j #a
return a, a ^ b #a=a,a^a^b=b
if __name__ == '__main__':
ary = [1,2,1,3,1,1,3,3]
print(find_twice_odd_num(ary).EO())
插入排序
实现数组升序
过程描述:从0开始逐渐增加历遍范围(0~0,0~1...,0~N),在每个范围内执行比较和移动数的操作。
从最右侧开始与左边的数比较,比左边的数小就交换位置,直到不比左边的数小或者到数组左侧尽头。
时间复杂度:O(n^2):(历遍范围n)+(最坏的交换情况,1+2+...+n-1)在一些良好数据排列下,会优于前两种。
空间复杂度:O(1)
class insertion_sort:
def __init__(self, ary):
self.ary = ary
def exchange(self):
for n in range(1, len(self.ary)):
j = n
while j > 0 and self.ary[j] < self.ary[j-1]: #直到不比左边的数小或者到数组左侧尽头
self.ary[j], self.ary[j-1] = self.ary[j-1], self.ary[j] #偷懒了,建议用位运算交换
j -= 1 #向左迭代
return self.ary
if __name__ == '__main__':
ary = [7,6,5,4,3,2,1]
print(insertion_sort(ary).exchange())
二分法
题型1:在升序数组中搜索指定数的位置
过程描述:从数组中心取数与指定数比大小,
中心数<指定数:指定数在右侧数组内,取右侧数组right,取right数组的中心数再次比较,以此类推。
中心数>指定数:指定数在左侧数组内,取左侧数组left,取left数组的中心数再次比较,以此类推。
中心数=指定数:直接返回
时间复杂度:O(log2 n):n个数每次切一刀,相当于2^k=n,k就是log2 n。
class search_num:
def __init__(self, ary, num):
self.ary = ary
self.num = num
def search(self):
if self.ary == None or len(self.ary) < 1:
return print('Please input an array with values.')
left = 0
right = len(self.ary)-1
while left <= right:
mid = (left + right)//2
if self.ary[mid] < self.num:
left = mid + 1
elif self.ary[mid] > self.num:
right = mid - 1
else:
return print(f'{self.num} is at location {mid+1}')
return print(f'{self.num} not found in the array.')
if __name__ == '__main__':
ary = []
num = 7
search_num(ary, num).search()
题型2:在升序数组中,找到大于等于指定数的最左侧位置。
过程描述:类似于题型1,不赘述
时间复杂度:O(log2 n)
class SearchNum:
def __init__(self, ary, num):
self.ary = ary
self.num = num
def search_left(self):
# 检查数组是否为空
if self.ary is None or len(self.ary) < 1:
return print('Please input an array with values.')
left, right = 0, len(self.ary)-1
while left < right:
mid = (left + right) // 2
if self.ary[mid] < self.num:
left = mid + 1 # 移动到右半部分
else:
right = mid # mid可能是答案,继续在左半部分查找
# left是第一个大于或等于num的位置
if left < len(self.ary):
return print(f'The leftmost position of {self.num} is at location {left+1}.')
else:
return print(f'No element greater than or equal to {self.num} found.')
if __name__ == '__main__':
ary = [1,3,5,7,7,7,7,7,7,7,7,9,13]
num = 7
SearchNum(ary, num).search_left()
题型3:无序数组,相邻数皆不相等,找局部最小值
过程描述:
0位置数小于1位置数,直接返回。
n-1位置数小于n-2位置数,直接返回。
前两种情况不符合,中间必存在局部最小。
取中点数与两侧数比较,如果符合局部最小直接返回。不符合取比中点数小的那一侧继续比较,直到符合为止。
时间复杂度:O(log2 n)
class SearchMinNum:
def __init__(self, ary):
self.ary = ary
def search_min(self):
# 检查数组是否为空
if self.ary is None or len(self.ary) < 1:
return print('Please input an array with values.')
left, right = 0, len(self.ary)-1
while left < right:
if self.ary[left] < self.ary[left+1]:
return print(left)
elif self.ary[right] < self.ary[right - 1]:
return print(right)
else:
mid = (left + right)//2
if self.ary[mid] < self.ary[mid-1] and self.ary[mid] > self.ary[mid-1]:
return print(mid)
elif self.ary[mid] > self.ary[mid-1]:
right = mid
else:
left = mid
if __name__ == '__main__':
ary = [9,8,7,6,5,4,13,19]
SearchMinNum(ary).search_min() # 创建搜索对象并执行搜索
对数器
用来测试代码对错
思路:有两套独立方法用于实现某个功能,我们随机生成多次两套相同数据,将数据载入到两个方法中,用两个方法互相验证结果。
以上内容对应b站1.认识复杂度和简单排序算法_哔哩哔哩_bilibili,是自己对课程的理解。编程语言使用的是python。左老师讲的很仔细,很推荐去看看,给左老师个三连,配合食用更佳。