一.插入排序
插入排序原理是通过构建有序序列,对未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。(类似于在摸牌时调整手上牌的顺序)。
代码来源:B站up主码农论坛码农论坛的个人空间-码农论坛个人主页-哔哩哔哩视频
代码:
// 参数arr是待排序数组的首地址,len是数组元素的个数。
void insertsort(int *arr,unsigned int len)
{
if (len<2) return; // 数组小于2个元素不需要排序。
int itmp; // 当前需要排序的元素的值。
int ii; // 需要排序元素的计数器。
int jj; // 插入排序时,需要后移元素的计数器。
for (ii=1;ii<len;ii++)
{
itmp=arr[ii]; // 待排序元素
// 从已排序的最右边开始,把大于当前排序的元素后移。
// for (jj=ii-1;(jj>=0&&arr[jj]>itmp);jj--)
for (jj=ii-1;jj>=0;jj--)
{
if (arr[jj]<=itmp) break;
arr[jj+1]=arr[jj]; // 逐个元素后移。
}
arr[jj+1]=itmp; // 插入当前排序元素。
}
}
int main(int argc,char *argv[])
{
int arr[]={44,3,38,5,47,15,36,26,27,2,46,4,19,50,48};
int len=sizeof(arr)/sizeof(int);
insertsort(arr,len); // 调用插入排序函数对数组排序。
// 显示排序结果。
int yy; for (yy=0;yy<len;yy++) printf("%2d ",arr[yy]); printf("\n");
// system("pause"); // widnows下的C启用本行代码。
return 0;
}
插入排序优化:
插入排序存在两个不足:
1.寻找插入位置
2.移动元素
优化方案
1.对已排好序的序列,采用二分查找法
2.携带多个元素(一般只插一个可以携带多个,在合适的位置插入)
3.数据链表化
4.希尔排序
二.希尔排序
把待排序的数列分为多个组,然后在对每个组进行插入排序,先让数组整体大致有序,然后多次调整分组方式,使数列更加有序最后再使用一次插入排序,整个数列将全部有序。
希尔排序的核心思想是化远为近:
1.查找次数减少
2.移动元素的次数减少
// 对希尔排序中的单个组进行排序。
// arr-待排序的数组,len-数组总的长度,ipos-分组的起始位置,istep-分组的步长(增量)。
void groupsort(int *arr, int len, int ipos,int istep)
{
int itmp; // 当前需要排序的元素的值。
int ii; // 需要排序元素的计数器。
int jj; // 插入排序时,需要后移元素的计数器。
for (ii=ipos+istep;ii<len;ii=ii+istep)
{
itmp=arr[ii]; // 待排序元素
// 从已排序的最右边开始,把大于当前排序的元素后移。
// for (jj=ii-istep;(jj>=0&&arr[jj]>itmp);jj=jj-istep)
for (jj=ii-istep;jj>=0;jj=jj-istep)
{
if (arr[jj]<=itmp) break;
arr[jj+istep]=arr[jj]; // 逐个元素后移。
}
arr[jj+istep]=itmp; // 插入当前排序元素。
}
}
// 希尔排序,arr是待排序数组的首地址,len是数组的大小。
void shellsort(int *arr,unsigned int len)
{
int ii,istep;
// istep为步长,每次减为原来的一半取整数,最后一次必定为1。
for (istep=len/2;istep>0;istep=istep/2)
{
// 共istep个组,对每一组都执行插入排序。
for (ii=0;ii<istep;ii++)
{
groupsort(arr,len,ii,istep);
}
}
}
int main(int argc,char *argv[])
{
int arr[]={44,3,38,5,47,15,36,26,27,2,46,4,19,50,48};
int len=sizeof(arr)/sizeof(int);
shellsort(arr,len); // 调用插入排序函数对数组排序。
// 显示排序结果。
int yy; for (yy=0;yy<len;yy++) printf("%2d ",arr[yy]); printf("\n");
// system("pause"); // widnows下的C启用本行代码。
return 0;
}
希尔排序也称为缩小增量排序
增量一般缩小一半。
三.冒泡排序
数值大的数往后面浮
// 采用两层循环实现的方法。
// 参数arr是待排序数组的首地址,len是数组元素的个数。
void bubblesort1(int *arr,unsigned int len)
{
if (len<2) return; // 数组小于2个元素不需要排序。
int ii; // 排序的趟数的计数器。
int jj; // 每趟排序的元素位置计数器。
int itmp; // 比较两个元素大小时交换位置用到的临时变量。
int ifswap; // 每趟排序过程中是否交换过元素,0-未交换;1-有交换。
// 44,3,38,5,47,15,36,26,27,2,46,4,19,50,48
for (ii=len-1;ii>0;ii--) // 一共进行len-1趟比较。
{
printf("ii=%d\n",ii);
ifswap=0; // 初始化交换标志。
for (jj=0;jj<ii;jj++) // 每趟只需要比较0......ii之间的元素,ii之后的元素是已经排序好的。
{
if (arr[jj]>arr[jj+1]) // 如果前面的元素大于后面的元素,则交换它位的位置。
{
itmp=arr[jj+1];
arr[jj+1]=arr[jj];
arr[jj]=itmp;
ifswap=1; // 设置交换标志。
}
}
if (ifswap==0) return;
}
}
// 采用递归实现的方法。
// 参数arr是待排序数组的首地址,len是数组元素的个数。
void bubblesort2(int *arr,unsigned int len)
{
if (len<2) return; // 数组小于2个元素不需要排序。
printf("len=%d\n",len);
int ii; // 排序的元素位置计数器。
int itmp; // 比较两个元素大小时交换位置用到的临时变量。
int ifswap=0; // 排序过程中是否交换过元素,0-未交换;1-有交换。
for (ii=0;ii<len-1;ii++) // 每趟只需要比较0......len-1之间的元素,len-1之后的元素是已经排序好的。
{
if (arr[ii]>arr[ii+1]) // 如果前面的元素大于后面的元素,则交换它位的位置。
{
itmp=arr[ii+1];
arr[ii+1]=arr[ii];
arr[ii]=itmp;
ifswap=1; // 设置交换标志。
}
}
if (ifswap==0) return;
bubblesort2(arr,--len);
}
int main(int argc,char *argv[])
{
int arr[]={44,3,38,5,47,15,36,26,27,2,46,4,19,50,48};
int len=sizeof(arr)/sizeof(int);
bubblesort2(arr,len);
// 显示排序结果。
int ii;
for (ii=0;ii<len;ii++) printf("%2d ",arr[ii]);
printf("\n");
// system("pause"); // widnows下的C启用本行代码。
return 0;
}
三.快速排序
1.先从数列中选取一个元素作为基准数。
2.扫描数列,将比基准数小的元素全部放到它的左边,大于或等于基准数的元素全部放到它的右边,得到两个区间。
3.再对左右区间重复第二步,直到各区间少于两个元素。
void quicksort(int *arr,unsigned int len)
{
if (len<2) return; // 数组的元素小于2个就不用排序了。
int itmp=arr[0]; // 选取最左边的数作为中心轴。
int ileft=0; // 左下标。
int iright=len-1; // 右下标。
int imoving=2; // 当前应该移动的下标,1-左下标;2-右下标。
while (ileft<iright)
{
if (imoving==2) // 移动右下标的情况。
{
// 如果右下标位置元素的值大于等于中心轴,继续移动右下标。
if (arr[iright]>=itmp) { iright--; continue; }
// 如果右下标位置元素的值小于中心轴,把它填到左下标的坑中。
arr[ileft]=arr[iright];
ileft++; // 左下标向右移动。
imoving=1; // 下次循环将移动左下标。
continue;
}
if (imoving==1) // 移动左下标的情况。
{
// 如果左下标位置元素的值小等于中心轴,继续移动左下标。
if (arr[ileft]<=itmp) { ileft++; continue; }
// 如果左下标位置元素的值大于中心轴,把它填到右下标的坑中。
arr[iright]=arr[ileft];
iright--; // 右下标向左移动。
imoving=2; // 下次循环将移动右下标。
continue;
}
}
// 如果循环结束,左右下标重合,把中心轴的值填进去。
arr[ileft]=itmp;
quicksort(arr,ileft); // 对中心轴左边的序列进行排序。
quicksort(arr+ileft+1,len-ileft-1); // 对中心轴右边的序列进行排序。
}
int main(int argc,char *argv[])
{
int arr[]={44,3,38,5,47,15,36,26,27,2,46,4,19,50,48};
int len=sizeof(arr)/sizeof(int);
quicksort(arr,len); // 调用插入排序函数对数组排序。
// 显示排序结果。
int yy; for (yy=0;yy<len;yy++) printf("%2d ",arr[yy]); printf("\n");
// system("pause"); // widnows下的C启用本行代码。
return 0;
}
快速排序的优化:
1.采用更合理的基准数,减少递归深度,从数列中选多个数取中间数。
2.结合插入排序,区间再10个元素之内采用插入排序,效率更高。
四.选择排序
从头至尾扫描序列,找出最小的一个元素,和第一个元素交换,接着从剩下的元素中继续这种选择和交换方式,最终得到一个有序数列。
// 交换两个变量的值。
void swap(int *x,int *y)
{
int itmp=*x;
*x=*y;
*y=itmp;
}
// 采用两层循环实现的方法。
// 参数arr是待排序数组的首地址,len是数组元素的个数。
void selectsort1(int *arr,unsigned int len)
{
if (len<2) return; // 数组小于2个元素不需要排序。
int ii; // 排序的趟数的计数器。
int jj; // 每趟排序的元素位置计数器。
int iminpos; // 每趟循环选出的最小值的位置(数组的下标)。
// 44,3,38,5,47,15,36,26,27,2,46,4,19,50,48
for (ii=0;ii<len-1;ii++) // 一共进行len-1趟比较。
{
iminpos=ii;
for (jj=ii+1;jj<len;jj++) // 每趟只需要比较ii+1......len-1之间的元素,ii之前的元素是已经排序好的。
{
// 找出值更小的元素,记下它的位置。
if (arr[jj]<arr[iminpos]) iminpos=jj;
}
// 如果本趟循环的最小的元素不是起始位置的元素,则交换它们的位置。
if (iminpos!=ii) swap(&arr[ii],&arr[iminpos]);
}
}
// 采用递归实现的方法。
// 参数arr是待排序数组的首地址,len是数组元素的个数。
void selectsort2(int *arr,unsigned int len)
{
if (len<2) return; // 数组小于2个元素不需要排序。
int ii; // 排序的趟数的计数器。
int iminpos=0; // 每趟循环选出的最小值的位置(数组的下标)。
for (ii=1;ii<len;ii++)
{
// 找出值更小的元素,记下它的位置。
if (arr[ii]<arr[iminpos]) iminpos=ii;
}
// 如果本趟循环的最小的元素不是起始位置的元素,则交换它们的位置。
if (iminpos!=0) swap(&arr[0],&arr[iminpos]);
selectsort2(arr+1,--len);
}
int main(int argc,char *argv[])
{
int arr[]={44,3,38,5,47,15,36,26,27,2,46,4,19,50,48};
//int arr[]={3,4,5,1};
int len=sizeof(arr)/sizeof(int);
// selectsort1(arr,len); // 采用两层循环排序的方法。
selectsort2(arr,len); // 采用递归排序的方法。
// 显示排序结果。
int ii; for (ii=0;ii<len;ii++) printf("%2d ",arr[ii]);
printf("\n");
// system("pause"); // widnows下的C启用本行代码。
return 0;
}
选择排序优化:把一趟最小和最大值都选出来,最小的放左边,最大的放右边,优化后,循环趟数减半。
五.堆排序
堆排序是利用堆这种数据结构二设计的一种排序算法,堆具有以下特点:
1.完全二叉树
2.二叉树每个节点的值都大于或等于其左右子树的值,称为大顶堆;
每个节点的值都小于或等于其左右子树的值,称为小顶堆。
完全二叉树的下标:
N[i] 的左子节点:N[2i+1]
N[i] 的右子节点:N[2i+2]
N[i] 的父节点:N[(i-1)/2]
先把数组存成完全二叉树,然后构建大顶堆,跟节点是最大的,显赫最后一个元素交换,然后根节点下沉到值的的根节点,下沉完后,交换根节点(从底到根换完后底部的元素即拍好了,不用动了)不断下沉直到排好序。
// 交换两个元素的值。
void swap(int *a,int *b) { int temp=*b; *b=*a; *a=temp; }
// 采用循环实现heapify(元素下沉)。
// arr-待排序数组的地址,start-待heapify节点的下标,end-待排序数组最后一个元素的下标。
void heapify(int *arr,int start,int end)
{
// 确定父节点和左子节点的数组下标。
int dad=start;
int son=dad*2+1;
// 如果子节点的下标没有超出范围,循环继续。
while (son<=end)
{
// 先比较两個子节点大小,选择最大的。
if ((son+1<=end) && (arr[son]<arr[son+1])) son++;
// 如果父节点大于子节点代表调整完毕,直接跳出函数。
if (arr[dad]>arr[son]) return;
// 否则交换父子內容再继续子节点和孙节点比较。
swap(&arr[dad],&arr[son]);
dad=son;
son=dad*2+1;
}
}
// 采用递归实现heapify。
void heapify1(int *arr,int start,int end)
{
// 确定父节点和左子节点的数组下标。
int dad=start;
int son=dad*2+1;
// 如果子节点的下标没有超出范围,循环继续。
if (son>end ) return;
// 先比较两個子节点大小,选择最大的。
if ((son+1<=end) && (arr[son]<arr[son+1])) son++;
// 如果父节点大于子节点代表调整完毕,直接跳出函数。
if (arr[dad]>arr[son]) return;
// 否则交换父子內容再继续子节点和孙节点比较。
swap(&arr[dad],&arr[son]);
heapify(arr,son,end);
}
void heapsort(int *arr, int len)
{
int ii;
// 初始化堆,从最后一個父节点开始调整。
for (ii=(len-1)/2;ii>=0;ii--) heapify(arr,ii,len-1);
// 把第一个元素和堆最后一个元素交换,然后重新调整,直到排序完毕。
for (ii=len-1;ii>0;ii--)
{
swap(&arr[0],&arr[ii]);
heapify(arr,0,ii-1);
}
}
int main()
{
int arr[]={44,3,38,5,47,15,36,26,27,2,46,4,19,50,48};
int len=sizeof(arr)/sizeof(int);
heapsort(arr,len);
// 显示排序结果。
int yy; for (yy=0;yy<len;yy++) printf("%2d ",arr[yy]); printf("\n");
// system("pause"); // widnows下的C启用本行代码。
return 0;
}
六.归并算法
将已经有序的子树合并,得到另一个有序的数列。
// 采用递归的方法实现归并排序函数。
// arr-待排序数组的首地址,arrtmp-用于排序的临时数组的首地址
// start-排序区间第一个元素的位置,end-排序区间最后一个元素的位置。
void _mergesort(int *arr,int *arrtmp,int start,int end)
{
// 如果start>=end,表示该区间的元素少于两个,递归终止。
if (start>=end) return;
int mid=start+(end-start)/2; // 计算排序区间中间的位置。
int istart1=start,iend1=mid; // 区间左边元素的第一和最后一个元素的位置。
int istart2=mid+1,iend2=end; // 区间右边元素的第一和最后一个元素的位置。
_mergesort(arr,arrtmp,istart1,iend1); // 对区间左边元素递归排序。
_mergesort(arr,arrtmp,istart2,iend2); // 对区间右边元素递归排序。
int ii=start; // 已排序数组arrtmp的计数器。
// 把区间左右两边数列合并到已排序数组arrtmp中。
while (istart1<=iend1 && istart2<=iend2)
arrtmp[ii++]=arr[istart1]<arr[istart2] ? arr[istart1++] : arr[istart2++];
// 把左边数列其它的元素追加到已排序数组。
while (istart1<=iend1) arrtmp[ii++]=arr[istart1++];
// 把右边数列其它的元素追加到已排序数组。
while (istart2<=iend2) arrtmp[ii++]=arr[istart2++];
// 把已排序数组arrtmp中的元素复制到arr中。
memcpy(arr+start,arrtmp+start,(end-start+1)*sizeof(int));
}
// 排序主函数,arr为待排序的数组的地址,len为数组的长度。
void mergesort(int *arr,unsigned int len)
{
if (len<2) return; // 小于两个元素不需要排序。
int arrtmp[len]; // 分配一个与待排序数组相同大小的数组。
_mergesort(arr,arrtmp,0,len-1); // 调用递归函数进行排序。
}
int main(int argc,char *argv[])
{
int arr[]={44,3,38,5,47,15,36,26,27,2,46,4,19,50,48};
int len=sizeof(arr)/sizeof(int);
mergesort(arr,len);
// 显示排序结果。
int yy; for (yy=0;yy<len;yy++) printf("%2d ",arr[yy]); printf("\n");
// system("pause"); // widnows下的C启用本行代码。
return 0;
}
优化:
用循环的效率高于递归。
七.基数排序
是桶排序的扩展,基本思想:将整数按位数切割成不同的数字,然后按每个位数分别比较。
// 获取数组arr中最大值,arr-待排序的数组,len-数组arr的长度。
int arrmax(int *arr,unsigned int len)
{
int ii,imax;
imax=arr[0];
for (ii=1;ii<len;ii++)
if (arr[ii]>imax) imax=arr[ii];
return imax;
}
// 对数组arr按指数位进行排序。
// arr-待排序的数组,len-数组arr的长度。
// exp-排序指数,exp=1:按个位排序;exp=10:按十位排序;......
void _radixsort(int *arr,unsigned int len,unsigned int exp)
{
int ii;
int result[len]; // 存放从桶中收集后数据的临时数组。
int buckets[10]={0}; // 初始化10个桶。
// 遍历arr,将数据出现的次数存储在buckets中。
for (ii=0;ii<len;ii++)
buckets[(arr[ii]/exp)%10]++;
// 调整buckets各元素的值,调整后的值就是arr中元素在result中的位置。
for (ii=1;ii<10;ii++)
buckets[ii]=buckets[ii]+buckets[ii-1];
// 将arr中的元素填充到result中。
for (ii=len-1;ii>=0;ii--)
{
int iexp=(arr[ii]/exp)%10;
result[buckets[iexp]-1]=arr[ii];
buckets[iexp]--;
}
// 将排序好的数组result复制到数组arr中。
memcpy(arr,result,len*sizeof(int));
}
// 基数排序主函数,arr-待排序的数组,len-数组arr的长度。
void radixsort(int *arr,unsigned int len)
{
int imax=arrmax(arr,len); // 获取数组arr中的最大值。
int iexp; // 排序指数,iexp=1:按个位排序;iexp=10:按十位排序;......
// 从个位开始,对数组arr按指数位进行排序。
for (iexp=1;imax/iexp>0;iexp=iexp*10)
{
_radixsort(arr,len,iexp);
int yy; printf("exp=%-5d ",iexp); for (yy=0;yy<len;yy++) printf("%2d ",arr[yy]); printf("\n");
}
}
int main(int argc,char *argv[])
{
int arr[]={144,203,738,905,347,215,836,26,527,602,946,504,219,750,848};
int len=sizeof(arr)/sizeof(int);
radixsort(arr,len); // 基数排序。
// 显示排序结果。
int yy; for (yy=0;yy<len;yy++) printf("%2d ",arr[yy]); printf("\n");
// system("pause"); // widnows下的C启用本行代码。
return 0;
}
八.计数排序
计数排序适用于整数或有限范围内的非负整数排序,且当数据范围不大时,计数排序是一种非常高效的的排序算法。
优化: 有空间浪费问题{101,109,108,102,103}最大109,要创一个长110的计数数组的话[0,100]空间浪费了。
/ 获取待排序数组的最小和最大元素的值。
void arrminmax(int *arr,unsigned int len,int *min,int *max)
{
int ii=0;
*min=*max=arr[0];
for (ii=0;ii<len;ii++)
{
if (*max<arr[ii]) *max=arr[ii];
if (*min>arr[ii]) *min=arr[ii];
}
}
// 计数排序主函数,arr-待排序数组的地址,len-数组的长度。
void countsort(int *arr,unsigned int len)
{
if (len<2) return;
// 获取待排序数组的最小和最大大元素的值。
int imin,imax;
arrminmax(arr,len,&imin,&imax);
int arrtmp[imax-imin+1]; // 临时数组的大小为imax+1。
memset(arrtmp,0,sizeof(arrtmp)); // 初始化临时数组。
int ii,jj,kk;
// 临时数组计数。
for (ii=0;ii<len;ii++) arrtmp[arr[ii]-imin]++;
// 把临时数组计数的内容填充到arr中。
ii=0;
for (jj=0;jj<imax-imin+1;jj++)
{
for (kk=0;kk<arrtmp[jj];kk++) arr[ii++]=jj+imin;
}
}
int main()
{
//int arr[]={2,3,8,7,1,2,2,2,7,3,9,8,2,1,4,2,4,6,9,2};
int arr[]={102,103,108,107,101,102,102,102,107,103,109,108,102,101,104,102,104,106,109,102};
int len=sizeof(arr)/sizeof(int);
int xx; for (xx=0;xx<len;xx++) printf("%2d ",arr[xx]); printf("\n");
countsort(arr,len);
// 显示排序结果。
int yy; for (yy=0;yy<len;yy++) printf("%2d ",arr[yy]); printf("\n");
// system("pause"); // widnows下的C启用本行代码。
return 0;
}