选择排序-直接选择排序
思想:每一次直接从待排序元素中选取最小的一个元素放在序列的起始位置,选取最大的一个元素放在末尾。
//直接选择排序
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while(begin<end)
{
int min = begin;//记录下标
int max = begin;
for (int i = begin+1; i <= end; i++)
{
if (a[i] < a[min])
{
min = i;
}
if (a[max] < a[i])
{
max = i;
}
}
swap1(&a[begin], &a[min]);//下标和begin互换
if (max == begin)// 如果最大值原来在begin位置,本来max=0,第一个交换过后,最大值被换到min处
{
max = min;//更新max的最大值下标
}
swap1(&a[end], &a[max]);//这样才能找到新的最大值位置
begin++;
end--;
}
}
要注意第一次交换完,如果begin下标处正好对应着最大值,此时begin与最小值交换,最大值的下标其实是在min处,但如果没有及时更新,会误以为最大值的下标在begin,可是begin已经与min互换,begin处记录已经不是最大值,如果继续交换会出错。
解决办法是:如果max在begin处,begin被交换,到min处,及时更新max下标为min.
稳定性:
不稳定,6 6 1这一组,begin与min交换,1 6 1,相对顺序改变。
时间复杂度:
核心:比较次数+交换次数(可忽略,对复杂度级别无影响)
比较次数:每一轮的核心是找最大和最小的元素
每一轮能找到当前元素中的最小值与最大值
第1轮,n个元素,比较n-1次,找到最小的元素
n-1个元素,比较n-2次,找到最大的元素
第2轮:n-2个元素,比较n-3次,找到最小的元素
n-3个元素,比较n-4次,找到最大的元素
第3轮,n-5个元素
。。。
第x轮,2个元素,比较1次
总比较次数:(n-1)+(n-2)+(n-3)+...1=n(n-1)/2;
时间复杂度O(n^2)
选择排序-堆排序
之前的二叉树-堆的文章有提到过,这里简单说一下
升序建大堆,每次将堆顶元素移入到末尾,再进行调整。
降序建小堆,每次将堆顶元素移入末尾,再进行调整。
给定一个数组,如何建大堆?
向下调整算法效率高O(n*log n),那这里就采用向下调整算法建堆,从倒数第一个非叶子节点开始建大堆。
//向下调整算法
void AdjustDown(HDatatype* arr, int parent, int n)
{
int child = 2 * parent + 1;
//假设左孩子大,如果不是,则右孩子大
while(child<n)
{
if (child + 1 < n && arr[child] < arr[child + 1])
child = child + 1;
if (arr[parent] < arr[child])
{
swap1(&arr[parent], &arr[child]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
//堆排序
void heapSort(HDatatype* arr, int n)
{
//向下调整算法建立大堆
//先调整左右子树为大堆,先从倒数第一个非叶子节点开始调整
for (int i = ((n - 1) - 1) / 2;i>=0;i--)
{
AdjustDown(arr, i, n);
}
//先交换,再进行向下调整,此时左右子树都为大堆
int end = n - 1;
while(end>0)
{
swap1(&arr[0], &arr[end]);
end--;
AdjustDown(arr, 0, end);//对剩下的元素进行堆排序
}
}
时间复杂度:
向下调整的时间复杂度O(n) ,排序阶段的时间复杂度O(nlogn)
稳定性:
堆排序所有元素都相同,交换a[0]与末尾元素,相对顺序改变。
字典排序-归并
1.递归实现
分治思想,先将序列分解,使每个子序列有序,将有序的子序列合并成一个有序表。
将序列区间分解为最小,元素个数为1时,认为两个区间有序,再进行合并。
注意:每次合并完子区间时,要利用memcpy将temp拷贝给原数组,让原数组部分区间有序, 这样才能进行下一次大区间合并。
通过比较原数组的数据大小,利用temp暂时存储合并后的有序元素。再将temp赋给原数组。
void _MergeSort(int* a, int* temp, int begin, int end)
{
if (begin >= end)
{
return;
}
int mid = (begin + end) / 2;
_MergeSort(a, temp, begin, mid);
_MergeSort(a, temp, mid+1, end);
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin;
while(begin1<=end1 && begin2<=end2)
{
if (a[begin1] <= a[begin2])
{
temp[i++] = a[begin1++];
}
else
{
temp[i++] = a[begin2++];
}
}
//处理剩余数据,上层递归依赖下层递归的结果。合并[0,3]时,需要依赖[0,1]和[2,3]已排序的结果;
//如果不及时写回,原数组中的数据会保持无序状态,导致上层合并错误。
while (begin1 <= end1)
{
temp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
temp[i++] = a[begin2++];
}
//写回原数组
memcpy(a+begin, temp+begin, sizeof(int) * (end-begin+1));
}
void MergeSort(int* a, int n)
{
int* temp = (int*)malloc(sizeof(int) * n);
if (temp == NULL)
{
perror("malloc fail");
return;
}
_MergeSort(a, temp, 0, n-1);
free(temp);
temp = NULL;
return;
}
这里的区间划分是[begin,mid] [mid+1,end],而不是[begin,mid-1] [mid,end],否则会不断递归导致栈溢出。
时间复杂度:
核心是:分解和合并
1.分解:将数组不断二分,高度是log n层
2.合并:每层的所有元素参与合并,每层的操作次数是O(n)
3.总的时间复杂度:O(n log n).
稳定性:
这里a[begin1]<=a[begin2],这就意味着元素相同时,相对位置仍然不变。
2.非递归实现
用循环模拟实现
三种情况需要特殊考虑[begin1,end1][begin2,end2]
情况一:end2越界了
但是begin2没有越界,那就将end2=n-1;
情况二:begin2,end2都越界了
情况三:end1,begin2,end2都越界了
情况二和情况三可以合并在一起考虑,即第二组不存在。
,见下图
最后一组的第二组已经有序,就不需要再归并了
void MergeSortNo(int* a, int n)
{
int* temp = (int*)malloc(sizeof(int) * n);
if (temp == NULL)
{
perror("malloc fail");
return;
}
//先对10和6排序
//int gap = 1;
//对6 10 1 7排序
//int gap = 2;
//gap为每组归并的元素个数,1 2 4 .。。
int gap = 1;
while(gap<n)
{
//对每一对间隔为gap=1的小数组进行排序,循环
for (int i = 0; i < n; i = i + 2 * gap)//有越界的情况
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
////end1,begin2,end2都有越界的情况
////end1越界了,那begin2也越界
//if (end1 > n - 1)
//{
// /*free(temp);
// temp = NULL;
// return;*/
// break;
//}
//第二组不存在
if (begin2 > n - 1)//begin2都越界了,那end2也越界了
{
/*free(temp);
temp = NULL;
return;*/
break;
}
if (end2 > n - 1)
{
end2 = n - 1;
}
int j = i;//让temp数组中的j从当前的begin1位置处开始放入元素
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
temp[j++] = a[begin1++];
else
temp[j++] = a[begin2++];
}
while (begin1 <= end1)
temp[j++] = a[begin1++];
while (begin2 <= end2)
temp[j++] = a[begin2++];
//将这一段拷贝到原数组中去,用来进行下一次大的合并
memcpy(a + i, temp + i, (end2 - i + 1) * sizeof(int));//i不能用begin1替换
}
gap = gap * 2;
}
free(temp);
temp = NULL;
}