目录
上一篇我们讲了各大排序之间的差距,以及他们实现的思路和代码,本期我们将详细的研究一下快速排序和归并排序的奥秘
一、快速排序优化
1.1 选取关键字
上一期我们直到,快速排序的主要逻辑是先将一个关键字排到合适的位置上,在通过递归依次排后面的数字
但是如果这串数字本来就接近于有序了或者他本来就是有序的,我们选的关键字都是最左边的那个,那么快排的优势有没有了,他甚至没有插入排序快,因此我们可以在选择关键字的时候先比较一下最左边、最右边和中间的,选出他们中间的那个数值作为关键字,就能让快速排序的效率条一些
int FindMid(int a, int b, int c)
{
if (a > b)
{
if (b > c)
return b;
else
if (a > c)
return c;
else
return a;
}
else //a < b
{
if (a > c)
return a;
else
if (b > c)
return c;
else
return b;
}
}
int PartSort1(int* a, int left, int right)
{
int mid = FindMid(a[left], a[right], a[(left + right) / 2]);
if (mid != left)
{
swap(&a[mid], &a[left]);
}
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;
}
1.2 小区间优化
在快排递归的时候,当他的区间只有五个的时候,可能还需要递归调用十次才能排序好有点大材小用了,因此我们也可以将快排区间划小之后再用插入排序搞定,这样可以减少很多递归的次数,效率也能提升很多
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int begin = left, end = right;
//小区间优化
if (end - begin + 1 <= 10)
{
InsertSort(a + begin, begin - end + 1);
return;
}
//int mid = PartSort1(a, begin, end);
//int mid = PartSort2(a, begin, end);
int mid = PartSort1(a, begin, end);
QuickSort(a, begin, mid - 1);
QuickSort(a, mid + 1, end);
}
1.3 快速排序非递归实现
在有些情况下,我们也需要用到快速排序的非递归实现,这里我们就需要一个栈来模拟递归的情况,每出一个大区间,就入他的两个小区间,直到区间不存在
void QuickSortNonR(int* a, int left, int right)
{
int begin = left, end = right;
ST st;
InitStack(&st);
STPush(&st, end);
STPush(&st, begin);
while (!STEmpty(&st))
{
begin = STTop(&st);
STPop(&st);
end = STTop(&st);
STPop(&st);
if (begin < end)
{
int key = PartSort3(a, begin, end);
STPush(&st, end);
STPush(&st, key + 1);
STPush(&st, key - 1);
STPush(&st, begin);
}
}
DestoryStack(&st);
}
二、归并排序
2.1 归并排序的思想
归并排序和快速排序类似,都是需要把大区间分为小区间排序,不同的是快速排序是先将数字排好再去划分小区间(先序遍历),而归并排序是需要先递归到最小区间,再依次归并上来(后序遍历)
2.2 归并排序代码实现
void _MergeSort(int* a, int left, int right, int* tmp)
{
if (left >= right)
return;
int mid = (left + right) / 2;
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid + 1, right, tmp);
int begin1 = left, begin2 = mid + 1;
int end1 = mid, end2 = right;
int i = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
tmp[i++] = a[begin1++];
else
tmp[i++] = a[begin2++];
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(n * sizeof(int));
if (tmp == NULL)
{
perror("MergeSort::malloc");
return;
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
2.3 归并排序非递归实现
上面代码是用递归实现的,就有可能存在栈溢出的情况,因此我们还需要想想如何用非递归来完成它,递归是先把大区间全部化为小区间,在依次归并上去,所以我们可以直接从小区间开始归并上去直到归并到最大的区间
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(n * sizeof(int));
if (tmp == NULL)
{
perror("MergeSort::malloc");
return;
}
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2*gap)
{
int begin1 = i, end1 = begin1 + gap - 1;
int begin2 = begin1 + gap, end2 = begin1 + 2 * gap - 1;
int j = i;
if (end1 >= n || begin2 >= n)
{
break;
}
if (end2 >= n)
{
end2 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[j++] = a[begin1++];
}
else
{
tmp[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
memcpy(a + i, tmp + i, sizeof(int)*(end2 - i + 1));
}
gap *= 2;
}
free(tmp);
}
至此,数据结构的所有内容已全部更完啦!(鼓掌)(欢呼)(尖叫)
从下一期我们将开始c++部分和Linux部分的学习~
感谢各位的陪伴,记得留个三连哟