目录
1.直接插入排序
步骤:
1.从第二个元素开始,与前面的元素依次比较,如果比前面的元素小就向前移动
2.当该元素大于前一个元素时,插入
3.重复上述过程
代码实现:
void InsertSort(int* a, int n)
{
for (int i = 1; i < n; i++)
{
int tmp = a[i];
int end = i - 1;
while (end >=0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];
--end;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
时间复杂度:
时间复杂度:最坏情况O(N^2)——逆序(遍历每个元素N * 每个元素与前面N个数比较)
最好情况O(N)—— 升序(遍历每个元素N)
2.希尔排序
步骤:
1.选定一个gap作为跨度距离(通常选 n/3+1 or n/2),然后将所有距离为gap分在同一组 ,对每一组的元素进行直接插入排序
2.重复上述操作 到gap==1时,整个序列被分到一组,相当于进行一次直接插入排序,排序完成
思路:
现将待排序列进行预排序,使待排序列接近有序,最后对该序列进行一次插入排序,最后一次插入排序的时间复杂度可以看做最好情况O(N)
代码实现:
void ShellSort(int* a, int n)
{
// gap > 1 预排序
// gap == 1 直接插入排序
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[i + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
时间复杂度:
一共有log2 N层或者log3 N层 每一次操作的时间复杂度为N
近似可以看作:O(N*logN)
经过更复杂的计算,最终的平均时间复杂度为:O(N^1.3)
3.选择排序
步骤:在每一次遍历中,选择本次遍历数列的的最小值,交换放到放到数列的最前端
我们可以一趟选出最大(放在末端)的和最小(放在前段)两个值,这样可以使得效率增大一倍
代码实现:
//最坏 O(N^2)
//最好 O(N^2)
void SelectSort(int* a, int n)
{
//记录最小值和最大值的位置
int left = 0, right = n - 1;
while (left < right)
{
//将最左边的值认为是最大的,最右边的值认为是最小的
//如果有比它还小的数 更新下标
int mini = left, maxi = right;
for (int i = left+1; i <= right; i++)
{
if (a[i] < a[mini])
{
mini = i;
}
if (a[i] > a[maxi])
{
maxi = i;
}
}
if (left == maxi)//如果i==maxi, 交换后要修正一下
{
maxi = mini;
}
Swap(&a[left], &a[mini]);
Swap(&a[right], &a[maxi]);
++left;
--right;
}
}
注意:如果选择的最大值和left重叠,需要修正maxi的位置
时间复杂度:
最好情况:O(N^2) 最坏情况:O(N^2)(每选一个数N * 遍历剩下所有数N)
4.堆排序
见这篇博客:
5.冒泡排序
步骤:
每一次从数列开头进行比较,如果左边大于右边,就交换
每一次循环结束后,数列的最后端就确立了
代码实现:
void BubbleSort(int* a, int n)
{
int flag = 0;
for (int j = 0; j < n - 1; j++)
{
for (int i = 1; i < n-j; i++)
{
if (a[i] < a[i - 1])
{
Swap(&a[i], &a[i - 1]);
flag = 1;
}
}
if (flag == 0)
{
break;
}
}
}
建立一个flag,如果本次循环一次都没有交换的话,说明数列已经有序,可以减少循环次数
时间复杂度:
时间复杂度:最坏情况:O(N^2)(每个数都要遍历一次N*每一趟操作的时间复杂度N)
最好情况:O(N)
6.快速排序
1.hoare版本
步骤:
1.将数列的第一个值作为key
2.设置左右指针,分别指向数列的起始和末尾
3.R先向前移动,找到小于key位置,L再向后移动,找到大于key的位,换L和R位置的数值
4. 重复上述行为
5.L与R相遇时,将该位置的值与key交换
思路:将key的值看做数列的中间值,将>key 的值都放在数列的左边 <key的值都放在数列的右边
代码实现:
int PartSort1(int* a, int left, int right)
{
//当数组为顺序有序时,时间复杂度为O(N^2)
//因此选择keyi,让它变为无序
//随机选keyi(但仍有几率为顺序数组)
//int randi = left + rand() % (right - left);
//Swap(&a[left], &a[randi]);
//三数取中
//int midi = GetMidNumi(a, left, right);
//Swap(&a[left], &a[midi]);
int keyi = left;
while (left < right)
{
//左边找小
while (left < right && a[right] >= a[keyi])
{
--right;
}
//右边找大
while (left < right && a[left] <= a[keyi])
{
++left;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
return keyi;
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int keyi = PartSort3(a, left, right);
QuickSort(a, left,keyi-1);
QuickSort(a, keyi+ 1,right);
}
时间复杂度:
快速排序的时间复杂度在O(nlogn)~ O(n^2)
深度:logN ---> N(轴是区间的最大值或者最小值)
每一次操作都需要遍历一次剩下的所有元素,这个操作的时间复杂度是O(n)
最坏情况为:当对于每一个区间选取的轴刚好就是这个区间的最大值或者最小值
解决方案:
1.随机选keyi
//随机选keyi(但仍有几率为顺序数组) int randi = left + rand() % (right - left); Swap(&a[left], &a[randi]);
2.三数取中
//三数取中 int GetMidNumi(int* a, int left, int right) { int midi = (right - left) / 2; if (a[left] > a[midi]) { if (a[midi] > a[right]) { return midi; } else if (a[left] > a[right]) { return right; } else { return left; } } else//a[left] < a[midi] { if (a[midi] < a[right]) { return midi; } else if (a[right] > a[left]) { return right; } else { return left; } } } int midi = GetMidNumi(a, left, right); Swap(&a[left], &a[midi]);
2 .挖坑法:
步骤:
1.记录key的值
2.设置左右指针,走法与hoare相同,当R找到比key小的值,将R所在位置的值放入坑中,同时坑位变为R所在位置,L类似(相遇一定在坑中)
思路:甩值变坑
代码实现:
int PartSort2(int* a, int left, int right)
{
int begin = left, end = right;
int midi = GetMidNumi(a, left, right);
Swap(&a[left], &a[midi]);
int key = a[left];//记录key的值
int holei = left;//记录坑的位置
while (left < right)
{
//左边找小
while (left < right && a[right] >= key)
{
--right;
}
a[holei] = a[right];
holei = right;
//右边找大
while (left < right && a[left] <= key)
{
++left;
}
a[holei] = a[left];
holei = left;
}
a[holei] = key;//一定在空的坑位相遇
return holei;
}
3.前后指针法
步骤:
1.cur遇到比key小的值,++prev,将prev所在位置的值和cur交换,cur++
2.当cur遇到比key大的值,cur++
3.当cur越界时,将prev的值和key交换
说明:
1.prev要么紧跟着cur(prev的下一个就是cur)
2.prev与cur相隔一段 >key 的值区间
代码实现:
int PartSort3(int* a, int left, int right)
{
//三数取中
int midi = GetMidNumi(a, left, right);
Swap(&a[left], &a[midi]);
int key = a[left];
int prev = left, cur = left + 1;
while (cur <= right)
{
if (a[cur] < key && ++prev!=cur)
{
Swap(&a[prev], &a[cur]);
}
++cur;
}
Swap(&a[left], &a[prev]);
return prev;
}
7.归并排序
1.递归
步骤:递归的第一层,将n个数划分为2个子区间,每个子区间的数字个数为n/2;
递归的第二层,将n个数划分为4个子区间,每个子区间的数字个数为n/4;
递归的第三层,将n个数划分为8个子区间,每个子区间的数字个数为n/8;
递归的第logn层,将n个数划分为n个子区间,每个子区间的数字个数为1; 之后将每两个相邻子区间进行合并(先左后右)
分而治之(分:将问题分成一些小的问题然后求解。治:分阶段得到的答案修补在一起)
思路:从后往前,递归
代码实现:
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin >= end)
return;
int mid = (end+begin) / 2;
//[begin , mid] [mid+1 , end]
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid+1, end, tmp);
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin;
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 + begin, tmp + begin, sizeof(int)*(end-begin+1));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail\n");
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
时间复杂度:
时间复杂度:O(N*logN) ——— 一共有logN层,每层遍历N个
2.非递归
步骤:用gap将原数列分为不同的组,排序,在将排好的数列拷贝到原数列中
思想:从前往后
代码实现:
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc failed");
return;
}
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i+=2*gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
/*if (end1 > n-1)
{
end1 = n-1;
begin2 = n;
end2 = n-1;
}
else if (begin2 > n - 1)
{
begin2 = n;
end2 = n - 1;
}
else if (end2 > n - 1)
{
end2 = n - 1;
}*/
if (end1 > n - 1 || begin2 > n - 1)
{
break;
}
if (end2 > n - 1)
{
end2 = n - 1;
}
printf("[%d][%d],[%d][%d] ", begin1, end1, begin2, end2);
int j = i;
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));//归并一部分,拷贝回去一部分
}
printf("\n");
//memcpy(a , tmp, sizeof(int) * n);//全部归并完拷贝
gap *= 2;
}
free(tmp);
}