目录
前言
冒泡排序、快速排序的三种递归方法及非递归方法的原理特性代码解析。
一、交换排序
1.1 基本思想
所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
1.2 冒泡排序
冒泡排序就是从左到右两两依次比较,左边大于右边则交换,每趟右边值为最大。
1.3 冒泡排序代码实现
void BubbleSort(int* a, int n)
{
for (int i = 0; i < n; i++)
{
int flag = 0;
for (int j = 1; j < n-i; j++)
{
if (a[j - 1] > a[j])
{
Swap(&a[j - 1], &a[j]);
flag = 1;
}
}
if (flag == 0)
{
break;
}
}
}
1.4 冒泡排序特性
1. 时间复杂度:O(N^2)
2. 空间复杂度:O(1)
3. 稳定性:稳定
二、快速排序
2.1 基本思想
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
2.2 hoare版本
根据动图了解到:快速排序开始设定第一个值为key值,设定末尾(right)的值向前走找到比key小的值停下,设定最开始(begin)的值找到比key大的值停下,然后交换,交换后begin值与end值继续上述规则走,直到相遇时,相遇值与key值交换,后续的right值应为前一轮相遇的值-1.此时保证key值(相遇的地方)的左侧全部比key值小,右侧全部比key值大。由此一来再进行左侧的值进行快速排序,右侧值也进行快速排序,随后全部排好顺序。
2.3 hoare版本代码实现
int PartSort1(int* a, int left, int right)
{
int key = left;
while (left < right)
{
//找小
while (left < right && a[right] >= a[key])
{
--right;
}
//找大
while (left < right && a[left] <= a[key])
{
++left;
}
Swap(&a[left], &a[right]);
}
Swap(&a[key], &a[left]);
return left;
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int key = PartSort1(a, left, right);
QuickSort(a, left, key - 1);
QuickSort(a, key + 1, right);
}
根据原理,hoare版本代码与二叉树的规则很像,比key小的分成左子树进行排序,比key大的分成右子树进行排序,所以我们可以用递归写出。
以 9,1,2,5,7,4,8,6,3,5一串数字进行解析:
第一轮:从右往左找到比key值小的数,此时key为9,right值为5比key小不动,left向右走,找不到比key大的值,此时left为5,key值与left值进行交换,此时key为9,运行结束后值为5,1,2,5,7,4,8,6,3,9
第二轮:找到比key值小的数,此时key为5,right值为3,left向右走后left值为7,left与right进行交换,left与right继续走,走到4时比key值小停下,走到4时相遇,key值与left交换,此时key为5,运行结束后值为4,1,2,5,3,5,8,6,7,9
第三轮:找到比key值小的数,此时key为4,right为3,left向右走后left值为5,left与right交换,right向左走时left与right值在5相遇,left值5与key值4交换,运行结束后值为3,1,2,4,5,5,8,6,7,9
第四轮:找到比key值小的数,此时key为3,right为2,2与3交换,运行结束后值为2,1,3,4,5,5,8,6,7,9
第五轮:找到比key值小的数,此时key为2,right为1,1与2交换,运行结束后值为1,2,3,4,5,5,8,6,7,9
第六轮:进行到此时,left值已于right值相等皆为1,进行右子树8,6,7,9的排序,此时left值为8,right值为7,key值为8,6比8小交换后值为1,2,3,4,5,5,6,8,7,9
第七轮第八轮不再赘述。
2.4 挖坑法
挖坑法是Hoare版本的优化版,只需与坑位交换即可,思想上更易理解。
2.5 挖坑法代码实现
int PartSort2(int* a, int left, int right)
{
int key = a[left];
int hole = left;
while (left < right)
{
//找小
while (left < right && a[right] >= key)
{
--right;
}
a[hole] = a[right];
hole = right;
while (left < right && a[left] <= key)
{
++left;
}
a[hole] = a[left];
hole = left;
}
a[hole] = key;
return hole;
}
2.6 前后指针法
前后指针法思想:cur向后走,cur<key且prev+1!=cur时cur与prev交换,当cur走到最后越界时,key与prev交换,key值变为prev结束循环,此时保证key左侧比key小,右侧比key大。
2.7 前后指针法代码实现
int PartSort3(int* a, int left, int right)
{
int key = left;
int prev = left, cur = prev + 1;
while (cur <= right)
{
if (a[cur] < a[key] && cur != ++prev)
Swap(&a[cur], &a[prev]);
++cur;
}
Swap(&a[prev], &a[key]);
return prev;
}
2.8 快排优化:三数取中
快排到这里可以发现,运行效率其实是由key的位置影响的。当数组接近有序时,即key为最大或最小时效率较低为O(N^2),如果我们取key在中间是不是可以像二叉树那样提高效率到O(N*logN)呢?
//三数取中
int GetMid(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 left;
else return right;
}
else
{
if (a[mid] > a[right])
return mid;
else if (a[left] < a[right])
return left;
else return right;
}
}
寻找中间数为key即可使快排没有最快情况,执行效率为O(N*logN)。
2.9 非递归快速排序算法
由于栈的特性是先进后出,所以我们可以用栈来模拟实现递归。
void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, right);
StackPush(&st, left);
while (!StackEmpty)
{
int left = StackTop(&st);
StackPop(&st);
int right = StackTop(&st);
StackPop(&st);
int key = PartSort2(a, left, right);
if (key + 1 < right)
{
StackPush(&st, right);
StackPush(&st, key + 1);
}
if (key - 1 > left)
{
StackPush(&st, key-1);
StackPush(&st, left);
}
}
StackDestroy(&st);
}
我们首先将尾值入栈,后入左值,由于是先进后出,这样出栈时才能保证先取左值后取右值。
随后我们进行一次挖坑法单趟排序,保证此时key值左侧比key小,右侧比key大。
排好后被分为左区间与右区间,先入右区间后入左区间保证先排左后排右。
非递归快排和递归思想几乎一样,但是用到了栈避免了栈溢出。