交换排序
利用交换元素的位置进行排序的方法称作交换排序。
常见的交换排序的方法:冒泡排序和快速排序。
冒泡排序
基本思想
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素就是是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
-持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
代码如下
void BubbleSort(int array[], int size)
{
for (int i = 0; i < size; ++i)
{
//j会取到j+1,因此应该小于size-i-1
for (int j = 0; j < size - i - 1; ++j)
{
if (array[j + 1] < array[j])
{
Swop(&array[j + 1], &array[j]);
}
}
}
}
总结
- 冒泡排序最好情况时间复杂度O(n),冒泡排序最坏情况下时间复杂度O(n^2)
- 冒泡排序空间复杂度O(1)
- 冒泡排序是一种稳定的排序算法
快速排序
基本思想
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
代码实现
交换函数
void Swop(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
a. 递归方式
void _QuickSort(int array[], int begin, int end)
{
if (end - begin <= 1)
return;
//取最后一个元素为基准值
int mid = Partion_3(array, begin, end);
//排序左子区间
_QuickSort(array, begin, mid);
//排序右子区间
//mid+1是因为我们用的是左闭右开的区间
_QuickSort(array, mid + 1, end);
}
void QuickSort(int array[], int size)
{
_QuickSort(array, 0, size);
}
b. 非递归
void QuickSortNor(int array[], int size)
{
if (size < 1)
return;
Stack s;
StackInit(&s);
//Partion函数用的都是左闭右开区间
int begin = 0;
int end = size;
StackPush(&s, end);
StackPush(&s, begin);
while (!StackEmpty(&s))
{
begin = StackTop(&s);
StackPop(&s);
end = StackTop(&s);
StackPop(&s);
int mid = Partion(array, begin, end);
//先入栈左区间
StackPush(&s, begin);
StackPush(&s, mid);
//入栈右区间
StackPush(&s, mid + 1);
StackPush(&s, end);
}
}
Partion函数的三种实现方式
- 交换法
int Partion(int array[], int begin, int end)
{
//左闭右开
int left = begin;
int right = end - 1;
int key = array[end-1];
//因为是左闭右开的区间,所以没有等号
while (left < right)
{
//从左边开始找,找到一个大于基准值的值
while (left < right && array[left] <= key)
{
++left;
}
//从右边开始找,找到一个小于基准值的值
while (left < right && array[right] >= key)
{
--right;
}
//找到后,交换
if (left < right)
{
Swop(&array[left], &array[right]);
}
}
//当循环终止后,交换下标为left和end-1的元素
//这样基准值的左边都小于基准值,右边都大于基准值
Swop(&array[left], &array[end - 1]);
return left;
}
- 挖坑法
int Partion_2(int array[], int begin, int end)
{
//左闭右开区间
int left = begin;
int right = end - 1;
//确定基准值,并且左右边的元素已经被key保存,
//此时出现了一个坑
int key = array[right];
while (left < right)
{
while (left < right && array[left] <= key)
{
++left;
}
if (left < right)
{
//填右边坑,并且右边出现新的坑
array[right--] = array[left];
}
while (left < right && array[right] >= key)
{
--right;
}
if (left < right)
{
//填左边的坑,左边出现新的坑
array[left++] = array[right];
}
}
//最后只剩一个坑,left位置的坑。
//将key填入
array[left] = key;
return left;
}
- 前后指针法
int Partion_3(int array[], int begin, int end)
{
int pre = begin - 1;
int cur = begin;
int key = array[end - 1];
while (cur < end)
{
if (array[cur] < key)
{
//当pre和cur相等时,说明前边元素都小于基准值
pre++;
//如果pre不等于cur,说明下标为pre的值大于基准值
//cur继续往后走,当cur找到一个小于基准值的值时,
//交换下表为pre,cur的值,
if (pre != cur)
Swop(&array[cur], &array[pre]);
}
cur++;
}
//这时cur已经走到end,如果pre不等于cur,pre指向的是小于基准值的值
//说明下标大于pre的的值都大于基准值,
//++pre交换,将基准值放在这个位置
if (++pre != end)
Swop(&array[pre], &array[end - 1]);
return pre;
}
快速排序的优化
- 三只取中确定基准值
与一般的快速排序方法不同,它并不是选择待排数组的第一个数作为中轴,而是选用待排数组最左边、最右边和最中间的三个元素的中间值作为中轴。这一改进对于原来的快速排序算法来说,主要有两点优势:
(1) 首先,它使得最坏情况发生的几率减小了。
(2) 其次,未改进的快速排序算法为了防止比较时数组越界,在最后要设置一个哨点。 - 当区间比较小的时候,可以使用插入排序,直接对这个区间进行排序,从而减小递归次数。
当递归深度达到一定的程度时,使用堆排序对待排序区间进行排序。
这里未进行优化,读者可自己进行实践
总结
- 快速排序的时间复杂度为O(N^2),最好为O(NlgN),最差为O(N^2)。
- 快速排序的空间复杂度为O(lgN)
- 当待排序的关键字是随机分布时,快速排序的平均时间最短。