目录
1.直接插入排序
void Insertsort(int* a,int n)
{
int end = 0;
//这里的i控制每次比较最多要执行多少次
for(int i = 0;i < n-1;i++)
{
//end保存当前位置
end = i;
//tmp保存要插入排序的数
//[0,end]有序,插入a[end+1],使得[0,end+1]有序
int tmp = a[end + 1];
while (end >= 0)
{
//当a[end] > tmp时,就要往后移一位
if (a[end] > tmp)
{
a[end + 1] = a[end];
//赋值后end--,取得下一个比较数
end--;
}
else
{
//因为排序是从都一个元素开始的,所以当a[end] =/< tmp时即代表不用排序
break;
}
}
//无论哪种结束将tmp保存end加一的位置
a[end + 1] = tmp;
}
}
思想:直接插入排序的思想就是从end+1的位置(也就是第二个元素的位置)往后比较,如果后一个元素比前一个元素大,就把后一个元素往前移,因为是从第二个元素的位置开始,所以当执行到else时,即代表后面有序,所以无论是在if中停止还是在else中停止,最后的a[end+1] 都是key保存的正确位置(因为end放的第一个元素的下标,进入if代表往后移了,往前移end就--,那么end就是被移动的元素的位置,座椅所以加一是正确位置)。
2.希尔排序
void shellsort(int* a, int n)
{
int end = 0;
int gap = n;
int tmp = 0;
//当gap等于一时,为直接插入排序
while(gap > 1)
{
//每一趟gap/2,这里gap/3也可以,但是要加1,因为/3有可能到不了一
gap = gap / 2;
//这里第一个元素开始先排序,排序条件要控制为小于n-gap因为大于这个值end加gap就会导致数组越界访问
for (int i = 0; i < n - gap; i++)
{
//end为比较的起始位置
end = i;
//要比较的元素
tmp = a[end + gap];
while (end >= 0)
{
//如果tmp < a[end]就将end的元素换到end+gap上,然后end-=gap,去到下一个比较的位置,此时end再加gap就是上一个位置和下一个位置的比较
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
//如果遇到等于大于的就不用交换,因为我们从零开始的,所以直接break掉就可以了
else
{
break;
}
}
//最后将tmp保存在end+gap的位置,因为上面又可能一次交换后就直接走到break,也可能再-=gap才结束循环,所以en+gap是tmp这趟排序的正确位置
a[end + gap] = tmp;
}
}
}
原理:希尔排序和直接插入排序很像,直接插入排序时往后面的元素一个一个的比,希尔排序就是往后gap个比,所以当gap=1时,希尔排序就是直接插入排序,当gap逐渐到一的过程,整个数组就比较趋于有序(大的换到右边,小的换到左边)
3.堆排序
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void adjustdown(int* a, int root, int n)
{
//父亲节点
int parent = root;
//孩子节点,默认是左孩子结点
int child = (parent * 2) + 1;
//当孩子节点不超过数组的元素个数时,循环可以继续
while (child < n)
{
//child+为右节点,比较左右节点找出大的那一个
if (child + 1 < n && a[child] < a[child + 1])
{
child += 1;
}
//比较父节点与较大的孩子节点,满足条件交换
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
//交换后父节点来到了子节点这里,子节点则需要重新计算
parent = child;
child = parent * 2 + 1;
}
else
{
//如果a[child] < a[parent]直接结束即可,因为我们是从最下面排上来的,如果已经符合了那么下面一定符合大堆
break;
}
}
}
void heapsort(int *a,int n)
{
//这里传递的为元素的下标,所以公式为parent = (child-1)/2计算最后一个元素的的父亲,所以应当是n-1才是其下标
int parent = (n - 1-1) / 2;
for (int i = parent; i >= 0; i--)
{
//这里parent就是最后一个有子节点的父节点,因为本质是数组,所以--就是剩下的父节点
adjustdown(a, i, n);
}
int end = n - 1;
//当还剩最后一个元素就停止循环
while (end > 0)
{
//n-1为最后一个元素下标,排大堆将首尾交换,在向下调整
Swap(&a[0], &a[end]);
//这里的end是元素个数
adjustdown(a, 0, end);
--end;
}
}
- 堆排序的前提是左右子树得是大堆或小堆
- 堆的逻辑结构是一颗完全二叉树,物理结构是一个数组
- 通过数组下标计算父子结点的公式:
leftchild = parent*2+1
rightchild = parent*2+2
parent= (child-1)/2
向下调整算法
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void adjustdown(int* a, int root, int n)
{
//父亲节点
int parent = root;
//孩子节点,默认是左孩子结点
int child = (parent * 2) + 1;
//当孩子节点不超过数组的元素个数时,循环可以继续
while (child < n)
{
//child+为右节点,比较左右节点找出大的那一个
if (child + 1 < n && a[child] < a[child + 1])
{
child += 1;
}
//比较父节点与较大的孩子节点,满足条件交换
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
//交换后父节点来到了子节点这里,子节点则需要重新计算
parent = child;
child = parent * 2 + 1;
}
else
{
//如果a[child] < a[parent]直接结束即可,因为我们是从最下面排上来的,如果已经符合了那么下面一定符合大堆
break;
}
}
}
堆排序要用到向下调整算法,可以选择建大堆或者建小堆,但排升序建议建大堆,因为建小堆最小值在最上面,想要升序堆的结构会改变,需要重新建堆,而建大堆不需要重新建堆,交换在控制坐标即可。上面是建大堆过程,根据坐标计算公式,计算出左右孩子,选出较大的与父节点交换,然后再调整坐标。
建堆
void heapsort(int *a,int n)
{
//这里传递的为元素的下标,所以公式为parent = (child-1)/2计算最后一个元素的的父亲,所以应当是n-1才是其下标
int parent = (n - 1-1) / 2;
for (int i = parent; i >= 0; i--)
{
//这里parent就是最后一个有子节点的父节点,因为本质是数组,所以--就是剩下的父节点
adjustdown(a, i, n);
}
int end = n - 1;
//当还剩最后一个元素就停止循环
while (end > 0)
{
//n-1为最后一个元素下标,排大堆将首尾交换,在向下调整
Swap(&a[0], &a[end]);
//这里的end是元素个数
adjustdown(a, 0, end);
--end;
}
}
建堆我么们考虑从最后一个叶子节点的父亲开始,因为堆的结构的逻辑结构是完全二叉树,所以找到其父亲在作为循环条件即可遍历全部的父亲,从下往上建堆。
建完堆后此时最大值就在最上面的位置,这个值的位置刚好是数组的最后一个元素所以将首尾元素交换,将尾坐标减减,然后再进行向下调整,这里的end是元素个数,因为上面向下调整的循环时<n及只遍历到n-1位,所以传递的end在是元素个数来看待,就有少一个的效果。
4.选择排序
void selectsort(int* a,int n)
{
//初始位置
int begin = 0;
//结束位置
int end = n - 1;
//min保存最小值的下标,max保存最大值的下标
int min = 0;
int max = 0;
while (begin < end)
{
//初始化为begin所在位置
min = begin;
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[begin], &a[min]);
//这里要避免min要换的位置与max相同,及begin=max的情况,因为这样原本处于max的值就被换成min所指的值,然后min下标的值又被换到end
if (begin == max)
{
max = min;
}
Swap(&a[max], &a[end]);
begin++;
end--;
}
}
选择排序就是找出最大或者最小的放到指定位置,我这里采用了同时找大找小,放到首尾在控制坐标begin和end排除已经放好的位置。
5.冒泡排序
void bubblesort(int *a,int n)
{
//这里的j决定趟数,第一趟决定最大值,第二趟决定次大值
for (int j = 0; j < n; j++)
{
//这里-j刚好控制排序范围,因为一趟排序就确定一个值,这个值不需要排
for (int i = 1; i < n - j; i++)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
}
}
}
}
冒泡排序应该是最简单得了,每一趟找到该趟范围内的最大值放到后面,这里就不多赘述,代码有注释。
6.快速排序
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//三数取中
int getmid(int* a, int left, int right)
{
int mid = (left + right) >> 1;
if (a[left] > a[mid])
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[right] > a[left])
{
return left;
}
else
{
return right;
}
}
else
{
if (a[left] > a[right])
{
return left;
}
else if (a[right] > a[mid])
{
return mid;
}
else
{
return right;
}
}
}
//快速排序
void quicksort(int* a, int left,int right)
{
//如果只有一个元素或者没有元素就返回
if (left >= right)
{
return;
}
//三数取中,取最小的那个
int index = getmid(a, left, right);
//这里除了key保存的是值,剩下三个都是下标
//这里我们一般取第一个位置为坑,所以将上面下标的值与第一个下标的值交换即可
Swap(&a[left], &a[index]);
//这里right是元素最后的下标,不是元数个数,如果是元数个数每次进来递归进来每次都要减一,会导致递归排序的范围减少
int begin = left;
int end = right;
int pivot = begin;
//选第一个元素为key值
int key = a[begin];
while (begin < end)
{
//右边找小,a[end] >= key就继续,找到小的就停止,这个就是新的坑位
//注意下面两个while都要加上判断条件begin < end,因为有可能在下面两个循环的过程中,当begin= end时,该位置的值还是>=key,导致end--,导致排序出错
while (begin < end && a[end] >= key)
{
end--;
}
//找到小的给上一个坑位,并且形成新的坑位
a[pivot] = a[end];
pivot = end;
//左边找大,a[begin] <= key就继续,找到大的就停止,这个就是新的坑位
if (begin < end && a[begin] <= key)
{
begin++;
}
//找到大的给上一个坑位,并且形成新的坑位
a[pivot] = a[begin];
pivot = begin;
}
//这里当循环结束,begin = end 因为条件是begin<end才能进入,当begin = end 就会结束
pivot = begin;
a[begin] = key;
//分治递归,pivot的左右两个区间
quicksort(a, left, pivot - 1);
quicksort(a, pivot + 1, right);
}
思想
- 将第一个元素作为key值,左边找比key值大的,右边找比key值小的,找完key值就放到了合适的位置,然后将这个位置的左右位置进行递归
- begin :初始下标,end:最后下标 ,pivot:坑位下标,key保存选出的值
7.归并排序
//归并排序
void _mergesort(int* a, int left, int right,int* tmp)
{
//如果只有一个元素就不要递归了
if (left >= right)
{
return;
}
//取分割点
int mid = (left + right) >> 1;
//递归
_mergesort(a, left, mid, tmp);
_mergesort(a, mid + 1, right, tmp);
//四个变量存放归并的区间的范围
int begin1 = left;
int end1 = mid;
int begin2 = mid + 1;
int end2 = right;
//记录临时数组的开始范围
int index = left;
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++];
}
//拷贝回原数组
for (int i = 0; i <= right; i++)
{
a[i] = tmp[i];
}
}
void mergesort(int *a,int n)
{
//如果在这个函数递归每次都要malloc一个数组,所以在上面创建一个用于递归的子函数
int* tmp = (int*)malloc(sizeof(int) * n);
_mergesort(a, 0, n - 1, tmp);
free(tmp);
}
归并排序其实就是将整个数组分为两部分,再将这两部分递归又分别分成两部分,直到不可再分割,然后进行排序,根据调用传递的参数不同,将数据排好序放到开辟的临时数组中,再将其拷贝回原数组。这里注意不能直接排序放到原数组中,这样可能会导致覆盖。
大概就是这样子的思想。