【排序篇】实现快速排序的三种方法

🌈个人主页:Yui_

🌈Linux专栏:Linux

🌈C语言笔记专栏:C语言笔记

🌈数据结构专栏:数据结构

文章目录

  • [1 交换排序](#1 交换排序)
    • [1.1 冒泡排序](#1.1 冒泡排序)
    • [1.2 快速排序](#1.2 快速排序)
      • [1.2.1 hoare版本](#1.2.1 hoare版本)
      • [1.2.2 挖坑法](#1.2.2 挖坑法)
      • [1.2.3 前后指针版本](#1.2.3 前后指针版本)

1 交换排序

基本思想:所谓交换,就是根据序列中的两个记录键位的比较结果来交换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

1.1 冒泡排序

冒泡排序的特点就是,从前到后两两比较找到最大的数放在数组的末尾。因为已经找到数组最大元素并放置在末尾,也就是说最大元素已经放置在最终的位置,我们接下来就是把末尾提前来来一次找到数组中次大的元素,以此类推将数组彻底排序。

c 复制代码
#include <stdio.h>
void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void bubblesort(int* a, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		int flag = 1;
		for (int j = 0; j < n - i - 1; ++j)
		{
			if (a[j] > a[j + 1])
			{
				swap(&a[j], &a[j + 1]);
				flag = 0;
			}
		}
		if (flag)
			break;
	}
}
int main()
{
	int arr[10] = { 0,9,8,7,6,5,4,3,2,1 };
	//slectsort(arr, 10);
	bubblesort(arr, 10);
	for (int i = 0; i < 10; ++i)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
//打印结果
/*
0 1 2 3 4 5 6 7 8 9
*/

冒泡排序总结:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

1.2 快速排序

快速排序是Hoare在1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序的元素序列中的某元素作为基准,按照该排序码将待排序序集分割成两子序列,左子序列中所有元素均小于其基准值,右子序列中的所有元素均大于基准值,然后左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

下面会给出快速排序递归实现的主框架,发现于二叉树前序遍历的逻辑非常像,大家在写递归框架时可以想想二叉树前序遍历的过程快速写成来。后续只需要分析如何对区间中的数据进行划分就可以了。

c 复制代码
//假设按照升序对arr数组中的[left,right]区间中的元素进行排序
void quicksort(int* a, int left,int right)
{
	if (left >= right)
		return;
	//按照基准值对arr数组的[left,right]区间中的元素进行划分
	int mid = PartSort1(a, left, right);
	//划分成功后以mid为边界形成左右两部分[left,mid-1][mid+1,right]
	//递归排[left,mid-1]
	quicksort(a, left, mid - 1);
	//递归排[mid+1,right]
	quicksort(a, mid + 1, right);
}

将区间按照基准值划分为左右部分的常见方式:

1.2.1 hoare版本

通过动图我们可以发现,hoare版本的做法就是先确立一个基准值key的坐标,然后定义两个指针一左一右,左指针找大于a[key]的,右指针找小于a[key]的,找到后交换左右的数据,一直进行到左右指针相遇,相遇后就把a[key]和左右指针相遇的位置的数据交换。如此一来,就做到了把a[key]放到数组的最终位置,因为此时a[key]的左边都是小于它的数,右边都是大于它的数,不就是a[key]的最终位置嘛。
提问 :为什么最终左右指针相遇时的数据一定小于a[key]?
回答:这就关系到左右指针谁先走的问题。当我排升序的时候一定要让右指针先走,右指针是找小嘛。在它们相遇前会存在两种情况:

  1. 左指针去与右指针相遇,因为右指针是先走的,同时右指针又是找小于a[key]的值,所以如果是左指针去与右指针相遇,此时的右指针移动指向了一个小于a[key]的值。
  2. 右指针去与左指针相遇,因为右指针先走,左指针就会有两种情况:还没走,指向a[key],走过了,走过了然后于右指针交换数据,导致指向小于a[key]的数据。当右指针与左指针相遇时的数据还是小于a[key]的数据
    如此一来就说明了最终左右指针相遇时的数据一定小于a[key]
c 复制代码
//hoare版本
int PartSort1(int* a, int left, int right)
{
	int key = left;
	while (left < right)
	{
		while (right > left && a[right] >= a[key])
			right -= 1;
		while (right > left && a[left] <= a[key])
			left += 1;
		swap(&a[left], &a[right]);
	}
	swap(&a[left], &a[key]);
	return left;
}

优化

这种情况有个致命的缺陷,当数组接近有序时效率就会退化为O(N^2)。如图所示:

为了解决这个问题,我们在选择基准值的时候可以不选择数组的第一个数字,而是选择数组中大小适中的元素。我们把这个方法叫做三数取中法。
修改后的版本

c 复制代码
//三数取中
int GetMidi(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] > a[mid])
	{
		if (a[mid] > a[right])
			return mid;
		else
		{
			if (a[left] > a[right])
				return right;
			else
				return left;
		}
	}
	else//left<=mid
	{
		if (a[mid] < a[right])
			return mid;
		else//mid>right
		{
			if (a[left] > a[right])//mid>left>right
				return left;
			else
				return right;
		}
	}
}


//hoare版本
int PartSort1(int* a, int left, int right)
{
	int mid = GetMidi(a, left, right);
	swap(&a[mid], &a[left]);
	int key = left;
	while (left < right)
	{
		while (right > left && a[right] >= a[key])
			right -= 1;
		while (right > left && a[left] <= a[key])
			left += 1;
		swap(&a[left], &a[right]);
	}
	swap(&a[left], &a[key]);
	return left;
}

1.2.2 挖坑法

解释:先利用key保存基准值,初始让left为坑位,然后开始左右指针的遍历,让右指针先走去找小于key的值,找到后将值填入坑位,然后更新坑位为right,同理左指针是找大,找到大于key的值后,将值填入坑位,然后再更新坑位位left,直到相遇。循环结束后将基准值填入左右指针相遇位置。

c 复制代码
//挖坑法
int PartSort2(int* a, int left, int right)
{
	int mid = GetMidi(a, left, right);
	swap(&a[mid], &a[left]);
	int hole = left;
	int key = a[left];
	while (left < right)
	{
		while (left < right && a[right] >= key)
			right -= 1;
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] <= key)
			left += 1;
		a[hole] = a[left];
		hole = left;
	}
	a[left] = key;
	return left;
}

1.2.3 前后指针版本

创建两个指针,一前一后,正常情况我们一定cur指针去遍历数组每当我们指向的数据小于a[key]时,就让prev+=1,然后交换a[prev]和a[cur]的值。

c 复制代码
//前后指针法
int PartSort3(int* a, int left, int right)
{
	int mid = GetMidi(a, left, right);
	swap(&a[left], &a[mid]);
	int key = left;
	int prev = left;
	int cur = prev + 1;
	while (cur<=right)
	{
		if (a[cur] < a[key])
		{
			prev += 1;
			swap(&a[prev], &a[cur]);
		}
		cur += 1;
	}
	swap(&a[prev], &a[key]);
	return prev;
}
相关推荐
sp_fyf_202426 分钟前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01
人工智能·深度学习·神经网络·算法·机器学习·语言模型·数据挖掘
ChoSeitaku1 小时前
链表交集相关算法题|AB链表公共元素生成链表C|AB链表交集存放于A|连续子序列|相交链表求交点位置(C)
数据结构·考研·链表
偷心编程1 小时前
双向链表专题
数据结构
香菜大丸1 小时前
链表的归并排序
数据结构·算法·链表
jrrz08281 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
oliveira-time1 小时前
golang学习2
算法
黑叶白树1 小时前
简单的签到程序 python笔记
笔记·python
@小博的博客1 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
幸运超级加倍~2 小时前
软件设计师-上午题-15 计算机网络(5分)
笔记·计算机网络
南宫生2 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法