1. 插入排序
基本思想:
每次外循环将遍历到的当前元素和左边的元素依次进行(内循环)数值比较,将其插入合适的位置。
class Solution(object):
def InsertSort(self, nums):
nums_length = len(nums)
for i in range(nums_length):
cur_val = nums[i] #取当前i索引的数值,暂存下来(后面可能会被覆盖)
pre_index = i - 1 #取前一个元素的索引
while pre_index >= 0 and cur_val < nums[pre_index]: #条件1:索引有效 and 条件2:当前元素数值<前一个元素
#将前一个元素的数值赋给下一位,而非交换。相当于覆盖了下一位,不过由于事先保存了i位元素,所以无碍
nums[pre_index+1] = nums[pre_index]
#左移指针,继续比较之前的元素
pre_index -= 1
nums[pre_index+1] = cur_val #将暂存的当前元素数值插入到合格位置
return nums
2. 选择排序
基本思想:
一个数组可以看做由两部分构成,左边是已排序的,右边是未排序的;每一次的排序循环从未排序部分的第一个元素 x 开始,遍历整个未排序部分找到其中最小的元素,然后和 x 交换位置(效果等同于将这个当前最小元素加入已排序末位)。
class Solution(object):
def SelectionSort(self, nums):
nums_length = len(nums)
for i in range(nums_length -1): # 因为最小/最大的元素在前len-1次中已经被交换了,剩下的一定是最大/最小的,排序结束
min_index = i
for k in range(i, nums_length): # 和 i 后面的所有元素做比较,每次的行程-1
if nums[min_index] >= nums[k]:
min_index = k
nums[min_index], nums[i] = nums[i], nums[min_index]
return nums
3. 归并排序
基本思想:
分治法+合并子列。合并子列的意思是假定有两个分别已经排好顺序的子列,将其合并成一个有序的数列。分治法就是将数列不断二分,直到子列只有一个元素时,子列天然有序,然后一步步再慢慢合并上来,最后得到一个完整的有序数列。
class Solution(object):
def MergeSort(self, nums):
def merge(left, right): #合并子列
result = []
p_l = p_r = 0
# 通过左右指针来实现对左右两个子列的检索
# 某一子列到头说明另一子列剩下元素较大且有序,直接写进result就行
while p_l < len(left) and p_r < len(right):
if left[p_l] <= right[p_r]:
result.append(left[p_l])
p_l += 1 #左指针
else:
result.append(right[p_r])
p_r += 1 #右指针
#将左右子列剩下的元素写入result(某一子列已经到头,extend空List,不影响result)
result.extend(left[p_l:len(left)])
result.extend(right[p_r:len(right)])
return result
if len(nums) == 1:
return nums
mid = len(nums) // 2
left = nums[:mid]
right = nums[mid:]
return merge(self.MergeSort(left), self.MergeSort(right))
4. 快速排序
基本思想:
分治+递归。
首先选定一个基准元素(比如数组中第一个元素),然后两个指针分别从左右对数组进行遍历,将大于基准元素的数放到较右边,将小于基准元素的数放到较左边(这一步的代码实现是关键)。当左右指针相遇时,循环结束,此时以相遇位置为界,左边全是小于基准的数,右边全是大于基准的数,将基准插入相遇位置,完成一次排序操作(这个操作可称为Partition,可以封装进函数),然后递归地对左边子列和右边子列进行同样的Partition操作。
class Solution(object):
def QuickSort_Partition(self, nums, left, right):
if left > right:
return
pivot = nums[left] #取左1为基数
index = 0 #基数的索引
l = left
r = right
while l < r:
while l < r and nums[r] >= pivot: #从右往左寻找小于基准pivot的数
r -= 1
nums[l] = nums[r] #将大于基准的数甩到左边
while l < r and nums[l] < pivot: #从左往右寻找大于基准pivot的数
l += 1
nums[r] = nums[l] #将小于基准的数甩到右边
# left = right时,退出循环
nums[r] = pivot #基准pivot已经被覆盖了,需要重新插入到循环退出的位置
index = r #更新基准的索引
self.QuickSort_Partition(nums, left, index-1)
self.QuickSort_Partition(nums, index+1, right)
return nums
5. 堆排序
基本思想:
将数组看做一个完全二叉树从左至右、从上到下的一维展开式,将这个完全二叉树(也即这个数组)通过若干次heapify操作转化为大顶堆。此时根节点为二叉树中最大值,将其和最后一个节点交换,然后切掉这个最大值节点,再对根节点进行heapify操作将剩下的树恢复大顶堆性质。重复多次(每次都输出当前树中的最大值)直到排序完成。
关键:
- 完全二叉树性质
- 大顶堆与heapify操作
- 大顶堆性质
- heapify操作
- 构建大顶堆
- 使用堆进行排序
class Solution(object):
# 堆排序
def HeapSort(self, arr):
# 建立大顶堆 (in-place):
# 大顶堆性质:1.完全二叉树 2.任何一个父节点均大于其子节点(如果有)
def build_heap(arr, length):
last_parent_node = (length-2) // 2 #最后一个非叶节点
i = last_parent_node
while (i >= 0): #从右至左,从下往上heapify,构建大顶堆
heapify(arr, i, length)
i -= 1
# heapify操作(in-place):
# 比较父节点、左子节点、右子节点,将最大值推到父节点(即构建小型大顶堆)
def heapify(arr, index, length):
left = 2 * index + 1
right = 2 * index + 2
# 以父节点作为初始比较值
max_index = index
# 第一个条件是避免越界
if left < length and arr[left] > arr[max_index]:
max_index = left
if right < length and arr[right] > arr[max_index]:
max_index = right
if max_index != index:
swap(arr, max_index, index) # 将最大值推到父节点
heapify(arr, max_index, length) # 子节点被换掉了,对子节点下方子树进行递归,保证子树的大顶堆性质
# 按索引进行数组元素的交换 (in-place):
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
# 执行堆排序
length = len(arr) # 长度
build_heap(arr, length) # 建立大顶堆
k = length - 1 # 从最后一个节点开始
while k >= 0:
swap(arr, 0, k) # 将根节点(最大值)和最后一个节点交换
k -= 1 #长度-1,相当于把最后一个节点(当前最大值)切掉(即后续不再对其操作)
heapify(arr, 0, k) #heapify操作,确保新的根节点为最大值
return arr