1. 冒泡排序
冒泡排序之前已经熟悉其过程,不在画图,直接上代码
void Bubble(DataType* a,size_t n)
{
assert(a != NULL && n > 0);
for(int end = n;end > 0;--end)
{
int flag = 0;
for(int i = 1;i < end;++i)
{
if(a[i-1] > a[i])
{
Swap(&a[i-1],&a[i]);
flag=1;
}
}
if(flag == 0)
{
break;
}
}
}
2.快速排序
三种方法
1.左右指针法
(1)从数组当中选取一个关键元素key;
(2)begin在数组最左边,从数组的左边走,直到找到比key大的数
(3)end在数组最右边,从右向左走,直到找到比key小的数
(4)交换begin和end所在位置的值
(5)当begin和end走到相同位置时,交换关键元素key和begin位置的元素
(6)此时一趟排序完成,以begin为分界线,划分为左区间和右区间,在进行左区间和右区间的排序
(7)当左区间右区间任意一个不存在或者只剩一个元素时,排序即可完成
代码:
//左右指针法
int PartSort1(DataType* a1, int left, int right)
{
assert(a);
DataType key = a[right];
int begin = left, end = right;
while (begin < end)
{
//选出比key大的数据
while (begin < end && a[begin] <= key)
++begin;
//选出比key小的数
while (begin < end && a[end] >= key)
--end;
//交换begin和end两个位置的值
Swap(&a[begin], &a[end]);
}
//注意这里是换a[right]和a[begin]的值
Swap(&a[begin], &a[right]);
return begin;
}
//快速排序
void QuickSort(DataType* a, int left, int right)
{
assert(a);
if (right - left > 10)
{
InsertSort(a, right - left + 1);
}
else
{
//划分区间返回条件,当左区间或右区间不存在,或只有一个数时,开始返回
if (left >= right)
return;
int div = PartSort1(a, left, right);
//去左区间排序
QuickSort(a, left, div - 1);
//去右区间排序
QuickSort(a, div + 1, right);
}
}
2.挖坑法
(1)从数组当中选取一个关键元素key,以key的位置作为第一个坑index
(2)begin在数组最左边,从数组的左边走,直到找到比key大的数,将begin位置的元素填入坑,a[index]=a[begin],此时begin作为新的坑,index=begin
(3)end在数组最右边,从右向左走,直到找到比key小的数a[end],将end的元素给坑index,a[index]=a[end],此时end作为新的坑
(4)当begin和end走到相同位置,此时坑就在begin位置,将key填入坑
(5)此时一趟排序完成,以begin为分界线,划分为左区间和右区间,在进行左区间和右区间的排序
(6)当左区间右区间任意一个不存在或者只剩一个元素时,排序即可完成
代码:
//填坑法
int PartSort2(DataType* a1, int left, int right)
{
assert(a);
DataType key = a[right];
int begin = left, end = right;
while (begin < end)
{
//坑在end,找到比key大的值,将数据填入坑
while (begin < end && a[begin] <= key)
++begin;
a[end] = a[begin];
//此时坑在begin处,找到比key小的值,将数据填入坑
while (begin < end && a[end] >= key)
--end;
a[begin] = a[end];
}
//当begin和end在同一位置处,将key填在坑处
a[begin] = key;
return begin;
}
//快速排序
void QuickSort(DataType* a, int left, int right)
{
assert(a);
if (right - left > 10)
{
InsertSort(a, right - left + 1);
}
else
{
//划分区间返回条件,当左区间或右区间不存在,或只有一个数时,开始返回
if (left >= right)
return;
int div = PartSort2(a, left, right);
//去左区间排序
QuickSort(a, left, div - 1);
//去右区间排序
QuickSort(a, div + 1, right);
}
}
3.前后指针法
(1)选取一个关键元素可以,并定义两个指针,int cur=left;int prev=cur-1;
(2)a[cur] >= key,cur++
(3)a[cur] < key,cur停下来,prev++,如果prev != cur,则交换两位置的数值;
(4)当cur走到数组的最后,表示一趟已经结束,将cur的值和prev的后一位置进行交换
(5)以prev为界限,划分为左区间和右区间
(6)当左区间或右区间不存在,或只有一个数时,排序即完成
代码
//前后指针
int PartSort3(DataType* a1, int left, int right)
{
assert(a);
DataType key = a[right];
int cur = left;
int prev = left - 1;
while (cur < right)
{
//当a[cur]比key小,prev走,并且当prev不在同一位置上时,进行交换
if ( a[cur] < key)
{
++prev;
if (cur!=prev)
Swap(&a[cur], &a[prev]);
}
//不管比key小还是大,cur继续向后走
++cur;
}
//当cur走到数组的最后,表示一趟已经结束,将cur的值和prev的后一位置进行交换
Swap(&a[++prev], &a[right]);
return prev;//以prev为界限,划分为左区间和右区间
}
//快速排序
void QuickSort(DataType* a1, int left, int right)
{
assert(a);
if (right - left > 10)
{
InsertSort(a, right - left + 1);
}
else
{
//划分区间返回条件,当左区间或右区间不存在,或只有一个数时,开始返回
if (left >= right)
return;
int div = PartSort2(a, left, right);
//去左区间排序
QuickSort(a, left, div - 1);
//去右区间排序
QuickSort(a, div + 1, right);
}
}
快速排序的优化
1. 小区间优化
当区间中元素数量较小时在用递归反而会使效率降低,当区间元素数据较小,就越接近排序,此时,我们采用直接插入排序进一步提高效率
2. 随机值法
在选key时,在数组中随机选取一个数作为key进行排序;
代码:
sand(time(0));
int index = rand()%(right - left + 1);//随机产生下标
int key = a[index];
注意:这种方法并不实用,随机产生的key是大还是小并不确定,key选取的不合适反而会降低效率
3. 三数取中法
选取数组的首元素a[0]、尾元素a[n-1]以及中间元素a[(0+n-1)/2]选出三者的中位数作为key值。这时候选出来的key可以保证绝对不是最大数或最小数(如果三个数都是相同的,那key还是最大或最小值,不过出现这种情况概率及其低)
int Getkey(DataType* a, int left, int right)
{
assert(a);
DataType mid = left + ((right - left) >> 1);
// mid恰好为key
//情况1:left <mid ,mid <right
//情况2:left>mid,mid > right
if ((a[left] < a[mid] && a[mid] < a[right])
|| (a[right] < a[mid] && a[mid] < a[left]))
{
return mid;
}
//left为key
//情况1:left > right,left < mid
//情况2:left >mid, left < right
else if ((a[right] < a[left] && a[left] < a[mid])
|| (a[mid] < a[left] && a[left] < a[right]))
{
return left;
}
else
{
//right为key
//情况1:right > left ,right < mid
//情况2:right < left,right >mid
return right;
}
}
快速排序的非递归
递归是依赖于函数栈帧,那么我们可以用我们的栈去模拟实现函数的递归调用
void QuickSortNonR(DataType* a, int left, int right)
{
assert(a);
Stack s;
StackInit(&s);
//先将左右区间进行入栈
StackPush(&s, left);
StackPush(&s, right);
while (left < right && StackEmpty(&s) != 0)
{
//从栈中取出区间坐标
int end = StackTop(&s);
StackPop(&s);
int begin = StackTop(&s);
StackPop(&s);
//计算下一次区间的临界值
int div = PartSort1(a, begin, end);
//如果区间内有两个数以上,进行入栈
if (begin < div-1 )
{
StackPush(&s, begin);
StackPush(&s, div-1 );
}
if (div+1 < end)
{
StackPush(&s, div+1 );
StackPush(&s, end);
}
}
}
两种算法分析
稳定性
- 冒泡排序:稳定算法,在单趟排序和多趟排序的过程中,元素的相对位置都不会发生变化
- 快速排序:不稳定算法,三种方法实现过程中都存在元素交换,相对位置会发生改变
时间复杂度
- 冒泡排序:O( n2 n 2 )
- 快速排序:经优化过后为O(nlgn),最坏的情况为O( n2 n 2 )
空间复杂度
- 冒泡排序:O(1)
- 快速排序:O(lgn)