目录
1.0:辅助函数
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
typedef int HPDataTroy;
//交换
void Swap(HPDataTroy* p1, HPDataTroy* p2) {
HPDataTroy* tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//打印函数
void SortPrint(HPDataTroy* a, int n) {
assert(a);
for (int i = 0; i < n; i++) {
printf("%d ",a[i]);
}
printf("\n");
}
因为排序需要较多的交换操作和打印测试,所以我们直接写成函数,后期调用即可。
2.0:冒泡排序
//冒泡排序
void BubbleSort(HPDataTroy* a, int n) {
assert(a);
int count = 0;
for (int i = 0; i < n; i++) {
for (int j = 1; j < n-i; j++) {
if (a[j - 1] > a[j]) {
count = 1;
Swap(&a[j-1],&a[j]);
}
}
if (count == 0) {
break;
}
}
}
冒泡排序思想:对这个长度为n的数组,我们进行相邻两个元素之间的比较,一次这样的操作就可以把最大的元素放到最后,以此类推,n-1次这样的重复操作即可把长度为n的数组进行有序化操作。我们也可以对冒泡排序进行一次优化,即如果在某一次遍历的过程中,前一个元素一直小于后一个元素,如果这样的话,我们的数组已经完成了有序化,中止剩余循环即可。
3.0:插入排序
//插入排序
void InsertionkSort(HPDataTroy* a, int n) {
assert(a);
for (int i = 0; i < n-1; i++) {
//单趟
int cur =i;
int end = a[cur+1];
while (cur >= 0) {
if (a[cur] > end) {
a[cur + 1] = a[cur];
cur--;
}
else {
break;
}
}
a[cur + 1] = end;
}
}
插入排序思想:对于长度为n的数组,我们首先存储一个下标值(cur),存储下标值的下一个元素的值(简称end),然后通过下标值索引元素让它和存储的下一个元素进行对比,如果前者大,那就覆盖后面的元素,然后下标值向前走(cur-=1),一直到下标值索引元素小于end时,或者cur=-1时,单趟遍历结束,将cur+1位置的值替换成end即可,所以,我们需要控制cur从0到n-1即可完成对整体的控制。
4.0:希尔排序
//希尔排序
void ShellSort(HPDataTroy* a, int n) {
assert(a);
int gap = n;
while (gap > 0) {
gap /= 2;//也可以写为gap=gap/3+1;
//注意:当gap=1时,就是插入排序,gap!=1时,为预排序
for (int j = 0; j < gap; j++) {
for (int i = j; i < n - gap; i += gap) {
int cur = i;
int end = a[i + gap];
while (cur >= 0) {
if (a[cur] > end) {
a[cur + gap] = a[cur];
cur -= gap;
}
else {
break;
}
}
a[cur + gap] = end;
}
}
}
}
希尔排序分为两步:
1.预排序
2.插入排序
希尔排序思想:希尔排序在插入排序的基础上,通过用数组长度n来确定gap的预排序,使得相邻gap长度的元素有序,通过有限次的预排序,最后配合一次插入排序(gap=1时),即可对数组进行有序化。
5.0:选择排序
//选择排序
void SelectSort(HPDataTroy* a, int n) {
assert(a);
int begin1 = 0;
int end1 = n-1;
while (begin1 < end1) {
int begin2 = begin1;
int end2 = begin1;
//遍历找大找小
for (int i = begin1; i <= end1; i++) {
//找小
if (a[i] < a[begin2]) {
begin2 = i;
}
//找大
if (a[i] > a[end2]) {
end2 = i;
}
}
Swap(&a[begin1], &a[begin2]);
if (begin1 == end2) {
end2 = begin2;
}
Swap(&a[end1], &a[end2]);
begin1++;
end1--;
}
}
选择排序思想:我们通过begin1和end1控制数组的范围,在可控的范围内,找到最大的和最小的元素的下标,然后交换begin1和begin2、end1和end2,最后我们通过begin1和end1逐次减少数组长度即可控制数组有序化。
6.0:堆排序
//向下调整
void AdjustDown(HPDataTroy*a,int n,int parent) {
assert(a);
int child = parent * 2 + 1;
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(HPDataTroy* a, int n) {
assert(a);
//建大堆-向下调整
for (int i = (n - 1 - 1) / 2; i >= 0; --i) {
AdjustDown(a,n,i);
}
//利用删除的思想排升序
int end = n - 1;
while (end > 0) {
Swap(&(a[0]), &(a[end]));
AdjustDown(a, end, 0);
end--;
}
}
堆排序思想:我们将这个数组进行向下调整建大堆之后,此时,堆顶元素就是最大的元素,我们利用删除的思想,将最大的元素与第一个元素交换,并进行一次向下调整,向下调整元素个数依次递减(第一次为n-1,以此类推),用end控制让其走到0的位置即可完成数组有序化。
7.0:快速排序
7.1:递归
7.1.1:三数取中
//三数取中
int Threenums(HPDataTroy* a, int begin, int end) {
int keyi = (begin + end) / 2;
if (a[begin] < a[end]) {
//a[begin] a[end]
if (a[keyi] < a[begin]) {
//a[keyi] a[begin] a[end]
return begin;
}
else if (a[keyi] > a[end]) {
//a[begin] a[end] a[keyi]
return end;
}
else {
//a[begin] a[keyi] a[end]
return keyi;
}
}
else {
//a[end] a[begin]
if (a[keyi] < a[end]) {
//a[keyi] a[end] a[begin]
return end;
}
else if (a[keyi] > a[begin]) {
//a[end] a[begin] a[keyi]
return begin;
}
else {
//a[end] a[keyi] a[begin];
return keyi;
}
}
}
对于快速排序而言,我们都希望第一个元素的值尽可能为中间值,这样有利于缩减递归的层数,达到效率优化。
7.1.2:霍尔思想
//霍尔思想
int Past1(HPDataTroy* a, int begin, int end) {
int midi = Threenums(a,begin,end);
Swap(&a[begin], &a[midi]);
int keyi = begin;
while (begin < end) {
//右侧找小
while (begin<end && a[end]>=a[keyi]) {
end--;
}
//左侧找大
while (begin < end && a[begin] <= a[keyi]) {
begin++;
}
Swap(&(a[begin]), &(a[end]));
}
Swap(&a[begin],&a[keyi]);
return begin;
}
霍尔大佬的思想:先记录首元素的位置,再用两个一前一后指针,先让后指针前移找小的元素,然后让前指针后移找到大的元素,注意:这里的大和小都是相对于第一个元素值而言,如果两指针相遇,则交换第一个位置的元素和相遇位置的元素。若没有相遇,则前后指针所对应的元素值交换后,再重复相同的找大找小操作,直到相遇,执行对应操作。此时,相遇的位置的元素值就是排序完成后该元素所出现的最终位置。
7.1.3:挖坑法
//挖坑法
int Past2(HPDataTroy* a, int begin, int end) {
int midi = Threenums(a, begin, end);
Swap(&a[begin], &a[midi]);
int hole = begin;
int tmp = a[hole];
while (begin < end) {
while (begin < end && a[end] >= a[hole]) {
end--;
}
a[hole] = a[end];
hole = end;
while (begin < end && a[begin] <= a[hole]) {
begin++;
}
a[hole] = a[begin];
hole = begin;
}
a[hole] = tmp;
return hole;
}
挖坑法的思想在于,起始给定一个坑位(第一个元素的位置),利用一前一后指针,后指针找小,然后将小的元素值覆盖给坑位的元素值,坑位此时就到了后指针的位置,然后前指针同理操作,最后将坑位位置的元素值覆盖成起始坑位的值(tmp)即可,最终,坑位位置的值就是排序完成后该元素所出现的最终位置。
7.1.4:前后指针法
//前后指针法
int Past3(HPDataTroy* a, int begin, int end) {
int midi = Threenums(a, begin, end);
Swap(&a[begin], &a[midi]);
int prev = begin;
int cur = prev + 1;
int tmp = begin;
while (cur <= end) {
if (a[cur] < a[tmp]) {
prev++;
Swap(&(a[prev]), &(a[cur]));
}
cur++;
}
Swap(&a[prev], &(a[tmp]));
return prev;
}
前后指针的思想在于利用前后指针,前指针所指向的值如果小,那么后指针向前走一步,交换前后指针指向的值,最后,前指针向前走一步,如果前指针所指向的值大,那么后指针不做任何操作。最终,前指针遍历结束之后,交换后指针与起始后指针所指向的值(tmp位置)即可。之后,后指针位置的值就是排序完成后该元素所出现的最终位置。
7.1.5:快速排序递归式函数
//快速排序
void QuickSort(HPDataTroy* a, int begin, int end) {
assert(a);
if (begin >= end) {
return;
}
int keyi = Past1(a, begin, end);
//int keyi = Past2(a, begin, end);
//int keyi = Past3(a, begin, end);
//[begin,keyi-1] keyi [keyi+1,end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi+1, end);
}
7.1.6:小区间优化
//快速排序
void QuickSort(HPDataTroy* a, int begin, int end) {
assert(a);
if (begin >= end) {
return;
}
if (end - begin + 1 < 10) {
int keyi = Past1(a, begin, end);
//int keyi = Past2(a, begin, end);
//int keyi = Past3(a, begin, end);
//[begin,keyi-1] keyi [keyi+1,end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
else {
InsertionkSort( a, end-begin+1);
}
}
如果递归后数组长度大于10,那么快速排序效率高,如果小于10,此时就可以用一个插入排序,节省空间,提高效率。
7.1.7:三路划分
//三路划分
int left = begin;
int right = end;
int cur = left + 1;
int key = a[left];
while (cur <= right) {
if (a[cur] < key) {
Swap(&a[cur], &a[left]);
cur++;
left++;
}
else if (a[cur] > key) {
Swap(&a[cur], &a[right]);
right--;
}
else {
cur++;
}
}
//[begin,left-1] [left,right] [right+1,end]
三路划分将以第一个值为标准,将小的换到左边,相等的换到中间,大的换到右边,三路划分之后,我们只需要递归左边和右边排序即可。
最终:快速排序递归算法:三数取中+三路划分+小区间优化
7.1.8:最终优化代码
void QuickSort2(int* a, int begin, int end) {
if (begin >= end) {
return;
}
//小区间优化
if (end - begin + 1 < 10) {
//三数取中
int midi = Threenums(a, begin, end);
Swap(&a[begin], &a[midi]);
//三路划分
int left = begin;
int right = end;
int cur = left + 1;
int key = a[left];
while (cur <= right) {
if (a[cur] < key) {
Swap(&a[cur], &a[left]);
cur++;
left++;
}
else if (a[cur] > key) {
Swap(&a[cur], &a[right]);
right--;
}
else {
cur++;
}
}
//[begin,left-1] [left,right] [right+1,end]
QuickSort2(a, begin, left - 1);
QuickSort2(a, right + 1, end);
}
else {
InsertSort(a + begin, end - begin + 1);
}
}
7.2:非递归
//快速排序---非递归
void QuickSortNonR(int* a, int begin, int end) {
//创建栈
ST s;
//初始化
Init(&s);
//入尾部位置
Push(&s,end);
//入头部位置
Push(&s,begin);
//只要栈不为空,循环继续
while (!determine(&s)) {
//读取栈顶元素
int left= obtain(&s);
Pop(&s);
int right = obtain(&s);
Pop(&s);
int keyi = Past1(a, left, right);
//[left keyi-1] keyi [keyi+1 right]
//如果成区间,那就入栈
if (keyi + 1 < right) {
Push(&s, right);
Push(&s, keyi+1);
}
if (left<keyi-1) {
Push(&s, keyi-1);
Push(&s, left);
}
}
//销毁
Distroy(&s);
}
非递归主要借助与栈,利用栈的后进先出的特点,记录区间,从而避免递归的空间浪费,达到优化效果。栈的内容可以查阅本人之前发布的文章查看。
8.0:计数排序
//计数排序
void CountSort(HPDataTroy* a,int n) {
int min = a[0];
int max = a[0];
//遍历找最大和最小元素
for (int i = 0; i < n; i++) {
//找最小
if (a[i] < min) {
min = a[i];
}
//找最大
if (a[i] > max) {
max = a[i];
}
}
//开空间
HPDataTroy* tmp = (HPDataTroy*)malloc(sizeof(HPDataTroy) * (max-min+ 1));
if (tmp == NULL) {
perror("malloc fail");
exit(-1);
}
//初始化空间
for (int i = 0; i < (max - min + 1); i++) {
tmp[i] = 0;
}
//计数
for (int i = 0; i < n; i++) {
tmp[a[i]-min]++;
}
//写入
int index = 0;
for (int i = 0; i < (max - min + 1); i++) {
while (tmp[i]) {
a[index] = i + min;
index++;
tmp[i]--;
}
}
}
计数排序思想:将每个元素出现的次数记录在一个新的空间中,利用新空间将原数组每个值出现的次数记录下来,再将这个空间每个位置的值的下标来覆盖掉原数组的值,额外开辟空间的对应的值就是每次循环的次数。
9.0:归并排序
9.1:递归
//递归子函数
void _MergerSort(HPDataTroy* a, HPDataTroy* tmp, int begin, int end) {
//递归
if (begin >= end) {
return;
}
int mid = (begin + end) / 2;
//[begin mid] [mid+1 end]
_MergerSort(a, tmp, begin,mid);
_MergerSort(a, tmp, mid+1,end);
//合并
int begin1 = begin;
int end1 = mid;
int begin2 = mid + 1;
int end2 = end;
int index = begin;
while (begin1 <= end1 && begin2 <= end2) {
//放小
if (a[begin1] < a[begin2]) {
tmp[index] = a[begin1];
index++;
begin1++;
}
else {
tmp[index] = a[begin2];
index++;
begin2++;
}
}
//放剩余的元素
while (begin1 <= end1) {
tmp[index] = a[begin1];
index++;
begin1++;
}
while (begin2 <= end2) {
tmp[index] = a[begin2];
index++;
begin2++;
}
//CV到a数组对应位置对应长度上
memcpy(a + begin, tmp + begin, sizeof(HPDataTroy) * (end - begin + 1));
}
//归并排序
void MergerSort(HPDataTroy* a, int n) {
HPDataTroy* tmp = (HPDataTroy*)malloc(sizeof(HPDataTroy) * n);
if (tmp == NULL) {
perror("malloc fail");
exit(-1);
}
//递归子函数
_MergerSort(a,tmp,0,n-1);
}
递归排序思想在于先递归到单个元素,然后两两合并成有序存储到辅助空间内(tmp),之后再将已经合并的序列看成一个,和下一个继续合并,从逐个两两合并到四四合并以此类推即可。
9.2:非递归
//归并排序-非递归
void MergerSortNOR(HPDataTroy* a, int n) {
assert(a);
assert(n > 0);
HPDataTroy* tmp = (HPDataTroy*)malloc(sizeof(HPDataTroy));
if (tmp == NULL) {
perror("malloc fail");
exit(-1);
}
int gap = 1;
while (gap < n) {
for (int i = 0; i < n; i = i + 2 * gap) {
int begin1 = i;
int end1 = i + gap - 1;
int begin2 = i + gap;
int end2 = i + gap * 2 - 1;
int index = i;
//[begin1,end1][begin2,end2]
//修正防止不越界
if (end1 >= n) {
end1 = n - 1;
}
if (begin2 >= n) {
break;
}
if (end2 >= n) {
end2 = n - 1;
}
//合并
while (begin1 <= end1 && begin2 <= end2) {
//放小
if (a[begin1] < a[begin2]) {
tmp[index] = a[begin1];
index++;
begin1++;
}
else {
tmp[index] = a[begin2];
index++;
begin2++;
}
}
//放剩余的元素
while (begin1 <= end1) {
tmp[index] = a[begin1];
index++;
begin1++;
}
while (begin2 <= end2) {
tmp[index] = a[begin2];
index++;
begin2++;
}
//CV到a数组对应位置对应长度上
memcpy(a + i, tmp + i, sizeof(HPDataTroy) * (end2-i+1));
}
gap = gap * 2;
}
}
递归改非递归借助循环,控制好边界即可,大致思想和递归类似,非递归是递归的倒序。
最后,如有不足,请各位大佬指正!!!