一、选择排序
1、概念和思想
简单选择排序(Simple Selection Sort)就是通过 n - i 次关键字间的比较,从n - i + 1个记录中选出关键字最小的记录,并和第 i (1 <= i <= n)个记录交换之。
2、排序过程分析
排序过程简单来说,就是每次选择第i个元素做为关键字,然后从 i + 1 开始依次遍历后面的元素,如果比 i 对应的元素小,就进行交换。
3、代码实现
void SelectSort(int *arr,int len)
{
int tmp = 0;
int j = 0;
int i = 0;
for(i;i<len;i++)
{
for(j = i+1;j<len;j++)
{
if(arr[j] < arr[i])
{
tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
}
}
}
}
时间复杂度最好最坏情况下都是 O(n),空间复杂度是O(1),有跳跃式交换,所以不是稳定排序。
二、快速排序
1、概念和思想
快速排序( Quick Sort )的基本思想是:先通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录进行排序,以达到整个序列有序的目的。
2、排序过程分析
第一次选择9作为参考的标准,在一趟快排之后,9的左边都是比它小的,而9的右边都是比它大的。接下来只要不断的缩小区间,使得每个区间有序。最终得到整个序列有序。
其实这个不断划分区间有点像二分查找,先找到中间位置,在不断的缩小区间查找。快排也是因为这一特点,所以排序速度是相当的牛逼。也算配得上自己的名称了,名副其实哈哈哈。
3、快排递归实现
static int Partition(int *arr, int low,int high)//时:O(n) 空:O(1)
{
int tmp = arr[low];
while (low < high)
{
while ((low < high) && arr[high] >= tmp)
{
--high;
}
if (low == high)
break;
else
arr[low] = arr[high];
while ((low < high) && arr[low] <= tmp)
{
++low;
}
if (low == high)
break;
else
arr[high] = arr[low];
}
arr[low] = tmp;
return low;
}
static void Quick(int *arr,int low,int high)//时:2^x=n ==> log2n 空:O(log2n)
{
int par = Partition(arr, low, high);
if (low + 1 < par)
{
Quick(arr, low, par - 1);
}
if (par < high - 1)
{
Quick(arr, par + 1, high);
}
}
//快排有序时时间复杂度为O(n^2),退化成选择排序
void QuickSort(int *arr, int len)//总的时:O(n*log2n) 空:O(log2n) 不稳定
{
Quick(arr, 0, len - 1);
}
4、快排非递归实现
static int Partition(int *arr, int low,int high)//时:O(n) 空:O(1)
{
int tmp = arr[low];
while (low < high)
{
while ((low < high) && arr[high] >= tmp)
{
--high;
}
if (low == high)
break;
else
arr[low] = arr[high];
while ((low < high) && arr[low] <= tmp)
{
++low;
}
if (low == high)
break;
else
arr[high] = arr[low];
}
arr[low] = tmp;
return low;
}
void QuickSort2(int *arr,int len)
{
stack<int> s;
int low = 0;
int high = len - 1;
int par = Partition(arr, low, high);
if (par > low + 1)
{
s.push(low);
s.push(high);
}
if (par < high - 1)
{
s.push(par + 1);
s.push(high);
}
while (!s.empty())
{
high = s.top();
s.pop();
low = s.top();
s.pop();
par = Partition(arr, low, high);
if (par > low + 1)
{
s.push(low);
s.push(high);
}
if (par < high - 1)
{
s.push(par + 1);
s.push(high);
}
}
}
非递归实现时,最重要的问题就是怎么保存第一次找到基准之后所划分的区间的下标,和不断更新的区间下标。
栈就是一种很好的数据结构,可以将最初的两个区间都保存下来。确定区间时,出栈获取要排序的下标,然后在更新下标。
可以使得一个区间完全有序之后,再来处理另外一个区间。最终使得整个序列完全有序。
三、归并排序
1、概念和思想
归并排序 ( Merging Sort)就是利用归并的思想实现的排序方法。它的原理是假设初始序列有 n 个记录,则可以看成是 n 个有序的子序列,每个子序列的长度为1,然后两两归并,得到 [ n/2 ] ([ x ] 表示不小于 x 的最小整数)个长度为 2 或 1 的有序子序列;然后在两两归并,……,如此重复,最终得到长度为 n 的有序序列为止,这种排序方法被称为 2 路归并排序。
2、排序过程分析
排序过程可以描述为,从每组 1 个元素开始,然后相邻两组进行有序合并。每一趟归并排序中的组内元素个数以 2 的次方增长,然后在进行相邻组的有序合并。
3、归并排序非递归实现
static void Merge(int *arr,int len,int gap)//O(n)
{
int *brr = (int*)malloc(len * sizeof(int));
if (brr == nullptr)
return;
int i = 0;//brr下标
int low1 = 0;//第一个归并段的起始下标
int high1 = low1 + gap - 1;//第一个归并段的结束下标
int low2 = high1 + 1;第二个归并段的起始下标
int high2 = (low2 + gap - 1) <= len - 1 ? (low2 + gap - 1) : len - 1;//第二个归并段的结束下标
//有两个归并段
while (low2 < len)
{
//两个归并段都有数据
while (low1 <= high1 && low2 <= high2)
{
if (arr[low1] <= arr[low2])
brr[i++] = arr[low1++];
else
brr[i++] = arr[low2++];
}
//一个归并段没有数据,另一个还有
while (low1 <= high1)
brr[i++] = arr[low1++];
while (low2 <= high2)
brr[i++] = arr[low2++];
low1 = high2 + 1;
high1 = low1 + gap - 1;
low2 = high1 + 1;
high2 = (low2 + gap - 1) <= len - 1 ? (low2 + gap - 1) : len - 1;
}
//不足两个归并段
while (low1 < len)
{
brr[i++] = arr[low1++];
}
for (i = 0; i < len; ++i)
{
arr[i] = brr[i];
}
free(brr);
}
void MergeSort(int *arr,int len)
{
for (int i = 1; i < len; i *= 2)
{
Merge(arr, len, i);
}
}
MergeSort函数的时间复杂度是O(log2n),每一次归并Merge的时间复杂度是 O( n ),所以总的时间复杂度就是 O(log2n*n)。
归并实现借助了辅助空间,所以空间复杂度是 O (n),是稳定排序。