//交换函数在多数排序中都会用到,特地写一个交换函数比较方便。
void Swap(int* p, int* q)
{
int tmp = *q;
*q = *p;
*p = tmp;
}
//三数取中,在快速排序中,取头尾中三数中的中位数,可一定程度降低时间复杂度
int GetMidIndex(int* a, int begin, int end)
{
int mid = (begin + end) / 2;
if (a[begin] < a[mid])
{
if (a[mid] < a[end])
return mid;
else if (a[begin] > a[end])
return begin;
else
return end;
}
else // a[begin] > a[mid]
{
if (a[mid] > a[end])
return mid;
else if (a[begin] < a[end])
return begin;
else
return end;
}
}
插入排序
思路:插入排序基本操作就是将一个数据插入到已经排序好的有序序列中,从而得到一个新增一个数据的序列;
在初始数组中默认第一个元素为有序区间,从第二个元素开始取出插入到已有序序列中。
// 插入排序
void InsertSort(int* a, int n)
{
assert(a);
for (int i = 0; i < n-1; i++)
{
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
if (tmp < a[end])
{
//a[end+1] = a[end];
Swap(&a[end], &a[end + 1]);
--end;
}
else
{
break;
}
}
//a[end + 1] = tmp;
}
return a;
}
希尔排序
在希尔排序里我们首先会设置一个增量值 gap,然后把数据按照增量分为几组(分组只是逻辑上的分组并非真的分组) 然后对分组内的数据进行排序。排序完成后 增量变小,然后再根据增量进行排序。依次类推,当增量为1时,再进行排序的时候,此时的数据经过前几次的处理,变得相对很规律。
// 希尔排序
void ShellSort(int* a, int n)
{
assert(a);
int gap = n;
while (gap > 1)
{
//确保最后一次时gap为1;
//gap逐渐缩小;
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
//a[end+1] = a[end];
Swap(&a[end], &a[end + gap]);
end -= gap;
}
else
{
break;
}
}
//a[end + 1] = tmp;
}
}
return a;
}
选择排序
选择排序是在一组数据中找到最小的数之后,将其与第一个数交换,第一个数就变的有序,然后再从第二个数开始遍历,找到最小的数,与第二个数交换,以此类推;
在这里对其进行优化:同时找到最大的数和最小的数,放到最后一位和第一位,以此类推;
//选择排序
void SelectSort(int* a, int n)
{
assert(a);
int end = n - 1, begin = 0;
while (begin < end)
{
int min,max;//最大数和最小数的下标
min = max = begin;
//循环找到最大的数和最小的数
for (int i = begin+1; i <= end; i++)
{
if (a[i] > a[max])
{
max = i;
}
if (a[i] < a[min])
{
min = i;
}
}
Swap(&a[begin], &a[min]);
//防止第一个元素为max,若第一个元素最大,此时min下标为第一个,先让max=min;
if (begin == max)
{
max = min;
}
Swap(&a[end], &a[max]);
//缩小范围
begin++; end--;
}
}
冒泡排序
将相邻元素两两比较,反序则交换,每一趟都将最大的数交换到最后面;
//冒泡排序
void BubbleSort(int* a, int n)
{
assert(a);
for (int i = 0; i < n-1; i++)
{
for (int j = 0; j < n-i-1; j++)
{
if (a[j] > a[j + 1])
{
Swap(&a[j], &a[j + 1]);
}
}
}
return a;
}
快速排序(三种方法)
一共三种方法,都是设定一个基准数,把比基准数大的数放到后面,把比基准数小的数放到前面,再进行递归,使其有序
一、左右指针法
一前一后两个指针,right从最后面出发,left从最前面出发,left找到比key(key即为基准值)大的之后,right再往前走找到比key小的,两数交换,以此类推,最后key恰好落到最终排序后的位置上,然后在key前后两个区间递归;
// 快速排序左右指针法
int PartSort1(int* a, int left, int right)
{
//int key = a[right];
//三数取中,防止逆序时为时间复杂度最大;
int midIndex = GetMidIndex(a, left, right);
Swap(&a[midIndex], &a[right]);
int keyi = right;
while (right > left)
{
while (right > left && a[left] <= a[keyi]) left++;
while (right > left && a[right] >= a[keyi]) right--;
Swap(&a[right], &a[left]);
}
Swap(&a[keyi], &a[right]);
return left;
}
void QuickSort1(int* a, int left, int right)
{
assert(a);
if (left >= right) return;
int meet = PartSort1(a, left, right);
QuickSort1(a, left, meet - 1);
QuickSort1(a, meet + 1, right);
return a;
}
二、挖坑法
先将key保存,将key的位置作为坑的位置,left先找到一个比key大的数,将其填入key的位置,left的位置变成坑,right再找到一个比可以key小的数,然后填入坑,以此类推,最后key恰好落到最终排序后的位置上,然后在key前后两个区间递归;
// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
int midIndex = GetMidIndex(a, left, right);
Swap(&a[midIndex], &a[right]);
int key = a[right];
while (right > left)
{
while (right > left && a[left] <= key) left++;
a[right] = a[left];
while (right > left && a[right] >= key) right--;
a[left] = a[right];
}
a[left] = key;
return left;
}
void QuickSort2(int* a, int left, int right)
{
assert(a);
if (left >= right) return;
int meet = PartSort2(a, left, right);
QuickSort2(a, left, meet - 1);
QuickSort2(a, meet + 1, right);
return a;
}
三、前后指针法
一前一后两个指针,初始cur=0,prev=cur-1,cur像是火车头,拉着比key值大的数往最后面走;
// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
int midIndex = GetMidIndex(a, left, right);
Swap(&a[midIndex], &a[right]);
int prev = left - 1;
int cur = left;
int keyindex = right;
while (cur < right)
{
if (a[cur] < a[keyindex] && ++prev != cur)
Swap(&a[prev], &a[cur]);
++cur;
}
Swap(&a[++prev], &a[keyindex]);
return prev;
}
void QuickSort3(int* a, int left, int right)
{
assert(a);
if (left >= right) return;
int meet = PartSort1(a, left, right);
QuickSort3(a, left, meet - 1);
QuickSort3(a, meet + 1, right);
return a;
}
归并排序
递归实现:
归并排序是利用归并的思想实现的排序算法,该算法采用经典的分治策略(分治法将问题分成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的各答案‘修补’在一起,即分而治之)。
// 归并排序递归实现
void _MergeSort(int* a, int L,int R,int* tmp)
{
if (L >= R)
return;
int M = (L + R) / 2;
_MergeSort(a, L, M, tmp);
_MergeSort(a, M + 1, R, tmp);
int begin1 = L, end1 = M;
int begin2 = M + 1, end2 = R;
int i = L;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] > a[begin2])
tmp[i++] = a[begin2++];
else
tmp[i++] = a[begin1++];
}
while (begin1 <= end1)
tmp[i++] = a[begin1++];
while (begin2 <= end2)
tmp[i++] = a[begin2++];
for (int i = L; i <= R; i++)
{
a[i] = tmp[i];
}
}
void MergeSort(int* a, int n)
{
assert(a);
int* tmp = malloc(sizeof(int) * n);
_MergeSort(a, 0, n - 1, tmp);
return a;
free(tmp);
}
// 归并排序非递归实现
void MergeArr(int* a, int begin1, int end1, int begin2, int end2, int* tmp)
{
int left = begin1, right = end2;
int index = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
tmp[index++] = a[begin1++];
else
tmp[index++] = a[begin2++];
}
while (begin1 <= end1)
tmp[index++] = a[begin1++];
while (begin2 <= end2)
tmp[index++] = a[begin2++];
// 把归并好的再tmp的数据在拷贝回到原数组
for (int i = left; i <= right; ++i)
a[i] = tmp[i];
}
void MergeSortNonR(int* a, int n)
{
assert(a);
int* tmp = malloc(sizeof(int) * n);
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
// [i,i+gap-1] [i+gap, i+2*gap-1]
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
// 1、合并时只有第一组,第二组不存在,就不需要合并
if (begin2 >= n)
break;
// 2、合并时第二组只有部分数据,需要修正end2边界
if (end2 >= n)
end2 = n - 1;
MergeArr(a, begin1, end1, begin2, end2, tmp);
}
//PrintArray(a, n);
gap *= 2;
}
free(tmp);
}