1、插入排序
什么是插入排序,插入插入排序的原理是什么呢?
插入排序就和摸牌一样摸一张牌然后插入到合适位置。
代码实现原理:
for (int i = 1; i < n; i++)
{
int end = i - 1;
int tmp = a[i];
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + 1] = a[end];
--end;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
原理模拟图:
时间复杂度:最坏情况下为O(N*N),此时待排序列为逆序,或者说接近逆序
最好情况下为O(N),此时待排序列为升序,或者说接近升序。
空间复杂度:O(1)
2、希尔排序
什么是希尔排序,希尔排序的原理是什么呢?
希尔排序类似于插入排序只不过在插入排序前增加了一个步骤 预排序。
预排序 就是在排序之前选定一个gap,在每相隔gap位置下的小数组排好序,比如一个长度为8的数组 1,3,6,4,7,2,8,5 选定gap为2 那么 1 6 7 8 和3 4 2 5为一组先排序一遍 1 6 7 8和2 3 4 5 然后数组为 1 2 6 3 7 4 8 5 成为相对更加有序的数组
原理代码实现:
void ShellSort(int* a, int n)//希尔排序 先预排序 再插入排序
{
int gap = n;
while (gap > 1)
{
gap = gap / 2;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
图解:
时间复杂度平均:O(N^1.3)
空间复杂度:O(1)
3、选择排序
思路:每次从待排序列中选出一个最小值,然后放在序列的起始位置,直到全部待排数据排完即可。
图解:
代码实现:
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; i <= end; i++)
{
if (a[i] < a[min])
{
min = i;
}
if (a[i] > a[max])
{
max = i;
}
}
Swap(&a[min],&a[begin]);
//如果begin和max重叠 max会掉换到min位置 所以得交换一次
if (begin == max)
{
max = min;
}
Swap(&a[max],&a[end]);
begin++;
end--;
}
}
时间复杂度:最坏情况:O(N^2)
最好情况:O(N^2)
空间复杂度:O(1)
4、堆排序
思路:
第一步将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆
第二步将堆顶元素与末尾元素交换,将最大元素/最小元素“沉”到数组末端
第三步重新调整结构使其满足堆定义,然后继续交换堆顶元素与当前末尾元素
图文讲解:想要数组是升序 先建立大根堆 我们用向上调整算法建立大根堆
定义最后元素的下标为len
然后将根结点和最后一个结点交换 len再减一到了倒数第二个位置下标 然后使用向下调整算法 找出第二大的元素再与倒数第二个元素交换,循环直到排成升序为止。
void AdjustUp(int* a, int child)//大堆
{
int parent = (child - 1) / 2;
//while (parent >= 0)
while (child > 0)
{
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void AdjustDown(int* a, int n, int parent)//前提:左子树必须是大堆/小堆 这里我们用大堆
{
int child = parent * 2 + 1;//父母等于(孩子-1)/2
while (child < n)//孩子在数组范围内就继续
{
//右孩子存在 并且 选出左右孩子中大的那个
if (child + 1 <= n && a[child + 1] > a[child])
{
++child;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, int len)
{
for (int child = 1; child < len; child++)//建立大顶堆
{
AdjustUp(a, child);
}
int n = len - 1;//最后孩子的下标
while (n > 0)
{
Swap(&a[0], &a[n]);
n--;//让最大的值 位于当前数组的最后
AdjustDown(a, n, 0);
}
}
时间复杂度为:
5、快速排序
思路:这里我们讲一种最好理解的挖坑法
1.选出一个数据(一般是最左边或是最右边的)存放在key变量中,在该数据位置形成一个坑
2、定义一个L和一个R,L从左向右走,R从右向左走。(若在最左边挖坑,则需要R先走;若在最右边挖坑,则需要L先走)
3、我们在最左边挖坑为例,R先走找到比key小的位置并将其填放入坑中,且此位置为新的坑,再用L往右找比key大的找到之后,将该值放入坑中,并将该位置设置为新的坑,由此往复直到L>=R
,最后将key填入最后一个坑中,第一趟快速排序完成。
4、第一趟完成之后将数组分为了 比key小的一边 key 比key大的一边,然后继续用递归法将key的左右两边排成升序
第一趟图解:
int PartSort2(int* a, int left, int right)
{
int key = a[left];
int hole = left;
while (left < right)
{ //右边找小 把小的值填到左边的坑里
while (left<right&&a[right] >= key)
{
right--;
}
a[hole] = a[right];
hole = right;
//左边找大 把大的值填到右边的坑里
while (left < right && a[left] <= key)
{
left++;
}
a[hole] = a[left];
hole = left;
}
a[left] = key;//把坑填上
return left;
}
void QuickSort(int* a, int left, int right)
{
if (left>=right)
{
return;
}
int keyi = PartSort2(a, left, right);
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi+1, right);
}
6、计数排序
思路:用哈希表将数组中每个元素都记录有多少个,比如1个1,2个3,4个6,3个9然后按序全部输出
void CountSort(int* a, int n)
{
int min = a[0], max = a[0];
for (int i = 0; i < n; i++)
{
if (a[i] < min)
{
min = a[i];
}
if (a[i] > max)
{
max = a[i];
}
}
int range = max - min + 1;
int* countA = (int*)malloc(sizeof(int) * range);
memset(countA, 0, sizeof(int) * range);
//统计次数
for (int i = 0; i < length; i++)
{
countA[a[i] - min]++;
}
//排序
for (int j = 0; j < range; j++)
{
while (countA[j]--)//升序排序
{
a[k++] = j + min;
}
}
}