1、冒泡排序
1.1、2层 for 循环的起始点与终止点是什么?
1.2、冒泡时总是比较哪两个数?
1.3、有序子数组的生长情况是什么样的?
1.4、优化冒泡的flag有什么作用?
python版本
class Solution(object):
def sortArray(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
# i 的取值区间[0, len-1)
# nums[len-1]是合法的,为什么 i 不取 len-1 呢?
# 因为下面出现了nums[j] > nums[j + 1],为了避免数组越界。
for i in range(len(nums) - 1):
flag = 0
# j 的取值区间[0, len-i-1),随着i增长,j的取值区间越来越小
# 有序子数组[len-i-1, len)从数组末尾向数组头生长,使得无序区间越来越小
for j in range (len(nums) - 1 - i):
if nums[j] > nums[j + 1]:
flag = 1
nums[j], nums[j + 1] = nums[j + 1], nums[j]
# 经过第一轮冒泡,max位于数组末尾,然后i++,j的取值区间被缩减了1
if flag == 0:
# 如果在第i次遍历数组时发现,flag没有被更改过,说明子数组nums[0...len-1-i]是有序的。
# 没有泡泡可以再冒了
break
C++版本
vector<int>& sort1(vector<int>& nums) {
unsigned int i, j;
// i的取值范围:[0, size-1)
for (i = 0; i < nums.size() - 1; ++i) {
int flag = 0;
// j的取值范围:[0, size-1-i)
// 有序子数组从末尾向头部生长,[0, size-1-i)会越来越小
for (j = 0; j < nums.size() - 1 - i; ++j) {
if (nums[j] > nums[j + 1]) {
int tmp = nums[j + 1];
nums[j + 1] = nums[j];
nums[j] = tmp;
flag = 1;
}
// 第一次冒泡后,max值被移动到数组末尾,++i,则j的取值[0, size-1-i)区间缩小
// 此时数组的分布是nums[0:len-2]为无序数组,nums[len-i, len-1]为有序数组
}
if (!flag) {
return nums;
}
}
return nums;
}
2、选择排序
比较哪两个数?
比冒泡优在哪个方面?
从nums[0]开始,选定1个基准数,再遍历剩余数nums[1…end]且与基准数比较,然后得到最小数,把最小数挪到nums[0]。比冒泡的赋值操作少。
python版本
class Solution:
def sortArray(nums):
for i in range(len(nums)):
min_idx = i
for j in range(i+1, len(nums)):
if nums[min_idx] > nums[j]:
min_idx = j
nums[i], nums[min_idx] =nums[min_idx], nums[i]
return nums
C++版本
/* 选择排序 */
vector<int>& sort2(vector<int>& nums) {
int i, j, min;
for (i = 0; i < nums.size() - 1; ++i) {
min = nums[i];
for (j = i + 1; j < nums.size(); ++j) {
if (nums[j] < nums[i]) {
min = nums[j];
nums[j] = nums[i];
nums[i] = min;
}
}
}
return nums;
}
3、插入排序
图解过程
第1层for表示:需要插入的次数,比数组大小少1,因为第1个数不需要插入
第2层for表示:从数组尾部向头部移动,找到插入位置
class Solution:
def sortArray(nums):
# 第一层for表示循环插入的遍数
for i in range(1, len(nums)):
for j in range(i, 0, -1):
# 逐个比较交换,往左移动
if nums[j] < nums[j - 1]:
nums[j], nums[j - 1] = nums[j - 1], nums[j]
else:
break
return nums
上面的算法的缺点:在第i-1趟插入时,需要把第i个元素插入到前面的i-1个元素中,该算法总是从i-1个元素开始逐个比较之前的每个元素,直到找到第i个元素的插入位置,这显然没有利用前面0~i-1个元素已经有序的特点
利用有序子数组,定位插入位置
优化:在0~i-1个有序元素给第i个元素寻找插入的位置时,使用二分查找法可以有效提高查找插入位置的时间效率,经过优化的插入排序称为折半插入排序,折半插入排序的时间复杂度为O(n*logn)
python版本
class Solution:
def fineInsertIndex(self, nums, left, right, num):
# 原理是二分法查找,但是实现细节还得根据情况修改,比如num = nums[mid]的情况该怎么处理?
while left <= right:
mid = left + (right - left) // 2
if num < nums[mid]:
right = mid - 1
else:
# num < nums[mid]
# num = nums[mid]时不返回位置,而是继续二分。
left = mid + 1
# 思考为什么是返回left而不是right?
return left
def sortArray(self, nums):
# 第一层for表示循环插入的遍数
for i in range(1, len(nums)):
tmp = nums[i]
# 使用二分查找找到插入位置,前提是查找的数组是有序的
insertIndex = self.fineInsertIndex(nums, 0, i - 1, nums[i])
# 插入位置右边的元素依次往右移动一格
for j in range(i, insertIndex, -1):
nums[j] = nums[j - 1]
# 往插入位置写入
nums[insertIndex] = tmp
return nums
C++版本
/* 二分插入排序 */
int binary_search(vector<int>& nums, int value, int length); // 二分查找
vector<int>& sort3(vector<int>& nums) {
int i, j, insert_index, tmp;
// i = 0时为第一个数,不需要插入,i从1开始
for (i = 1; i < nums.size(); ++i) {
/* 获取插入位置的下标 */
insert_index = binary_search(nums, nums[i], i);
// 带插入的数
tmp = nums[i];
// 将插入位置往后的数组依次向右挪动1格
for (j = i - 1; j >= insert_index; --j) {
nums[j + 1] = nums[j];
}
// 被挪动后,插入目标值
nums[insert_index] = tmp;
}
return nums;
}
/* 二分查找 */
int binary_search(vector<int>& nums, int value, int length) {
int start = 0, end = length - 1, mid;
/* 有效区间是[start, end] */
while (start <= end) {
mid = start + (end - start) / 2;
if (nums[mid] < value) {
start = mid + 1;
}
else if (nums[mid] > value) {
end = mid - 1;
}
else if (nums[mid] == value) {
/* 二分查找,当遇到连续重复的数时,想要寻找该重复数组段的右边界 */
/* 若是想要查找左边界,则end = mid - 1 */
/* 若是不想要取边界,则直接返回mid */
start = mid + 1;
}
}
return start;
}
4、快速排序
任意选取一个数据作为关键数据,然后将所有比它小的数都“丢”到前半段,所有比它大的数都“丢”到后半段,这个过程称为一趟快速排序。
为什么使用“丢”这个词?因为一趟“丢”完,前半段的数是较小的数,后半段的数是较大的数。但是前、后半段的子数列内部依旧是无序的,所以用“丢”这个词。
快速排序的关键:
1、左右指针靠拢的终止条件,为什么没有 == ?
2、参考点的选取。不管采用什么方法选取了参考点,都把参考点移到数组首地址,再套用朴素快排的模板即可。
3、左指针与右指针向中间靠拢,左右轮流移动。
4、基数选取方法:首元素法、3选1采样法、随机数法(平均效率更高)
5、改进版:三向切分快速排序(针对含有大量重复元素的数组而设计),3个指针
6、迭代版:需要建立栈存放准备排序的区间边界
python版本
class Solution:
def quickSort(self, arr,left,right):
# 递归终止条件
if left >= right:
return -1
i, j = left, right
# 若使用其它基数选取方法,都是要将选取的基数与首元素交换
pivot = arr[left]
# 一趟循环i右移1格,j左移1格
while i < j:
# 从右往左过滤>=pivot的数
while i < j and arr[j] >= pivot:
j -= 1
# 碰到一个小数,“丢”到左半段
if i < j:
arr[i] = arr[j]
i += 1
# 过滤<pivot的数
while i < j and arr[i] < pivot:
i += 1
# 碰到一个大数,“丢”到右半段
if i < j:
arr[j] = arr[i]
j -= 1
# 填入第i格,i即是mid
arr[i] = pivot
# 对2段序列快速排序
self.quickSort(arr, left, i - 1)
self.quickSort(arr, i + 1, right)
def sortArray(self, nums):
self.quickSort(nums, 0, len(nums) - 1)
return nums
C++版本
/* 快速排序 */
void sort4_recursive(vector<int>& nums, int left, int right);
vector<int>& sort4(vector<int>& nums) {
if (nums.size() < 2) return nums;
sort4_recursive(nums, 0, nums.size() - 1);
return nums;
}
void sort4_recursive(vector<int>& nums, int left, int right) {
if (left >= right) return ;
int value = nums[left], start = left, end = right;
// 为什么不能出现=,因为当left==right时,需要把value存入left(right)
// value自从被取出来后,一直没有存回去。
// 它的源地址nums[0]在第一次while循环时就被占据了
while (left < right) {
// 右指针从右向左扫描,过滤掉大数,同时要注意数组越界
while (left < right && nums[right] >= value) --right;
// 遇到小数,while停止,此时把小数丢到左侧
if (left < right) {
nums[left++] = nums[right];
}
// 左右指针轮流移动
// 左指针从左向右扫描,过滤掉小数,同时要注意数组越界
while (left < right && nums[left] < value) ++left;
// 遇到大数,while停止,此时把大数丢到右侧
if (left < right) {
nums[right--] = nums[left];
}
}
// 把value存回去
nums[left] = value;
// 循环结束时,left == right
sort4_recursive(nums, start, right - 1);
sort4_recursive(nums, left + 1, end);
}
- 改进版:三向切分快速排序(针对含有大量重复元素的数组而设计)
- 相反,若没有重复元素,则获得最坏时间复杂度
三向切分的快速排序的过程是这样的:
从左到右遍历数组一次,维护3个指针,left,mid,right
1)、a[mid]小于v,将a[left]和a[mid]交换,然后将left++和mid++;
2)、a[mid]大于v,将a[right]和a[mid]交换,然后将right–;
3)、a[mid]等于v,mid++;
除非和切分元素相等,其他元素都会被交换。
class Solution:
def sortArray(self, s):
n = len(s)
if n < 2:
return s
self.quickSort(s, 0, n - 1)
return s
def quickSort(self, s, start, end):
if start < end:
left, mid, right = start, start + 1, end
pivot = s[start]
while mid <= right:
if s[mid] < pivot:
s[left], s[mid] = s[mid], s[left]
left += 1
mid += 1
elif s[mid] == pivot:
mid += 1
else:
s[right], s[mid] = s[mid], s[right]
right -= 1
self.quickSort(s, start, left - 1)
self.quickSort(s, right + 1, end)
5、归并排序
图解
归并排序的重点:
1、对输入数组的拆分,二分法。
2、对返回的子数组边比较边排序合并。
3、当数组较小时,改用插入排序。能增加效率。
class Solution:
def sortArray(self, nums):
# 递归终止条件
if len(nums) <= 1:
return nums
# 将输入数列进行拆分,各自排序
mid = len(nums) // 2
left = self.sortArray(nums[:mid])
right = self.sortArray(nums[mid:])
# 下面对有序数列left和right进行合并
res = []
while len(left) > 0 and len(right) > 0:
if left[0] <= right[0]:
res.append(left.pop(0))
else:
res.append(right.pop(0))
# 将剩余的有序数列直接加到末尾
res += left
res += right
return res
C++版本
/* 归并排序 */
void sort5_recursive(vector<int>& nums, int left, int right);
vector<int>& sort5(vector<int>& nums) {
if (nums.size() < 2) return nums;
sort5_recursive(nums, 0, nums.size() - 1);
return nums;
}
void sort5_recursive(vector<int>& nums, int left, int right) {
// 递归终止
if (left >= right) return;
int mid = (left + right) / 2;
// 对数组切片,分成2段
vector<int> L(nums.begin() + left, nums.begin() + mid + 1);
vector<int> R(nums.begin() + mid + 1, nums.begin() + right + 1);
// 递归时,要假定最底层递归已经返回了正确结果,只管使用就行。
sort5_recursive(L, 0, L.size() - 1);
sort5_recursive(R, 0, R.size() - 1);
// 边排序边合并2个有序数组
int i = 0, j = 0;
while (i < L.size() && j < R.size()) {
if (L[i] < R[j]) {
nums[left++] = L[i++];
}
else if (L[i] >= R[j]) {
nums[left++] = R[j++];
}
}
// i、j必有一个先到达边界,在原来地址上将数重新赋值
while (i < L.size()) nums[left++] = L[i++];
while (j < R.size()) nums[left++] = R[j++];
}
6、堆排序
6.1、不使用[0]元素
约定:数组首元素[0]不使用
1、堆的整理,也叫上浮(大顶堆)或下沉(小顶堆)
- 这是堆排序的核心,单次调整的Time = O(logn)
heapAdjust()
的原理是什么?
- 移动3个节点,使得
根>max(左子树,右子树)
,然后递归调整左子树与右子树。
2、堆顶元素的弹出流程是什么样的?
- 堆顶元素arr[1]与堆底元素arr[end]交换
- max值就转移到了arr[end],接着把arr[end]排除在堆外。
- 无序数组的大小-1,即需要调整的范围变成arr[1…end-1],因为最大值已经被取出并且放在数组末尾了。
- 由于堆顶元素arr[1]改变了,需要再进行一次调整,这次调整时,堆顶索引不变,但是无序数组的大小需要-1。
3、堆整理需要哪3个参数?
- 数组首地址,无序数组的大小,待整理的堆顶索引,
4、完全二叉堆的构造过程是怎么样的?
- 从最后一个非叶节点开始,扫描至最顶部的根节点,每扫描1个都要调整最大堆。
注意点:构造时的堆大小总是n,只是堆顶索引在移动
,为什么?
因为n严格来讲是指数组索引的边界。
5、最后一个非叶节点索引怎么求?
- n / 2
6、为什么是非叶节点开始?
- 因为非叶节点也相当于一个完全二叉堆。所以,其实是从最后一个子堆开始。
class Solution:
def sortArray(self, nums):
n = len(nums)
if n < 2:
return nums
# 构建大顶堆,数组下标0不使用
res = [-1] + nums
# n // 2指向最后一个非叶结点
for i in range(n // 2, 0, -1):
self.heapAdjust(res, i, n)
# 参数:堆地址,堆顶的索引,堆的数组边界
# print(res)
# 弹出堆顶元素res[1]
for i in range(n, 0, -1):
res[i], res[1] = res[1], res[i]
self.heapAdjust(res, 1, i)
# 参数:堆地址,堆顶的索引,堆的数组边界
# print(res, i)
return res[1:]
# 参数:堆地址,堆顶的索引,堆的数组边界
def heapAdjust(self, nums, root, n):
child = 2 * root
if child < n: # 存在孩子,若刚好child==n-1,则只有左孩子
# 本层递归的任务,比较双亲与孩子结点的大小,调整双亲结点成为最大
if child == n - 1 :
# 只有1个孩子,并且是左孩子
if nums[root] > nums[child]:
# 说明双亲比孩子大,不必调整
return 0
nums[root], nums[child] = nums[child], nums[root]
self.heapAdjust(nums, child, n)
if child < n - 1:
if nums[child] < nums[child + 1]:
# 转移指针指向大孩子
child += 1
if nums[child] > nums[root]:
nums[root], nums[child] = nums[child], nums[root]
self.heapAdjust(nums, child, n)
6.2、使用[0]元素
个人觉得数组【0】不使用很别扭,只是单纯方便理解算法而已。
如何把数组【0】用上呢?代码怎么实现?
3点区别:
- 构造大顶堆时
最后一个子堆的索引不变,依旧是n/2;
但是,子堆的索引 i 有效范围从[n/2, n]变成[n/2, n-1] - 逐个弹出堆顶元素时
弹出个数不变,依旧是n;
但是,堆顶元素由 [1] 变成 [0] - 堆调整函数的孩子索引
左孩子索引由[root x 2
] 变成 [root x 2 + 1
]
class Solution:
def sortArray(self, nums):
n = len(nums)
if n < 2:
return nums
# n // 2指向最后一个非叶结点
for i in range(n // 2, -1, -1): # 区别1:没有res = [-1] + nums,且数组范围i=n//2....0
self.heapAdjust(nums, i, n)
# 参数:堆地址,堆顶的索引,数组的边界
# print(nums)
# 弹出堆顶元素nums[0] # 区别2:堆顶元素nums[0]
for i in range(n - 1, -1, -1): # 区别3:i=n-1....0
nums[i], nums[0] = nums[0], nums[i]
self.heapAdjust(nums, 0, i)
# 参数:堆地址,堆顶的索引,数组的边界
# print(nums, i)
return nums
# 参数:堆地址,堆顶的索引,数组的边界
def heapAdjust(self, nums, root, n):
child = 2 * root + 1 # 区别4:左孩子索引2root+1
if child < n: # 存在孩子。若刚好child==n-1,则只有左孩子
# 本层递归的任务,比较双亲与孩子结点的大小,调整双亲结点成为最大
if child == n - 1 :
# 只有1个孩子,并且是左孩子
if nums[root] > nums[child]:
# 说明双亲比孩子大,不必调整
return 0
nums[root], nums[child] = nums[child], nums[root]
self.heapAdjust(nums, child, n)
if child < n - 1:
if nums[child] < nums[child + 1]:
# 转移指针指向大孩子
child += 1
if nums[child] > nums[root]:
nums[root], nums[child] = nums[child], nums[root]
self.heapAdjust(nums, child, n)
C++版本
/* 堆排序 */
void heap_adjust(vector<int>& heap, int size, int root) ;
vector<int>& sort6(vector<int>& nums) {
if (nums.size() < 2) return nums;
// 从最后一个子堆开始,逐个子堆调整,i != 0
for (int i = nums.size() / 2; i >= 0; --i) {
// 传入的是数组、数组的边界、堆顶元素索引
heap_adjust(nums, nums.size(), i);
}
// 调整完大顶堆后,逐个弹出堆顶元素,按顺序存入数组
int tmp;
for (int i = nums.size() - 1; i >= 0; --i) {
tmp = nums[0];
nums[0] = nums[i];
nums[i] = tmp;
// 传入的是数组、数组的边界、堆顶元素索引
heap_adjust(nums, i, 0);
}
return nums;
}
void heap_adjust(vector<int>& heap, int size, int root) {
int child = root * 2 + 1;
// 判断是否存在孩子
if (child < size) {
// 判断是否存在2个孩子
if (child + 1 == size) {
// 只有一个孩子,并且是左孩子
// 判断双亲与左孩子的大小
if (heap[root] >= heap[child]) return;
// 孩子比双亲大,则交换2者
int tmp = heap[child];
heap[child] = heap[root];
heap[root] = tmp;
// 双亲与孩子交换后,重新调整子堆
// 传入的是数组、无序数组的大小、堆顶元素索引
heap_adjust(heap, size, child);
}
if (child + 1 < size) {
// 有2个孩子
if (heap[child] < heap[child + 1]) {
// 将child指向大孩子
child += 1;
}
// 把双亲与孩子比较
if (heap[root] < heap[child]) {
// 孩子比双亲大,则交换2者
int tmp = heap[child];
heap[child] = heap[root];
heap[root] = tmp;
// 双亲与孩子交换后,重新调整子堆
// 传入的是数组、无序数组的大小、堆顶元素索引
heap_adjust(heap, size, child);
}
}
}
}
6.3、堆排序步骤小结
使用数组首元素[0]的思路小结:
(1)记住堆调整函数的三个参数
数组首地址,无序数组的大小,待调整的堆顶索引
(2)构建大顶堆时,起点的最后一个非叶子节点
因此,应该从[n/2]逐个调整到[n-1],n是数组的大小
(3)弹出大顶堆时,把堆顶元素与堆底元素交换,然后把堆底元素排除出去,堆大小-1
弹出的堆顶元素总是被存放到堆底,第一次弹出最大值,第二次弹出次大值,都一次被放在数组的倒数第一位、倒数第二位…因此数组被分成前半段(无序)与后半段(升序)。
(4)堆调整函数
- 从输入的堆顶元素索引获取孩子的索引:child = root x 2 + 1
- 判断左孩子、右孩子的存在情况:利用数组所以是否越界来判断
- 比较孩子与双亲的大小。孩子大于双亲,则交换2者,同时以孩子为子堆再执行一次调整。每次交换必须紧跟一次子堆调整。
7、计数排序
计数排序的基本思想:对每一个元素x,确定小于x的元素个数。利用“个数”这一信息,就可以直接把x放到输出排序数组的正确位置上了。
结合直方图理解更加形象。
优点:时间复杂度O(n)
缺点:空间复杂度O(n)+O(N),只能处理非负整数
若事先不了解序列的分布情况
1.可能因为分布过于离散导致空间浪费
2.可能因为分布过于密集,大量重复元素,导致临时空间不足而排序出错
class Solution:
def sortArray(self, s):
if len(s) < 2:
return s
# 找到最大最小值
min_num = min(s)
max_num = max(s)
# 计数列表,-min_num可以节省一点空间
count_list = [0]*(max_num -min_num + 1)
# 计数
for i in s:
count_list[i - min_num] += 1
print(count_list)
res = []
# 填回。
for index, count in enumerate(count_list):
print(index, count)
while count != 0:
# 填入的数+min_num,对应于计数阶段的-min_num
res.append(index + min_num)
count -= 1
return res
8、桶排序
桶排序:均匀分布
1.优点:
- 是对计数排序的改进,计数排序申请的额外空间跨度从最小元素值到最大元素值,若待排序集合中元素不是依次递增的,则必然有空间浪费情况。
- 桶排序则是弱化了这种浪费情况,将 ** “最小值到最大值之间的每一个位置申请空间” **,更新为 ** “最小值到最大值之间每一个固定区域申请空间” **,尽量减少了元素值大小不连续情况下的空间浪费情况。
2.缺点:
- 1.需要事先知晓序列分布情况,才能确定一个高效的映射函数
- 2.对于max范围大的序列需要更多的空间
基本思想:
- 1.根据待排序集合中最大元素和最小元素的差值范围和映射规则,确定申请的桶个数;
- 2.遍历待排序集合,将每一个元素移动到对应的桶中(此映射可以将数据初步排序);
- 3.对每一个桶中元素进行排序,并移动到初始集合中。
- 4.桶越多则桶内分到的数据越少,排序越快。
class Solution:
def sortArray(self, s):
if len(s) < 2:
return s
min_num = min(s)
max_num = max(s)
# 桶的大小,+1是向上取整
bucket_range = (max_num - min_num) // len(s) + 1
# 桶数组
count_list = [ [] for i in range(len(s))]
# 向桶数组填数
for i in s:
# 映射函数:int((i - min_num) // bucket_range)
count_list[int((i - min_num) // bucket_range)].append(i)
s = list([])
# 回填,这里桶内部排序直接调用了sorted
for i in count_list:
for j in sorted(i):
s.append(j)
return s
9、基数排序
- 基数排序的基本思想:按-个-十-百-千位的顺序排序
- 每一轮排序都是根据序列中的某一位数进行排序,**从低位到高位(!!!)**各进行一次这种排序思想,最终序列便是有序的。
- 由于输入序列每一位数都是有限的,比如十进制序列,每位数都是0~9的数字,于是可以选择计数排序对序列某一位数进行排序。
- 同样,基数排序也不能处理非负数。
1.弥补了计数排序的2个缺点,不论分布离散还是密集都可以
2.只能处理非负数
class Solution:
def sortArray(self, s):
"""基数排序"""
i = 0 # 记录当前正在排拿一位,最低位为1
max_num = max(s) # 最大值
j = len(str(max_num)) # 记录最大值的位数
while i < j:
bucket_list =[[] for _ in range(10)] #每趟循环都要初始化桶数组
for x in s:
bucket_list[int(x / (10**i)) % 10].append(x) # 找到位置放入桶数组
s = list([])
for x in bucket_list: # 从桶中取出放回原序列
for y in x:
s.append(y)
i += 1
return s
例,输入:[90,21,20,30,14,13,15,55,25,43]**
第一趟排序:按个位数排序
[90, 20, 30, 21, 13, 43, 14, 15, 55, 25]
第二趟排序:按十位数排序
[13, 14, 15, 20, 21, 25, 30, 43, 55, 90]
10、排序算法的稳定性
- 稳定的:如果存在多个具有相同排序码的记录,经过排序后,这些记录的相对次序仍然保持不变,则这种排序算法称为稳定的。
归并排序、插入排序、冒泡排序、桶排序、基数排序、计数排序,都是稳定的排序算法。
- 否则称为不稳定的。
快速排序、堆排序、shell排序、选择排序,都是不稳定的排序算法。
11、如何选择排序算法?
(1)、数组规模大小
- 若n较小,使用插入排序、冒泡排序,因为算法原理简单。
- 若n较大,则应采用时间复杂度较小的排序方法:快速排序(nlogn)、堆排序(nlogn)或归并排序(nlogn)。一般考虑快速排序。
- 还有线性时间的排序算法,但是限制条件更多。
(2)、要求算法稳定
- 归并排序、插入排序、冒泡排序、桶排序、基数排序、计数排序
(3)、要求时间稳定
- 程序无法承受最差排序时间带来的后果时可以选择对排序。堆排序的最大特点就是时间稳定,best与worst都是O(nlogn)
(4)、要求空间消耗少
- 排除计数排序、基数排序、桶排序。这3种排序算法都是以空间换取时间。一般考虑快速排序。
(5)、数组大量重复
- 使用三向切分法的改进快速排序。
(6)、数组基本有序
- 使用随机快速排序、插入排序、冒泡排序。
(7)、数组全为非0数
- 若对空间复杂度要求不高,可使用桶排序、基数排序、计数排序。
(8)、数组是流形式输入
- 插入排序、堆排序。