高效且应用广泛的排序 —— 快速排序算法
快速排序是一种常用的排序算法,主要采用分治的思想。以下是对快速排序算法的详细介绍及代码示例:
快速排序的基本思路是,每次将一个位置上的数据归位,使得该数左边的所有数据都比该数小,右边所有的数据都比该数大,然后递归将已归位的数据左右两边再次进行快排,从而实现所有数据的归位。
算法步骤如下:
- 从数列中挑出一个元素,称为“基准”。
- 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区操作。
- 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
例如,对于数组,选择最左边的元素 29 作为中间点元素,然后将数组分成三部分:(0, 14, 15, 20, 25),(29),(44, 37)。中间节点 29 已经排好序了,不需要处理。接下来再对左右分别进行快速排序。
下面是 Java 代码示例:
public class QuickSort {
// 测试
public static void main(String[] args) {
int[] arr = {5,2,9,6,3,1,7,8,4};
int left = 0;
int right = arr.length - 1;
quickSort(arr, left, right);
System.out.println("排序结果:");
for(int num : arr){
System.out.print(num + " ");
}
}
// 快速排序算法
public static void quickSort(int[] arr,int left,int right) {
if(left < right) {
int partitionIndex = partition(arr, left, right);
// 将数组划分为两部分,并返回划分点的索引
quickSort(arr, left, partitionIndex - 1);
// 递归排序左子数组
quickSort(arr, partitionIndex + 1, right);
// 递归排序右子数组
}
}
// 划分函数
public static int partition(int[] arr,int left,int right) {
int pivot = arr[right];
// 选择最后一个元素作为基准值
int i = left - 1;
// i 为较小元素的索引
for(int j = left; j < right; j++){
if(arr[j] <= pivot){
i++;
swap(arr, i, j);
// 交换较小元素与当前元素
}
// 如果数组元素大于等于 middleValue,则继续向后遍历,middleIndex 值不变
}
swap(arr, i + 1, right);
// 将基准值放置到正确的位置上
return i + 1;
// 返回划分点的索引
}
// 交换数组中两个元素的位置
public static void swap(int[] arr,int i,int j) {
int temp = arr[i];
arr[i]= arr[j];
arr[j]= temp;
}
}
快速排序在平均情况下时间复杂度为 O(n log n),但在最坏情况下时间复杂度会变为 O(n²)。可以通过随机选择基准元素或使用三数取中法等方法来避免最坏情况。空间复杂度为 O(log n),因为在递归调用中需要使用栈来存储中间结果。快速排序虽然在最坏情况下时间复杂度可能较高,但在实际应用中通常表现良好,尤其对于大规模数据集的排序。如果实现得当,它是一种高效的排序算法。
快速排序算法基本思路
快速排序是一种高效的排序算法,其基本思路是分治法。首先从数列中取出一个数作为基准数,然后进行分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。接着再对左右区间重复这个步骤,直到各区间只有一个数。概括来说就是“挖坑填数+分治法”。
快速排序就像整理一堆杂乱的卡片。假设我们有一叠无序的数字卡片,我们随机选取一张卡片作为基准数,比如选择第一张卡片。然后我们从这叠卡片的右边开始,找到比基准数小的卡片,从左边开始找到比基准数大的卡片,找到后交换这两张卡片的位置。这样不断进行,直到左右指针相遇。此时,我们把基准数放到正确的位置上,这个位置左边的数字都小于等于基准数,右边的数字都大于基准数。然后我们再对左右两边的子序列重复这个过程,直到所有的子序列都只有一个数字,此时整个序列就有序了。
例如,有一组数字。我们选择4作为基准数,首先从右边开始,9大于4,继续向左,3小于4,此时停下。然后从左边开始,1小于4,继续向右,6大于4,停下。交换3和6的位置,得到。接着继续这个过程,指针不断移动,直到左右指针相遇。最后把4放到正确的位置上,此时序列变为。然后再对左边的子序列和右边的子序列分别进行快速排序,直到所有子序列都有序。
快速排序算法步骤
- 从序列中任选一个数作为基准数,一般就使用第一个数。
- 进行分区操作,将大于基准数的数放在右边,小于基准数的数放在左边,注意一定要先右后左。具体操作是设置两个指针i和j,分别指向数组的第一个元素和最后一个元素。从j开始向左挪动,直到找到一个小于基准数的数停下来;然后切换到i再向右挪动,直到找到一个数大于基准数的数停下来。最后将i和j指向的元素进行交换位置。重复这个过程直到i和j重合,此时把重合点的元素与基准元素交换位置。
- 对左右区间分别执行上述步骤,直到每个区间只有一个数为止。
例如对于数组,选择5作为基准数。首先j从6开始向左移动,找到3小于5停下。i从0开始向右移动,找到8大于5停下。交换3和8的位置,得到。接着j继续向左移动,找到1小于5停下。i继续向右移动,找到2小于5不停下,继续移动找到8大于5停下。交换1和8的位置,得到。此时i和j重合在1的位置,把1和5交换位置,得到。然后对左边的子序列和右边的子序列分别进行快速排序,直到所有子序列都有序。
快速排序代码示例解析
以下是一段快速排序的 Python 代码示例:
def quickSort(lists, left, right):
# 快速排序
if left >= right:
return lists
key = lists[left]
low = left
high = right
while left < right:
while left < right and lists[right] >= key:
right -= 1
lists[left] = lists[right]
while left < right and lists[left] <= key:
left += 1
lists[right] = lists[left]
lists[right] = key
print("lists:", lists)
quickSort(lists, low, left - 1)
quickSort(lists, left + 1, high)
return lists
这段代码首先判断左右指针的位置,如果左指针大于等于右指针,说明该子序列已经有序,直接返回。然后选择左指针指向的元素作为基准数key
。接着使用两个循环,从右向左找到小于基准数的元素,从左向右找到大于基准数的元素,然后交换这两个元素的位置。当左右指针相遇时,将基准数放到正确的位置上。最后递归地对左右子序列进行快速排序。
以列表为例,调用quickSort
函数进行排序。首先选择3作为基准数,从右向左找到2小于3停下,从左向右找到6大于3停下,交换2和6的位置,得到。接着继续这个过程,直到左右指针相遇,把3放到正确的位置上。然后对左边的子序列和右边的子序列分别进行快速排序,直到整个列表有序。
快速排序时间复杂度分析
快速排序的时间复杂度与划分是否平衡密切相关。最优情况下,时间复杂度为 O(nlogn)。这是因为每次划分都能将序列均匀地分成两部分,此时快速排序的性能与归并排序一样。
例如,对于一个包含 n 个数的序列,递归的第一层,将 n 个数划分为 2 个子区间,每个子区间的数字个数约为 n/2;递归的第二层,将 n 个数划分为 4 个子区间,每个子区间的数字个数约为 n/4;以此类推,递归的第 logn 层,将 n 个数划分为 n 个子区间,每个子区间的数字个数为 1。在每一层的划分过程中,时间复杂度约为 O(n),而总共需要划分 logn 层,所以最优情况下的时间复杂度为 O(nlogn)。
然而,在最坏情况下,时间复杂度为 O(n^2)。当每次选择的基准元素是最大或最小元素时,快速排序的时间复杂度是 O(n^2)。例如,序列已经是有序的,每次选择第一个元素作为基准数,那么每次划分只能分出一个元素,导致递归的深度达到 n,而每一层的时间复杂度为 O(n),所以最坏情况下的时间复杂度为 O(n^2)。
平均情况下,快速排序的时间复杂度也是 O(nlogn)。在实际应用中,快速排序通常表现良好,尤其对于大规模数据集的排序。
快速排序避免最坏情况方法
为了避免快速排序的最坏情况,可以采用以下几种方法:
- 随机选取划分点:每次随机从待排序序列中选取一个元素作为划分点,从而降低最坏情况的概率。这样可以避免每次都选择到最大或最小元素作为基准数,使得划分更加平衡。
- 三数取中法:选取待排序序列的第一个、中间一个、最后一个元素中的中间值作为划分点,从而降低最坏情况的概率。例如对于序列,可以选择1、6、3中的中间值3作为基准数,这样可以在一定程度上避免选择到最大或最小元素。
- 在递归深度较浅时采用其他排序算法,比如插入排序或归并排序等,从而避免快速排序退化成 O(n^2) 的情况。当问题规模小于某一 k 值时,采用插入排序,提高算法效率。
总结
快速排序算法是一种高效的排序算法,其基本思路是分治法,通过不断地划分和递归,将序列分成越来越小的子序列进行排序,直到所有子序列都有序。在实际应用中,可以根据不同的情况选择合适的方法来避免最坏情况的发生,以提高算法的性能。