3、数组排序
(1)冒泡排序
算法思想:
通过相邻元素之间的比较与交换,使值较小的元素逐步从后面移到前面,值较大的元素从前面移到后面。
算法步骤:
第 1 次:先将序列中第 1 个元素与第 2 个元素进行比较,较大者置于后面,然后将第 2 个元素与第 3 个元素比较,较大者置于后面。依次类推,直到第 n−1 个元素与第 n 个元素比较。
······
依次类推,进行多次冒泡排序,直到排出正确序列,排序结束。
代码实现:
排序 arr = [5, 2, 3, 6, 1, 4] :
arr = [5, 2, 3, 6, 1, 4]
l = len(arr)
for i in range(l-1):
flag = False
for j in range(l-i-1):
if arr[j]>arr[j+1]:
arr[j],arr[j+1]=arr[j+1],arr[j]
flag = True
if not flag:
break
print(arr)
算法分析:
最佳时间复杂度:O(n) 初始时序列已经排列好,只需经过 1 次排序、 n 次元素之间的比较。
最坏时间复杂度:O(n^2) 需要进行 n次排序,总共进行 n(n−1)/2 次元素之间的比较。
空间复杂度:O(1) 冒泡排序只用常数项变量。
适用情况:冒泡排序比较适合数据量较小的情况,尤其是当序列的初始状态基本有序的情况。
排序稳定性:排序不会改变元素的相对顺序,是一种稳定排序算法。
(2)选择排序
算法思想:
每次从数组中选择出一个最小元素,放到数组未排序序列最左端,从而实现该元素左部分为已排序数组,右端为未排序数组,依次遍历直到排序完成
算法步骤:
1、遍历区间 [0,n−1], 记录区间中最小元的素下标。
2、将最小元素下标与下标为 0 处的元素交换位置。
3、遍历区间 [1,n−1],记录区间中最小元的素下标。
4、将最小元素下标与下标为 1 处的元素交换位置。
5、依次类推,对剩余区间排序,直到所有元素都完成排序。
图解:
代码实现:
排序 arr = [5, 2, 3, 6, 1, 4]:
arr = [5, 2, 3, 6, 1, 4]
l=len(arr)
for i in range(l):
min=i
for j in range(i,l):
if arr[j]<arr[min]:
min=j
arr[i],arr[min]=arr[min],arr[i]
print(arr)
算法分析:
时间复杂度:O(n^2)。每次排序都要进行n-i次元素之间的比较,共为 n(n−1)/2 次。
空间复杂度:O(1)。只用到常数项变量
适用情况:数据量较小,空间复杂度要求较低时,可以考虑选择排序。
稳定性:可能会改变相等元素的相对顺序,因此选择排序法是一种不稳定排序算法。
(3)插入排序
算法思想:
将数组分为左侧有序区间和右侧无序区间,每次从无序区间取出一个元素,插入到有序区间的适当位置。
算法步骤:
1、取出无序区间 [1,n−1] 中的第 1 个元素,nums[1]。
2、从右到左遍历有序区间中的元素,将比 nums[1] 大的元素向后移动 1 位
3、遇到小于或等于 nums[1] 的元素,将 nums[1] 插入到该位置
4、取出无序区间 [2,n−1] 中的第 1 个元素。
5、从右到左遍历有序区间中的元素,将比 nums[2] 大的元素向后移动 1 位
6、遇到小于或等于 nums[1] 的元素,将 nums[2] 插入到该位置
7、依次类推,直到所有元素都插入到有序区间中。
图解:
代码实现:
排序 arr = [5, 2, 3, 6, 1, 4] :
arr = [5, 2, 3, 6, 1, 4]
l = len(arr)
for i in range(1,l):
temp = arr[i]
j = i
while j>0 and arr[j-1]>temp:
arr[j]=arr[j-1]
j-=1
arr[j]=temp
print(arr)
算法分析:
最佳时间复杂度:O(n) : 每个元素只进行一次比较,共n-1次
最差时间复杂度:O(n^2) : 每个元素都要进行 i−1 次比较,总次数为n(n-1)/2
空间复杂度 : O(1) :插入排序只用到常数项变量
排序稳定性:插入排序每次都插入到相等元素的右侧,不会改变相等元素的顺序,是稳定排序算法
(4)归并排序
算法思想:
先递将当前数组平均分成两半,然后将有序数组两两合并,最终合并成一个有序数组。
算法步骤:
1、分解:找到数组中心位置,将数组分为两部分,再对子数组进行分解,直到子数组长度均为1
2、归并:从长度为1的子数组开始,进行两两排序、合并直到数组末尾
代码实现:
arr = [0,5,7,3,1,6,8,4]
def merge_sort(nums):
l = len(nums)
if l<= 1:
return nums
mid = l // 2
left_l= merge_sort(nums[:mid])
right_l= merge_sort(nums[mid:])
p, q = 0, 0
temp = []
len_left, len_right = len(left_l), len(right_l)
while len_left > p and len_right > q:
if left_l[p] <= right_l[q]:
temp.append(left_l[p])
p += 1
else:
temp.append(right_l[q])
q += 1
temp += left_l[p:]
temp += right_l[q:]
return temp
nums=[0, 5, 7, 3, 1, 6, 8, 4]
print(merge_sort(nums))
算法分析:
时间复杂度:O(n×logn) 等于归并次数与每一次归并复杂度的乘积
空间复杂度:O(n×logn) 归并排序方法需要用到与参加排序的数组同样大小的辅助空间
排序稳定性:稳定排序
(5)希尔排序
算法思想:
将整个数组按照一定的间隔划分为若干个子数组,每个子数组分别进行插入排序。然后逐渐缩小间隔进行下一轮操作。直至最后一轮排序间隔为 1,对整个数组进行插入排序。
算法步骤:
1、确定一个间隔数
2、将数组按照间隔分成若干个子数组
3、对各个子数组进行排序
4、减少间隔数,并重新分组,再分别对各个子数组进行排序。
5、依次类推,直到间隔数为 1,对仅有的一个数组进行排序。
图解:
代码实现:
排序 arr = [5, 7, 2, 3, 6, 8, 1, 4]
arr = [5, 7, 2, 3, 6, 8, 1, 4]
print(arr)
l = len(arr)
gap = l//2
while gap > 0:
for i in range(gap,l):
temp = arr[i]
j = i
while j>=gap and arr[j-gap]>temp:
arr[j] = arr[j-gap]
j-=gap
arr[j] = temp
gap = gap//2
print(arr)
算法分析:
时间复杂度:介于 O(n×log2n) 与 O(n^2) 之间
空间复杂度:O(1) 只使用了常数项变量
排序稳定性:在不同的插入排序中,相等元素能在各自的排序中移动,因此,希尔排序不稳定
(6)快速排序
算法思想:
选择一个数组中的元素作为参照,通过排序将数组中小于参照的元素放入一个数组中,大于参照的放入另一个数组中,按同样的方式对两个子数组再进行快速排序。
算法步骤:
1、选定参照数
2、使用指针i,j 分别指向数组头部和尾部
3、向右移动指针i,直到 i 遇到比参照大的元素,i停止
4、向左移动指针j,直到 j 遇到比参照小的元素,j停止
5、交换i,j指针元素位置
6、重复操作,直到i,j指针相遇停止
7、对划分好的子数组进行上述排序,直到每个子数组元素个数为一。
图示:
代码实现:
arr = [5, 2, 3, 6, 1, 4]
def quicksort(arr, start, end):
if start >= end:
return
p, i, j = arr[start], start, end
while i < j:
while arr[j] >= p and i < j:
j -= 1
arr[i] = arr[j]
while arr[i] < p and i < j:
i += 1
arr[j] = arr[i]
arr[i] = p
quicksort(arr, start, i - 1)
quicksort(arr, i + 1, end)
quicksort(arr, 0, len(arr) - 1)
print(arr)
算法分析:
最佳时间复杂度:O(n×logn) 每一次选择参照数都是数组的中位数
最坏时间复杂度:O(n2) 每一次选择参照数都是数组的末端元素
空间复杂度:O(n) 块速排序过程中用到堆栈或其他空间来存放当前数组的首、尾位置
排序稳定性:进行哨兵划分时,参照数可能会换到相等元素右边,是一种不稳定排序
(7)堆排序
堆结构:
1、堆的定义
堆:一种完全二叉树,有大顶堆、小顶堆两种形式
大顶堆:任意节点值 ≥ 其子节点值 小顶堆:任意节点值 ≤ 其子节点值
2、堆的存储结构
对于完全二叉树,采用顺序存储结构。
节点下标:若某节点下标为i,则其左子节点下标为:2*i+1;右子节点下标为:2*i+2
父根节点下标为:i-/2(向下取整)
3、访问堆顶元素
堆顶元素位于根节点,使用顺序结构表示堆时,堆顶元素位于数组队首位置
4、向堆中插入元素
步骤:
1、将新元素插入到堆的末尾
2、从该元素父节点开始比较,若大于父节点就交换位置,若小于则位置不变
3、重复上述步骤,直至新元素小于父节点或达到根节点
5、删除堆顶元素
步骤:
1、将堆顶元素与堆尾元素互换
2、移除末尾元素
3、从堆顶元素开始,逐步与较大的子节点比较,调整至合适位置
4、重复上述步骤,直到新的堆顶元素不再小于其子节点或者达到了堆的底部
堆排序:
1、算法思想
借助堆结构,将数组转化为大顶堆,重复从大顶堆中取出最大的节点
2、算法步骤
1、定义堆结构的数组,将原数组的元素存入该数组中,通过排序形成大顶堆
2、交换堆顶元素与最后一个元素位置,将堆顶元素放在最后,堆长度减一
3、从根节点开始对新结构排序,使其形成大顶堆
4、重复上述步骤、直至堆的大小为1
3、代码实现
arr =[10, 25, 6, 8, 7, 1, 20, 23, 16, 19, 17, 3, 18, 14]
def heap(arr, index, end):
left = index * 2 + 1
right = left + 1
while left <= end:
max_index = index
if arr[left] > arr[max_index]:
max_index = left
if right <= end and arr[right] > arr[max_index]:
max_index = right
if index == max_index:
break
arr[index], arr[max_index] = arr[max_index], arr[index]
index = max_index
left = index * 2 + 1
right = left + 1
def build(arr):
size = len(arr)
for i in range((size - 2) // 2, -1, -1):
heap(arr, i, size - 1)
return arr
def heapsort(arr):
build(arr)
size = len(arr)
for i in range(size):
arr[0], arr[size - i - 1] = arr[size - i - 1], arr[0]
heap(arr, 0, size - i - 2)
return arr
print(heapsort(arr))
4、算法分析
时间复杂度:O(n×logn)。
空间复杂度:O(1),堆排序只使用了一个空间
排序稳定性:排序时相同元素位置会发生变化,是一种不稳定算法
(8)计数排序
算法思想:
统计数组中每个元素在数组中出现的次数,从而将数组元素有序的放置到正确位置。
算法步骤:
1、遍历数组,找出最大元素和最小元素,从而计算出排序的范围
2、定义一个大小为排序范围的数组 ,统计每个元素的出现次数。
3、遍历排序数组,在计数数组中统计每个元素出现次数
4、按计数数组元素显示的数量填充结果数组
代码实现:
arr = [3, 4, 2, 5, 1, 3, 1, 4, 5]
nums_min, nums_max = min(arr), max(arr)
size = nums_max - nums_min + 1
counts = [0 for _ in range(size)]
for i in arr:
counts[i-nums_min]+=1
arr.clear()
for i in range(size):
for j in range(counts[i]):
arr.append(i+nums_min)
print(arr)
算法分析:
时间复杂度:O(n+k):k 代表待排序数组的值域。
空间复杂度:O(k)
适用情况:计数排序一般用于整数排序,不适用于按字母顺序、人名顺序排序。
(9)桶排序
算法思想:
将数组中的元素分到多个桶中,然后对每个桶中的元素再进行排序。
算法步骤:
1、根据数组元素大小,将数组分为 k 个桶。
2、将每个元素根据大小分配到对应的桶中。
3、对每个桶内的元素进行排序。
4、将排好序的各个桶中的元素按照区间顺序依次合并起来,形成一个完整的有序数组。
代码实现:
排序 arr = [39, 49, 8, 13, 22, 15, 10, 30, 5, 44]
arr=[39, 49, 8, 13, 22, 15, 10, 30, 5, 44]
arr_min, arr_max = min(arr), max(arr)
count = (arr_max - arr_min) // 5 + 1
buckets = [[] for _ in range(count)]
for i in arr:
buckets[(i-arr_min) // 5].append(i)
new_arr=[]
for bucket in buckets:
bucket.sort()
new_arr.extend(bucket)
print(new_arr)
算法分析:
时间复杂度 :O(n) :当桶的个数 m 接近于数据个数 n 时,桶排序时间复杂度接近于 O(n)。
空间复杂度:O(n+m):桶排序使用了辅助空间,空间复杂度是 O(n+m)。
排序稳定性:桶排序的稳定性取决于桶内使用的排序算法
(10)基数排序
算法思想:
将整数各位数进行分解比较,从而达到排序的目的。
算法步骤:
从低位到高位比较:
1、求出位数:遍历数组元素,获取最大元素,求出最大位数。
2、定义一个长度为10的桶数组,每个桶代表数字0-9
3、按照每个元素当前位上的数字,将元素放入对应数字的桶
4、清空原始数组,按桶中的顺序重新将元素放入到原始数组中
5、前移一位,重复上述过程,直至最后一位
代码实现:
排序 arr=[692,924,969,503,871,704,542,436]:
arr = [692, 924, 969, 503, 871, 704, 542, 436]
size = len(str(max(arr)))
for i in range(size):
buckets = [[] for _ in range(10)]
for a in arr:
buckets[a//(10**i)%10].append(a)
arr.clear()
for bucket in buckets:
for a in bucket:
arr.append(a)
print(arr)
算法分析:
时间复杂度:O(n×k) : n 是待排序元素的个数,k 是数字位数。
空间复杂度:O(n+k) 共调用k个桶空间
排序稳定性:内部采用的桶排序是稳定算法,所以基数排序也是一种稳定排序算法。
排序算法适用在排序元素位数较多时
(11)练习题目
把数组排成最小的数:
题目:给定一个非负整数数组 nums。将数组中的数字拼接起来排成一个数,打印能拼接出的所有数字中的最小的一个。
class Solution(object):
def minNumber(self, nums):
"""
:type nums: List[int]
:rtype: str
"""
l = len(nums)
for i in range(l):
nums[i]=str(nums[i])
for i in range(l):
for j in range(i+1,l):
if nums[i]+nums[j]>nums[j]+nums[i]:
nums[i],nums[j]=nums[j],nums[i]
s = ''.join(nums)
return s
移动零:
题目:给定一个数组 nums,将所有 0 移动到末尾,并保持原有的非 0 数字的相对顺序。
class Solution(object):
def moveZeroes(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
l = len(nums)
i = 0
while i<l:
if nums[i]==0:
nums.pop(i)
nums.append(0)
l-=1
i=0
else : i+=1
排序数组:
题目:给定一个整数数组 nums。将该数组升序排列。
最简便方法:使用列表类方法sort()升序排列;sort(reverse=True)降序排列
class Solution(object):
def sortArray(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
nums.sort()
return nums
相对名次:
题目:给定一个长度为 n 的数组 score。其中 score[i] 表示第 i 名运动员在比赛中的成绩。所有成绩互不相同。找出他们的相对名次,并授予前三名对应的奖牌。前三名运动员将会被分别授予「金牌("Gold Medal"
)」,「银牌("Silver Medal"
)」和「铜牌("Bronze Medal"
)」。
class Solution(object):
def findRelativeRanks(self, score):
"""
:type score: List[int]
:rtype: List[str]
"""
new_score = []
for i in score:
new_score.append(i)
score.sort(reverse= True)
medal=["Gold Medal","Silver Medal","Bronze Medal"]+[str(i) for i in range(1,len(score)+1) if i > 3]
for i in range(len(score)):
for j in range(len(score)):
if new_score[j]==score[i]:
new_score[j] = medal[i]
return new_score
合并两个有序数组:
题目:给定两个有序数组 nums1、nums2,将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
class Solution(object):
def merge(self, nums1, m, nums2, n):
"""
:type nums1: List[int]
:type m: int
:type nums2: List[int]
:type n: int
:rtype: None Do not return anything, modify nums1 in-place instead.
"""
nums1[m:] = nums2
nums1.sort()
数组中的逆序对:
题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
暴力算法:
class Solution(object):
def reversePairs(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n = 0
l = len(nums)
for i in range(l-1):
for j in range(i+1,l):
if nums[i]>nums[j]:
n+=1
return n
时间更短的算法:
class Solution(object):
def reversePairs(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
def merge_sort(l, r):
if l >= r:
return 0
m = (l + r)//2
i, j = l, m+1
res = merge_sort(l, m) + merge_sort(m+1, r)
tmp[l:r+1] = nums[l:r+1]
for k in range(l,r+1):
if i == m+1:
nums[k] = tmp[j]
j += 1
elif j == r+1 or tmp[i] <= tmp[j]:
nums[k] = tmp[i]
i += 1
else:
nums[k] = tmp[j]
j += 1
res += m - i + 1
return res
tmp = [0] * len(nums)
return merge_sort(0, len(nums)-1)
颜色分类:
题目:给定一个包含红色、白色和蓝色、共 n
个元素的数组 nums
,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。使用整数 0
、 1
和 2
分别表示红色、白色和蓝色。不使用 sort 函数
class Solution(object):
def sortColors(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
a=0
num=[0,0,0]
for i in nums:
num[i]+=1
for i in range(3):
for j in range(num[i]):
nums[a]=i
a+=1
数组中的第k个最大元素:
题目:给定整数数组 nums
和整数 k
,请返回数组中第 k
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
你必须设计并实现时间复杂度为 O(n)
的算法解决此问题。
class Solution(object):
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
l = len(nums)
nums.sort()
return nums[l-k]
最小的k个数:
题目:输入整数数组 arr
,找出其中最小的 k
个数
class Solution(object):
def getLeastNumbers(self, arr, k):
"""
:type arr: List[int]
:type k: int
:rtype: List[int]
"""
arr.sort()
return arr[:k]
数组的相对排序:
题目:给你两个数组,arr1
和 arr2
,arr2
中的元素各不相同,arr2
中的每个元素都出现在 arr1
中。
对 arr1
中的元素进行排序,使 arr1
中项的相对顺序和 arr2
中的相对顺序相同。未在 arr2
中出现过的元素需要按照升序放在 arr1
的末尾。
class Solution(object):
def relativeSortArray(self, arr1, arr2):
"""
:type arr1: List[int]
:type arr2: List[int]
:rtype: List[int]
"""
arr1_max = max(arr1)
nums=[0]*(arr1_max+1)
for i in arr1:
nums[i]+=1
arraylist = []
for i in arr2:
arraylist.extend([i]*nums[i])
nums[i]=0
for i in range(arr1_max+1):
if nums[i]>0:
arraylist.extend([i]*nums[i])
return arraylist
存在重复元素Ⅲ:
题目:给定一个整数数组 nums,以及两个整数 k、t。判断数组中是否存在两个不同下标的 i 和 j,其对应元素满足 abs(nums[i]−nums[j])≤t,同时满足 abs(i−j)≤k。如果满足条件则返回 True
,不满足条件返回 False
。
class Solution(object):
def containsNearbyAlmostDuplicate(self, nums, indexDiff, valueDiff):
"""
:type nums: List[int]
:type indexDiff: int
:type valueDiff: int
:rtype: bool
"""
l = len(nums)
for i in range(l):
for j in range(i+1,l):
if abs(i-j)<=indexDiff and abs(nums[i]-nums[j])<=valueDiff:
return True
return False
最大间距:
题目:给定一个无序的数组 nums
,返回 数组在排序之后,相邻元素之间最大的差值 。如果数组元素个数小于 2,则返回 0
。
class Solution(object):
def maximumGap(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if len(nums)<2:
return 0
nums.sort()
num=[]
for i in range(1,len(nums)):
num.append(nums[i]-nums[i-1])
num.sort()
l = len(num)-1
return num[l]